From 246295bdeb00ec6d533f22937992d1f9a06f8e71 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Tue, 21 Jul 2009 19:32:31 +0200 Subject: git-gui: display summary when showing diff of a submodule As it is hard to say what changed in a submodule by looking at the hashes, let's show the colored submodule summary instead. Signed-off-by: Jens Lehmann Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 14b92ba..3c0ce26 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3212,7 +3212,7 @@ proc popup_diff_menu {ctxm ctxmmg x y X Y} { set l [mc "Stage Hunk For Commit"] set t [mc "Stage Line For Commit"] } - if {$::is_3way_diff + if {$::is_3way_diff || $::is_submodule_diff || $current_diff_path eq {} || {__} eq $state || {_O} eq $state diff --git a/lib/diff.tcl b/lib/diff.tcl index 925b3f5..ae1ea3a 100644 --- a/lib/diff.tcl +++ b/lib/diff.tcl @@ -255,7 +255,7 @@ proc show_other_diff {path w m cont_info} { proc start_show_diff {cont_info {add_opts {}}} { global file_states file_lists - global is_3way_diff diff_active repo_config + global is_3way_diff is_submodule_diff diff_active repo_config global ui_diff ui_index ui_workdir global current_diff_path current_diff_side current_diff_header @@ -265,6 +265,7 @@ proc start_show_diff {cont_info {add_opts {}}} { set s $file_states($path) set m [lindex $s 0] set is_3way_diff 0 + set is_submodule_diff 0 set diff_active 1 set current_diff_header {} @@ -295,6 +296,11 @@ proc start_show_diff {cont_info {add_opts {}}} { lappend cmd $path } + if {[string match {160000 *} [lindex $s 2]] + || [string match {160000 *} [lindex $s 3]]} { + set cmd {submodule summary -- $current_diff_path} + } + if {[catch {set fd [eval git_read --nice $cmd]} err]} { set diff_active 0 unlock_index @@ -312,7 +318,7 @@ proc start_show_diff {cont_info {add_opts {}}} { } proc read_diff {fd cont_info} { - global ui_diff diff_active + global ui_diff diff_active is_submodule_diff global is_3way_diff is_conflict_diff current_diff_header global current_diff_queue global diff_empty_count @@ -337,6 +343,9 @@ proc read_diff {fd cont_info} { } set ::current_diff_inheader 0 + if {[regexp {^\* } $line]} { + set is_submodule_diff 1 + } # -- Automatically detect if this is a 3 way diff. # if {[string match {@@@ *} $line]} {set is_3way_diff 1} @@ -374,6 +383,23 @@ proc read_diff {fd cont_info} { set tags {} } } + } elseif {$is_submodule_diff} { + if {$line == ""} continue + if {[regexp {^\* } $line]} { + set line [string replace $line 0 1 {Submodule }] + set tags d_@ + } else { + set op [string range $line 0 2] + switch -- $op { + { <} {set tags d_-} + { >} {set tags d_+} + { W} {set tags {}} + default { + puts "error: Unhandled submodule diff marker: {$op}" + set tags {} + } + } + } } else { set op [string index $line 0] switch -- $op { -- cgit v0.10.2-6-g49f6 From 2ee94d141eb86c1e43453664eea7a45b37dbf72f Mon Sep 17 00:00:00 2001 From: Jimmy Angelakos Date: Tue, 23 Jun 2009 21:35:35 +0300 Subject: git-gui: Added Greek translation & glossary Signed-off-by: Shawn O. Pearce diff --git a/po/el.po b/po/el.po new file mode 100644 index 0000000..3634ba4 --- /dev/null +++ b/po/el.po @@ -0,0 +1,2005 @@ +# Translation of git-gui to Greek +# Copyright (C) 2009 Jimmy Angelakos +# This file is distributed under the same license as the git-gui package. +# Jimmy Angelakos , 2009. +msgid "" +msgstr "" +"Project-Id-Version: el\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2008-03-14 07:18+0100\n" +"PO-Revision-Date: 2009-06-23 21:33+0300\n" +"Last-Translator: Jimmy Angelakos \n" +"Language-Team: Greek \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 0.3\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744 +#: git-gui.sh:763 +msgid "git-gui: fatal error" +msgstr "git-gui: κρίσιμο σφάλμα" + +#: git-gui.sh:593 +#, tcl-format +msgid "Invalid font specified in %s:" +msgstr "Μη έγκυρη γραμματοσειρά στο %s:" + +#: git-gui.sh:620 +msgid "Main Font" +msgstr "Κύρια Γραμματοσειρά" + +#: git-gui.sh:621 +msgid "Diff/Console Font" +msgstr "Γραμματοσειρά Διαφοράς/Κονσόλας" + +#: git-gui.sh:635 +msgid "Cannot find git in PATH." +msgstr "Δε βρέθηκε το git στο PATH." + +#: git-gui.sh:662 +msgid "Cannot parse Git version string:" +msgstr "Αδύνατη η ανάγνωση στοιχειοσειράς έκδοσης Git:" + +#: git-gui.sh:680 +#, tcl-format +msgid "" +"Git version cannot be determined.\n" +"\n" +"%s claims it is version '%s'.\n" +"\n" +"%s requires at least Git 1.5.0 or later.\n" +"\n" +"Assume '%s' is version 1.5.0?\n" +msgstr "" +"Δε μπορεί να ανιχνευτεί η έκδοση του Git. \n" +"\n" +"Το %s υποστηρίζει πως είναι η έκδοση '%s'.\n" +"\n" +"Το %s απαιτεί τουλάχιστον Git 1.5.0 ή πιό πρόσφατη.\n" +"\n" +"Να υποτεθεί πως το '%s' είναι η έκδοση 1.5.0;\n" + +#: git-gui.sh:918 +msgid "Git directory not found:" +msgstr "Δε βρέθηκε φάκελος Git:" + +#: git-gui.sh:925 +msgid "Cannot move to top of working directory:" +msgstr "Δεν είναι δυνατή η μετακίνηση στην κορυφή του φακέλου εργασίας:" + +#: git-gui.sh:932 +msgid "Cannot use funny .git directory:" +msgstr "Δεν είναι δυνατή η χρήση περίεργου φακέλου .git:" + +#: git-gui.sh:937 +msgid "No working directory" +msgstr "Δεν υπάρχει φάκελος εργασίας" + +#: git-gui.sh:1084 lib/checkout_op.tcl:283 +msgid "Refreshing file status..." +msgstr "Ανανέωση κατάστασης αρχείου..." + +#: git-gui.sh:1149 +msgid "Scanning for modified files ..." +msgstr "Ανίχνευση για τροποποιημένα αρχεία..." + +#: git-gui.sh:1324 lib/browser.tcl:246 +msgid "Ready." +msgstr "Έτοιμο." + +#: git-gui.sh:1590 +msgid "Unmodified" +msgstr "Μη τροποποιημένο" + +#: git-gui.sh:1592 +msgid "Modified, not staged" +msgstr "Τροποποιημένο, μη σταδιοποιημένο" + +#: git-gui.sh:1593 git-gui.sh:1598 +msgid "Staged for commit" +msgstr "Σταδιοποιημένο προς υποβολή" + +#: git-gui.sh:1594 git-gui.sh:1599 +msgid "Portions staged for commit" +msgstr "Μέρη σταδιοποιημένα προς υποβολή" + +#: git-gui.sh:1595 git-gui.sh:1600 +msgid "Staged for commit, missing" +msgstr "Σταδιοποιημένο προς υποβολή, λείπει" + +#: git-gui.sh:1597 +msgid "Untracked, not staged" +msgstr "Μη παρακολουθούμενο, μη σταδιοποιημένο" + +#: git-gui.sh:1602 +msgid "Missing" +msgstr "Λείπει" + +#: git-gui.sh:1603 +msgid "Staged for removal" +msgstr "Σταδιοποιημένο προς αφαίρεση" + +#: git-gui.sh:1604 +msgid "Staged for removal, still present" +msgstr "Σταδιοποιημένο προς αφαίρεση, ακόμα παρόν" + +#: git-gui.sh:1606 git-gui.sh:1607 git-gui.sh:1608 git-gui.sh:1609 +msgid "Requires merge resolution" +msgstr "Απαιτεί επίλυση συγχώνευσης" + +#: git-gui.sh:1644 +msgid "Starting gitk... please wait..." +msgstr "Γίνεται εκκίνηση του gitk... παρακαλώ περιμένετε..." + +#: git-gui.sh:1653 +#, tcl-format +msgid "" +"Unable to start gitk:\n" +"\n" +"%s does not exist" +msgstr "" +"Αδυναμία εκκίνησης του gitk:\n" +"\n" +"Το %s δεν υπάρχει" + +#: git-gui.sh:1860 lib/choose_repository.tcl:36 +msgid "Repository" +msgstr "Αποθετήριο" + +#: git-gui.sh:1861 +msgid "Edit" +msgstr "Επεξεργασία" + +#: git-gui.sh:1863 lib/choose_rev.tcl:561 +msgid "Branch" +msgstr "Κλάδος" + +#: git-gui.sh:1866 lib/choose_rev.tcl:548 +msgid "Commit@@noun" +msgstr "Υποβολή@@noun" + +#: git-gui.sh:1869 lib/merge.tcl:120 lib/merge.tcl:149 lib/merge.tcl:167 +msgid "Merge" +msgstr "Συγχώνευση" + +#: git-gui.sh:1870 lib/choose_rev.tcl:557 +msgid "Remote" +msgstr "Απομακρυσμένο" + +#: git-gui.sh:1879 +msgid "Browse Current Branch's Files" +msgstr "Περιήγηση Αρχείων Τρέχοντα Κλάδου" + +#: git-gui.sh:1883 +msgid "Browse Branch Files..." +msgstr "Περιήγηση Αρχείων Κλάδου..." + +#: git-gui.sh:1888 +msgid "Visualize Current Branch's History" +msgstr "Απεικόνιση Ιστορικού Τρέχοντα Κλάδου" + +#: git-gui.sh:1892 +msgid "Visualize All Branch History" +msgstr "Απεικόνιση Ιστορικού Όλων των Κλάδων" + +#: git-gui.sh:1899 +#, tcl-format +msgid "Browse %s's Files" +msgstr "Περιήγηση Αρχείων του %s" + +#: git-gui.sh:1901 +#, tcl-format +msgid "Visualize %s's History" +msgstr "Απεικόνιση Ιστορικού του %s" + +#: git-gui.sh:1906 lib/database.tcl:27 lib/database.tcl:67 +msgid "Database Statistics" +msgstr "Στατιστικά Βάσης Δεδομένων" + +#: git-gui.sh:1909 lib/database.tcl:34 +msgid "Compress Database" +msgstr "Συμπίεση Βάσης Δεδομένων" + +#: git-gui.sh:1912 +msgid "Verify Database" +msgstr "Επαλήθευση Βάσης Δεδομένων" + +#: git-gui.sh:1919 git-gui.sh:1923 git-gui.sh:1927 lib/shortcut.tcl:7 +#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 +msgid "Create Desktop Icon" +msgstr "Δημιουργία Εικονιδίου Επιφάνειας Εργασίας" + +#: git-gui.sh:1932 lib/choose_repository.tcl:177 lib/choose_repository.tcl:185 +msgid "Quit" +msgstr "Έξοδος" + +#: git-gui.sh:1939 +msgid "Undo" +msgstr "Αναίρεση" + +#: git-gui.sh:1942 +msgid "Redo" +msgstr "Ξανά" + +#: git-gui.sh:1946 git-gui.sh:2443 +msgid "Cut" +msgstr "Αποκοπή" + +#: git-gui.sh:1949 git-gui.sh:2446 git-gui.sh:2520 git-gui.sh:2614 +#: lib/console.tcl:69 +msgid "Copy" +msgstr "Αντιγραφή" + +#: git-gui.sh:1952 git-gui.sh:2449 +msgid "Paste" +msgstr "Επικόλληση" + +#: git-gui.sh:1955 git-gui.sh:2452 lib/branch_delete.tcl:26 +#: lib/remote_branch_delete.tcl:38 +msgid "Delete" +msgstr "Διαγραφή" + +#: git-gui.sh:1959 git-gui.sh:2456 git-gui.sh:2618 lib/console.tcl:71 +msgid "Select All" +msgstr "Επιλογή Όλων" + +#: git-gui.sh:1968 +msgid "Create..." +msgstr "Δημιουργία..." + +#: git-gui.sh:1974 +msgid "Checkout..." +msgstr "Εξαγωγή..." + +#: git-gui.sh:1980 +msgid "Rename..." +msgstr "Μετονομασία..." + +#: git-gui.sh:1985 git-gui.sh:2085 +msgid "Delete..." +msgstr "Διαγραφή..." + +#: git-gui.sh:1990 +msgid "Reset..." +msgstr "Επαναφορά..." + +#: git-gui.sh:2002 git-gui.sh:2389 +msgid "New Commit" +msgstr "Νέα Υποβολή" + +#: git-gui.sh:2010 git-gui.sh:2396 +msgid "Amend Last Commit" +msgstr "Διόρθωση Τελευταίας Υποβολής" + +#: git-gui.sh:2019 git-gui.sh:2356 lib/remote_branch_delete.tcl:99 +msgid "Rescan" +msgstr "Επανανίχνευση" + +#: git-gui.sh:2025 +msgid "Stage To Commit" +msgstr "Σταδιοποίηση Προς Υποβολή" + +#: git-gui.sh:2031 +msgid "Stage Changed Files To Commit" +msgstr "Σταδιοποίηση Αλλαγμένων Αρχείων Προς Υποβολή" + +#: git-gui.sh:2037 +msgid "Unstage From Commit" +msgstr "Αποσταδιοποίηση Από Υποβολή" + +#: git-gui.sh:2042 lib/index.tcl:395 +msgid "Revert Changes" +msgstr "Αναίρεση Αλλαγών" + +#: git-gui.sh:2049 git-gui.sh:2368 git-gui.sh:2467 +msgid "Sign Off" +msgstr "Αποσύνδεση" + +#: git-gui.sh:2053 git-gui.sh:2372 +msgid "Commit@@verb" +msgstr "Υποβολή@@verb" + +#: git-gui.sh:2064 +msgid "Local Merge..." +msgstr "Τοπική Συγχώνευση..." + +#: git-gui.sh:2069 +msgid "Abort Merge..." +msgstr "Ακύρωση Συγχώνευσης..." + +#: git-gui.sh:2081 +msgid "Push..." +msgstr "Ώθηση..." + +#: git-gui.sh:2092 lib/choose_repository.tcl:41 +#, fuzzy +msgid "Apple" +msgstr "" + +#: git-gui.sh:2095 git-gui.sh:2117 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 +#, tcl-format +msgid "About %s" +msgstr "Περί %s" + +#: git-gui.sh:2099 +msgid "Preferences..." +msgstr "Προτιμήσεις..." + +#: git-gui.sh:2107 git-gui.sh:2639 +msgid "Options..." +msgstr "Επιλογές..." + +#: git-gui.sh:2113 lib/choose_repository.tcl:47 +msgid "Help" +msgstr "Βοήθεια" + +#: git-gui.sh:2154 +msgid "Online Documentation" +msgstr "Διαδικτυακή Τεκμηρίωση" + +#: git-gui.sh:2238 +#, tcl-format +msgid "fatal: cannot stat path %s: No such file or directory" +msgstr "κρίσιμο: δε βρέθηκε η διαδρομή: %s: Δεν υπάρχει το αρχείο ή ο φάκελος" + +#: git-gui.sh:2271 +msgid "Current Branch:" +msgstr "Τρέχων Κλάδος:" + +#: git-gui.sh:2292 +msgid "Staged Changes (Will Commit)" +msgstr "Σταδιοποιημένες Αλλαγές (Θα Υποβληθούν)" + +#: git-gui.sh:2312 +msgid "Unstaged Changes" +msgstr "Μη Σταδιοποιημένες Αλλαγές" + +#: git-gui.sh:2362 +msgid "Stage Changed" +msgstr "Σταδιοποίηση Αλλαγών" + +#: git-gui.sh:2378 lib/transport.tcl:93 lib/transport.tcl:182 +msgid "Push" +msgstr "Ώθηση" + +#: git-gui.sh:2408 +msgid "Initial Commit Message:" +msgstr "Αρχικό Μήνυμα Υποβολής:" + +#: git-gui.sh:2409 +msgid "Amended Commit Message:" +msgstr "Διορθωμένο Μήνυμα Υποβολής:" + +#: git-gui.sh:2410 +msgid "Amended Initial Commit Message:" +msgstr "Διορθωμένο Αρχικό Μήνυμα Υποβολής:" + +#: git-gui.sh:2411 +msgid "Amended Merge Commit Message:" +msgstr "Διορθωμένο Μήνυμα Υποβολής Συγχώνευσης:" + +#: git-gui.sh:2412 +msgid "Merge Commit Message:" +msgstr "Μήνυμα Υποβολής Συγχώνευσης:" + +#: git-gui.sh:2413 +msgid "Commit Message:" +msgstr "Μήνυμα Υποβολής:" + +#: git-gui.sh:2459 git-gui.sh:2622 lib/console.tcl:73 +msgid "Copy All" +msgstr "Αντιγραφή Όλων" + +#: git-gui.sh:2483 lib/blame.tcl:107 +msgid "File:" +msgstr "Αρχείο:" + +#: git-gui.sh:2589 +msgid "Apply/Reverse Hunk" +msgstr "Εφαρμογή/Αντιστροφή Κομματιού" + +#: git-gui.sh:2595 +msgid "Show Less Context" +msgstr "Προβολή Στενότερου Πλαισίου" + +#: git-gui.sh:2602 +msgid "Show More Context" +msgstr "Προβολή Ευρύτερου Πλαισίου" + +#: git-gui.sh:2610 +msgid "Refresh" +msgstr "Ανανέωση" + +#: git-gui.sh:2631 +msgid "Decrease Font Size" +msgstr "Μείωση Μεγέθους Γραμματοσειράς" + +#: git-gui.sh:2635 +msgid "Increase Font Size" +msgstr "Αύξηση Μεγέθους Γραμματοσειράς" + +#: git-gui.sh:2646 +msgid "Unstage Hunk From Commit" +msgstr "Αποσταδιοποίηση Κομματιού Από Υποβολή" + +#: git-gui.sh:2648 +msgid "Stage Hunk For Commit" +msgstr "Σταδιοποίηση Κομματιού Προς Υποβολή" + +#: git-gui.sh:2667 +msgid "Initializing..." +msgstr "Γίνεται αρχικοποίηση..." + +#: git-gui.sh:2762 +#, tcl-format +msgid "" +"Possible environment issues exist.\n" +"\n" +"The following environment variables are probably\n" +"going to be ignored by any Git subprocess run\n" +"by %s:\n" +"\n" +msgstr "" +"Υπάρχουν πιθανά θέματα με το περιβάλλον.\n" +"\n" +"Οι εξής μεταβλητές περιβάλλοντος μάλλον θα\n" +"αγνοηθούν από πιθανή εκτέλεση υποδιεργασίας Git\n" +"από το %s:\n" +"\n" + +#: git-gui.sh:2792 +msgid "" +"\n" +"This is due to a known issue with the\n" +"Tcl binary distributed by Cygwin." +msgstr "" +"\n" +"Αυτό οφείλεται σε ένα γνωστό θέμα με το\n" +"εκτελέσιμο Tcl που διανέμεται με το Cygwin." + +#: git-gui.sh:2797 +#, tcl-format +msgid "" +"\n" +"\n" +"A good replacement for %s\n" +"is placing values for the user.name and\n" +"user.email settings into your personal\n" +"~/.gitconfig file.\n" +msgstr "" +"\n" +"\n" +"Ένα καλό υποκατάστατο για το %s\n" +"είναι η τοποθέτηση τιμών για τις ρυθμίσεις\n" +"user.name και user.email στο προσωπικό σας\n" +"αρχείο ~/.gitconfig .\n" + +#: lib/about.tcl:26 +msgid "git-gui - a graphical user interface for Git." +msgstr "git-gui - ένα γραφικό περιβάλλον για το Git." + +#: lib/blame.tcl:77 +msgid "File Viewer" +msgstr "Εφαρμογή Προβολής Αρχείου" + +#: lib/blame.tcl:81 +msgid "Commit:" +msgstr "Υποβολή:" + +#: lib/blame.tcl:264 +msgid "Copy Commit" +msgstr "Αντιγραφή Υποβολής" + +#: lib/blame.tcl:384 +#, tcl-format +msgid "Reading %s..." +msgstr "Ανάγνωση %s..." + +#: lib/blame.tcl:488 +msgid "Loading copy/move tracking annotations..." +msgstr "Γίνεται φόρτωση σχολίων παρακολούθησης αντιγραφής/μετακίνησης..." + +#: lib/blame.tcl:508 +msgid "lines annotated" +msgstr "γραμμές σχολιασμένες" + +#: lib/blame.tcl:689 +msgid "Loading original location annotations..." +msgstr "Γίνεται φόρτωση σχολίων αρχικής τοποθεσίας..." + +#: lib/blame.tcl:692 +msgid "Annotation complete." +msgstr "Έγινε ολοκλήρωση του σχολιασμού." + +#: lib/blame.tcl:746 +msgid "Loading annotation..." +msgstr "Φόρτωση σχολίου..." + +#: lib/blame.tcl:802 +msgid "Author:" +msgstr "Δημιουργός:" + +#: lib/blame.tcl:806 +msgid "Committer:" +msgstr "Υποβολέας:" + +#: lib/blame.tcl:811 +msgid "Original File:" +msgstr "Αρχικό Αρχείο:" + +#: lib/blame.tcl:925 +msgid "Originally By:" +msgstr "Αρχικά Από:" + +#: lib/blame.tcl:931 +msgid "In File:" +msgstr "Στο Αρχείο:" + +#: lib/blame.tcl:936 +msgid "Copied Or Moved Here By:" +msgstr "Αντιγράφηκε ή Μετακινήθηκε Εδώ Από:" + +#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 +msgid "Checkout Branch" +msgstr "Εξαγωγή Κλάδου" + +#: lib/branch_checkout.tcl:23 +msgid "Checkout" +msgstr "Εξαγωγή" + +#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 +#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282 +#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:171 +#: lib/option.tcl:103 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 +msgid "Cancel" +msgstr "Ακύρωση" + +#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 +msgid "Revision" +msgstr "Αναθεώρηση" + +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:242 +msgid "Options" +msgstr "Επιλογές" + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "Ανάκτηση Κλάδου Παρακολούθησης" + +#: lib/branch_checkout.tcl:44 +msgid "Detach From Local Branch" +msgstr "Αποκόλληση Από Τοπικό Κλάδο" + +#: lib/branch_create.tcl:22 +msgid "Create Branch" +msgstr "Δημιουργία Κλάδου" + +#: lib/branch_create.tcl:27 +msgid "Create New Branch" +msgstr "Δημιουργία Νέου Κλάδου" + +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:371 +msgid "Create" +msgstr "Δημιουργία" + +#: lib/branch_create.tcl:40 +msgid "Branch Name" +msgstr "Όνομα Κλάδου" + +#: lib/branch_create.tcl:43 +msgid "Name:" +msgstr "Όνομα:" + +#: lib/branch_create.tcl:58 +msgid "Match Tracking Branch Name" +msgstr "Συμφωνία Ονόματος Κλάδου Παρακολούθησης" + +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "Αρχική Αναθεώρηση" + +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "Ενημέρωση Υπάρχοντα Κλάδου:" + +#: lib/branch_create.tcl:75 +#, fuzzy +msgid "No" +msgstr "Όχι" + +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "Συγχώνευση Επιτάχυνσης Μόνο" + +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +msgid "Reset" +msgstr "Επαναφορά" + +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "Εξαγωγή Μετά τη Δημιουργία" + +#: lib/branch_create.tcl:131 +msgid "Please select a tracking branch." +msgstr "Παρακαλώ επιλέξτε έναν κλάδο παρακολούθησης." + +#: lib/branch_create.tcl:140 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "" +"Ο κλάδος παρακολούθησης %s δεν είναι κλάδος που βρίσκεται στο απομακρυσμένο " +"αποθετήριο." + +#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 +msgid "Please supply a branch name." +msgstr "Παρακαλώ δώστε ένα όνομα κλάδου." + +#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#, tcl-format +msgid "'%s' is not an acceptable branch name." +msgstr "'%s' δεν είναι αποδεκτό όνομα κλάδου." + +#: lib/branch_delete.tcl:15 +msgid "Delete Branch" +msgstr "Διαγραφή Κλάδου" + +#: lib/branch_delete.tcl:20 +msgid "Delete Local Branch" +msgstr "Διαγραφή Τοπικού Κλάδου" + +#: lib/branch_delete.tcl:37 +msgid "Local Branches" +msgstr "Τοπικοί Κλάδοι" + +#: lib/branch_delete.tcl:52 +msgid "Delete Only If Merged Into" +msgstr "Διαγραφή Μόνο Εάν Είναι Συγχωνευμένο Με" + +#: lib/branch_delete.tcl:54 +msgid "Always (Do not perform merge test.)" +msgstr "Πάντα (Μη διενεργηθεί δοκιμή συγχώνευσης.)" + +#: lib/branch_delete.tcl:103 +#, tcl-format +msgid "The following branches are not completely merged into %s:" +msgstr "Οι εξής κλάδοι δεν είναι πλήρως συγχωνευμένοι με το %s:" + +#: lib/branch_delete.tcl:115 +msgid "" +"Recovering deleted branches is difficult. \n" +"\n" +" Delete the selected branches?" +msgstr "" +"Η ανάκτηση διεγραμμένων κλάδων είναι δύσκολη.\n" +"\n" +"Διαγραφή των επιλεγμένων κλάδων;" + +#: lib/branch_delete.tcl:141 +#, tcl-format +msgid "" +"Failed to delete branches:\n" +"%s" +msgstr "" +"Αποτυχία διαγραφής κλάδων:\n" +"%s" + +#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 +msgid "Rename Branch" +msgstr "Μετονομασία Κλάδου" + +#: lib/branch_rename.tcl:26 +msgid "Rename" +msgstr "Μετονομασία" + +#: lib/branch_rename.tcl:36 +msgid "Branch:" +msgstr "Κλάδος:" + +#: lib/branch_rename.tcl:39 +msgid "New Name:" +msgstr "Νέο Όνομα:" + +#: lib/branch_rename.tcl:75 +msgid "Please select a branch to rename." +msgstr "Παρακαλώ επιλέξτε κλάδο προς μετονομασία:" + +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "Ο Κλάδος '%s' υπάρχει ήδη." + +#: lib/branch_rename.tcl:117 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "Αποτυχία μετονομασίας '%s'." + +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "Γίνεται Εκκίνηση..." + +#: lib/browser.tcl:26 +msgid "File Browser" +msgstr "Περιηγητής Αρχείων" + +#: lib/browser.tcl:126 lib/browser.tcl:143 +#, tcl-format +msgid "Loading %s..." +msgstr "Γίνεται φόρτωση %s..." + +#: lib/browser.tcl:187 +#, fuzzy +msgid "[Up To Parent]" +msgstr "[Πάνω Προς Γονέα]" + +#: lib/browser.tcl:267 lib/browser.tcl:273 +msgid "Browse Branch Files" +msgstr "Περιήγηση Αρχείων Κλάδου" + +#: lib/browser.tcl:278 lib/choose_repository.tcl:387 +#: lib/choose_repository.tcl:474 lib/choose_repository.tcl:484 +#: lib/choose_repository.tcl:987 +msgid "Browse" +msgstr "Περιήγηση" + +#: lib/checkout_op.tcl:79 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "Ανάκτηση %s από το %s" + +#: lib/checkout_op.tcl:127 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "κρίσιμο: Δε μπόρεσε να επιλυθεί το %s" + +#: lib/checkout_op.tcl:140 lib/console.tcl:81 lib/database.tcl:31 +msgid "Close" +msgstr "Κλείσιμο" + +#: lib/checkout_op.tcl:169 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "Ο Κλάδος '%s' δεν υπάρχει." + +#: lib/checkout_op.tcl:206 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." +msgstr "" +"Ο Κλάδος '%s' υπάρχει ήδη.\n" +"\n" +"Δε γίνεται συγχώνευση επιτάχυνσής του στο %s.\n" +"Απαιτείται συγχώνευση." + +#: lib/checkout_op.tcl:220 +#, tcl-format +msgid "Merge strategy '%s' not supported." +msgstr "Η στρατηγική Συγχώνευσης %s δεν υποστηρίζεται." + +#: lib/checkout_op.tcl:239 +#, tcl-format +msgid "Failed to update '%s'." +msgstr "Αποτυχία ενημέρωσης '%s'." + +#: lib/checkout_op.tcl:251 +msgid "Staging area (index) is already locked." +msgstr "Η περιοχή σταδιοποίησης (το ευρετήριο) είναι ήδη κλειδωμένη." + +#: lib/checkout_op.tcl:266 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Η τελευταία κατάσταση που ανιχνεύθηκε δε συμφωνεί με την κατάσταση του " +"αποθετηρίου.\n" +"\n" +"Κάποιο άλλο πρόγραμμα Git τροποποίησε το αποθετήριο από την τελευταία " +"ανίχνευση. Πρέπει να γίνει επανανίχνευση πριν να αλλαχθεί ο τρέχων κλάδος.\n" +"\n" +"Η επανανίχνευση θα ξεκινήσει αυτόματα τώρα.\n" + +#: lib/checkout_op.tcl:322 +#, tcl-format +msgid "Updating working directory to '%s'..." +msgstr "Ενημέρωση φακέλου εργασίας σε '%s'..." + +#: lib/checkout_op.tcl:323 +msgid "files checked out" +msgstr "αρχεία έχουν εξαχθεί" + +#: lib/checkout_op.tcl:353 +#, tcl-format +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "" +"Έγινε ακύρωση εξαγωγής του '%s' (απαιτείται συγχώνευση επιπέδου αρχείου)." + +#: lib/checkout_op.tcl:354 +msgid "File level merge required." +msgstr "Απαιτείται συγχώνευση επιπέδου αρχείου." + +#: lib/checkout_op.tcl:358 +#, tcl-format +msgid "Staying on branch '%s'." +msgstr "Παραμονή στον κλάδο '%s'." + +#: lib/checkout_op.tcl:429 +msgid "" +"You are no longer on a local branch.\n" +"\n" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." +msgstr "" +"Δε βρίσκεστε πια σε τοπικό κλάδο.\n" +"\n" +"Αν θέλατε να βρίσκεστε σε κλάδο, δημιουργήστε έναν τώρα αρχίζοντας από 'This " +"Detached Checkout'." + +#: lib/checkout_op.tcl:446 lib/checkout_op.tcl:450 +#, tcl-format +msgid "Checked out '%s'." +msgstr "Έγινε εξαγωγή του '%s'." + +#: lib/checkout_op.tcl:478 +#, tcl-format +msgid "Resetting '%s' to '%s' will lose the following commits:" +msgstr "Η επαναφορά '%s' στο '%s' θα χάσει τις εξής υποβολές:" + +#: lib/checkout_op.tcl:500 +msgid "Recovering lost commits may not be easy." +msgstr "Η ανάκτηση χαμένων υποβολών μπορεί να είναι δύσκολη." + +#: lib/checkout_op.tcl:505 +#, tcl-format +msgid "Reset '%s'?" +msgstr "Επαναφορά '%s';" + +#: lib/checkout_op.tcl:510 lib/merge.tcl:163 +msgid "Visualize" +msgstr "Απεικόνιση" + +#: lib/checkout_op.tcl:578 +#, tcl-format +msgid "" +"Failed to set current branch.\n" +"\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" +"\n" +"This should not have occurred. %s will now close and give up." +msgstr "" +"Αποτυχία ορισμού τρέχοντος κλάδου.\n" +"\n" +"Αυτός ο φάκελος εργασίας είναι μόνο εν μέρει επιλεγμένος. 'Εγινε επιτυχής " +"ενημέρωση των αρχείων σας, αλλά απέτυχε η ενημέρωση ενός εσωτερικού αρχείου " +"του Git.\n" +"\n" +"Αυτό δε θα έπρεπε να συμβεί. Το %s θα κλείσει και θα εγκαταλείψει τώρα." + +#: lib/choose_font.tcl:39 +msgid "Select" +msgstr "Επιλογή" + +#: lib/choose_font.tcl:53 +msgid "Font Family" +msgstr "Οικογένεια Γραμματοσειράς" + +#: lib/choose_font.tcl:74 +msgid "Font Size" +msgstr "Μέγεθος Γραμματοσειράς" + +#: lib/choose_font.tcl:91 +msgid "Font Example" +msgstr "Παράδειγμα Γραμματοσειράς" + +#: lib/choose_font.tcl:103 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" +"Αυτό είναι ένα παράδειγμα κειμένου.\n" +"Αν σας αρέσει αυτό το κείμενο, μπορεί να γίνει δικό σας." + +#: lib/choose_repository.tcl:28 +msgid "Git Gui" +msgstr "Γραφικό Περιβάλλον Git" + +#: lib/choose_repository.tcl:81 lib/choose_repository.tcl:376 +msgid "Create New Repository" +msgstr "Δημιουργία Νέου Αποθετηρίου" + +#: lib/choose_repository.tcl:87 +msgid "New..." +msgstr "Νέο..." + +#: lib/choose_repository.tcl:94 lib/choose_repository.tcl:460 +msgid "Clone Existing Repository" +msgstr "Κλωνοποίηση Υπάρχοντος Αποθετηρίου" + +#: lib/choose_repository.tcl:100 +msgid "Clone..." +msgstr "Κλωνοποίηση..." + +#: lib/choose_repository.tcl:107 lib/choose_repository.tcl:976 +msgid "Open Existing Repository" +msgstr "Άνοιγμα Υπάρχοντος Αποθετηρίου" + +#: lib/choose_repository.tcl:113 +msgid "Open..." +msgstr "Άνοιγμα..." + +#: lib/choose_repository.tcl:126 +msgid "Recent Repositories" +msgstr "Πρόσφατα Αποθετήρια" + +#: lib/choose_repository.tcl:132 +msgid "Open Recent Repository:" +msgstr "Άνοιγμα Πρόσφατου Αποθετηρίου:" + +#: lib/choose_repository.tcl:296 lib/choose_repository.tcl:303 +#: lib/choose_repository.tcl:310 +#, tcl-format +msgid "Failed to create repository %s:" +msgstr "Αποτυχία δημιουργίας αποθετηρίου %s:" + +#: lib/choose_repository.tcl:381 lib/choose_repository.tcl:478 +msgid "Directory:" +msgstr "Φάκελος:" + +#: lib/choose_repository.tcl:412 lib/choose_repository.tcl:537 +#: lib/choose_repository.tcl:1011 +msgid "Git Repository" +msgstr "Αποθετήριο Git" + +#: lib/choose_repository.tcl:437 +#, tcl-format +msgid "Directory %s already exists." +msgstr "Ο Φάκελος '%s' υπάρχει ήδη." + +#: lib/choose_repository.tcl:441 +#, tcl-format +msgid "File %s already exists." +msgstr "Το αρχείο %s υπάρχει ήδη." + +#: lib/choose_repository.tcl:455 +msgid "Clone" +msgstr "Κλώνος" + +#: lib/choose_repository.tcl:468 +#, fuzzy +msgid "URL:" +msgstr "" + +#: lib/choose_repository.tcl:489 +msgid "Clone Type:" +msgstr "Τύπος Κλώνου:" + +#: lib/choose_repository.tcl:495 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "Τυπικό (Ταχύ, Ημι-Πλεονάζον, Hardlinks)" + +#: lib/choose_repository.tcl:501 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "Πλήρες Αντίγραφο (Πιο αργό, Πλεονάζον Αντίγραφο Ασφαλείας)" + +#: lib/choose_repository.tcl:507 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "Κοινή Χρήση (Ταχύτατο, Δε Συνιστάται, Κανένα Αντίγραφο Ασφαλείας)" + +#: lib/choose_repository.tcl:543 lib/choose_repository.tcl:590 +#: lib/choose_repository.tcl:736 lib/choose_repository.tcl:806 +#: lib/choose_repository.tcl:1017 lib/choose_repository.tcl:1025 +#, tcl-format +msgid "Not a Git repository: %s" +msgstr "Δεν είναι αποθετήριο Git: %s" + +#: lib/choose_repository.tcl:579 +msgid "Standard only available for local repository." +msgstr "\"Τυπικό\" διαθέσιμο μόνο για τοπικό αποθετήριο." + +#: lib/choose_repository.tcl:583 +msgid "Shared only available for local repository." +msgstr "\"Κοινή Χρήση\" διαθέσιμη μόνο για τοπικό αποθετήριο." + +#: lib/choose_repository.tcl:604 +#, tcl-format +msgid "Location %s already exists." +msgstr "Η Τοποθεσία %s υπάρχει ήδη." + +#: lib/choose_repository.tcl:615 +msgid "Failed to configure origin" +msgstr "Αποτυχία ρύθμισης πηγής" + +#: lib/choose_repository.tcl:627 +msgid "Counting objects" +msgstr "Γίνεται καταμέτρηση αντικειμένων" + +#: lib/choose_repository.tcl:628 +#, fuzzy +msgid "buckets" +msgstr "" + +#: lib/choose_repository.tcl:652 +#, tcl-format +msgid "Unable to copy objects/info/alternates: %s" +msgstr "Αδυναμία αντιγραφής αντικειμένων/πληροφοριών/ενναλακτικών: %s" + +#: lib/choose_repository.tcl:688 +#, tcl-format +msgid "Nothing to clone from %s." +msgstr "Τίποτα προς κλωνοποίηση από το %s." + +#: lib/choose_repository.tcl:690 lib/choose_repository.tcl:904 +#: lib/choose_repository.tcl:916 +msgid "The 'master' branch has not been initialized." +msgstr "Ο κλάδος 'master' δεν έχει αρχικοποιηθεί." + +#: lib/choose_repository.tcl:703 +msgid "Hardlinks are unavailable. Falling back to copying." +msgstr "Hardlinks μη διαθέσιμα. Μετάπτωση σε αντιγραφή." + +#: lib/choose_repository.tcl:715 +#, tcl-format +msgid "Cloning from %s" +msgstr "Γίνεται κλωνοποίηση από το %s" + +#: lib/choose_repository.tcl:746 +msgid "Copying objects" +msgstr "Γίνεται αντιγραφή αντικειμένων" + +#: lib/choose_repository.tcl:747 +#, fuzzy +msgid "KiB" +msgstr "" + +#: lib/choose_repository.tcl:771 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "Αδυναμία αντιγραφής αντικειμένου: %s" + +#: lib/choose_repository.tcl:781 +msgid "Linking objects" +msgstr "Γίνεται σύνδεση αντικειμένων" + +#: lib/choose_repository.tcl:782 +msgid "objects" +msgstr "αντικείμενα" + +#: lib/choose_repository.tcl:790 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "Αδυναμία hardlink αντικειμένου: %s" + +#: lib/choose_repository.tcl:845 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "" +"Δε μπόρεσε να γίνει ανάκτηση κλάδων και αντικειμένων. Δείτε την έξοδο " +"κονσόλας για λεπτομέρειες." + +#: lib/choose_repository.tcl:856 +msgid "Cannot fetch tags. See console output for details." +msgstr "" +"Δε μπόρεσε να γίνει ανάκτηση ετικετών. Δείτε την έξοδο κονσόλας για " +"λεπτομέρειες." + +#: lib/choose_repository.tcl:880 +msgid "Cannot determine HEAD. See console output for details." +msgstr "" +"Δε μπόρεσε να γίνει καθορισμός του HEAD (παρακλαδιού). Δείτε την έξοδο " +"κονσόλας για " +"λεπτομέρειες." + +#: lib/choose_repository.tcl:889 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "Αδυναμία εκκαθάρισης %s" + +#: lib/choose_repository.tcl:895 +msgid "Clone failed." +msgstr "Αποτυχία κλωνοποίησης." + +#: lib/choose_repository.tcl:902 +msgid "No default branch obtained." +msgstr "Δεν έγινε ανάκτηση προεπιλεγμένου κλάδου." + +#: lib/choose_repository.tcl:913 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "Δε μπόρεσε να επιλυθεί το %s ως υποβολή." + +#: lib/choose_repository.tcl:925 +msgid "Creating working directory" +msgstr "Δημιουργία φακέλου εργασίας" + +#: lib/choose_repository.tcl:926 lib/index.tcl:65 lib/index.tcl:127 +#: lib/index.tcl:193 +msgid "files" +msgstr "αρχεία" + +#: lib/choose_repository.tcl:955 +msgid "Initial file checkout failed." +msgstr "Η αρχική εξαγωγή αρχείου απέτυχε." + +#: lib/choose_repository.tcl:971 +msgid "Open" +msgstr "Άνοιγμα" + +#: lib/choose_repository.tcl:981 +msgid "Repository:" +msgstr "Αποθετήριο:" + +#: lib/choose_repository.tcl:1031 +#, tcl-format +msgid "Failed to open repository %s:" +msgstr "Αποτυχία ανοίγματος αποθετηρίου %s:" + +#: lib/choose_rev.tcl:53 +#, fuzzy +msgid "This Detached Checkout" +msgstr "Αποσυνδεδεμένη Εξαγωγή" + +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "Έκφραση Αναθεώρησης:" + +#: lib/choose_rev.tcl:74 +msgid "Local Branch" +msgstr "Τοπικός Κλάδος" + +#: lib/choose_rev.tcl:79 +msgid "Tracking Branch" +msgstr "Κλάδος Παρακολούθησης" + +#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538 +msgid "Tag" +msgstr "Ετικέτα" + +#: lib/choose_rev.tcl:317 +#, tcl-format +msgid "Invalid revision: %s" +msgstr "Μη έγκυρη αναθεώρηση: %s" + +#: lib/choose_rev.tcl:338 +msgid "No revision selected." +msgstr "Δεν έχει επιλεγεί αναθεώρηση." + +#: lib/choose_rev.tcl:346 +msgid "Revision expression is empty." +msgstr "Η έκφραση αναθεώρησης είναι κενή." + +#: lib/choose_rev.tcl:531 +msgid "Updated" +msgstr "Ενημερωμένο" + +#: lib/choose_rev.tcl:559 +#, fuzzy +msgid "URL" +msgstr "" + +#: lib/commit.tcl:9 +msgid "" +"There is nothing to amend.\n" +"\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" +msgstr "" +"Δεν υπάρχει κάτι προς διόρθωση.\n" +"\n" +"Πρόκειται να δημιουργήσετε την αρχική υποβολή. Δεν υπάρχει υποβολή πριν από " +"αυτή για να διορθώσετε.\n" + +#: lib/commit.tcl:18 +msgid "" +"Cannot amend while merging.\n" +"\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" +msgstr "" +"Δε γίνεται διόρθωση καθώς συγχωνεύετε.\n" +"\n" +"Βρίσκεστε στο μέσο μιας συγχώνευσης που δεν έχει ολοκληρωθεί. Δε μπορείτε να " +"διορθώσετε την προηγούμενη υποβολή εκτός εάν ακυρώσετε την τρέχουσα ενέργεια " +"συγχώνευσης.\n" + +#: lib/commit.tcl:49 +msgid "Error loading commit data for amend:" +msgstr "Σφάλμα φόρτωσης δεδομένων υποβολής προς διόρθωση:" + +#: lib/commit.tcl:76 +msgid "Unable to obtain your identity:" +msgstr "Αδυναμία ανάκτησης της ταυτότητάς σας:" + +#: lib/commit.tcl:81 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "Μη έγκυρο GIT_COMMITTER_IDENT:" + +#: lib/commit.tcl:133 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Η τελευταία κατάσταση που ανιχνεύθηκε δε συμφωνεί με την κατάσταση του " +"αποθετηρίου.\n" +"\n" +"Κάποιο άλλο πρόγραμμα Git τροποποίησε το αποθετήριο από την τελευταία " +"ανίχνευση. Πρέπει να γίνει επανανίχνευση πριν τη δημιουργία νέας υποβολής.\n" +"\n" +"Η επανανίχνευση θα ξεκινήσει αυτόματα τώρα.\n" + +#: lib/commit.tcl:154 +#, tcl-format +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" +"Τα μη συγχωνευμένα αρχεία δε μπορούν να υποβληθούν.\n" +"\n" +"Το αρχείο %s έχει συγκρούσεις συγχώνευσης. Πρέπει να τις επιλύσετε και να " +"σταδιοποιήσετε το αρχείο πριν την υποβολή.\n" + +#: lib/commit.tcl:162 +#, tcl-format +msgid "" +"Unknown file state %s detected.\n" +"\n" +"File %s cannot be committed by this program.\n" +msgstr "" +"Άγνωστη κατάσταση αρχείου %s ανιχνεύθηκε.\n" +"\n" +"Το αρχείο %s δε μπορεί να υποβληθεί από αυτό το πρόγραμμα.\n" + +#: lib/commit.tcl:170 +msgid "" +"No changes to commit.\n" +"\n" +"You must stage at least 1 file before you can commit.\n" +msgstr "" +"Δεν υπάρχουν αλλαγές προς υποβολή.\n" +"\n " +"Πρέπει να σταδιοποιήσετε τουλάχιστον 1 αρχείο πριν να κάνετε υποβολή.\n" + +#: lib/commit.tcl:183 +msgid "" +"Please supply a commit message.\n" +"\n" +"A good commit message has the following format:\n" +"\n" +"- First line: Describe in one sentence what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" +msgstr "" +"Παρακαλώ δώστε ένα μήνυμα υποβολής.\n" +"\n" +"Ένα σωστό μήνυμα υποβολής έχει την εξής μορφή:\n" +"\n" +"- Πρώτη γραμμή: Περιγραφή σε μία πρόταση του τι κάνατε.\n" +"- Δεύτερη γραμμή: Κενή\n" +"- Υπόλοιπες γραμμές: Περιγραφή του γιατί αυτή η αλλαγή είναι σωστή.\n" + +#: lib/commit.tcl:207 +#, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "προειδοποίηση: H Tcl δεν υποστηρίζει την κωδικοποίηση '%s'." + +#: lib/commit.tcl:221 +msgid "Calling pre-commit hook..." +msgstr "Γίνεται κλήση του pre-commit hook..." + +#: lib/commit.tcl:236 +msgid "Commit declined by pre-commit hook." +msgstr "Η υποβολή απορρίφθηκε από το pre-commit hook." + +#: lib/commit.tcl:259 +msgid "Calling commit-msg hook..." +msgstr "Γίνεται κλήση του commit-msg hook..." + +#: lib/commit.tcl:274 +msgid "Commit declined by commit-msg hook." +msgstr "Η υποβολή απορρίφθηκε από το commit-msg hook." + +#: lib/commit.tcl:287 +msgid "Committing changes..." +msgstr "Γίνεται υποβολή των αλλαγών..." + +#: lib/commit.tcl:303 +msgid "write-tree failed:" +msgstr "το write-tree απέτυχε:" + +#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368 +msgid "Commit failed." +msgstr "Η υποβολή απέτυχε." + +#: lib/commit.tcl:321 +#, tcl-format +msgid "Commit %s appears to be corrupt" +msgstr "Η υποβολή %s δείχνει κατεστραμμένη" + +#: lib/commit.tcl:326 +msgid "" +"No changes to commit.\n" +"\n" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" +msgstr "" +"Δεν υπάρχουν αλλαγές προς υποβολή.\n" +"\n" +"Δεν τροποποιήθηκαν αρχεία από αυτή την υποβολή και δεν ήταν υποβολή " +"συγχώνευσης.\n" +"\n" +"Θα ξεκινήσει αυτόματα επανανίχνευση τώρα.\n" + +#: lib/commit.tcl:333 +msgid "No changes to commit." +msgstr "Δεν υπάρχουν αλλαγές προς υποβολή." + +#: lib/commit.tcl:347 +msgid "commit-tree failed:" +msgstr "το commit-tree απέτυχε:" + +#: lib/commit.tcl:367 +msgid "update-ref failed:" +msgstr "το update-ref απέτυχε:" + +#: lib/commit.tcl:454 +#, tcl-format +msgid "Created commit %s: %s" +msgstr "Δημιουργήθηκε υποβολή %s: %s" + +#: lib/console.tcl:59 +msgid "Working... please wait..." +msgstr "Γίνεται εργασία... Παρακαλώ περιμένετε..." + +#: lib/console.tcl:186 +msgid "Success" +msgstr "Επιτυχία" + +#: lib/console.tcl:200 +msgid "Error: Command Failed" +msgstr "Σφάλμα: Η Εντολή Απέτυχε" + +#: lib/database.tcl:43 +msgid "Number of loose objects" +msgstr "Αριθμός ελεύθερων αντικειμένων" + +#: lib/database.tcl:44 +msgid "Disk space used by loose objects" +msgstr "Χώρος κατειλλημένος από ελεύθερα αντικείμενα" + +#: lib/database.tcl:45 +msgid "Number of packed objects" +msgstr "Αριθμός πακεταρισμένων αντικειμένων" + +#: lib/database.tcl:46 +msgid "Number of packs" +msgstr "Αριθμός πακέτων" + +#: lib/database.tcl:47 +msgid "Disk space used by packed objects" +msgstr "Χώρος κατειλλημένος από πακεταρισμένα αντικείμενα" + +#: lib/database.tcl:48 +msgid "Packed objects waiting for pruning" +msgstr "Πακεταρισμένα αντικείμενα έτοιμα για κλάδεμα" + +#: lib/database.tcl:49 +msgid "Garbage files" +msgstr "Άχρηστα αρχεία" + +#: lib/database.tcl:72 +msgid "Compressing the object database" +msgstr "Γίνεται συμπίεση της βάσης δεδομένων αντικειμένων" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "" +"Γίνεται επαλήθευση της βάσης δεδομένων αντικειμένων με αντικείμενα fsck" + +#: lib/database.tcl:108 +#, tcl-format +msgid "" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database when more than %i loose objects exist.\n" +"\n" +"Compress the database now?" +msgstr "" +"Αυτό το αποθετήριο έχει αυτή τη στιγμή περίπου %i ελεύθερα αντικείμενα.\n" +"\n" +"Για τη διατήρηση βέλτιστων επιδόσεων συνιστάται να συμπιέσετε τη βάση " +"δεδομένων όταν υπάρχουν περισσότερα από %i ελεύθερα αντικείμενα.\n" +"\n" +"Συμπίεση της βάσης δεδομένων τώρα;" + +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "Μη έγκυρη ημερομηνία από το Git: %s" + +#: lib/diff.tcl:42 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" +"Δεν ανιχνεύθηκαν διαφορές.\n" +"\n" +"Το %s δεν έχει αλλαγές." +"\n" +"Η ημερομηνία τροποποίησης αυτού του αρχείου ενημερώθηκε από άλλη εφαρμογή, " +"αλλά το περιεχόμενο του αρχείου δεν άλλαξε.\n" +"\n" +"Θα ξεκινήσει αυτόματα επανανίχνευση για να βρεθούν άλλα αρχεία που μπορεί να " +"βρίσκονται σε ίδια κατάσταση." + +#: lib/diff.tcl:81 +#, tcl-format +msgid "Loading diff of %s..." +msgstr "Γίνεται φόρτωση διαφοράς του %s..." + +#: lib/diff.tcl:114 lib/diff.tcl:184 +#, tcl-format +msgid "Unable to display %s" +msgstr "Δεν είναι δυνατή η προβολή του %s" + +#: lib/diff.tcl:115 +msgid "Error loading file:" +msgstr "Σφάλμα φόρτωσης αρχείου:" + +#: lib/diff.tcl:122 +msgid "Git Repository (subproject)" +msgstr "Αποθετήριο Git (θυγατρικό έργο)" + +#: lib/diff.tcl:134 +msgid "* Binary file (not showing content)." +msgstr "* Δυαδικό αρχείο (μη εμφάνιση περιεχομένου)." + +#: lib/diff.tcl:185 +msgid "Error loading diff:" +msgstr "Σφάλμα φόρτωσης διαφοράς:" + +#: lib/diff.tcl:303 +msgid "Failed to unstage selected hunk." +msgstr "Αποτυχία αποσταδιοποίησης επιλεγμένου κομματιού." + +#: lib/diff.tcl:310 +msgid "Failed to stage selected hunk." +msgstr "Αποτυχία σταδιοποίησης επιλεγμένου κομματιού." + +#: lib/error.tcl:20 lib/error.tcl:114 +msgid "error" +msgstr "σφάλμα" + +#: lib/error.tcl:36 +msgid "warning" +msgstr "προειδοποίηση" + +#: lib/error.tcl:94 +msgid "You must correct the above errors before committing." +msgstr "Πρέπει να διορθώσετε τα παραπάνω λάθη πριν την υποβολή." + +#: lib/index.tcl:6 +msgid "Unable to unlock the index." +msgstr "Αδυναμία ξεκλειδώματος του ευρετηρίου." + +#: lib/index.tcl:15 +msgid "Index Error" +msgstr "Σφάλμα Ευρετηρίου" + +#: lib/index.tcl:21 +msgid "" +"Updating the Git index failed. A rescan will be automatically started to " +"resynchronize git-gui." +msgstr "" +"Η ενημέρωση του ευρετηρίου Git απέτυχε. Θα ξεκινήσει αυτόματα επανανίχνευση " +"για επανασυγχρονισμό του git-gui." + +#: lib/index.tcl:27 +msgid "Continue" +msgstr "Συνέχεια" + +#: lib/index.tcl:31 +msgid "Unlock Index" +msgstr "Ξεκλείδωμα Ευρετηρίου" + +#: lib/index.tcl:282 +#, tcl-format +msgid "Unstaging %s from commit" +msgstr "Αποσταδιοποίηση %s από υποβολή" + +#: lib/index.tcl:313 +msgid "Ready to commit." +msgstr "Έτοιμο προς υποβολή." + +#: lib/index.tcl:326 +#, tcl-format +msgid "Adding %s" +msgstr "Προσθήκη %s" + +#: lib/index.tcl:381 +#, tcl-format +msgid "Revert changes in file %s?" +msgstr "Αναίρεση αλλαγών στο αρχείο %s;" + +#: lib/index.tcl:383 +#, tcl-format +msgid "Revert changes in these %i files?" +msgstr "Αναίρεση αλλαγών σε αυτά τα %i αρχεία;" + +#: lib/index.tcl:391 +msgid "Any unstaged changes will be permanently lost by the revert." +msgstr "" +"Όλες οι μη σταδιοποιημένες αλλαγές θα χαθούν οριστικά από την αναίρεση." + +#: lib/index.tcl:394 +msgid "Do Nothing" +msgstr "Καμία Ενέργεια" + +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" +"Δε γίνεται συγχώνευση καθώς διορθώνετε.\n" +"\n" +"Πρέπει να τελειώσετε τη διόρθωση αυτής της υποβολής πριν να ξεκινήσετε " +"οποιασδήποτε μορφής συγχώνευση.\n" + +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"Η τελευταία κατάσταση που ανιχνεύθηκε δε συμφωνεί με την κατάσταση του " +"αποθετηρίου.\n" +"\n" +"Κάποιο άλλο πρόγραμμα Git τροποποίησε το αποθετήριο από την τελευταία " +"ανίχνευση. Πρέπει να γίνει επανανίχνευση πριν τη διενέργεια συγχώνευσης.\n" +"\n" +"Η επανανίχνευση θα ξεκινήσει αυτόματα τώρα.\n" + +#: lib/merge.tcl:44 +#, tcl-format +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" +"Βρίσκεστε στο μέσο μιας συγκρουόμενης συγχώνευσης.\n" +"\n" +"Το αρχείο %s έχει συγκρούσεις συγχώνευσης.\n" +"\n" +"Πρέπει να τις επιλύσετε, να σταδιοποιήσετε το αρχείο, και να κάνετε υποβολή " +"για να ολοκληρώσετε την τρέχουσα συγχώνευση. Μόνο τότε μπορείτε να " +"ξεκινήσετε άλλη συγχώνευση.\n" + +#: lib/merge.tcl:54 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" +"Βρίσκεστε στο μέσο μιας αλλαγής.\n" +"\n" +"Το αρχείο %s έχει τροποποιηθεί.\n" +"\n" +"Πρέπει να ολοκληρώσετε την τρέχουσα συγχώνευση πριν να ξεκινήσετε συγχώνευση." +" Αυτό βοηθά στην ακύρωση αποτυχημένης συγχώνευσης, εάν χρειαστεί.\n" + +#: lib/merge.tcl:106 +#, tcl-format +msgid "%s of %s" +msgstr "%s από %s" + +#: lib/merge.tcl:119 +#, tcl-format +msgid "Merging %s and %s..." +msgstr "Γίνεται συγχώνευση του %s με το %s..." + +#: lib/merge.tcl:130 +msgid "Merge completed successfully." +msgstr "Η συγχώνευση ολοκληρώθηκε επιτυχώς." + +#: lib/merge.tcl:132 +msgid "Merge failed. Conflict resolution is required." +msgstr "Η συγχώνευση απέτυχε. Απαιτείται επίλυση συγκρούσεων." + +#: lib/merge.tcl:157 +#, tcl-format +msgid "Merge Into %s" +msgstr "Συγχώνευση με %s" + +#: lib/merge.tcl:176 +msgid "Revision To Merge" +msgstr "Αναθεώρηση Προς Συγχώνευση" + +#: lib/merge.tcl:211 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" +"Δε γίνεται ακύρωση καθώς διορθώνετε.\n" +"\n" +"Πρέπει να τελειώσετε τη διόρθωση αυτής της υποβολής.\n" + +#: lib/merge.tcl:221 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" +"Ακύρωση συγχώνευσης;\n" +"\n" +"Η ακύρωση της τρέχουσας συγχώνευσης θα προκαλέσει απώλεια *ΟΛΩΝ* των μη " +"υποβεβλημένων αλλαγών.\n" +"\n" +"Να προχωρήσει η ακύρωση της τρέχουσας συγχώνευσης;" + +#: lib/merge.tcl:227 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" +"Επαναφορά αλλαγών;\n" +"\n" +"Η επαναφορά των αλλαγών θα προκαλέσει απώλεια *ΟΛΩΝ* των μη υποβεβλημένων " +"αλλαγών.\n" +"\n" +"Να συνεχίσει η επαναφορά των τρέχουσων αλλαγών;" + +#: lib/merge.tcl:238 +msgid "Aborting" +msgstr "Γίνεται ακύρωση" + +#: lib/merge.tcl:238 +msgid "files reset" +msgstr "αρχεία που επαναφέρθηκαν" + +#: lib/merge.tcl:265 +msgid "Abort failed." +msgstr "Η ακύρωση απέτυχε." + +#: lib/merge.tcl:267 +msgid "Abort completed. Ready." +msgstr "Η ακύρωση απέτυχε. Έτοιμο." + +#: lib/option.tcl:95 +msgid "Restore Defaults" +msgstr "Επαναφορά Προεπιλογών" + +#: lib/option.tcl:99 +msgid "Save" +msgstr "Αποθήκευση" + +#: lib/option.tcl:109 +#, tcl-format +msgid "%s Repository" +msgstr "%s Αποθετήριο" + +#: lib/option.tcl:110 +msgid "Global (All Repositories)" +msgstr "Ολικό (Όλα τα Αποθετήρια)" + +#: lib/option.tcl:116 +msgid "User Name" +msgstr "Όνομα Χρήστη" + +#: lib/option.tcl:117 +msgid "Email Address" +msgstr "Διεύθυνση Email" + +#: lib/option.tcl:119 +msgid "Summarize Merge Commits" +msgstr "Περίληψη Υποβολών Συγχώνευσης" + +#: lib/option.tcl:120 +msgid "Merge Verbosity" +msgstr "Λεπτομέρεια Συγχώνευσης" + +#: lib/option.tcl:121 +msgid "Show Diffstat After Merge" +msgstr "Προβολή Στατιστικών Διαφοράς Μετά από Συγχώνευση" + +#: lib/option.tcl:123 +msgid "Trust File Modification Timestamps" +msgstr "Εμπιστοσύνη Ημερομηνιών Μετατροπής Αρχείων" + +#: lib/option.tcl:124 +msgid "Prune Tracking Branches During Fetch" +msgstr "Κλάδεμα Κλάδων Παρακολούθησης Κατά Την Ανάκτηση" + +#: lib/option.tcl:125 +msgid "Match Tracking Branches" +msgstr "Συμφωνία Κλάδων Παρακολούθησης" + +#: lib/option.tcl:126 +msgid "Number of Diff Context Lines" +msgstr "Αριθμός Γραμμών Εννοιολογικού Πλαισίου Διαφοράς" + +#: lib/option.tcl:127 +msgid "Commit Message Text Width" +msgstr "Πλάτος Κειμένου Μηνύματος Υποβολής" + +#: lib/option.tcl:128 +msgid "New Branch Name Template" +msgstr "Νέο Πρότυπο Ονόματος Κλάδου" + +#: lib/option.tcl:192 +msgid "Spelling Dictionary:" +msgstr "Λεξικό Ορθογραφίας:" + +#: lib/option.tcl:216 +msgid "Change Font" +msgstr "Αλλαγή Γραμματοσειράς" + +#: lib/option.tcl:220 +#, tcl-format +msgid "Choose %s" +msgstr "Επιλογή %s" + +#: lib/option.tcl:226 +#, fuzzy +msgid "pt." +msgstr "" + +#: lib/option.tcl:240 +msgid "Preferences" +msgstr "Προτιμήσεις" + +#: lib/option.tcl:275 +msgid "Failed to completely save options:" +msgstr "Αποτυχία πλήρους αποθήκευσης επιλογών:" + +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Remote Branch" +msgstr "Διαγραφή Απομακρυσμένου Κλάδου" + +#: lib/remote_branch_delete.tcl:47 +msgid "From Repository" +msgstr "Από Αποθετήριο" + +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +msgid "Remote:" +msgstr "Απομακρυσμένο:" + +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 +#, fuzzy +msgid "Arbitrary URL:" +msgstr "Αυθαίρετο URL:" + +#: lib/remote_branch_delete.tcl:84 +msgid "Branches" +msgstr "Κλάδοι" + +#: lib/remote_branch_delete.tcl:109 +msgid "Delete Only If" +msgstr "Διαγραφή Μόνο Εάν" + +#: lib/remote_branch_delete.tcl:111 +msgid "Merged Into:" +msgstr "Συγχωνευμένο Με:" + +#: lib/remote_branch_delete.tcl:119 +msgid "Always (Do not perform merge checks)" +msgstr "Πάντα (Μη διενεργηθούν έλεγχοι συγχώνευσης)" + +#: lib/remote_branch_delete.tcl:152 +msgid "A branch is required for 'Merged Into'." +msgstr "Απαιτείται ένας κλάδος για 'Συγχωνευμένο Με'." + +#: lib/remote_branch_delete.tcl:184 +#, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "" +"Οι εξής κλάδοι δεν είναι πλήρως συγχωνευμένοι με το %s:\n" +"\n" +" - %s" + +#: lib/remote_branch_delete.tcl:189 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" +"Μία ή περισσότερες από τις δοκιμές συγχώνευσης απέτυχαν επειδή δεν έχετε " +"φέρει τις αναγκαίες υποβολές. Δοκιμάστε ανάκτηση από το %s πρώτα." + +#: lib/remote_branch_delete.tcl:207 +msgid "Please select one or more branches to delete." +msgstr "Παρακαλώ επιλέξτε έναν ή περισσότερους κλάδους προς διαγραφή." + +#: lib/remote_branch_delete.tcl:216 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"Η ανάκτηση διεγραμμένων κλάδων είναι δύσκολη.\n" +"\n" +"Διαγραφή των επιλεγμένων κλάδων;" + +#: lib/remote_branch_delete.tcl:226 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "Γίνεται διαγραφή κλάδων από %s" + +#: lib/remote_branch_delete.tcl:286 +msgid "No repository selected." +msgstr "Δεν έχει επιλεγεί αποθετήριο." + +#: lib/remote_branch_delete.tcl:291 +#, tcl-format +msgid "Scanning %s..." +msgstr "Ανίχνευση %s..." + +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "Κλάδεμα από" + +#: lib/remote.tcl:170 +msgid "Fetch from" +msgstr "Ανάκτηση από" + +#: lib/remote.tcl:213 +msgid "Push to" +msgstr "Ώθηση σε" + +#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +msgid "Cannot write shortcut:" +msgstr "Δε μπόρεσε να αποθηκευτεί η συντόμευση:" + +#: lib/shortcut.tcl:136 +msgid "Cannot write icon:" +msgstr "Δε μπόρεσε να αποθηκευτεί το εικονίδιο:" + +#: lib/spellcheck.tcl:57 +msgid "Unsupported spell checker" +msgstr "Mη υποστηριζόμενος ελεγκτής ορθογραφίας" + +#: lib/spellcheck.tcl:65 +msgid "Spell checking is unavailable" +msgstr "Έλεγχος ορθογραφίας μη διαθέσιμος" + +#: lib/spellcheck.tcl:68 +msgid "Invalid spell checking configuration" +msgstr "Μη έγκυρη ρύθμιση ελέγχου ορθογραφίας" + +#: lib/spellcheck.tcl:70 +#, tcl-format +msgid "Reverting dictionary to %s." +msgstr "Γίνεται επαναφορά του λεξικού σε %s." + +#: lib/spellcheck.tcl:73 +msgid "Spell checker silently failed on startup" +msgstr "Ο ελεγκτής ορθογραφίας απέτυχε σιωπηλά κατά την εκκίνηση" + +#: lib/spellcheck.tcl:80 +msgid "Unrecognized spell checker" +msgstr "Μη αναγνωρίσιμος ελεγκτής ορθογραφίας" + +#: lib/spellcheck.tcl:180 +msgid "No Suggestions" +msgstr "Καμία Πρόταση" + +#: lib/spellcheck.tcl:381 +msgid "Unexpected EOF from spell checker" +msgstr "Μη αναμενόμενο τέλος αρχείου από τον ελεγκτή ορθογραφίας" + +#: lib/spellcheck.tcl:385 +msgid "Spell Checker Failed" +msgstr "Αποτυχία Ελεγκτή Ορθογραφίας" + +#: lib/status_bar.tcl:83 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "%s ... %*i από %*i %s (%3i%%)" + +#: lib/transport.tcl:6 +#, tcl-format +msgid "fetch %s" +msgstr "ανάκτηση %s" + +#: lib/transport.tcl:7 +#, tcl-format +msgid "Fetching new changes from %s" +msgstr "Ανάκτηση νέων αλλαγών από το %s" + +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "απομακρυσμένο κλάδεμα %s" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "Γίνεται κλάδεμα κλάδων παρακολούθησης που διεγράφησαν από το %s" + +#: lib/transport.tcl:25 lib/transport.tcl:71 +#, tcl-format +msgid "push %s" +msgstr "ώθηση %s" + +#: lib/transport.tcl:26 +#, tcl-format +msgid "Pushing changes to %s" +msgstr "Γίνεται ώθηση αλλαγών στο %s" + +#: lib/transport.tcl:72 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "Γίνεται ώθηση %s %s στο %s" + +#: lib/transport.tcl:89 +msgid "Push Branches" +msgstr "Ώθηση Κλάδων" + +#: lib/transport.tcl:103 +msgid "Source Branches" +msgstr "Πηγαίοι Κλάδοι" + +#: lib/transport.tcl:120 +msgid "Destination Repository" +msgstr "Αποθετήριο Προορισμού" + +#: lib/transport.tcl:158 +msgid "Transfer Options" +msgstr "Επιλογές Μεταφοράς" + +#: lib/transport.tcl:160 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "" +"Εξαναγκασμός επεγγραφής υπάρχοντος κλάδου (μπορεί να απορρίψει αλλαγές)" + +#: lib/transport.tcl:164 +msgid "Use thin pack (for slow network connections)" +msgstr "Χρήση ισχνού πακέτου (για αργές συνδέσεις δικτύου)" + +#: lib/transport.tcl:168 +msgid "Include tags" +msgstr "Συμπερίληψη ετικετών" + + diff --git a/po/glossary/el.po b/po/glossary/el.po new file mode 100644 index 0000000..1d3cc81 --- /dev/null +++ b/po/glossary/el.po @@ -0,0 +1,171 @@ +# Translation of git-gui glossary to Greek +# Copyright (C) 2009 Jimmy Angelakos +# This file is distributed under the same license as the git-gui package. +# Jimmy Angelakos , 2009. +msgid "" +msgstr "" +"Project-Id-Version: git-gui-glossary\n" +"POT-Creation-Date: 2008-01-07 21:20+0100\n" +"PO-Revision-Date: 2009-06-23 20:41+0300\n" +"Last-Translator: Jimmy Angelakos \n" +"Language-Team: Greek \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 0.3\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)" +msgid "English Term (Dear translator: This file will never be visible to the user!)" +msgstr "" + +#. "" +msgid "amend" +msgstr "διόρθωση" + +#. "" +msgid "annotate" +msgstr "σχολιασμός" + +#. "A 'branch' is an active line of development." +msgid "branch [noun]" +msgstr "κλάδος [αντικείμενο]" + +#. "" +msgid "branch [verb]" +msgstr "διακλάδωση [ενέργεια]" + +#. "" +msgid "checkout [noun]" +msgstr "εξαγωγή [αντικείμενο]" + +#. "The action of updating the working tree to a revision which was stored in the object database." +msgid "checkout [verb]" +msgstr "εξαγωγή [ενέργεια]" + +#. "" +msgid "clone [verb]" +msgstr "κλωνοποίηση [ενέργεια]" + +#. "A single point in the git history." +msgid "commit [noun]" +msgstr "υποβολή [αντικείμενο] " + +#. "The action of storing a new snapshot of the project's state in the git history." +msgid "commit [verb]" +msgstr "υποβολή [ενέργεια]" + +#. "" +msgid "diff [noun]" +msgstr "διαφορά [αντικείμενο] " + +#. "" +msgid "diff [verb]" +msgstr "διαφορά [ενέργεια]" + +#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have." +msgid "fast forward merge" +msgstr "συγχώνευση επιτάχυνσης" + +#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too." +msgid "fetch" +msgstr "ανάκτηση" + +#. "One context of consecutive lines in a whole patch, which consists of many such hunks" +msgid "hunk" +msgstr "κομμάτι" + +#. "A collection of files. The index is a stored version of your working tree." +msgid "index (in git-gui: staging area)" +msgstr "ευρετήριο (στο git-gui: περιοχή σταδιοποίησης)" + +#. "A successful merge results in the creation of a new commit representing the result of the merge." +msgid "merge [noun]" +msgstr "συγχώνευση [αντικείμενο]" + +#. "To bring the contents of another branch into the current branch." +msgid "merge [verb]" +msgstr "συγχώνευση [ενέργεια]" + +#. "" +msgid "message" +msgstr "μήνυμα" + +#. "Deletes all stale tracking branches under . These stale branches have already been removed from the remote repository referenced by , but are still locally available in 'remotes/'." +msgid "prune" +msgstr "κλάδεμα" + +#. "Pulling a branch means to fetch it and merge it." +msgid "pull" +msgstr "λήψη" + +#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)" +msgid "push" +msgstr "ώθηση" + +#. "" +msgid "redo" +msgstr "ξανά" + +#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks." +msgid "remote" +msgstr "απομακρυσμένο" + +#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" +msgid "repository" +msgstr "αποθετήριο" + +#. "" +msgid "reset" +msgstr "επαναφορά" + +#. "" +msgid "revert" +msgstr "αναίρεση" + +#. "A particular state of files and directories which was stored in the object database." +msgid "revision" +msgstr "αναθεώρηση" + +#. "" +#, fuzzy +msgid "sign off" +msgstr "αποσύνδεση" + +#. "" +msgid "staging area" +msgstr "περιοχή σταδιοποίησης" + +#. "" +msgid "status" +msgstr "κατάσταση" + +#. "A ref pointing to a tag or commit object" +msgid "tag [noun]" +msgstr "ετικέτα [αντικείμενο]" + +#. "" +msgid "tag [verb]" +msgstr "ετικέτα [ενέργεια]" + +#. "A regular git branch that is used to follow changes from another repository." +msgid "tracking branch" +msgstr "κλάδος παρακολούθησης" + +#. "" +msgid "undo" +msgstr "αναίρεση" + +#. "" +msgid "update" +msgstr "ενημέρωση" + +#. "" +msgid "verify" +msgstr "επαλήθευση" + +#. "The tree of actual checked out files." +msgid "working copy, working tree" +msgstr "αντίγραφο εργασίας" + + -- cgit v0.10.2-6-g49f6 From 2112be76508aa5d480a8cefeb634444acefcf598 Mon Sep 17 00:00:00 2001 From: Heiko Voigt Date: Mon, 15 Jun 2009 23:19:56 +0200 Subject: git-gui: remove warning when deleting correctly merged remote branch If the user wants to delete a remote branch and selects the correct "merged into" we should not warn that "Recovering deleted branches is difficult". For local branches we do the same already. Signed-off-by: Heiko Voigt Signed-off-by: Shawn O. Pearce diff --git a/lib/remote_branch_delete.tcl b/lib/remote_branch_delete.tcl index 4e02fc0..31e0947 100644 --- a/lib/remote_branch_delete.tcl +++ b/lib/remote_branch_delete.tcl @@ -208,13 +208,15 @@ method _delete {} { return } - if {[tk_messageBox \ - -icon warning \ - -type yesno \ - -title [wm title $w] \ - -parent $w \ - -message [mc "Recovering deleted branches is difficult.\n\nDelete the selected branches?"]] ne yes} { - return + if {$checktype ne {head}} { + if {[tk_messageBox \ + -icon warning \ + -type yesno \ + -title [wm title $w] \ + -parent $w \ + -message [mc "Recovering deleted branches is difficult.\n\nDelete the selected branches?"]] ne yes} { + return + } } destroy $w -- cgit v0.10.2-6-g49f6 From dd6451f9c7c5a36d3006231b618ac6da06c7c7b4 Mon Sep 17 00:00:00 2001 From: Dan Zwell Date: Tue, 11 Aug 2009 13:50:00 -0500 Subject: git-gui: Limit display to a maximum number of files When there is a large number of new or modified files, "display_all_files" takes a long time, and git-gui appears to hang. This change limits the number of files that are displayed. This limit can be set as gui.maxfilesdisplayed, and is 5000 by default. A warning is shown the first time the list of files is truncated in this GUI session. Subsequent truncations are not mentioned to the user. Signed-off-by: Dan Zwell Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 3c0ce26..eae1f81 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -745,6 +745,8 @@ set default_config(gui.newbranchtemplate) {} set default_config(gui.spellingdictionary) {} set default_config(gui.fontui) [font configure font_ui] set default_config(gui.fontdiff) [font configure font_diff] +# TODO: this option should be added to the git-config documentation +set default_config(gui.maxfilesdisplayed) 5000 set font_descs { {fontui font_ui {mc "Main Font"}} {fontdiff font_diff {mc "Diff/Console Font"}} @@ -1698,10 +1700,12 @@ proc display_all_files_helper {w path icon_name m} { $w insert end "[escape_path $path]\n" } +set files_warning 0 proc display_all_files {} { global ui_index ui_workdir global file_states file_lists global last_clicked + global files_warning $ui_index conf -state normal $ui_workdir conf -state normal @@ -1713,7 +1717,18 @@ proc display_all_files {} { set file_lists($ui_index) [list] set file_lists($ui_workdir) [list] - foreach path [lsort [array names file_states]] { + set to_display [lsort [array names file_states]] + set display_limit [get_config gui.maxfilesdisplayed] + if {[llength $to_display] > $display_limit} { + if {!$files_warning} { + # do not repeatedly warn: + set files_warning 1 + info_popup [mc "Displaying only %s of %s files." \ + $display_limit [llength $to_display]] + } + set to_display [lrange $to_display 0 [expr {$display_limit-1}]] + } + foreach path $to_display { set s $file_states($path) set m [lindex $s 0] set icon_name [lindex $s 1] diff --git a/po/git-gui.pot b/po/git-gui.pot index 53b7d36..074582d 100644 --- a/po/git-gui.pot +++ b/po/git-gui.pot @@ -90,6 +90,11 @@ msgstr "" msgid "Ready." msgstr "" +#: git-gui.sh:1726 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "" + #: git-gui.sh:1819 msgid "Unmodified" msgstr "" -- cgit v0.10.2-6-g49f6 From b350e460daac075a676f6ca449a3b83c9dc37a25 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Wed, 12 Aug 2009 17:24:10 +0200 Subject: git-gui: Update russian translation Signed-off-by: Alex Riesen Signed-off-by: Shawn O. Pearce diff --git a/po/ru.po b/po/ru.po index 0ffc4a4..364c074 100644 --- a/po/ru.po +++ b/po/ru.po @@ -90,12 +90,18 @@ msgstr "Вызов программы поддержки репозитория #: git-gui.sh:1384 msgid "Commit declined by prepare-commit-msg hook." -msgstr "Сохранение прервано программой поддержки репозитория prepare-commit-msg" +msgstr "" +"Сохранение прервано программой поддержки репозитория prepare-commit-msg" #: git-gui.sh:1542 lib/browser.tcl:246 msgid "Ready." msgstr "Готово." +#: git-gui.sh:1726 +#, tcl-format +msgid "Displaying only %s of %s files." +msgstr "Показано %s из %s файлов." + #: git-gui.sh:1819 msgid "Unmodified" msgstr "Не изменено" @@ -1297,8 +1303,8 @@ msgid "" msgstr "" "Невозможно исправить состояние во время операции слияния.\n" "\n" -"Текущее слияние не завершено. Невозможно исправить предыдущее " -"сохраненное состояние, не прерывая эту операцию.\n" +"Текущее слияние не завершено. Невозможно исправить предыдущее сохраненное " +"состояние, не прерывая эту операцию.\n" #: lib/commit.tcl:48 msgid "Error loading commit data for amend:" @@ -1723,8 +1729,7 @@ msgid "" msgstr "" "Невозможно выполнить слияние во время исправления.\n" "\n" -"Завершите исправление данного состояния перед выполнением операции " -"слияния.\n" +"Завершите исправление данного состояния перед выполнением операции слияния.\n" #: lib/merge.tcl:27 msgid "" @@ -1888,8 +1893,8 @@ msgstr "" #, tcl-format msgid "File %s seems to have unresolved conflicts, still stage?" msgstr "" -"Файл %s кажется содержит необработаные конфликты. " -"Продолжить подготовку к сохранению?" +"Файл %s кажется содержит необработаные конфликты. Продолжить подготовку к " +"сохранению?" #: lib/mergetool.tcl:60 #, tcl-format @@ -2213,8 +2218,8 @@ msgid "" "One or more of the merge tests failed because you have not fetched the " "necessary commits. Try fetching from %s first." msgstr "" -"Некоторые тесты на слияние не прошли, потому что Вы не " -"получили необходимые состояния. Попытайтесь получить их из %s." +"Некоторые тесты на слияние не прошли, потому что Вы не получили необходимые " +"состояния. Попытайтесь получить их из %s." #: lib/remote_branch_delete.tcl:207 msgid "Please select one or more branches to delete." @@ -2381,8 +2386,8 @@ msgstr "Выполнение: %s" #: lib/tools.tcl:149 #, tcl-format -msgid "Tool completed succesfully: %s" -msgstr "Программа %s успешно завершилась." +msgid "Tool completed successfully: %s" +msgstr "Программа %s завершилась успешно." #: lib/tools.tcl:151 #, tcl-format @@ -2538,4 +2543,3 @@ msgstr "Использовать thin pack (для медленных сетев #: lib/transport.tcl:179 msgid "Include tags" msgstr "Передать метки" - -- cgit v0.10.2-6-g49f6 From af413de47b8b285ffa489df14023180456986c05 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Wed, 26 Aug 2009 22:25:15 +0200 Subject: git-gui: fix diff for partially staged submodule changes When a submodule commit had already been staged and another commit had been checked out inside the submodule, the diff always displayed the submodule commit log messages between the last supermodule commit and the working tree, totally ignoring the commit in the index. Signed-off-by: Jens Lehmann Signed-off-by: Shawn O. Pearce diff --git a/lib/diff.tcl b/lib/diff.tcl index ae1ea3a..d593323 100644 --- a/lib/diff.tcl +++ b/lib/diff.tcl @@ -298,7 +298,12 @@ proc start_show_diff {cont_info {add_opts {}}} { if {[string match {160000 *} [lindex $s 2]] || [string match {160000 *} [lindex $s 3]]} { - set cmd {submodule summary -- $current_diff_path} + set is_submodule_diff 1 + if {$w eq $ui_index} { + set cmd {submodule summary --cached -- $current_diff_path} + } else { + set cmd {submodule summary --files -- $current_diff_path} + } } if {[catch {set fd [eval git_read --nice $cmd]} err]} { @@ -343,9 +348,6 @@ proc read_diff {fd cont_info} { } set ::current_diff_inheader 0 - if {[regexp {^\* } $line]} { - set is_submodule_diff 1 - } # -- Automatically detect if this is a 3 way diff. # if {[string match {@@@ *} $line]} {set is_3way_diff 1} -- cgit v0.10.2-6-g49f6 From 118d938812f3fc660f43bad9b546e7dadc3571a9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 26 Aug 2009 17:39:45 -0700 Subject: git-gui: Ensure submodule path is quoted properly When quoting an arbitrary user string in Tcl, its better to use [list ...] than to use {...}, in case the user string has spaces or { embedded within it. Signed-off-by: Shawn O. Pearce diff --git a/lib/diff.tcl b/lib/diff.tcl index d593323..bd5d189 100644 --- a/lib/diff.tcl +++ b/lib/diff.tcl @@ -300,9 +300,9 @@ proc start_show_diff {cont_info {add_opts {}}} { || [string match {160000 *} [lindex $s 3]]} { set is_submodule_diff 1 if {$w eq $ui_index} { - set cmd {submodule summary --cached -- $current_diff_path} + set cmd [list submodule summary --cached -- $path] } else { - set cmd {submodule summary --files -- $current_diff_path} + set cmd [list submodule summary --files -- $path] } } -- cgit v0.10.2-6-g49f6 From ef2035c5e55f852905b012dfb2dd242b4f77da70 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Sep 2009 01:41:47 -0700 Subject: apply --whitespace=fix: fix handling of blank lines at the eof b94f2ed (builtin-apply.c: make it more line oriented, 2008-01-26) broke the logic used to detect if a hunk adds blank lines at the end of the file. With the new code after that commit: - img holds the contents of the file that the hunk is being applied to; - preimage has the lines the hunk expects to be in img; and - postimage has the lines the hunk wants to update the part in img that corresponds to preimage with. and we need to compare if the last line of preimage (not postimage) matches the last line of img to see if the hunk applies at the end of the file. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 7a1ff04..5b5bde4 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2069,7 +2069,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, if (applied_pos >= 0) { if (ws_error_action == correct_ws_error && new_blank_lines_at_end && - postimage.nr + applied_pos == img->nr) { + preimage.nr + applied_pos == img->nr) { /* * If the patch application adds blank lines * at the end, and if the patch applies at the diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index f83322e..6898722 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -148,4 +148,33 @@ do done done + +test_expect_success 'blank at EOF with --whitespace=fix (1)' ' + : these can fail depending on what we did before + git config --unset core.whitespace + rm -f .gitattributes + + { echo a; echo b; echo c; } >one && + git add one && + { echo a; echo b; echo c; } >expect && + { cat expect; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at EOF with --whitespace=fix (2)' ' + { echo a; echo b; echo c; } >one && + git add one && + { echo a; echo c; } >expect && + { cat expect; echo; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + test_done -- cgit v0.10.2-6-g49f6 From efa574438fca4ed328d2e47ecdb6363c50a905ec Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 14:08:20 -0700 Subject: apply --whitespace=fix: detect new blank lines at eof correctly The command tries to strip blank lines at the end of the file added by a patch. It is done by first detecting if a hunk in patch has additional blank lines at the end of itself, and if so checking if such a hunk applies at the end of file. This patch addresses a bug in the logic to implement the former (the previous one addressed a bug in the latter). If the original ends with blank lines, often the patch hunk ends like this: @@ -l,5 +m,7 @@$ _context$ _context$ -deleted$ +$ +$ +$ _$ _$ where _ stands for SP and $ shows a end-of-line. This example patch adds three trailing blank lines, but the code fails to notice it, because it only pays attention to added blank lines at the very end of the hunk. In this example, the three added blank lines do not appear textually at the end in the patch, even though you can see that they are indeed added at the end, if you rearrange the diff like this: @@ -l,5 +m,7 @@$ _context$ _context$ -deleted$ _$ _$ +$ +$ +$ The fix is not to reset the number of (candidate) added blank lines at the end when the loop sees a context line that is empty. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 5b5bde4..c5e4048 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1913,6 +1913,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, int len = linelen(patch, size); int plen, added; int added_blank_line = 0; + int is_blank_context = 0; if (!len) break; @@ -1945,8 +1946,11 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, *new++ = '\n'; add_line_info(&preimage, "\n", 1, LINE_COMMON); add_line_info(&postimage, "\n", 1, LINE_COMMON); + is_blank_context = 1; break; case ' ': + if (plen && patch[1] == '\n') + is_blank_context = 1; case '-': memcpy(old, patch + 1, plen); add_line_info(&preimage, old, plen, @@ -1986,6 +1990,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } if (added_blank_line) new_blank_lines_at_end++; + else if (is_blank_context) + ; else new_blank_lines_at_end = 0; patch += len; diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 6898722..fedc8b9 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -177,4 +177,28 @@ test_expect_success 'blank at EOF with --whitespace=fix (2)' ' test_cmp expect one ' +test_expect_success 'blank at EOF with --whitespace=fix (3)' ' + { echo a; echo b; echo; } >one && + git add one && + { echo a; echo c; echo; } >expect && + { cat expect; echo; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at end of hunk, not at EOF with --whitespace=fix' ' + { echo a; echo b; echo; echo; echo; echo; echo; echo d; } >one && + git add one && + { echo a; echo c; echo; echo; echo; echo; echo; echo; echo d; } >expect && + cp expect one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + test_done -- cgit v0.10.2-6-g49f6 From 92a1747eeab6bc497db748e79ca4f029b1c1dc10 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 22:26:33 -0700 Subject: apply.c: split check_whitespace() into two This splits the logic to record the presence of whitespace errors out of the check_whitespace() function, which checks and then records. The new function, record_ws_error(), can be used by the blank-at-eof check that does not use ws_check() logic to report its findings in the same output format. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index c5e4048..80ddf55 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1055,23 +1055,29 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc return -1; } -static void check_whitespace(const char *line, int len, unsigned ws_rule) +static void record_ws_error(unsigned result, const char *line, int len, int linenr) { char *err; - unsigned result = ws_check(line + 1, len - 1, ws_rule); + if (!result) return; whitespace_error++; if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) - ; - else { - err = whitespace_error_string(result); - fprintf(stderr, "%s:%d: %s.\n%.*s\n", - patch_input_file, linenr, err, len - 2, line + 1); - free(err); - } + return; + + err = whitespace_error_string(result); + fprintf(stderr, "%s:%d: %s.\n%.*s\n", + patch_input_file, linenr, err, len, line); + free(err); +} + +static void check_whitespace(const char *line, int len, unsigned ws_rule) +{ + unsigned result = ws_check(line + 1, len - 1, ws_rule); + + record_ws_error(result, line + 1, len - 2, linenr); } /* -- cgit v0.10.2-6-g49f6 From 77b15bbd88e2f48de093ff0e60de6dbc11e3329e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 16:02:32 -0700 Subject: apply --whitespace=warn/error: diagnose blank at EOF "git apply" strips new blank lines at EOF under --whitespace=fix option, but neigher --whitespace=warn nor --whitespace=error paid any attention to these errors. Introduce a new whitespace error class, blank-at-eof, to make the whitespace error handling more consistent. The patch adds a new "linenr" field to the struct fragment in order to record which line the hunk started in the input file, but this is needed solely for reporting purposes. The detection of this class of whitespace errors cannot be done while parsing a patch like we do for all the other classes of whitespace errors. It instead has to wait until we find where to apply the hunk, but at that point, we do not have an access to the original line number in the input file anymore, hence the new field. Depending on your point of view, this may be a bugfix that makes warn and error in line with fix. Or you could call it a new feature. The line between them is somewhat fuzzy in this case. Strictly speaking, triggering more errors than before is a change in behaviour that is not backward compatible, even though the reason for the change is because the code was not checking for an error that it should have. People who do not want added blank lines at EOF to trigger an error can disable the new error class. Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 113d9d1..871384e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -389,6 +389,8 @@ core.whitespace:: error (enabled by default). * `indent-with-non-tab` treats a line that is indented with 8 or more space characters as an error (not enabled by default). +* `blank-at-eof` treats blank lines added at the end of file as an error + (enabled by default). * `cr-at-eol` treats a carriage-return at the end of line as part of the line terminator, i.e. with it, `trailing-space` does not trigger if the character before such a carriage-return diff --git a/builtin-apply.c b/builtin-apply.c index 80ddf55..37d3bc0 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -126,6 +126,7 @@ struct fragment { const char *patch; int size; int rejected; + int linenr; struct fragment *next; }; @@ -1193,6 +1194,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc int len; fragment = xcalloc(1, sizeof(*fragment)); + fragment->linenr = linenr; len = parse_fragment(line, size, patch, fragment); if (len <= 0) die("corrupt patch at line %d", linenr); @@ -2079,17 +2081,24 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } if (applied_pos >= 0) { - if (ws_error_action == correct_ws_error && - new_blank_lines_at_end && - preimage.nr + applied_pos == img->nr) { + if (new_blank_lines_at_end && + preimage.nr + applied_pos == img->nr && + (ws_rule & WS_BLANK_AT_EOF) && + ws_error_action != nowarn_ws_error) { + record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr); + if (ws_error_action == correct_ws_error) { + while (new_blank_lines_at_end--) + remove_last_line(&postimage); + } /* - * If the patch application adds blank lines - * at the end, and if the patch applies at the - * end of the image, remove those added blank - * lines. + * We would want to prevent write_out_results() + * from taking place in apply_patch() that follows + * the callchain led us here, which is: + * apply_patch->check_patch_list->check_patch-> + * apply_data->apply_fragments->apply_one_fragment */ - while (new_blank_lines_at_end--) - remove_last_line(&postimage); + if (ws_error_action == die_on_ws_error) + apply = 0; } /* diff --git a/cache.h b/cache.h index 099a32e..7152fea 100644 --- a/cache.h +++ b/cache.h @@ -845,7 +845,8 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i #define WS_SPACE_BEFORE_TAB 02 #define WS_INDENT_WITH_NON_TAB 04 #define WS_CR_AT_EOL 010 -#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) +#define WS_BLANK_AT_EOF 020 +#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|WS_BLANK_AT_EOF) extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); extern unsigned parse_whitespace_rule(const char *); diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index fedc8b9..3a77a9a 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -201,4 +201,30 @@ test_expect_success 'blank at end of hunk, not at EOF with --whitespace=fix' ' test_cmp expect one ' +test_expect_success 'blank at EOF with --whitespace=warn' ' + { echo a; echo b; echo c; } >one && + git add one && + echo >>one && + cat one >expect && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=warn patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + +test_expect_success 'blank at EOF with --whitespace=error' ' + { echo a; echo b; echo c; } >one && + git add one && + cat one >expect && + echo >>one && + git diff -- one >patch && + + git checkout one && + test_must_fail git apply --whitespace=error patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + test_done diff --git a/ws.c b/ws.c index 7a7ff13..d56636b 100644 --- a/ws.c +++ b/ws.c @@ -15,6 +15,7 @@ static struct whitespace_rule { { "space-before-tab", WS_SPACE_BEFORE_TAB }, { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB }, { "cr-at-eol", WS_CR_AT_EOL }, + { "blank-at-eof", WS_BLANK_AT_EOF }, }; unsigned parse_whitespace_rule(const char *string) @@ -113,6 +114,11 @@ char *whitespace_error_string(unsigned ws) strbuf_addstr(&err, ", "); strbuf_addstr(&err, "indent with spaces"); } + if (ws & WS_BLANK_AT_EOF) { + if (err.len) + strbuf_addstr(&err, ", "); + strbuf_addstr(&err, "new blank line at EOF"); + } return strbuf_detach(&err, NULL); } -- cgit v0.10.2-6-g49f6 From 94ea026b358f9fd5395d9344a80f2d5a50e93e1f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Sep 2009 02:25:57 -0700 Subject: apply --whitespace: warn blank but not necessarily empty lines at EOF The whitespace error of adding blank lines at the end of file should trigger if you added a non-empty line at the end, if the contents of the line is full of whitespaces. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 37d3bc0..6662cc4 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1957,7 +1957,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, is_blank_context = 1; break; case ' ': - if (plen && patch[1] == '\n') + if (plen && (ws_rule & WS_BLANK_AT_EOF) && + ws_blank_line(patch + 1, plen, ws_rule)) is_blank_context = 1; case '-': memcpy(old, patch + 1, plen); @@ -1985,7 +1986,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, (first == '+' ? 0 : LINE_COMMON)); new += added; if (first == '+' && - added == 1 && new[-1] == '\n') + (ws_rule & WS_BLANK_AT_EOF) && + ws_blank_line(patch + 1, plen, ws_rule)) added_blank_line = 1; break; case '@': case '\\': diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 3a77a9a..778d45b 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -227,4 +227,17 @@ test_expect_success 'blank at EOF with --whitespace=error' ' grep "new blank line at EOF" error ' +test_expect_success 'blank but not empty at EOF' ' + { echo a; echo b; echo c; } >one && + git add one && + echo " " >>one && + cat one >expect && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=warn patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + test_done -- cgit v0.10.2-6-g49f6 From b8d9c1a66b99ad3ca8069add010dafdd1bc6cab8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 23:59:25 -0700 Subject: diff.c: the builtin_diff() deals with only two-file comparison The combined diff is implemented in combine_diff() and fn_out_consume() codepath never has to deal with anything but two-file comparision. Drop nparents from the emit_callback structure and simplify the code. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 6fea3c0..1eddd59 100644 --- a/diff.c +++ b/diff.c @@ -489,7 +489,7 @@ typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); struct emit_callback { struct xdiff_emit_state xm; - int nparents, color_diff; + int color_diff; unsigned ws_rule; sane_truncate_fn truncate; const char **label_path; @@ -549,9 +549,8 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons emit_line(ecbdata->file, set, reset, line, len); else { /* Emit just the prefix, then the rest. */ - emit_line(ecbdata->file, set, reset, line, ecbdata->nparents); - ws_check_emit(line + ecbdata->nparents, - len - ecbdata->nparents, ecbdata->ws_rule, + emit_line(ecbdata->file, set, reset, line, 1); + ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, ecbdata->file, set, reset, ws); } } @@ -576,7 +575,6 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u static void fn_out_consume(void *priv, char *line, unsigned long len) { - int i; int color; struct emit_callback *ecbdata = priv; const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); @@ -598,13 +596,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } - /* This is not really necessary for now because - * this codepath only deals with two-way diffs. - */ - for (i = 0; i < len && line[i] == '@'; i++) - ; - if (2 <= i && i < len && line[i] == ' ') { - ecbdata->nparents = i - 1; + if (line[0] == '@') { len = sane_truncate_line(ecbdata, line, len); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), @@ -614,15 +606,12 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } - if (len < ecbdata->nparents) { + if (len < 1) { emit_line(ecbdata->file, reset, reset, line, len); return; } color = DIFF_PLAIN; - if (ecbdata->diff_words && ecbdata->nparents != 1) - /* fall back to normal diff */ - free_diff_words_data(ecbdata); if (ecbdata->diff_words) { if (line[0] == '-') { diff_words_append(line, len, @@ -641,13 +630,10 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) emit_line(ecbdata->file, plain, reset, line, len); return; } - for (i = 0; i < ecbdata->nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } - + if (line[0] == '-') + color = DIFF_FILE_OLD; + else if (line[0] == '+') + color = DIFF_FILE_NEW; if (color != DIFF_FILE_NEW) { emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, color), -- cgit v0.10.2-6-g49f6 From 5b5061efd88e1d113a4484369dfab654b43364de Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 22:30:27 -0700 Subject: diff --whitespace=warn/error: obey blank-at-eof The "diff --check" code used to conflate trailing-space whitespace error class with this, but now we have a proper separate error class, we should check it under blank-at-eof, not trailing-space. The whitespace error is not about _having_ blank lines at end, but about adding _new_ blank lines. To keep the message consistent with what is given by "git apply", call whitespace_error_string() to generate it, instead of using a hardcoded custom message. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 1eddd59..a693d18 100644 --- a/diff.c +++ b/diff.c @@ -1650,10 +1650,14 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, ecb.priv = &data; xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - if ((data.ws_rule & WS_TRAILING_SPACE) && + if ((data.ws_rule & WS_BLANK_AT_EOF) && data.trailing_blanks_start) { - fprintf(o->file, "%s:%d: ends with blank lines.\n", - data.filename, data.trailing_blanks_start); + static char *err; + + if (!err) + err = whitespace_error_string(WS_BLANK_AT_EOF); + fprintf(o->file, "%s:%d: %s\n", + data.filename, data.trailing_blanks_start, err); data.status = 1; /* report errors */ } } diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index b1cbd36..a5d4461 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -335,10 +335,10 @@ test_expect_success 'line numbers in --check output are correct' ' ' -test_expect_success 'checkdiff detects trailing blank lines' ' +test_expect_success 'checkdiff detects new trailing blank lines (1)' ' echo "foo();" >x && echo "" >>x && - git diff --check | grep "ends with blank" + git diff --check | grep "new blank line" ' test_expect_success 'checkdiff allows new blank lines' ' diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 84a1fe3..1517fff 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -165,7 +165,7 @@ test_expect_success 'trailing empty lines (1)' ' rm -f .gitattributes && test_must_fail git diff --check >output && - grep "ends with blank lines." output && + grep "new blank line at" output && grep "trailing whitespace" output ' -- cgit v0.10.2-6-g49f6 From 467babf8d059caee9587567452fc8b46505b4e67 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 23:39:43 -0700 Subject: diff --whitespace=warn/error: fix blank-at-eof check The "diff --check" logic used to share the same issue as the one fixed for "git apply" earlier in this series, in that a patch that adds new blank lines at end could appear as @@ -l,5 +m,7 @@$ _context$ _context$ -deleted$ +$ +$ +$ _$ _$ where _ stands for SP and $ shows a end-of-line. Instead of looking at each line in the patch in the callback, simply count the blank lines from the end in two versions, and notice the presence of new ones. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index a693d18..c19c476 100644 --- a/diff.c +++ b/diff.c @@ -1149,7 +1149,6 @@ struct checkdiff_t { struct diff_options *o; unsigned ws_rule; unsigned status; - int trailing_blanks_start; }; static int is_conflict_marker(const char *line, unsigned long len) @@ -1193,10 +1192,6 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (line[0] == '+') { unsigned bad; data->lineno++; - if (!ws_blank_line(line + 1, len - 1, data->ws_rule)) - data->trailing_blanks_start = 0; - else if (!data->trailing_blanks_start) - data->trailing_blanks_start = data->lineno; if (is_conflict_marker(line + 1, len - 1)) { data->status |= 1; fprintf(data->o->file, @@ -1216,14 +1211,12 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) data->o->file, set, reset, ws); } else if (line[0] == ' ') { data->lineno++; - data->trailing_blanks_start = 0; } else if (line[0] == '@') { char *plus = strchr(line, '+'); if (plus) data->lineno = strtol(plus, NULL, 10) - 1; else die("invalid diff"); - data->trailing_blanks_start = 0; } } @@ -1437,6 +1430,44 @@ static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_fi return NULL; } +static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) +{ + char *ptr = mf->ptr; + long size = mf->size; + int cnt = 0; + + if (!size) + return cnt; + ptr += size - 1; /* pointing at the very end */ + if (*ptr != '\n') + ; /* incomplete line */ + else + ptr--; /* skip the last LF */ + while (mf->ptr < ptr) { + char *prev_eol; + for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--) + if (*prev_eol == '\n') + break; + if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule)) + break; + cnt++; + ptr = prev_eol - 1; + } + return cnt; +} + +static int adds_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, unsigned ws_rule) +{ + int l1, l2, at; + l1 = count_trailing_blank(mf1, ws_rule); + l2 = count_trailing_blank(mf2, ws_rule); + if (l2 <= l1) + return 0; + /* starting where? */ + at = count_lines(mf1->ptr, mf1->size); + return (at - l1) + 1; /* the line number counts from 1 */ +} + static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -1650,15 +1681,16 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, ecb.priv = &data; xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - if ((data.ws_rule & WS_BLANK_AT_EOF) && - data.trailing_blanks_start) { - static char *err; - - if (!err) - err = whitespace_error_string(WS_BLANK_AT_EOF); - fprintf(o->file, "%s:%d: %s\n", - data.filename, data.trailing_blanks_start, err); - data.status = 1; /* report errors */ + if (data.ws_rule & WS_BLANK_AT_EOF) { + int blank_at_eof = adds_blank_at_eof(&mf1, &mf2, data.ws_rule); + if (blank_at_eof) { + static char *err; + if (!err) + err = whitespace_error_string(WS_BLANK_AT_EOF); + fprintf(o->file, "%s:%d: %s.\n", + data.filename, blank_at_eof, err); + data.status = 1; /* report errors */ + } } } free_and_return: diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index a5d4461..e0b481d 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -341,6 +341,13 @@ test_expect_success 'checkdiff detects new trailing blank lines (1)' ' git diff --check | grep "new blank line" ' +test_expect_success 'checkdiff detects new trailing blank lines (2)' ' + { echo a; echo b; echo; echo; } >x && + git add x && + { echo a; echo; echo; echo; echo; } >x && + git diff --check | grep "new blank line" +' + test_expect_success 'checkdiff allows new blank lines' ' git checkout x && mv x y && -- cgit v0.10.2-6-g49f6 From 690ed8436326484fe7e3f4deac4cffd780c7d630 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Sep 2009 00:41:15 -0700 Subject: diff --color: color blank-at-eof Since the coloring logic processed the patch output one line at a time, we couldn't easily color code the new blank lines at the end of file. Reuse the adds_blank_at_eof() function to find where the runs of such blank lines start, keep track of the line number in the preimage while processing the patch output one line at a time, and paint the new blank lines that appear after that line to implement this. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index c19c476..2b285b8 100644 --- a/diff.c +++ b/diff.c @@ -491,6 +491,8 @@ struct emit_callback { struct xdiff_emit_state xm; int color_diff; unsigned ws_rule; + int blank_at_eof; + int lno_in_preimage; sane_truncate_fn truncate; const char **label_path; struct diff_words_data *diff_words; @@ -547,6 +549,12 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons if (!*ws) emit_line(ecbdata->file, set, reset, line, len); + else if ((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof && + (ecbdata->blank_at_eof <= ecbdata->lno_in_preimage) && + ws_blank_line(line + 1, len - 1, ecbdata->ws_rule)) + /* Blank line at EOF */ + emit_line(ecbdata->file, ws, reset, line, len); else { /* Emit just the prefix, then the rest. */ emit_line(ecbdata->file, set, reset, line, 1); @@ -573,9 +581,16 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u return allot - l; } +static int find_preimage_lno(const char *line) +{ + char *p = strchr(line, '-'); + if (!p) + return 0; /* should not happen */ + return strtol(p+1, NULL, 10); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { - int color; struct emit_callback *ecbdata = priv; const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); @@ -598,6 +613,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (line[0] == '@') { len = sane_truncate_line(ecbdata, line, len); + ecbdata->lno_in_preimage = find_preimage_lno(line); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), reset, line, len); @@ -611,7 +627,6 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } - color = DIFF_PLAIN; if (ecbdata->diff_words) { if (line[0] == '-') { diff_words_append(line, len, @@ -630,14 +645,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) emit_line(ecbdata->file, plain, reset, line, len); return; } - if (line[0] == '-') - color = DIFF_FILE_OLD; - else if (line[0] == '+') - color = DIFF_FILE_NEW; - if (color != DIFF_FILE_NEW) { - emit_line(ecbdata->file, - diff_get_color(ecbdata->color_diff, color), - reset, line, len); + + if (line[0] != '+') { + const char *color = + diff_get_color(ecbdata->color_diff, + line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN); + ecbdata->lno_in_preimage++; + emit_line(ecbdata->file, color, reset, line, len); return; } emit_add_line(reset, ecbdata, line, len); @@ -1557,6 +1571,9 @@ static void builtin_diff(const char *name_a, ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + if (ecbdata.ws_rule & WS_BLANK_AT_EOF) + ecbdata.blank_at_eof = + adds_blank_at_eof(&mf1, &mf2, ecbdata.ws_rule); ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 1517fff..1e75f1a 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -190,4 +190,13 @@ test_expect_success 'do not color trailing cr in context' ' ' +test_expect_success 'color new trailing blank lines' ' + { echo a; echo b; echo; echo; } >x && + git add x && + { echo a; echo; echo; echo; echo; } >x && + git diff --color x >output && + cnt=$(grep "${blue_grep}" output | wc -l) && + test $cnt = 2 +' + test_done -- cgit v0.10.2-6-g49f6 From aeb84b05ae448596c336807631d9633492b3049a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 5 Sep 2009 22:21:17 -0700 Subject: core.whitespace: split trailing-space into blank-at-{eol,eof} People who configured trailing-space depended on it to catch both extra white space at the end of line, and extra blank lines at the end of file. Earlier attempt to introduce only blank-at-eof gave them an escape hatch to keep the old behaviour, but it is a regression until they explicitly specify the new error class. This introduces a blank-at-eol that only catches extra white space at the end of line, and makes the traditional trailing-space a convenient synonym to catch both blank-at-eol and blank-at-eof. This way, people who used trailing-space continue to catch both classes of errors. Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 871384e..2a2f7fc 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -382,7 +382,7 @@ core.whitespace:: consider them as errors. You can prefix `-` to disable any of them (e.g. `-trailing-space`): + -* `trailing-space` treats trailing whitespaces at the end of the line +* `blank-at-eol` treats trailing whitespaces at the end of the line as an error (enabled by default). * `space-before-tab` treats a space character that appears immediately before a tab character in the initial indent part of the line as an @@ -391,6 +391,8 @@ core.whitespace:: space characters as an error (not enabled by default). * `blank-at-eof` treats blank lines added at the end of file as an error (enabled by default). +* `trailing-space` is a short-hand to cover both `blank-at-eol` and + `blank-at-eof`. * `cr-at-eol` treats a carriage-return at the end of line as part of the line terminator, i.e. with it, `trailing-space` does not trigger if the character before such a carriage-return diff --git a/cache.h b/cache.h index 7152fea..ee12e74 100644 --- a/cache.h +++ b/cache.h @@ -841,12 +841,13 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i * whitespace rules. * used by both diff and apply */ -#define WS_TRAILING_SPACE 01 +#define WS_BLANK_AT_EOL 01 #define WS_SPACE_BEFORE_TAB 02 #define WS_INDENT_WITH_NON_TAB 04 #define WS_CR_AT_EOL 010 #define WS_BLANK_AT_EOF 020 -#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|WS_BLANK_AT_EOF) +#define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF) +#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); extern unsigned parse_whitespace_rule(const char *); diff --git a/ws.c b/ws.c index d56636b..cd03bc0 100644 --- a/ws.c +++ b/ws.c @@ -15,6 +15,7 @@ static struct whitespace_rule { { "space-before-tab", WS_SPACE_BEFORE_TAB }, { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB }, { "cr-at-eol", WS_CR_AT_EOL }, + { "blank-at-eol", WS_BLANK_AT_EOL }, { "blank-at-eof", WS_BLANK_AT_EOF }, }; @@ -101,9 +102,19 @@ unsigned whitespace_rule(const char *pathname) char *whitespace_error_string(unsigned ws) { struct strbuf err; + strbuf_init(&err, 0); - if (ws & WS_TRAILING_SPACE) + if ((ws & WS_TRAILING_SPACE) == WS_TRAILING_SPACE) strbuf_addstr(&err, "trailing whitespace"); + else { + if (ws & WS_BLANK_AT_EOL) + strbuf_addstr(&err, "trailing whitespace"); + if (ws & WS_BLANK_AT_EOF) { + if (err.len) + strbuf_addstr(&err, ", "); + strbuf_addstr(&err, "new blank line at EOF"); + } + } if (ws & WS_SPACE_BEFORE_TAB) { if (err.len) strbuf_addstr(&err, ", "); @@ -114,11 +125,6 @@ char *whitespace_error_string(unsigned ws) strbuf_addstr(&err, ", "); strbuf_addstr(&err, "indent with spaces"); } - if (ws & WS_BLANK_AT_EOF) { - if (err.len) - strbuf_addstr(&err, ", "); - strbuf_addstr(&err, "new blank line at EOF"); - } return strbuf_detach(&err, NULL); } @@ -146,11 +152,11 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, } /* Check for trailing whitespace. */ - if (ws_rule & WS_TRAILING_SPACE) { + if (ws_rule & WS_BLANK_AT_EOL) { for (i = len - 1; i >= 0; i--) { if (isspace(line[i])) { trailing_whitespace = i; - result |= WS_TRAILING_SPACE; + result |= WS_BLANK_AT_EOL; } else break; @@ -266,7 +272,7 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro /* * Strip trailing whitespace */ - if ((ws_rule & WS_TRAILING_SPACE) && + if ((ws_rule & WS_BLANK_AT_EOL) && (2 <= len && isspace(src[len-2]))) { if (src[len - 1] == '\n') { add_nl_to_tail = 1; -- cgit v0.10.2-6-g49f6 From e4b48eaab724d9fd5941c6a683a34ee38a864897 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 7 Sep 2009 14:40:00 +0200 Subject: gitweb: Add 'show-sizes' feature to show blob sizes in tree view Add support for 'show-sizes' feature to show (in separate column, between mode and filename) the size of blobs (files) in the 'tree' view. It passes '-l' option to "git ls-tree" invocation. For the 'tree' and 'commit' (submodule) entries, '-' is shown in place of size; for generated '..' "up directory" entry nothing is shown. The 'show-sizes' feature is enabled by default. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 8f68fe3..d60bfc1 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -341,6 +341,12 @@ td.mode { font-family: monospace; } +/* format of (optional) objects size in 'tree' view */ +td.size { + font-family: monospace; + text-align: right; +} + /* styling of diffs (patchsets): commitdiff and blobdiff views */ div.diff.header, div.diff.extended_header { diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24b2193..7b1c60e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -297,6 +297,19 @@ our %feature = ( 'override' => 0, 'default' => [1]}, + # Enable showing size of blobs in a 'tree' view, in a separate + # column, similar to what 'ls -l' does. This cost a bit of IO. + + # To disable system wide have in $GITWEB_CONFIG + # $feature{'show-sizes'}{'default'} = [0]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'show-sizes'}{'override'} = 1; + # and in project config gitweb.showsizes = 0|1; + 'show-sizes' => { + 'sub' => sub { feature_bool('showsizes', @_) }, + 'override' => 0, + 'default' => [1]}, + # Make gitweb use an alternative format of the URLs which can be # more readable and natural-looking: project name is embedded # directly in the path and the query string contains other @@ -2764,16 +2777,31 @@ sub parse_ls_tree_line { my %opts = @_; my %res; - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; + if ($opts{'-l'}) { + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa 16717 panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s; - $res{'mode'} = $1; - $res{'type'} = $2; - $res{'hash'} = $3; - if ($opts{'-z'}) { - $res{'name'} = $4; + $res{'mode'} = $1; + $res{'type'} = $2; + $res{'hash'} = $3; + $res{'size'} = $4; + if ($opts{'-z'}) { + $res{'name'} = $5; + } else { + $res{'name'} = unquote($5); + } } else { - $res{'name'} = unquote($4); + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; + + $res{'mode'} = $1; + $res{'type'} = $2; + $res{'hash'} = $3; + if ($opts{'-z'}) { + $res{'name'} = $4; + } else { + $res{'name'} = unquote($4); + } } return wantarray ? %res : \%res; @@ -3564,6 +3592,9 @@ sub git_print_tree_entry { # and link is the action links of the entry. print "" . mode_str($t->{'mode'}) . "\n"; + if (exists $t->{'size'}) { + print "$t->{'size'}\n"; + } if ($t->{'type'} eq "blob") { print "" . $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, @@ -3609,12 +3640,14 @@ sub git_print_tree_entry { } elsif ($t->{'type'} eq "tree") { print ""; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, + file_name=>"$basedir$t->{'name'}", + %base_key)}, esc_path($t->{'name'})); print "\n"; print ""; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, + file_name=>"$basedir$t->{'name'}", + %base_key)}, "tree"); if (defined $hash_base) { print " | " . @@ -5088,10 +5121,14 @@ sub git_tree { } die_error(404, "No such tree") unless defined($hash); + my $show_sizes = gitweb_check_feature('show-sizes'); + my $have_blame = gitweb_check_feature('blame'); + my @entries = (); { local $/ = "\0"; - open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash + open my $fd, "-|", git_cmd(), "ls-tree", '-z', + ($show_sizes ? '-l' : ()), @extra_options, $hash or die_error(500, "Open git-ls-tree failed"); @entries = map { chomp; $_ } <$fd>; close $fd @@ -5102,7 +5139,6 @@ sub git_tree { my $ref = format_ref_marker($refs, $hash_base); git_header_html(); my $basedir = ''; - my $have_blame = gitweb_check_feature('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { my @views_nav = (); if (defined $file_name) { @@ -5118,7 +5154,8 @@ sub git_tree { # FIXME: Should be available when we have no hash base as well. push @views_nav, $snapshot_links; } - git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); + git_print_page_nav('tree','', $hash_base, undef, undef, + join(' | ', @views_nav)); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { undef $hash_base; @@ -5151,8 +5188,10 @@ sub git_tree { undef $up unless $up; # based on git_print_tree_entry print '' . mode_str('040000') . "\n"; + print ' '."\n" if $show_sizes; print ''; - print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base, + print $cgi->a({-href => href(action=>"tree", + hash_base=>$hash_base, file_name=>$up)}, ".."); print "\n"; @@ -5161,7 +5200,7 @@ sub git_tree { print "\n"; } foreach my $line (@entries) { - my %t = parse_ls_tree_line($line, -z => 1); + my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes); if ($alternate) { print "\n"; -- cgit v0.10.2-6-g49f6 From ed7b603381a57f706d758cf5a2f2ce604ef8c825 Mon Sep 17 00:00:00 2001 From: Alexey Borzenkov Date: Tue, 8 Sep 2009 22:39:33 +0400 Subject: git-gui: store wm state and fix wm geometry I often close git gui window when it is maximized, and when I reopen it next time the it would usually become out of place (e.g. a huge window with a top-left corner somewhere close to the center of the screen). Fix it by storing and restoring wm state in config, as well as setting wm state to normal before retrieving wm geometry info. Signed-off-by: Alexey Borzenkov Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index eae1f81..88d1025 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2025,6 +2025,19 @@ proc do_quit {{rc {1}}} { # -- Stash our current window geometry into this repository. # + set cfg_wmstate [wm state .] + if {[catch {set rc_wmstate $repo_config(gui.wmstate)}]} { + set rc_wmstate {} + } + if {$cfg_wmstate ne $rc_wmstate} { + catch {git config gui.wmstate $cfg_wmstate} + } + if {$cfg_wmstate eq {zoomed}} { + # on Windows wm geometry will lie about window + # position (but not size) when window is zoomed + # restore the window before querying wm geometry + wm state . normal + } set cfg_geometry [list] lappend cfg_geometry [wm geometry .] lappend cfg_geometry [lindex [.vpane sash coord 0] 0] @@ -3264,6 +3277,14 @@ wm geometry . [lindex $gm 0] unset gm } +# -- Load window state +# +catch { +set gws $repo_config(gui.wmstate) +wm state . $gws +unset gws +} + # -- Key Bindings # bind $ui_comm <$M1B-Key-Return> {do_commit;break} -- cgit v0.10.2-6-g49f6 From d68fe26f3e03b230ac9bbbcf002a9acdc4bebde9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 22:05:57 -0700 Subject: diff --whitespace: fix blank lines at end The earlier logic tried to colour any and all blank lines that were added beyond the last blank line in the original, but this was very wrong. If you added 96 blank lines, a non-blank line, and then 3 blank lines at the end, only the last 3 lines should trigger the error, not the earlier 96 blank lines. We need to also make sure that the lines are after the last non-blank line in the postimage as well before deciding to paint them. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 2b285b8..63a3bfc 100644 --- a/diff.c +++ b/diff.c @@ -491,8 +491,10 @@ struct emit_callback { struct xdiff_emit_state xm; int color_diff; unsigned ws_rule; - int blank_at_eof; + int blank_at_eof_in_preimage; + int blank_at_eof_in_postimage; int lno_in_preimage; + int lno_in_postimage; sane_truncate_fn truncate; const char **label_path; struct diff_words_data *diff_words; @@ -542,6 +544,17 @@ static void emit_line(FILE *file, const char *set, const char *reset, const char fputc('\n', file); } +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) +{ + if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof_in_preimage && + ecbdata->blank_at_eof_in_postimage && + ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && + ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) + return 0; + return ws_blank_line(line + 1, len - 1, ecbdata->ws_rule); +} + static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) { const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); @@ -549,11 +562,8 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons if (!*ws) emit_line(ecbdata->file, set, reset, line, len); - else if ((ecbdata->ws_rule & WS_BLANK_AT_EOF) && - ecbdata->blank_at_eof && - (ecbdata->blank_at_eof <= ecbdata->lno_in_preimage) && - ws_blank_line(line + 1, len - 1, ecbdata->ws_rule)) - /* Blank line at EOF */ + else if (new_blank_line_at_eof(ecbdata, line, len)) + /* Blank line at EOF - paint '+' as well */ emit_line(ecbdata->file, ws, reset, line, len); else { /* Emit just the prefix, then the rest. */ @@ -581,12 +591,19 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u return allot - l; } -static int find_preimage_lno(const char *line) +static void find_lno(const char *line, struct emit_callback *ecbdata) { - char *p = strchr(line, '-'); + const char *p; + ecbdata->lno_in_preimage = 0; + ecbdata->lno_in_postimage = 0; + p = strchr(line, '-'); if (!p) - return 0; /* should not happen */ - return strtol(p+1, NULL, 10); + return; /* cannot happen */ + ecbdata->lno_in_preimage = strtol(p + 1, NULL, 10); + p = strchr(p, '+'); + if (!p) + return; /* cannot happen */ + ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10); } static void fn_out_consume(void *priv, char *line, unsigned long len) @@ -613,7 +630,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (line[0] == '@') { len = sane_truncate_line(ecbdata, line, len); - ecbdata->lno_in_preimage = find_preimage_lno(line); + find_lno(line, ecbdata); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), reset, line, len); @@ -651,10 +668,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) diff_get_color(ecbdata->color_diff, line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN); ecbdata->lno_in_preimage++; + if (line[0] == ' ') + ecbdata->lno_in_postimage++; emit_line(ecbdata->file, color, reset, line, len); - return; + } else { + ecbdata->lno_in_postimage++; + emit_add_line(reset, ecbdata, line, len); } - emit_add_line(reset, ecbdata, line, len); } static char *pprint_rename(const char *a, const char *b) @@ -1470,16 +1490,23 @@ static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) return cnt; } -static int adds_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, unsigned ws_rule) +static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, + struct emit_callback *ecbdata) { int l1, l2, at; + unsigned ws_rule = ecbdata->ws_rule; l1 = count_trailing_blank(mf1, ws_rule); l2 = count_trailing_blank(mf2, ws_rule); - if (l2 <= l1) - return 0; - /* starting where? */ + if (l2 <= l1) { + ecbdata->blank_at_eof_in_preimage = 0; + ecbdata->blank_at_eof_in_postimage = 0; + return; + } at = count_lines(mf1->ptr, mf1->size); - return (at - l1) + 1; /* the line number counts from 1 */ + ecbdata->blank_at_eof_in_preimage = (at - l1) + 1; + + at = count_lines(mf2->ptr, mf2->size); + ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; } static void builtin_diff(const char *name_a, @@ -1572,8 +1599,7 @@ static void builtin_diff(const char *name_a, ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); if (ecbdata.ws_rule & WS_BLANK_AT_EOF) - ecbdata.blank_at_eof = - adds_blank_at_eof(&mf1, &mf2, ecbdata.ws_rule); + check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; @@ -1699,7 +1725,13 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); if (data.ws_rule & WS_BLANK_AT_EOF) { - int blank_at_eof = adds_blank_at_eof(&mf1, &mf2, data.ws_rule); + struct emit_callback ecbdata; + int blank_at_eof; + + ecbdata.ws_rule = data.ws_rule; + check_blank_at_eof(&mf1, &mf2, &ecbdata); + blank_at_eof = ecbdata.blank_at_eof_in_preimage; + if (blank_at_eof) { static char *err; if (!err) diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 1e75f1a..3a3663f 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -193,7 +193,7 @@ test_expect_success 'do not color trailing cr in context' ' test_expect_success 'color new trailing blank lines' ' { echo a; echo b; echo; echo; } >x && git add x && - { echo a; echo; echo; echo; echo; } >x && + { echo a; echo; echo; echo; echo c; echo; echo; echo; echo; } >x && git diff --color x >output && cnt=$(grep "${blue_grep}" output | wc -l) && test $cnt = 2 -- cgit v0.10.2-6-g49f6 From 6957eb9a39cc765862e125edeef0dd70f359cff1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: diff.c: shuffling code around Move function, type, and structure definitions for fill_mmfile(), count_trailing_blank(), check_blank_at_eof(), emit_line(), new_blank_line_at_eof(), emit_add_line(), sane_truncate_fn, and emit_callback up in the file, so that they can be refactored into helper functions and reused by codepath for emitting rewrite patches. This only moves the lines around to make the next two patches easier to read. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 63a3bfc..7548966 100644 --- a/diff.c +++ b/diff.c @@ -241,6 +241,23 @@ static struct diff_tempfile { char tmp_path[PATH_MAX]; } diff_temp[2]; +typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); + +struct emit_callback { + struct xdiff_emit_state xm; + int color_diff; + unsigned ws_rule; + int blank_at_eof_in_preimage; + int blank_at_eof_in_postimage; + int lno_in_preimage; + int lno_in_postimage; + sane_truncate_fn truncate; + const char **label_path; + struct diff_words_data *diff_words; + int *found_changesp; + FILE *file; +}; + static int count_lines(const char *data, int size) { int count, ch, completely_empty = 1, nl_just_seen = 0; @@ -301,6 +318,114 @@ static void copy_file_with_prefix(FILE *file, fprintf(file, "%s\n\\ No newline at end of file\n", reset); } +static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) { + mf->ptr = (char *)""; /* does not matter */ + mf->size = 0; + return 0; + } + else if (diff_populate_filespec(one, 0)) + return -1; + mf->ptr = one->data; + mf->size = one->size; + return 0; +} + +static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) +{ + char *ptr = mf->ptr; + long size = mf->size; + int cnt = 0; + + if (!size) + return cnt; + ptr += size - 1; /* pointing at the very end */ + if (*ptr != '\n') + ; /* incomplete line */ + else + ptr--; /* skip the last LF */ + while (mf->ptr < ptr) { + char *prev_eol; + for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--) + if (*prev_eol == '\n') + break; + if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule)) + break; + cnt++; + ptr = prev_eol - 1; + } + return cnt; +} + +static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, + struct emit_callback *ecbdata) +{ + int l1, l2, at; + unsigned ws_rule = ecbdata->ws_rule; + l1 = count_trailing_blank(mf1, ws_rule); + l2 = count_trailing_blank(mf2, ws_rule); + if (l2 <= l1) { + ecbdata->blank_at_eof_in_preimage = 0; + ecbdata->blank_at_eof_in_postimage = 0; + return; + } + at = count_lines(mf1->ptr, mf1->size); + ecbdata->blank_at_eof_in_preimage = (at - l1) + 1; + + at = count_lines(mf2->ptr, mf2->size); + ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; +} + +static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) +{ + int has_trailing_newline, has_trailing_carriage_return; + + has_trailing_newline = (len > 0 && line[len-1] == '\n'); + if (has_trailing_newline) + len--; + has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); + if (has_trailing_carriage_return) + len--; + + fputs(set, file); + fwrite(line, len, 1, file); + fputs(reset, file); + if (has_trailing_carriage_return) + fputc('\r', file); + if (has_trailing_newline) + fputc('\n', file); +} + +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) +{ + if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof_in_preimage && + ecbdata->blank_at_eof_in_postimage && + ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && + ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) + return 0; + return ws_blank_line(line + 1, len - 1, ecbdata->ws_rule); +} + +static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +{ + const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); + const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); + + if (!*ws) + emit_line(ecbdata->file, set, reset, line, len); + else if (new_blank_line_at_eof(ecbdata, line, len)) + /* Blank line at EOF - paint '+' as well */ + emit_line(ecbdata->file, ws, reset, line, len); + else { + /* Emit just the prefix, then the rest. */ + emit_line(ecbdata->file, set, reset, line, 1); + ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, + ecbdata->file, set, reset, ws); + } +} + static void emit_rewrite_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -345,20 +470,6 @@ static void emit_rewrite_diff(const char *name_a, copy_file_with_prefix(o->file, '+', two->data, two->size, new, reset); } -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) -{ - if (!DIFF_FILE_VALID(one)) { - mf->ptr = (char *)""; /* does not matter */ - mf->size = 0; - return 0; - } - else if (diff_populate_filespec(one, 0)) - return -1; - mf->ptr = one->data; - mf->size = one->size; - return 0; -} - struct diff_words_buffer { mmfile_t text; long alloc; @@ -485,23 +596,6 @@ static void diff_words_show(struct diff_words_data *diff_words) } } -typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); - -struct emit_callback { - struct xdiff_emit_state xm; - int color_diff; - unsigned ws_rule; - int blank_at_eof_in_preimage; - int blank_at_eof_in_postimage; - int lno_in_preimage; - int lno_in_postimage; - sane_truncate_fn truncate; - const char **label_path; - struct diff_words_data *diff_words; - int *found_changesp; - FILE *file; -}; - static void free_diff_words_data(struct emit_callback *ecbdata) { if (ecbdata->diff_words) { @@ -524,55 +618,6 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) -{ - int has_trailing_newline, has_trailing_carriage_return; - - has_trailing_newline = (len > 0 && line[len-1] == '\n'); - if (has_trailing_newline) - len--; - has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); - if (has_trailing_carriage_return) - len--; - - fputs(set, file); - fwrite(line, len, 1, file); - fputs(reset, file); - if (has_trailing_carriage_return) - fputc('\r', file); - if (has_trailing_newline) - fputc('\n', file); -} - -static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) -{ - if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && - ecbdata->blank_at_eof_in_preimage && - ecbdata->blank_at_eof_in_postimage && - ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && - ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) - return 0; - return ws_blank_line(line + 1, len - 1, ecbdata->ws_rule); -} - -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) -{ - const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); - const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); - - if (!*ws) - emit_line(ecbdata->file, set, reset, line, len); - else if (new_blank_line_at_eof(ecbdata, line, len)) - /* Blank line at EOF - paint '+' as well */ - emit_line(ecbdata->file, ws, reset, line, len); - else { - /* Emit just the prefix, then the rest. */ - emit_line(ecbdata->file, set, reset, line, 1); - ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, - ecbdata->file, set, reset, ws); - } -} - static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len) { const char *cp; @@ -1464,51 +1509,6 @@ static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_fi return NULL; } -static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) -{ - char *ptr = mf->ptr; - long size = mf->size; - int cnt = 0; - - if (!size) - return cnt; - ptr += size - 1; /* pointing at the very end */ - if (*ptr != '\n') - ; /* incomplete line */ - else - ptr--; /* skip the last LF */ - while (mf->ptr < ptr) { - char *prev_eol; - for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--) - if (*prev_eol == '\n') - break; - if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule)) - break; - cnt++; - ptr = prev_eol - 1; - } - return cnt; -} - -static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, - struct emit_callback *ecbdata) -{ - int l1, l2, at; - unsigned ws_rule = ecbdata->ws_rule; - l1 = count_trailing_blank(mf1, ws_rule); - l2 = count_trailing_blank(mf2, ws_rule); - if (l2 <= l1) { - ecbdata->blank_at_eof_in_preimage = 0; - ecbdata->blank_at_eof_in_postimage = 0; - return; - } - at = count_lines(mf1->ptr, mf1->size); - ecbdata->blank_at_eof_in_preimage = (at - l1) + 1; - - at = count_lines(mf2->ptr, mf2->size); - ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; -} - static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, -- cgit v0.10.2-6-g49f6 From 250f79930d84af54dce15320914ea911d58dd8ca Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: diff.c: split emit_line() from the first char and the rest of the line A new helper function emit_line_0() takes the first line of diff output (typically "-", " ", or "+") separately from the remainder of the line. No other functional changes. This change will make it easier to reuse the logic when emitting the rewrite diff, as we do not want to copy a line only to add "+"/"-"/" " immediately before its first character when we produce rewrite diff output. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 7548966..e4aaebf 100644 --- a/diff.c +++ b/diff.c @@ -377,18 +377,31 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; } -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) +static void emit_line_0(FILE *file, const char *set, const char *reset, + int first, const char *line, int len) { int has_trailing_newline, has_trailing_carriage_return; + int nofirst; - has_trailing_newline = (len > 0 && line[len-1] == '\n'); - if (has_trailing_newline) - len--; - has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); - if (has_trailing_carriage_return) - len--; + if (len == 0) { + has_trailing_newline = (first == '\n'); + has_trailing_carriage_return = (!has_trailing_newline && + (first == '\r')); + nofirst = has_trailing_newline || has_trailing_carriage_return; + } else { + has_trailing_newline = (len > 0 && line[len-1] == '\n'); + if (has_trailing_newline) + len--; + has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); + if (has_trailing_carriage_return) + len--; + nofirst = 0; + } fputs(set, file); + + if (!nofirst) + fputc(first, file); fwrite(line, len, 1, file); fputs(reset, file); if (has_trailing_carriage_return) @@ -397,6 +410,12 @@ static void emit_line(FILE *file, const char *set, const char *reset, const char fputc('\n', file); } +static void emit_line(FILE *file, const char *set, const char *reset, + const char *line, int len) +{ + emit_line_0(file, set, reset, line[0], line+1, len-1); +} + static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) { if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && -- cgit v0.10.2-6-g49f6 From 018cff70462eb59779c96a383531c4440fb35b9c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: diff.c: emit_add_line() takes only the rest of the line As the first character on the line that is fed to this function is always "+", it is pointless to send that along with the rest of the line. This change will make it easier to reuse the logic when emitting the rewrite diff, as we do not want to copy a line only to add "+"/"-"/" " immediately before its first character when we produce rewrite diff output. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index e4aaebf..07cf043 100644 --- a/diff.c +++ b/diff.c @@ -424,23 +424,25 @@ static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) return 0; - return ws_blank_line(line + 1, len - 1, ecbdata->ws_rule); + return ws_blank_line(line, len, ecbdata->ws_rule); } -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +static void emit_add_line(const char *reset, + struct emit_callback *ecbdata, + const char *line, int len) { const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); if (!*ws) - emit_line(ecbdata->file, set, reset, line, len); + emit_line_0(ecbdata->file, set, reset, '+', line, len); else if (new_blank_line_at_eof(ecbdata, line, len)) /* Blank line at EOF - paint '+' as well */ - emit_line(ecbdata->file, ws, reset, line, len); + emit_line_0(ecbdata->file, ws, reset, '+', line, len); else { /* Emit just the prefix, then the rest. */ - emit_line(ecbdata->file, set, reset, line, 1); - ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, + emit_line_0(ecbdata->file, set, reset, '+', "", 0); + ws_check_emit(line, len, ecbdata->ws_rule, ecbdata->file, set, reset, ws); } } @@ -737,7 +739,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) emit_line(ecbdata->file, color, reset, line, len); } else { ecbdata->lno_in_postimage++; - emit_add_line(reset, ecbdata, line, len); + emit_add_line(reset, ecbdata, line + 1, len - 1); } } -- cgit v0.10.2-6-g49f6 From 7f7ee2ff2dfbb8435a4b46750f573ef0f7d0b853 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: diff -B: colour whitespace errors We used to send the old and new contents more or less straight out to the output with only the original "old is red, new is green" colouring. Now all the necessary support routines have been prepared, call them with a line of data at a time from the output code and have them check and color whitespace errors in exactly the same way as they are called from the low level diff callback routines. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 07cf043..8414478 100644 --- a/diff.c +++ b/diff.c @@ -296,28 +296,6 @@ static void print_line_count(FILE *file, int count) } } -static void copy_file_with_prefix(FILE *file, - int prefix, const char *data, int size, - const char *set, const char *reset) -{ - int ch, nl_just_seen = 1; - while (0 < size--) { - ch = *data++; - if (nl_just_seen) { - fputs(set, file); - putc(prefix, file); - } - if (ch == '\n') { - nl_just_seen = 1; - fputs(reset, file); - } else - nl_just_seen = 0; - putc(ch, file); - } - if (!nl_just_seen) - fprintf(file, "%s\n\\ No newline at end of file\n", reset); -} - static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) { @@ -447,6 +425,38 @@ static void emit_add_line(const char *reset, } } +static void emit_rewrite_lines(struct emit_callback *ecb, + int prefix, const char *data, int size) +{ + const char *endp = NULL; + static const char *nneof = " No newline at end of file\n"; + const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD); + const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET); + + while (0 < size) { + int len; + + endp = memchr(data, '\n', size); + len = endp ? (endp - data + 1) : size; + if (prefix != '+') { + ecb->lno_in_preimage++; + emit_line_0(ecb->file, old, reset, '-', + data, len); + } else { + ecb->lno_in_postimage++; + emit_add_line(reset, ecb, data, len); + } + size -= len; + data += len; + } + if (!endp) { + const char *plain = diff_get_color(ecb->color_diff, + DIFF_PLAIN); + emit_line_0(ecb->file, plain, reset, '\\', + nneof, strlen(nneof)); + } +} + static void emit_rewrite_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -458,10 +468,23 @@ static void emit_rewrite_diff(const char *name_a, const char *name_a_tab, *name_b_tab; const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO); const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO); - const char *old = diff_get_color(color_diff, DIFF_FILE_OLD); - const char *new = diff_get_color(color_diff, DIFF_FILE_NEW); const char *reset = diff_get_color(color_diff, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; + struct emit_callback ecbdata; + + memset(&ecbdata, 0, sizeof(ecbdata)); + ecbdata.color_diff = color_diff; + ecbdata.found_changesp = &o->found_changes; + ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + ecbdata.file = o->file; + if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { + mmfile_t mf1, mf2; + fill_mmfile(&mf1, one); + fill_mmfile(&mf2, two); + check_blank_at_eof(&mf1, &mf2, &ecbdata); + } + ecbdata.lno_in_preimage = 1; + ecbdata.lno_in_postimage = 1; name_a += (*name_a == '/'); name_b += (*name_b == '/'); @@ -486,9 +509,9 @@ static void emit_rewrite_diff(const char *name_a, print_line_count(o->file, lc_b); fprintf(o->file, " @@%s\n", reset); if (lc_a) - copy_file_with_prefix(o->file, '-', one->data, one->size, old, reset); + emit_rewrite_lines(&ecbdata, '-', one->data, one->size); if (lc_b) - copy_file_with_prefix(o->file, '+', two->data, two->size, new, reset); + emit_rewrite_lines(&ecbdata, '+', two->data, two->size); } struct diff_words_buffer { -- cgit v0.10.2-6-g49f6 From e984c54ada086e7676419b6b1b8a2d7a2429da48 Mon Sep 17 00:00:00 2001 From: Julian Phillips Date: Thu, 17 Sep 2009 08:33:19 +0100 Subject: fetch: Speed up fetch by rewriting find_non_local_tags When trying to get a list of remote tags to see if we need to fetch any we were doing a linear search for the matching tag ref for the tag^{} commit entries. This proves to be incredibly slow for large numbers of tags. Rewrite the function so that we build up a string_list of refs to fetch and then process that instead. As an extreme example, for a repository with 50000 tags (and just a single commit on a single branch), a fetch that does nothing goes from ~1m50s to ~4.1s. Signed-off-by: Julian Phillips Signed-off-by: Junio C Hamano diff --git a/builtin-fetch.c b/builtin-fetch.c index cb48c57..a35a6f8 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -504,57 +504,98 @@ static int will_fetch(struct ref **head, const unsigned char *sha1) return 0; } +struct tag_data { + struct ref **head; + struct ref ***tail; +}; + +static int add_to_tail(struct string_list_item *item, void *cb_data) +{ + struct tag_data *data = (struct tag_data *)cb_data; + struct ref *rm = NULL; + + /* We have already decided to ignore this item */ + if (!item->util) + return 0; + + rm = alloc_ref(item->string); + rm->peer_ref = alloc_ref(item->string); + hashcpy(rm->old_sha1, item->util); + + **data->tail = rm; + *data->tail = &rm->next; + + return 0; +} + static void find_non_local_tags(struct transport *transport, struct ref **head, struct ref ***tail) { struct string_list existing_refs = { NULL, 0, 0, 0 }; - struct string_list new_refs = { NULL, 0, 0, 1 }; - char *ref_name; - int ref_name_len; - const unsigned char *ref_sha1; - const struct ref *tag_ref; - struct ref *rm = NULL; + struct string_list remote_refs = { NULL, 0, 0, 0 }; + struct tag_data data = {head, tail}; const struct ref *ref; + struct string_list_item *item = NULL; for_each_ref(add_existing, &existing_refs); for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { if (prefixcmp(ref->name, "refs/tags")) continue; - ref_name = xstrdup(ref->name); - ref_name_len = strlen(ref_name); - ref_sha1 = ref->old_sha1; - - if (!strcmp(ref_name + ref_name_len - 3, "^{}")) { - ref_name[ref_name_len - 3] = 0; - tag_ref = transport_get_remote_refs(transport); - while (tag_ref) { - if (!strcmp(tag_ref->name, ref_name)) { - ref_sha1 = tag_ref->old_sha1; - break; - } - tag_ref = tag_ref->next; - } + /* + * The peeled ref always follows the matching base + * ref, so if we see a peeled ref that we don't want + * to fetch then we can mark the ref entry in the list + * as one to ignore by setting util to NULL. + */ + if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) { + if (item && !has_sha1_file(ref->old_sha1) && + !will_fetch(head, ref->old_sha1) && + !has_sha1_file(item->util) && + !will_fetch(head, item->util)) + item->util = NULL; + item = NULL; + continue; } - if (!string_list_has_string(&existing_refs, ref_name) && - !string_list_has_string(&new_refs, ref_name) && - (has_sha1_file(ref->old_sha1) || - will_fetch(head, ref->old_sha1))) { - string_list_insert(ref_name, &new_refs); + /* + * If item is non-NULL here, then we previously saw a + * ref not followed by a peeled reference, so we need + * to check if it is a lightweight tag that we want to + * fetch. + */ + if (item && !has_sha1_file(item->util) && + !will_fetch(head, item->util)) + item->util = NULL; - rm = alloc_ref(ref_name); - rm->peer_ref = alloc_ref(ref_name); - hashcpy(rm->old_sha1, ref_sha1); + item = NULL; - **tail = rm; - *tail = &rm->next; - } - free(ref_name); + /* skip duplicates and refs that we already have */ + if (string_list_has_string(&remote_refs, ref->name) || + string_list_has_string(&existing_refs, ref->name)) + continue; + + item = string_list_insert(ref->name, &remote_refs); + item->util = (void *)ref->old_sha1; } string_list_clear(&existing_refs, 0); - string_list_clear(&new_refs, 0); + + /* + * We may have a final lightweight tag that needs to be + * checked to see if it needs fetching. + */ + if (item && !has_sha1_file(item->util) && + !will_fetch(head, item->util)) + item->util = NULL; + + /* + * For all the tags in the remote_refs string list, call + * add_to_tail to add them to the list of refs to be fetched + */ + for_each_string_list(add_to_tail, &remote_refs, &data); + + string_list_clear(&remote_refs, 0); } static void check_not_current_branch(struct ref *ref_map) -- cgit v0.10.2-6-g49f6 From 1414e5788b85787a712a30977b388200f1bc04da Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Tue, 22 Sep 2009 17:10:12 +0200 Subject: git submodule add: make the parameter optional When is not given, use the "humanish" part of the source repository instead. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 5ccdd18..4ef70c4 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git submodule' [--quiet] add [-b branch] - [--reference ] [--] + [--reference ] [--] [] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [...] 'git submodule' [--quiet] init [--] [...] 'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase] @@ -69,7 +69,11 @@ add:: to the changeset to be committed next to the current project: the current project is termed the "superproject". + -This requires two arguments: and . +This requires at least one argument: . The optional +argument is the relative location for the cloned submodule +to exist in the superproject. If is not given, the +"humanish" part of the source repository is used ("repo" for +"/path/to/repo.git" and "foo" for "host.xz:foo/.git"). + is the URL of the new submodule's origin repository. This may be either an absolute URL, or (if it begins with ./ diff --git a/git-submodule.sh b/git-submodule.sh index bfbd36b..0c617eb 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -5,7 +5,7 @@ # Copyright (c) 2007 Lars Hjemli dashless=$(basename "$0" | sed -e 's/-/ /') -USAGE="[--quiet] add [-b branch] [--reference ] [--] +USAGE="[--quiet] add [-b branch] [--reference ] [--] [] or: $dashless [--quiet] status [--cached] [--recursive] [--] [...] or: $dashless [--quiet] init [--] [...] or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference ] [--merge] [--recursive] [--] [...] @@ -160,6 +160,11 @@ cmd_add() repo=$1 path=$2 + if test -z "$path"; then + path=$(echo "$repo" | + sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') + fi + if test -z "$repo" -o -z "$path"; then usage fi diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 0f2ccc6..a0cc99a 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -306,4 +306,20 @@ test_expect_success 'submodule warns' ' ' +test_expect_success 'add submodules without specifying an explicit path' ' + mkdir repo && + cd repo && + git init && + echo r >r && + git add r && + git commit -m "repo commit 1" && + cd .. && + git clone --bare repo/ bare.git && + cd addtest && + git submodule add "$submodurl/repo" && + git config -f .gitmodules submodule.repo.path repo && + git submodule add "$submodurl/bare.git" && + git config -f .gitmodules submodule.bare.path bare +' + test_done -- cgit v0.10.2-6-g49f6 From cd846aa183a01b8d881122a938f93ba38bc12629 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Thu, 24 Sep 2009 18:56:28 +0200 Subject: git-gui: fix use of uninitialized variable This fixes a bug introduced by the "display summary when showing diff of a submodule" patch. It lead to a "no such variable" error when opening the diff context menu while no diff was shown. Signed-off-by: Jens Lehmann Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 88d1025..09b2720 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1134,6 +1134,7 @@ set current_branch {} set is_detached 0 set current_diff_path {} set is_3way_diff 0 +set is_submodule_diff 0 set is_conflict_diff 0 set selected_commit_type new set diff_empty_count 0 -- cgit v0.10.2-6-g49f6 From fdb0c36e903d13c184f9a465035c75565c5c072a Mon Sep 17 00:00:00 2001 From: Mark Rada Date: Sat, 26 Sep 2009 13:46:08 -0400 Subject: gitweb: check given hash before trying to create snapshot Makes things nicer in cases when you hand craft the snapshot URL but make a typo in defining the hash variable (e.g. netx instead of next); you will now get an error message instead of a broken tarball. Tests for t9501 are included to demonstrate added functionality. Signed-off-by: Mark Rada Signed-off-by: Shawn O. Pearce diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24b2193..8d4a2ae 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5196,8 +5196,11 @@ sub git_snapshot { die_error(403, "Unsupported snapshot format"); } - if (!defined $hash) { - $hash = git_get_head_hash($project); + my $type = git_get_type("$hash^{}"); + if (!$type) { + die_error(404, 'Object does not exist'); + } elsif ($type eq 'blob') { + die_error(400, 'Object is not a tree-ish'); } my $name = $project; diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh index d0ff21d..0688a57 100644 --- a/t/t9501-gitweb-standalone-http-status.sh +++ b/t/t9501-gitweb-standalone-http-status.sh @@ -75,4 +75,43 @@ test_expect_success \ test_debug 'cat gitweb.output' +# ---------------------------------------------------------------------- +# snapshot hash ids + +test_expect_success 'snapshots: good tree-ish id' ' + gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" && + grep "Status: 200 OK" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: bad tree-ish id' ' + gitweb_run "p=.git;a=snapshot;h=frizzumFrazzum;sf=tgz" && + grep "404 - Object does not exist" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: bad tree-ish id (tagged object)' ' + echo object > tag-object && + git add tag-object && + git commit -m "Object to be tagged" && + git tag tagged-object `git hash-object tag-object` && + gitweb_run "p=.git;a=snapshot;h=tagged-object;sf=tgz" && + grep "400 - Object is not a tree-ish" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: good object id' ' + ID=`git rev-parse --verify HEAD` && + gitweb_run "p=.git;a=snapshot;h=$ID;sf=tgz" && + grep "Status: 200 OK" gitweb.output +' +test_debug 'cat gitweb.output' + +test_expect_success 'snapshots: bad object id' ' + gitweb_run "p=.git;a=snapshot;h=abcdef01234;sf=tgz" && + grep "404 - Object does not exist" gitweb.output +' +test_debug 'cat gitweb.output' + + test_done -- cgit v0.10.2-6-g49f6 From 10d1432aece21ac65a89fe962b1c3019ec1f46e0 Mon Sep 17 00:00:00 2001 From: Mark Rada Date: Sat, 26 Sep 2009 14:12:32 -0400 Subject: instaweb: support mod_cgid for apache2 Some people have mod_cgid instead of mod_cgi, most likely as a result of choosing a threaded MPM. In cases where the user has both modules, mod_cgi will be preferred in order to maintain a simpler setup. This patch also causes instaweb to print a message and die in cases where there is no module that instaweb knows how to handle. Signed-off-by: Mark Rada Signed-off-by: Shawn O. Pearce diff --git a/git-instaweb.sh b/git-instaweb.sh index d96eddb..622a5f0 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -317,7 +317,21 @@ EOF resolve_full_httpd list_mods=$(echo "$full_httpd" | sed "s/-f$/-l/") $list_mods | grep 'mod_cgi\.c' >/dev/null 2>&1 || \ - echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf" + if test -f "$module_path/mod_cgi.so" + then + echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf" + else + $list_mods | grep 'mod_cgid\.c' >/dev/null 2>&1 || \ + if test -f "$module_path/mod_cgid.so" + then + echo "LoadModule cgid_module $module_path/mod_cgid.so" \ + >> "$conf" + else + echo "You have no CGI support!" + exit 2 + fi + echo "ScriptSock logs/gitweb.sock" >> "$conf" + fi cat >> "$conf" < -- cgit v0.10.2-6-g49f6 From c5022f576aa583429c245054d8600564b788ff33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20K=C3=A5gedal?= Date: Tue, 29 Sep 2009 17:12:57 +0200 Subject: git-blame.el: Change how blame information is shown. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is more customizable, and uses a line prefix to show the commit. Signed-off-by: David Kågedal Signed-off-by: Shawn O. Pearce diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el index 4fa70c5..7f4c792 100644 --- a/contrib/emacs/git-blame.el +++ b/contrib/emacs/git-blame.el @@ -80,6 +80,57 @@ (eval-when-compile (require 'cl)) ; to use `push', `pop' +(defface git-blame-prefix-face + '((((background dark)) (:foreground "gray" + :background "black")) + (((background light)) (:foreground "gray" + :background "white")) + (t (:weight bold))) + "The face used for the hash prefix." + :group 'git-blame) + +(defgroup git-blame nil + "A minor mode showing Git blame information." + :group 'git + :link '(function-link git-blame-mode)) + + +(defcustom git-blame-use-colors t + "Use colors to indicate commits in `git-blame-mode'." + :type 'boolean + :group 'git-blame) + +(defcustom git-blame-prefix-format + "%h %20A:" + "The format of the prefix added to each line in `git-blame' +mode. The format is passed to `format-spec' with the following format keys: + + %h - the abbreviated hash + %H - the full hash + %a - the author name + %A - the author email + %c - the committer name + %C - the committer email + %s - the commit summary +" + :group 'git-blame) + +(defcustom git-blame-mouseover-format + "%h %a %A: %s" + "The format of the description shown when pointing at a line in +`git-blame' mode. The format string is passed to `format-spec' +with the following format keys: + + %h - the abbreviated hash + %H - the full hash + %a - the author name + %A - the author email + %c - the committer name + %C - the committer email + %s - the commit summary +" + :group 'git-blame) + (defun git-blame-color-scale (&rest elements) "Given a list, returns a list of triples formed with each @@ -302,72 +353,69 @@ See also function `git-blame-mode'." (src-line (string-to-number (match-string 2))) (res-line (string-to-number (match-string 3))) (num-lines (string-to-number (match-string 4)))) - (setq git-blame-current - (if (string= hash "0000000000000000000000000000000000000000") - nil - (git-blame-new-commit - hash src-line res-line num-lines)))) - (delete-region (point) (match-end 0)) - t) - ((looking-at "filename \\(.+\\)\n") - (let ((filename (match-string 1))) - (git-blame-add-info "filename" filename)) - (delete-region (point) (match-end 0)) + (delete-region (point) (match-end 0)) + (setq git-blame-current (list (git-blame-new-commit hash) + src-line res-line num-lines))) t) ((looking-at "\\([a-z-]+\\) \\(.+\\)\n") (let ((key (match-string 1)) (value (match-string 2))) - (git-blame-add-info key value)) - (delete-region (point) (match-end 0)) - t) - ((looking-at "boundary\n") - (setq git-blame-current nil) - (delete-region (point) (match-end 0)) + (delete-region (point) (match-end 0)) + (git-blame-add-info (car git-blame-current) key value) + (when (string= key "filename") + (git-blame-create-overlay (car git-blame-current) + (caddr git-blame-current) + (cadddr git-blame-current)) + (setq git-blame-current nil))) t) (t nil))) -(defun git-blame-new-commit (hash src-line res-line num-lines) +(defun git-blame-new-commit (hash) + (with-current-buffer git-blame-file + (or (gethash hash git-blame-cache) + ;; Assign a random color to each new commit info + ;; Take care not to select the same color multiple times + (let* ((color (if git-blame-colors + (git-blame-random-pop git-blame-colors) + git-blame-ancient-color)) + (info `(,hash (color . ,color)))) + (puthash hash info git-blame-cache) + info)))) + +(defun git-blame-create-overlay (info start-line num-lines) (save-excursion (set-buffer git-blame-file) - (let ((info (gethash hash git-blame-cache)) - (inhibit-point-motion-hooks t) + (let ((inhibit-point-motion-hooks t) (inhibit-modification-hooks t)) - (when (not info) - ;; Assign a random color to each new commit info - ;; Take care not to select the same color multiple times - (let ((color (if git-blame-colors - (git-blame-random-pop git-blame-colors) - git-blame-ancient-color))) - (setq info (list hash src-line res-line num-lines - (git-describe-commit hash) - (cons 'color color)))) - (puthash hash info git-blame-cache)) - (goto-line res-line) - (while (> num-lines 0) - (if (get-text-property (point) 'git-blame) - (forward-line) - (let* ((start (point)) - (end (progn (forward-line 1) (point))) - (ovl (make-overlay start end))) - (push ovl git-blame-overlays) - (overlay-put ovl 'git-blame info) - (overlay-put ovl 'help-echo hash) + (goto-line start-line) + (let* ((start (point)) + (end (progn (forward-line num-lines) (point))) + (ovl (make-overlay start end)) + (hash (car info)) + (spec `((?h . ,(substring hash 0 6)) + (?H . ,hash) + (?a . ,(git-blame-get-info info 'author)) + (?A . ,(git-blame-get-info info 'author-mail)) + (?c . ,(git-blame-get-info info 'committer)) + (?C . ,(git-blame-get-info info 'committer-mail)) + (?s . ,(git-blame-get-info info 'summary))))) + (push ovl git-blame-overlays) + (overlay-put ovl 'git-blame info) + (overlay-put ovl 'help-echo + (format-spec git-blame-mouseover-format spec)) + (if git-blame-use-colors (overlay-put ovl 'face (list :background - (cdr (assq 'color (nthcdr 5 info))))) - ;; the point-entered property doesn't seem to work in overlays - ;;(overlay-put ovl 'point-entered - ;; `(lambda (x y) (git-blame-identify ,hash))) - (let ((modified (buffer-modified-p))) - (put-text-property (if (= start 1) start (1- start)) (1- end) - 'point-entered - `(lambda (x y) (git-blame-identify ,hash))) - (set-buffer-modified-p modified)))) - (setq num-lines (1- num-lines)))))) - -(defun git-blame-add-info (key value) - (if git-blame-current - (nconc git-blame-current (list (cons (intern key) value))))) + (cdr (assq 'color (cdr info)))))) + (overlay-put ovl 'line-prefix + (propertize (format-spec git-blame-prefix-format spec) + 'face 'git-blame-prefix-face)))))) + +(defun git-blame-add-info (info key value) + (nconc info (list (cons (intern key) value)))) + +(defun git-blame-get-info (info key) + (cdr (assq key (cdr info)))) (defun git-blame-current-commit () (let ((info (get-char-property (point) 'git-blame))) -- cgit v0.10.2-6-g49f6 From 6962b6b02c93c35163c25e07201f6219e5558f45 Mon Sep 17 00:00:00 2001 From: Thiago Farina Date: Wed, 23 Sep 2009 14:25:39 -0400 Subject: Documentation: update pt-BR Translate some english words to portuguese and fix some typos on translation. Signed-off-by: Thiago Farina Signed-off-by: Jeff King diff --git a/Documentation/pt_BR/gittutorial.txt b/Documentation/pt_BR/gittutorial.txt index 81e7ad7..beba065 100644 --- a/Documentation/pt_BR/gittutorial.txt +++ b/Documentation/pt_BR/gittutorial.txt @@ -1,15 +1,15 @@ gittutorial(7) ============== -NAME +NOME ---- gittutorial - Um tutorial de introdução ao git (para versão 1.5.1 ou mais nova) -SYNOPSIS +SINOPSE -------- git * -DESCRIPTION +DESCRIÇÃO ----------- Este tutorial explica como importar um novo projeto para o git, @@ -64,11 +64,11 @@ Git irá responder Initialized empty Git repository in .git/ ------------------------------------------------ -Você agora iniciou seu diretório de trabalho--você deve ter notado um -novo diretório criado, com o nome de ".git". +Agora que você iniciou seu diretório de trabalho, você deve ter notado que um +novo diretório foi criado com o nome de ".git". A seguir, diga ao git para gravar um instantâneo do conteúdo de todos os -arquivos sob o diretório corrente (note o '.'), com 'git-add': +arquivos sob o diretório atual (note o '.'), com 'git-add': ------------------------------------------------ $ git add . @@ -126,8 +126,8 @@ mudanças com: $ git commit ------------------------------------------------ -Isto irá novamente te pedir por uma mensagem descrevendo a mudança, e, -então, gravar a nova versão do projeto. +Ao executar esse comando, ele irá te pedir uma mensagem descrevendo a mudança, +e, então, irá gravar a nova versão do projeto. Alternativamente, ao invés de executar 'git-add' antes, você pode usar @@ -143,7 +143,7 @@ idéia começar a mensagem com uma simples e curta (menos de 50 caracteres) linha sumarizando a mudança, seguida de uma linha em branco e, então, uma descrição mais detalhada. Ferramentas que transformam commits em email, por exemplo, usam a primeira linha no campo de -cabeçalho Subject: e o resto no corpo. +cabeçalho "Subject:" e o resto no corpo. Git rastreia conteúdo, não arquivos ---------------------------- @@ -155,7 +155,7 @@ usado tanto para arquivos novos e arquivos recentemente modificados, e em ambos os casos, ele tira o instantâneo dos arquivos dados e armazena o conteúdo no índice, pronto para inclusão do próximo commit. -Visualizando história do projeto +Visualizando a história do projeto ----------------------- Em qualquer ponto você pode visualizar a história das suas mudanças @@ -165,7 +165,7 @@ usando $ git log ------------------------------------------------ -Se você também quer ver a diferença completa a cada passo, use +Se você também quiser ver a diferença completa a cada passo, use ------------------------------------------------ $ git log -p -- cgit v0.10.2-6-g49f6 From 6741aa6c399dec3d8f0b25699a73b8fcf974d702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Wed, 7 Oct 2009 08:13:23 +0200 Subject: Teach 'rebase -i' the command "reword" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make it easier to edit just the commit message for a commit using 'git rebase -i' by introducing the "reword" command. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0aefc34..33e0ef1 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -368,14 +368,17 @@ By replacing the command "pick" with the command "edit", you can tell the files and/or the commit message, amend the commit, and continue rebasing. +If you just want to edit the commit message for a commit, replace the +command "pick" with the command "reword". + If you want to fold two or more commits into one, replace the command "pick" with "squash" for the second and subsequent commit. If the commits had different authors, it will attribute the squashed commit to the author of the first commit. -In both cases, or when a "pick" does not succeed (because of merge -errors), the loop will stop to let you fix things, and you can continue -the loop with `git rebase --continue`. +'git-rebase' will stop when "pick" has been replaced with "edit" or +when a command fails due to merge errors. When you are done editing +and/or resolving conflicts you can continue with `git rebase --continue`. For example, if you want to reorder the last 5 commits, such that what was HEAD~4 becomes the new HEAD. To achieve that, you would call diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 23ded48..a43ee22 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -340,6 +340,14 @@ do_next () { pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" ;; + reword|r) + comment_for_reflog reword + + mark_action_done + pick_one $sha1 || + die_with_patch $sha1 "Could not apply $sha1... $rest" + output git commit --amend + ;; edit|e) comment_for_reflog edit @@ -752,6 +760,7 @@ first and then run 'git rebase --continue' again." # # Commands: # p, pick = use commit +# r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 260a231..62f452c 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -9,8 +9,8 @@ # # "[] []..." # -# If a line number is prefixed with "squash" or "edit", the respective line's -# command will be replaced with the specified one. +# If a line number is prefixed with "squash", "edit", or "reword", the +# respective line's command will be replaced with the specified one. set_fake_editor () { echo "#!$SHELL_PATH" >fake-editor.sh @@ -32,7 +32,7 @@ cat "$1".tmp action=pick for line in $FAKE_LINES; do case $line in - squash|edit) + squash|edit|reword) action="$line";; *) echo sed -n "${line}s/^pick/$action/p" diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 4cae019..3a37793 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -470,4 +470,18 @@ test_expect_success 'avoid unnecessary reset' ' test 123456789 = $MTIME ' +test_expect_success 'reword' ' + git checkout -b reword-branch master && + FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" git rebase -i A && + git show HEAD | grep "E changed" && + test $(git rev-parse master) != $(git rev-parse HEAD) && + test $(git rev-parse master^) = $(git rev-parse HEAD^) && + FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" git rebase -i A && + git show HEAD^ | grep "D changed" && + FAKE_LINES="reword 1 2 3 4" FAKE_COMMIT_MESSAGE="B changed" git rebase -i A && + git show HEAD~3 | grep "B changed" && + FAKE_LINES="1 reword 2 3 4" FAKE_COMMIT_MESSAGE="C changed" git rebase -i A && + git show HEAD~2 | grep "C changed" +' + test_done -- cgit v0.10.2-6-g49f6 From 1655c98790682aed8892eb8a9eb6d44e00d5f69f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 9 Oct 2009 14:26:44 +0200 Subject: gitweb: Do not show 'patch' link for merge commits The 'patch' view is about generating text/plain patch that can be given to "git am", and "git am" doesn't understand merges anyway. Therefore link to 'patch' view should not be shown for merge commits. Also call to git-format-patch inside the 'patch' action would fail when 'patch' action is called for a merge commit, with "Reading git-format-patch failed" text as 'patch' view body. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24b2193..c939e24 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5328,7 +5328,7 @@ sub git_commit { } @$parents ) . ')'; } - if (gitweb_check_feature('patches')) { + if (gitweb_check_feature('patches') && @$parents <= 1) { $formats_nav .= " | " . $cgi->a({-href => href(action=>"patch", -replay=>1)}, "patch"); @@ -5616,7 +5616,7 @@ sub git_commitdiff { $formats_nav = $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)}, "raw"); - if ($patch_max) { + if ($patch_max && @{$co{'parents'}} <= 1) { $formats_nav .= " | " . $cgi->a({-href => href(action=>"patch", -replay=>1)}, "patch"); @@ -5824,7 +5824,7 @@ sub git_commitdiff_plain { # format-patch-style patches sub git_patch { - git_commitdiff(-format => 'patch', -single=> 1); + git_commitdiff(-format => 'patch', -single => 1); } sub git_patches { -- cgit v0.10.2-6-g49f6 From 02461e0e2833753151ed71899f075c94e5fc495b Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Thu, 8 Oct 2009 10:03:26 -0700 Subject: git-send-email.perl: fold multiple entry "Cc:" and multiple single line "RCPT TO:"s Some MTAs reject Cc: lines longer than 78 chars. Avoid this by using the same join as "To:" ",\n\t" so each subsequent Cc entry is on a new line. RCPT TO: should have a single entry per line. see: http://www.ietf.org/rfc/rfc2821.txt Signed-off-by: Joe Perches Signed-off-by: Junio C Hamano diff --git a/git-send-email.perl b/git-send-email.perl index 0700d80..e188a89 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -835,7 +835,7 @@ sub send_message $gitversion = Git::version(); } - my $cc = join(", ", unique_email_list(@cc)); + my $cc = join(",\n\t", unique_email_list(@cc)); my $ccline = ""; if ($cc ne '') { $ccline = "\nCc: $cc"; @@ -976,7 +976,9 @@ X-Mailer: git-send-email $gitversion if ($smtp_server !~ m#^/#) { print "Server: $smtp_server\n"; print "MAIL FROM:<$raw_from>\n"; - print "RCPT TO:".join(',',(map { "<$_>" } @recipients))."\n"; + foreach my $entry (@recipients) { + print "RCPT TO:<$entry>\n"; + } } else { print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n"; } diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index fb606a9..84a7f03 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -103,10 +103,18 @@ cat >expected-show-all-headers <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: cc@example.com, A , One , two@example.com +Cc: cc@example.com, + A , + One , + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -164,7 +172,7 @@ test_expect_success 'cccmd works' ' --smtp-server="$(pwd)/fake.sendmail" \ cccmd.patch \ && - grep ^Cc:.*cccmd@example.com msgtxt1 + grep "^ cccmd@example.com" msgtxt1 ' z8=zzzzzzzz @@ -278,10 +286,17 @@ cat >expected-suppress-sob <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: cc@example.com, A , One , two@example.com +Cc: cc@example.com, + A , + One , + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -318,10 +333,15 @@ cat >expected-suppress-sob <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com +Cc: A , + One , + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -344,10 +364,17 @@ cat >expected-suppress-cccmd <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com, C O Mitter +Cc: A , + One , + two@example.com, + C O Mitter Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -392,10 +419,17 @@ cat >expected-suppress-body <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com, cc-cmd@example.com +Cc: A , + One , + two@example.com, + cc-cmd@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -416,10 +450,15 @@ cat >expected-suppress-body-cccmd <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com +Cc: A , + One , + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -440,10 +479,15 @@ cat >expected-suppress-sob <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com +Cc: A , + One , + two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -466,10 +510,17 @@ cat >expected-suppress-bodycc <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,,,, +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , One , two@example.com, C O Mitter +Cc: A , + One , + two@example.com, + C O Mitter Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -489,10 +540,13 @@ cat >expected-suppress-cc <<\EOF Dry-OK. Log says: Server: relay.example.com MAIL FROM: -RCPT TO:,, +RCPT TO: +RCPT TO: +RCPT TO: From: Example To: to@example.com -Cc: A , C O Mitter +Cc: A , + C O Mitter Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -605,7 +659,7 @@ test_expect_success 'utf8 Cc is rfc2047 encoded' ' --to=nobody@example.com \ --smtp-server="$(pwd)/fake.sendmail" \ outdir/*.patch && - grep "^Cc:" msgtxt1 | + grep "^ " msgtxt1 | grep "=?UTF-8?q?=C3=A0=C3=A9=C3=AC=C3=B6=C3=BA?= " ' -- cgit v0.10.2-6-g49f6 From b145b211baa4129a827cc1b1b1a7984523b8f903 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 10 Oct 2009 18:51:45 +0300 Subject: git-add--interactive: never skip files included in index Make "git add -p" to not skip files that are in index even if they are excluded (by .gitignore etc.). This fixes the contradictory behavior that "git status" and "git commit -a" listed such files as modified, but "git add -p FILENAME" ignored them. Signed-off-by: Pauli Virtanen Signed-off-by: Junio C Hamano diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 06f7060..3e90d71 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -186,7 +186,7 @@ sub list_modified { @tracked = map { chomp $_; unquote_path($_); - } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV); + } run_cmd_pipe(qw(git ls-files --), @ARGV); return if (!@tracked); } diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 62fd65e..687bd7a 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -138,6 +138,20 @@ test_expect_success 'real edit works' ' test_cmp expected output ' +test_expect_success 'skip files similarly as commit -a' ' + git reset && + echo file >.gitignore && + echo changed >file && + echo y | git add -p file && + git diff >output && + git reset && + git commit -am commit && + git diff >expected && + test_cmp expected output && + git reset --hard HEAD^ +' +rm -f .gitignore + if test "$(git config --bool core.filemode)" = false then say 'skipping filemode tests (filesystem does not properly support modes)' -- cgit v0.10.2-6-g49f6 From da8ba5e7da01be597aa9417c563dbd516ea5f204 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 17 Sep 2009 22:12:17 -0700 Subject: diff-lib.c: fix misleading comments on oneway_diff() 20a16eb (unpack_trees(): fix diff-index regression., 2008-03-10) adjusted diff-index to the new world order since 34110cd (Make 'unpack_trees()' have a separate source and destination index, 2008-03-06). Callbacks are expected to return anything non-negative as "success", and instead of reporting how many index entries they have processed, they are expected to advance o->pos themselves. The code did so, but a stale comment was left behind. Signed-off-by: Junio C Hamano diff --git a/diff-lib.c b/diff-lib.c index 0c74ef5..adf1c5f 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -383,7 +383,7 @@ static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_op * For diffing, the index is more important, and we only have a * single tree. * - * We're supposed to return how many index entries we want to skip. + * We're supposed to advance o->pos to skip what we have already processed. * * This wrapper makes it all more readable, and takes care of all * the fairly complex unpack_trees() semantic requirements, including -- cgit v0.10.2-6-g49f6 From 6caa7b555336dcb6e52bc7dfce1dd953bf53c3d5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 19 Sep 2009 17:26:16 -0700 Subject: unpack-trees: typofix I am not good at subject-verb concordance. Signed-off-by: Junio C Hamano diff --git a/unpack-trees.c b/unpack-trees.c index 720f7a1..d174fe0 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -617,7 +617,7 @@ static int verify_absent(struct cache_entry *ce, const char *action, * found "foo/." in the working tree. * This is tricky -- if we have modified * files that are in "foo/" we would lose - * it. + * them. */ ret = verify_clean_subdirectory(ce, action, o); if (ret < 0) -- cgit v0.10.2-6-g49f6 From 353c5eeb5cad63218ca796352cce9986df7b9c62 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 19 Sep 2009 16:36:45 -0700 Subject: unpack_callback(): use unpack_failed() consistently When unpack_index_entry() failed, consistently call unpack_failed(), instead of silently returning -1. Signed-off-by: Junio C Hamano diff --git a/unpack-trees.c b/unpack-trees.c index d174fe0..c424bab 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -277,6 +277,17 @@ static int unpack_nondirectories(int n, unsigned long mask, return 0; } +static int unpack_failed(struct unpack_trees_options *o, const char *message) +{ + discard_index(&o->result); + if (!o->gently) { + if (message) + return error("%s", message); + return -1; + } + return -1; +} + static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info) { struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, }; @@ -294,7 +305,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str int cmp = compare_entry(ce, info, p); if (cmp < 0) { if (unpack_index_entry(ce, o) < 0) - return -1; + return unpack_failed(o, NULL); continue; } if (!cmp) { @@ -352,17 +363,6 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str return mask; } -static int unpack_failed(struct unpack_trees_options *o, const char *message) -{ - discard_index(&o->result); - if (!o->gently) { - if (message) - return error("%s", message); - return -1; - } - return -1; -} - /* * N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the * resulting index, -2 on failure to reflect the changes to the work tree. -- cgit v0.10.2-6-g49f6 From 3240269dd9038ea12eb2f19a4be1218d6320a548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Sun, 11 Oct 2009 23:08:25 +0200 Subject: Documentation: add 'git replace' to main git manpage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano diff --git a/command-list.txt b/command-list.txt index fb03a2e..59b0adc 100644 --- a/command-list.txt +++ b/command-list.txt @@ -92,6 +92,7 @@ git-reflog ancillarymanipulators git-relink ancillarymanipulators git-remote ancillarymanipulators git-repack ancillarymanipulators +git-replace ancillarymanipulators git-repo-config ancillarymanipulators deprecated git-request-pull foreignscminterface git-rerere ancillaryinterrogators -- cgit v0.10.2-6-g49f6 From 2775d92c53d7b00758fa98e4ad8bce1e59445b05 Mon Sep 17 00:00:00 2001 From: Felipe Contreras Date: Sun, 11 Oct 2009 23:46:11 +0300 Subject: diff.c: stylefix Essentially; s/type* /type */ as per the coding guidelines. Signed-off-by: Felipe Contreras Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index e1be189..b39c1b6 100644 --- a/diff.c +++ b/diff.c @@ -999,7 +999,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) total_files, adds, dels); } -static void show_shortstats(struct diffstat_t* data, struct diff_options *options) +static void show_shortstats(struct diffstat_t *data, struct diff_options *options) { int i, adds = 0, dels = 0, total_files = data->nr; -- cgit v0.10.2-6-g49f6 From b5227d80aee5173bfda6aa43a890d03110b0df26 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 12 Oct 2009 01:11:57 -0400 Subject: ls-files: excludes should not impact tracked files In all parts of git, .gitignore and other exclude files impact only how we treat untracked files; they should have no effect on files listed in the index. This behavior was originally implemented very early on in 9ff768e, but only for --exclude-from. Later, commit 63d285c accidentally caused us to trigger the behavior for --exclude-per-directory. This patch totally ignores excludes for files found in the index. This means we are reversing the original intent of 9ff768e, while at the same time fixing the accidental behavior of 63d285c. This is a good thing, though, as the way that 9ff768e behaved does not really make sense with the way exclusions are used in modern git. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin-ls-files.c b/builtin-ls-files.c index da2daf4..16a1370 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -175,10 +175,6 @@ static void show_files(struct dir_struct *dir, const char *prefix) if (show_cached | show_stage) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - int dtype = ce_to_dtype(ce); - if (excluded(dir, ce->name, &dtype) != - !!(dir->flags & DIR_SHOW_IGNORED)) - continue; if (show_unmerged && !ce_stage(ce)) continue; if (ce->ce_flags & CE_UPDATE) @@ -191,10 +187,6 @@ static void show_files(struct dir_struct *dir, const char *prefix) struct cache_entry *ce = active_cache[i]; struct stat st; int err; - int dtype = ce_to_dtype(ce); - if (excluded(dir, ce->name, &dtype) != - !!(dir->flags & DIR_SHOW_IGNORED)) - continue; if (ce->ce_flags & CE_UPDATE) continue; err = lstat(ce->name, &st); diff --git a/t/t3003-ls-files-exclude.sh b/t/t3003-ls-files-exclude.sh new file mode 100755 index 0000000..fc1e379 --- /dev/null +++ b/t/t3003-ls-files-exclude.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test_description='ls-files --exclude does not affect index files' +. ./test-lib.sh + +test_expect_success 'create repo with file' ' + echo content >file && + git add file && + git commit -m file && + echo modification >file +' + +check_output() { +test_expect_success "ls-files output contains file ($1)" " + echo '$2' >expect && + git ls-files --exclude-standard --$1 >output && + test_cmp expect output +" +} + +check_all_output() { + check_output 'cached' 'file' + check_output 'modified' 'file' +} + +check_all_output +test_expect_success 'add file to gitignore' ' + echo file >.gitignore +' +check_all_output + +test_done -- cgit v0.10.2-6-g49f6 From 17225c49d501a304a0ae9ff370ca5cc3ae4afe4d Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 12 Oct 2009 11:00:09 +0200 Subject: bash completion: complete refs for git-grep Before the --, always attempt ref completion. This helps with entering the arguments to git-grep. As a bonus, you can work around git-grep's current lack of --all by hitting M-*, ugly as the resulting command line may be. Strictly speaking, completing the regular expression argument (or option argument) makes no sense. However, we cannot prevent _all_ completion (it will fall back to filenames), so we dispense with any additional complication to detect whether the user still has to enter a regular expression. Signed-off-by: Thomas Rast Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 7cf8557..d3fec32 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1069,7 +1069,8 @@ _git_grep () return ;; esac - COMPREPLY=() + + __gitcomp "$(__git_refs)" } _git_help () -- cgit v0.10.2-6-g49f6 From 0a0c342568458a15528778db1480dbbaa9a0b4d9 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Mon, 12 Oct 2009 21:37:39 +0200 Subject: git-stash documentation: mention default options for 'list' Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 1c64a02..3ff653d 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -66,7 +66,8 @@ stash@{1}: On master: 9cc0589... Add git-stash ---------------------------------------------------------------- + The command takes options applicable to the 'git-log' -command to control what is shown and how. See linkgit:git-log[1]. +command to control what is shown and how. If no options are set, the +default is `-n 10`. See linkgit:git-log[1]. show []:: -- cgit v0.10.2-6-g49f6 From 77abcbdc268d048b7a19af405607fb8d14e630ed Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 12 Oct 2009 22:34:12 +0200 Subject: Let --decorate show HEAD position 'git log --graph --oneline --decorate --all' is a useful way to get a general overview of the repository state, similar to 'gitk --all'. Let it indicate the position of HEAD by loading that ref too, so that the --decorate code can see it. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano diff --git a/log-tree.c b/log-tree.c index 1618f3c..f7d54f2 100644 --- a/log-tree.c +++ b/log-tree.c @@ -43,6 +43,7 @@ void load_ref_decorations(int flags) if (!loaded) { loaded = 1; for_each_ref(add_ref_decoration, &flags); + head_ref(add_ref_decoration, &flags); } } diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all index 903d9d9..d155e0b 100644 --- a/t/t4013/diff.log_--decorate=full_--all +++ b/t/t4013/diff.log_--decorate=full_--all @@ -1,5 +1,5 @@ $ git log --decorate=full --all -commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (refs/heads/master) +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, refs/heads/master) Merge: 9a6d494 c7a2ab9 Author: A U Thor Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all index 954210e..fd7c3e6 100644 --- a/t/t4013/diff.log_--decorate_--all +++ b/t/t4013/diff.log_--decorate_--all @@ -1,5 +1,5 @@ $ git log --decorate --all -commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (master) +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD, master) Merge: 9a6d494 c7a2ab9 Author: A U Thor Date: Mon Jun 26 00:04:00 2006 +0000 -- cgit v0.10.2-6-g49f6 From 58a05c74e7a9341af80eb98731d6b0dafe1b5c29 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 12 Oct 2009 00:27:04 -0500 Subject: Add tests for git check-ref-format The "git check-ref-format" command is a basic command various porcelains rely on. Test its functionality to make sure it does not unintentionally change. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh new file mode 100644 index 0000000..382bc6e --- /dev/null +++ b/t/t1402-check-ref-format.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +test_description='Test git check-ref-format' + +. ./test-lib.sh + +valid_ref() { + test_expect_success "ref name '$1' is valid" \ + "git check-ref-format '$1'" +} +invalid_ref() { + test_expect_success "ref name '$1' is not valid" \ + "test_must_fail git check-ref-format '$1'" +} + +valid_ref 'heads/foo' +invalid_ref 'foo' +valid_ref 'foo/bar/baz' +valid_ref 'refs///heads/foo' +invalid_ref 'heads/foo/' +invalid_ref './foo' +invalid_ref '.refs/foo' +invalid_ref 'heads/foo..bar' +invalid_ref 'heads/foo?bar' +valid_ref 'foo./bar' +invalid_ref 'heads/foo.lock' +valid_ref 'heads/foo@bar' +invalid_ref 'heads/v@{ation' +invalid_ref 'heads/foo\bar' + +test_expect_success "check-ref-format --branch @{-1}" ' + T=$(git write-tree) && + sha1=$(echo A | git commit-tree $T) && + git update-ref refs/heads/master $sha1 && + git update-ref refs/remotes/origin/master $sha1 + git checkout master && + git checkout origin/master && + git checkout master && + refname=$(git check-ref-format --branch @{-1}) && + test "$refname" = "$sha1" && + refname2=$(git check-ref-format --branch @{-2}) && + test "$refname2" = master' + +test_done -- cgit v0.10.2-6-g49f6 From 604e0cb5cbd2ea2e37273e13f99b6572cd82b4c1 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 12 Oct 2009 00:28:23 -0500 Subject: Documentation: describe check-ref-format --branch Unless one already knew, it was not obvious what sort of shorthand "git check-ref-format --branch" expands. Explain it. The --branch argument is not optional. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index c1ce268..664da8a 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git check-ref-format' -'git check-ref-format' [--branch] +'git check-ref-format' --branch DESCRIPTION ----------- @@ -57,8 +57,11 @@ reference name expressions (see linkgit:git-rev-parse[1]): . at-open-brace `@{` is used as a notation to access a reflog entry. -With the `--branch` option, it expands a branch name shorthand and -prints the name of the branch the shorthand refers to. +With the `--branch` option, it expands the ``previous branch syntax'' +`@{-n}`. For example, `@{-1}` is a way to refer the last branch you +were on. This option should be used by porcelains to accept this +syntax anywhere a branch name is expected, so they can act as if you +typed the branch name. EXAMPLE ------- -- cgit v0.10.2-6-g49f6 From 38eedc634bc5d30e8a7a2356d9eb3ae95d9b1d75 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 12 Oct 2009 16:39:43 -0700 Subject: git check-ref-format --print Tolerating empty path components in ref names means each ref does not have a unique name. This creates difficulty for porcelains that want to see if two branches are equal. Add a helper associating to each ref a canonical name. If a user asks a porcelain to create a ref "refs/heads//master", the porcelain can run "git check-ref-format --print refs/heads//master" and only deal with "refs/heads/master" from then on. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index 0b7982e..211ae1c 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -9,6 +9,7 @@ SYNOPSIS -------- [verse] 'git check-ref-format' +'git check-ref-format' --print 'git check-ref-format' [--branch] DESCRIPTION @@ -63,16 +64,28 @@ reference name expressions (see linkgit:git-rev-parse[1]): . at-open-brace `@{` is used as a notation to access a reflog entry. +With the `--print` option, if 'refname' is acceptable, it prints the +canonicalized name of a hypothetical reference with that name. That is, +it prints 'refname' with any extra `/` characters removed. + With the `--branch` option, it expands a branch name shorthand and prints the name of the branch the shorthand refers to. -EXAMPLE -------- - -git check-ref-format --branch @{-1}:: - -Print the name of the previous branch. +EXAMPLES +-------- +* Print the name of the previous branch: ++ +------------ +$ git check-ref-format --branch @{-1} +------------ + +* Determine the reference name to use for a new branch: ++ +------------ +$ ref=$(git check-ref-format --print "refs/heads/$newbranch") || +die "we do not like '$newbranch' as a branch name." +------------ GIT --- diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index f9381e0..b97b61a 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -17,6 +17,16 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) printf("%s\n", sb.buf + 11); exit(0); } + if (argc == 3 && !strcmp(argv[1], "--print")) { + char *refname = xmalloc(strlen(argv[2]) + 1); + + if (check_ref_format(argv[2])) + exit(1); + if (normalize_path_copy(refname, argv[2])) + die("Could not normalize ref name '%s'", argv[2]); + printf("%s\n", refname); + exit(0); + } if (argc != 2) usage("git check-ref-format refname"); return !!check_ref_format(argv[1]); diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index 382bc6e..eb45afb 100644 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -41,4 +41,21 @@ test_expect_success "check-ref-format --branch @{-1}" ' refname2=$(git check-ref-format --branch @{-2}) && test "$refname2" = master' +valid_ref_normalized() { + test_expect_success "ref name '$1' simplifies to '$2'" " + refname=\$(git check-ref-format --print '$1') && + test \"\$refname\" = '$2'" +} +invalid_ref_normalized() { + test_expect_success "check-ref-format --print rejects '$1'" " + test_must_fail git check-ref-format --print '$1'" +} + +valid_ref_normalized 'heads/foo' 'heads/foo' +valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo' +invalid_ref_normalized 'foo' +invalid_ref_normalized 'heads/foo/../bar' +invalid_ref_normalized 'heads/./foo' +invalid_ref_normalized 'heads\foo' + test_done -- cgit v0.10.2-6-g49f6 From 1ba447b8dc2ec4e6c8ebbbdaa449f38edc29ad3f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 12 Oct 2009 00:33:01 -0500 Subject: check-ref-format: simplify --print implementation normalize_path_copy() is a complicated function, but most of its functionality will never apply to a ref name that has been checked with check_ref_format(). Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index b97b61a..e3e7bdf 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -7,6 +7,28 @@ #include "builtin.h" #include "strbuf.h" +/* + * Replace each run of adjacent slashes in src with a single slash, + * and write the result to dst. + * + * This function is similar to normalize_path_copy(), but stripped down + * to meet check_ref_format's simpler needs. + */ +static void collapse_slashes(char *dst, const char *src) +{ + char ch; + char prev = '\0'; + + while ((ch = *src++) != '\0') { + if (prev == '/' && ch == prev) + continue; + + *dst++ = ch; + prev = ch; + } + *dst = '\0'; +} + int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { if (argc == 3 && !strcmp(argv[1], "--branch")) { @@ -22,8 +44,7 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) if (check_ref_format(argv[2])) exit(1); - if (normalize_path_copy(refname, argv[2])) - die("Could not normalize ref name '%s'", argv[2]); + collapse_slashes(refname, argv[2]); printf("%s\n", refname); exit(0); } -- cgit v0.10.2-6-g49f6 From b0fa7ab51b29d34579d8f6bb4443dfbcb8278c7a Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 12 Oct 2009 22:30:32 +0200 Subject: git: add --no-replace-objects option to disable replacing Commit dae556b (environment: add global variable to disable replacement) adds a variable to enable/disable replacement, and it is enabled by default for most commands. So there is no way to disable it for some commands, which is annoying when we want to get information about a commit that has been replaced. For example: $ git cat-file -p N would output information about the replacement commit if commit N is replaced. With the "--no-replace-objects" option that this patch adds it is possible to get information about the original commit using: $ git --no-replace-objects cat-file -p N While at it, let's add some documentation about this new option in the "git replace" man page too. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt index 915cb77..8adc1ef 100644 --- a/Documentation/git-replace.txt +++ b/Documentation/git-replace.txt @@ -23,6 +23,26 @@ replacement object. Unless `-f` is given, the replace reference must not yet exist in `.git/refs/replace/` directory. +Replace references will be used by default by all git commands except +those doing reachability traversal (prune, pack transfer and fsck). + +It is possible to disable use of replacement refs for any command +using the --no-replace-objects option just after "git". + +For example if commit "foo" has been replaced by commit "bar": + +------------------------------------------------ +$ git --no-replace-object cat-file commit foo +------------------------------------------------ + +show information about commit "foo", while: + +------------------------------------------------ +$ git cat-file commit foo +------------------------------------------------ + +show information about commit "bar". + OPTIONS ------- -f:: @@ -54,6 +74,7 @@ SEE ALSO -------- linkgit:git-tag[1] linkgit:git-branch[1] +linkgit:git[1] Author ------ diff --git a/Documentation/git.txt b/Documentation/git.txt index d97aaf5..2f45417 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] - [-p|--paginate|--no-pager] + [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS] @@ -237,6 +237,10 @@ help ...`. environment is not set, it is set to the current working directory. +--no-replace-objects:: + Do not use replacement refs to replace git objects. See + linkgit:git-replace[1] for more information. + FURTHER DOCUMENTATION --------------------- diff --git a/git.c b/git.c index 9883009..bd2c5fe 100644 --- a/git.c +++ b/git.c @@ -6,7 +6,7 @@ const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n" - " [-p|--paginate|--no-pager]\n" + " [-p|--paginate|--no-pager] [--no-replace-objects]\n" " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n" " [--help] COMMAND [ARGS]"; @@ -87,6 +87,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) use_pager = 0; if (envchanged) *envchanged = 1; + } else if (!strcmp(cmd, "--no-replace-objects")) { + read_replace_refs = 0; } else if (!strcmp(cmd, "--git-dir")) { if (*argc < 2) { fprintf(stderr, "No directory given for --git-dir.\n" ); diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 8b8bd81..d4818b4 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -70,6 +70,13 @@ test_expect_success 'replace the author' ' git show $HASH2 | grep "O Thor" ' +test_expect_success 'test --no-replace-objects option' ' + git cat-file commit $HASH2 | grep "author O Thor" && + git --no-replace-objects cat-file commit $HASH2 | grep "author A U Thor" && + git show $HASH2 | grep "O Thor" && + git --no-replace-objects show $HASH2 | grep "A U Thor" +' + cat >tag.sig < Date: Tue, 13 Oct 2009 17:02:24 -0400 Subject: bisect reset: Allow resetting to any commit, not just a branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ‘git bisect reset’ accepts an optional argument specifying a branch to check out after cleaning up the bisection state. This lets you specify an arbitrary commit. In particular, this provides a way to clean the bisection state without moving HEAD: ‘git bisect reset HEAD’. This may be useful if you are not interested in the state before you began a bisect, especially if checking out the old commit would be expensive and invalidate most of your compiled tree. Clarify the ‘git bisect reset’ documentation to explain this optional argument, which was previously mentioned only in the usage message. Signed-off-by: Anders Kaseorg Signed-off-by: Junio C Hamano diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 63e7a42..d2ffae0 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -20,7 +20,7 @@ on the subcommand: git bisect bad [] git bisect good [...] git bisect skip [(|)...] - git bisect reset [] + git bisect reset [] git bisect visualize git bisect replay git bisect log @@ -81,16 +81,27 @@ will have been left with the first bad kernel revision in "refs/bisect/bad". Bisect reset ~~~~~~~~~~~~ -To return to the original head after a bisect session, issue the -following command: +After a bisect session, to clean up the bisection state and return to +the original HEAD, issue the following command: ------------------------------------------------ $ git bisect reset ------------------------------------------------ -This resets the tree to the original branch instead of being on the -bisection commit ("git bisect start" will also do that, as it resets -the bisection state). +By default, this will return your tree to the commit that was checked +out before `git bisect start`. (A new `git bisect start` will also do +that, as it cleans up the old bisection state.) + +With an optional argument, you can return to a different commit +instead: + +------------------------------------------------ +$ git bisect reset +------------------------------------------------ + +For example, `git bisect reset HEAD` will leave you on the current +bisection commit and avoid switching commits at all, while `git bisect +reset bisect/bad` will check out the first bad revision. Bisect visualize ~~~~~~~~~~~~~~~~ diff --git a/git-bisect.sh b/git-bisect.sh index 6f6f039..8b3c585 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -13,8 +13,8 @@ git bisect skip [(|)...] mark ... untestable revisions. git bisect next find next bisection to test and check it out. -git bisect reset [] - finish bisection search and go back to branch. +git bisect reset [] + finish bisection search and go back to commit. git bisect visualize show bisect status in gitk. git bisect replay @@ -311,8 +311,8 @@ bisect_reset() { } case "$#" in 0) branch=$(cat "$GIT_DIR/BISECT_START") ;; - 1) git show-ref --verify --quiet -- "refs/heads/$1" || - die "$1 does not seem to be a valid branch" + 1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || + die "'$1' is not a valid commit" branch="$1" ;; *) usage ;; -- cgit v0.10.2-6-g49f6 From c6dfb399448f6de17ce417052f1bb345c9e022c9 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Tue, 13 Oct 2009 12:53:28 +0200 Subject: remote-curl: add missing initialization of argv0_path All programs, in particular also the stand-alone programs (non-builtins) must call git_extract_argv0_path(argv[0]) in order to help builds that derive the installation prefix at runtime, such as the MinGW build. Without this call, the program segfaults (or raises an assertion failure). Signed-off-by: Johannes Sixt Tested-by: Michael Wookey Signed-off-by: Junio C Hamano diff --git a/remote-curl.c b/remote-curl.c index ad6a163..d8d276a 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -82,6 +82,7 @@ int main(int argc, const char **argv) const char *url; struct walker *walker = NULL; + git_extract_argv0_path(argv[0]); setup_git_directory(); if (argc < 2) { fprintf(stderr, "Remote needed\n"); -- cgit v0.10.2-6-g49f6 From 452e2256d2d7cb5494ca10fcbbb6bdf29570f2c0 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Tue, 13 Oct 2009 21:51:36 +0200 Subject: gitweb: fix esc_param The custom CGI escaping done in esc_param failed to escape UTF-8 properly. Fix by using CGI::escape on each sequence of matched characters instead of sprintf()ing a custom escaping for each byte. Additionally, the space -> + escape was being escaped due to greedy matching on the first substitution. Fix by adding space to the list of characters not handled on the first substitution. Finally, remove an unnecessary escaping of the + sign. Signed-off-by: Giuseppe Bilotta Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24b2193..4b21ad2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1083,8 +1083,7 @@ sub to_utf8 { # correct, but quoted slashes look too horrible in bookmarks sub esc_param { my $str = shift; - $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg; - $str =~ s/\+/%2B/g; + $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg; $str =~ s/ /\+/g; return $str; } -- cgit v0.10.2-6-g49f6 From d01a8e32fe10f1086e5e427f85237baff218fb01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinbrink?= Date: Wed, 14 Oct 2009 00:11:09 +0200 Subject: clone: Supply the right commit hash to post-checkout when -b is used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we use -b , we may checkout something else than what the remote's HEAD references, but we still used remote_head to supply the new ref value to the post-checkout hook, which is wrong. So instead of using remote_head to find the value to be passed to the post-checkout hook, we have to use our_head_points_at, which is always correctly setup, even if -b is not used. This also fixes a segfault when "clone -b " is used with a remote repo that doesn't have a valid HEAD, as in such a case remote_head is NULL, but we still tried to access it. Reported-by: Devin Cofer Signed-off-by: Björn Steinbrink Acked-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin-clone.c b/builtin-clone.c index 4992c25..5762a6f 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -641,7 +641,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die("unable to write new index file"); err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1), - sha1_to_hex(remote_head->old_sha1), "1", NULL); + sha1_to_hex(our_head_points_at->old_sha1), "1", + NULL); if (!err && option_recursive) err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); diff --git a/remote-curl.c b/remote-curl.c index d8d276a..2faf1c6 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -3,6 +3,7 @@ #include "strbuf.h" #include "walker.h" #include "http.h" +#include "exec_cmd.h" static struct ref *get_refs(struct walker *walker, const char *url) { -- cgit v0.10.2-6-g49f6 From 583371af1f88e9cd48fedbb6bbb147d8091fd591 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 13 Oct 2009 23:02:04 -0400 Subject: change throughput display units with fast links Switch to MiB/s when the connection is fast enough (i.e. on a LAN). Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/progress.c b/progress.c index 132ed95..3971f49 100644 --- a/progress.c +++ b/progress.c @@ -131,7 +131,13 @@ static void throughput_string(struct throughput *tp, off_t total, } else { l -= snprintf(tp->display, l, ", %u bytes", (int)total); } - if (rate) + + if (rate > 1 << 10) { + int x = rate + 5; /* for rounding */ + snprintf(tp->display + sizeof(tp->display) - l, l, + " | %u.%2.2u MiB/s", + x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10); + } else if (rate) snprintf(tp->display + sizeof(tp->display) - l, l, " | %u KiB/s", rate); } -- cgit v0.10.2-6-g49f6 From b3118bdc91876cbc04b7e81dcf7bea71d86ce4f8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 14 Oct 2009 07:23:51 -0700 Subject: sha1_file: Fix infinite loop when pack is corrupted Some types of corruption to a pack may confuse the deflate stream which stores an object. In Andy's reported case a 36 byte region of the pack was overwritten, leading to what appeared to be a valid deflate stream that was trying to produce a result larger than our allocated output buffer could accept. Z_BUF_ERROR is returned from inflate() if either the input buffer needs more input bytes, or the output buffer has run out of space. Previously we only considered the former case, as it meant we needed to move the stream's input buffer to the next window in the pack. We now abort the loop if inflate() returns Z_BUF_ERROR without consuming the entire input buffer it was given, or has filled the entire output buffer but has not yet returned Z_STREAM_END. Either state is a clear indicator that this loop is not working as expected, and should not continue. This problem cannot occur with loose objects as we open the entire loose object as a single buffer and treat Z_BUF_ERROR as an error. Reported-by: Andy Isaacson Signed-off-by: Shawn O. Pearce Acked-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 4ea0b18..4cc8939 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1357,6 +1357,8 @@ unsigned long get_size_from_delta(struct packed_git *p, in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); + if (st == Z_BUF_ERROR && (stream.avail_in || !stream.avail_out)) + break; curpos += stream.next_in - in; } while ((st == Z_OK || st == Z_BUF_ERROR) && stream.total_out < sizeof(delta_head)); @@ -1594,6 +1596,8 @@ static void *unpack_compressed_entry(struct packed_git *p, in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); + if (st == Z_BUF_ERROR && (stream.avail_in || !stream.avail_out)) + break; curpos += stream.next_in - in; } while (st == Z_OK || st == Z_BUF_ERROR); git_inflate_end(&stream); diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh index 5132d41..5f6cd4f 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -275,4 +275,13 @@ test_expect_success \ git cat-file blob $blob_2 > /dev/null && git cat-file blob $blob_3 > /dev/null' +test_expect_success \ + 'corrupting header to have too small output buffer fails unpack' \ + 'create_new_pack && + git prune-packed && + printf "\262\001" | do_corrupt_object $blob_1 0 && + test_must_fail git cat-file blob $blob_1 > /dev/null && + test_must_fail git cat-file blob $blob_2 > /dev/null && + test_must_fail git cat-file blob $blob_3 > /dev/null' + test_done -- cgit v0.10.2-6-g49f6 From fe0a3cb23c7930be419937825591388465ceb47f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Oct 2009 11:51:03 -0700 Subject: info/grafts: allow trailing whitespaces at the end of line When creating an info/grafts under windows, one typically gets a CRLF file. There is no good reason to forbid trailing CR at the end of the line (for that matter, any trailing whitespaces); the code allowed only LF simply because that was good enough for the platforms with LF line endings. Signed-off-by: Junio C Hamano diff --git a/commit.c b/commit.c index aa3b35b..eee5ab8 100644 --- a/commit.c +++ b/commit.c @@ -136,8 +136,8 @@ struct commit_graft *read_graft_line(char *buf, int len) int i; struct commit_graft *graft = NULL; - if (buf[len-1] == '\n') - buf[--len] = 0; + while (len && isspace(buf[len-1])) + buf[--len] = '\0'; if (buf[0] == '#' || buf[0] == '\0') return NULL; if ((len + 1) % 41) { -- cgit v0.10.2-6-g49f6 From cfe370c6476392095bc3f18013d195b1cccd6184 Mon Sep 17 00:00:00 2001 From: Matt Kraai Date: Fri, 16 Oct 2009 07:13:25 -0700 Subject: grep: do not segfault when -f is used "git grep" would segfault if its -f option was used because it would try to use an uninitialized strbuf, so initialize the strbuf. Thanks to Johannes Sixt for the help with the test cases. Signed-off-by: Matt Kraai Signed-off-by: Junio C Hamano diff --git a/builtin-grep.c b/builtin-grep.c index fd450bc..e3b940b 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -599,7 +599,7 @@ static int file_callback(const struct option *opt, const char *arg, int unset) struct grep_opt *grep_opt = opt->value; FILE *patterns; int lno = 0; - struct strbuf sb; + struct strbuf sb = STRBUF_INIT; patterns = fopen(arg, "r"); if (!patterns) diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 6ca11d7..5f91d82 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -164,6 +164,72 @@ test_expect_success 'grep -e A --and --not -e B' ' test_cmp expected actual ' +test_expect_success 'grep -f, non-existent file' ' + test_must_fail git grep -f patterns +' + +cat >expected <pattern <actual && + test_cmp expected actual +' + +cat >expected <patterns <actual && + test_cmp expected actual +' + +cat >expected <patterns <actual && + test_cmp expected actual +' + cat >expected < Date: Fri, 16 Oct 2009 23:56:55 -0700 Subject: GIT 1.6.5.1 Signed-off-by: Junio C Hamano diff --git a/Documentation/RelNotes-1.6.5.1.txt b/Documentation/RelNotes-1.6.5.1.txt new file mode 100644 index 0000000..309ba18 --- /dev/null +++ b/Documentation/RelNotes-1.6.5.1.txt @@ -0,0 +1,20 @@ +GIT v1.6.5.1 Release Notes +========================== + +Fixes since v1.6.5 +------------------ + + * An corrupt pack could make codepath to read objects into an + infinite loop. + + * Download throughput display was always shown in KiB/s but on fast links + it is more appropriate to show it in MiB/s. + + * "git grep -f filename" used uninitialized variable and segfaulted. + + * "git clone -b branch" gave a wrong commit object name to post-checkout + hook. + + * "git pull" over http did not work on msys. + +Other minor documentation updates are included. diff --git a/Documentation/git.txt b/Documentation/git.txt index d97aaf5..d11c5c1 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -46,6 +46,7 @@ Documentation for older releases are available here: * link:v1.6.5/git.html[documentation for release 1.6.5] * release notes for + link:RelNotes-1.6.5.1.txt[1.6.5.1], link:RelNotes-1.6.5.txt[1.6.5]. * link:v1.6.4.4/git.html[documentation for release 1.6.4.4] diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 08e6073..d11e43c 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.5 +DEF_VER=v1.6.5.1 LF=' ' diff --git a/RelNotes b/RelNotes index b62449d..dda06d1 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.6.5.txt \ No newline at end of file +Documentation/RelNotes-1.6.5.1.txt \ No newline at end of file -- cgit v0.10.2-6-g49f6 From ad12b81271d76f3e4a8b1f527e252ac8452732fd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Oct 2009 00:11:43 -0700 Subject: Start 1.6.6 cycle Signed-off-by: Junio C Hamano diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes-1.6.6.txt new file mode 100644 index 0000000..5f1fecb --- /dev/null +++ b/Documentation/RelNotes-1.6.6.txt @@ -0,0 +1,60 @@ +GIT v1.6.6 Release Notes +======================== + +In git 1.7.0, which is planned to be the release after 1.6.6, "git +push" into a branch that is currently checked out will be refused by +default. + +You can choose what should happen upon such a push by setting the +configuration variable receive.denyCurrentBranch in the receiving +repository. + +Also, "git push $there :$killed" to delete the branch $killed in a remote +repository $there, when $killed branch is the current branch pointed at by +its HEAD, will be refused by default. + +You can choose what should happen upon such a push by setting the +configuration variable receive.denyDeleteCurrent in the receiving +repository. + +To ease the transition plan, the receiving repository of such a +push running this release will issue a big warning when the +configuration variable is missing. Please refer to: + + http://git.or.cz/gitwiki/GitFaq#non-bare + http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + +for more details on the reason why this change is needed and the +transition plan. + +Updates since v1.6.5 +-------------------- + +(subsystems) + +(portability) + +(performance) + +(usability, bells and whistles) + + * "git log --decorate" shows the location of HEAD as well. + +(developers) + +Fixes since v1.6.5 +------------------ + +All of the fixes in v1.6.5.X maintenance series are included in this +release, unless otherwise noted. + + * "git apply" and "git diff" (including patch output from "git log -p") + now flags trailing blank lines as whitespace errors correctly (only + "apply --whitespace=fix" stripped them but "apply --whitespace=warn" + did not even warn). + + * Two whitespace error classes, 'blank-at-eof' and 'blank-at-eol', have + been introduced (settable by core.whitespace configuration variable and + whitespace attribute). The 'trailing-space' whitespace error class has + become a short-hand to cover both of these and there is no behaviour + change for existing set-ups. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 08e6073..710d361 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.5 +DEF_VER=v1.6.5.GIT LF=' ' diff --git a/RelNotes b/RelNotes index b62449d..5274cee 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.6.5.txt \ No newline at end of file +Documentation/RelNotes-1.6.6.txt \ No newline at end of file -- cgit v0.10.2-6-g49f6 From c6e8c8005a2b1fc4cff72d279f29178767bd1a47 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Oct 2009 00:27:24 -0700 Subject: check_filename(): make verify_filename() callable without dying Make it possible to invole the logic of verify_filename() to make sure the pathname arguments are unambiguous without actually dying. The caller may want to do something different. diff --git a/cache.h b/cache.h index 96840c7..71a731d 100644 --- a/cache.h +++ b/cache.h @@ -396,6 +396,7 @@ extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern const char *prefix_path(const char *prefix, int len, const char *path); extern const char *prefix_filename(const char *prefix, int len, const char *path); +extern int check_filename(const char *prefix, const char *name); extern void verify_filename(const char *prefix, const char *name); extern void verify_non_filename(const char *prefix, const char *name); diff --git a/setup.c b/setup.c index 029371e..f67250b 100644 --- a/setup.c +++ b/setup.c @@ -61,6 +61,19 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) return path; } +int check_filename(const char *prefix, const char *arg) +{ + const char *name; + struct stat st; + + name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; + if (!lstat(name, &st)) + return 1; /* file exists */ + if (errno == ENOENT || errno == ENOTDIR) + return 0; /* file does not exist */ + die_errno("failed to stat '%s'", arg); +} + /* * Verify a filename that we got as an argument for a pathspec * entry. Note that a filename that begins with "-" never verifies @@ -70,18 +83,12 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) */ void verify_filename(const char *prefix, const char *arg) { - const char *name; - struct stat st; - if (*arg == '-') die("bad flag '%s' used after filename", arg); - name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; - if (!lstat(name, &st)) + if (check_filename(prefix, arg)) return; - if (errno == ENOENT) - die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" - "Use '--' to separate paths from revisions", arg); - die_errno("failed to stat '%s'", arg); + die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" + "Use '--' to separate paths from revisions", arg); } /* @@ -91,19 +98,14 @@ void verify_filename(const char *prefix, const char *arg) */ void verify_non_filename(const char *prefix, const char *arg) { - const char *name; - struct stat st; - if (!is_inside_work_tree() || is_inside_git_dir()) return; if (*arg == '-') return; /* flag */ - name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; - if (!lstat(name, &st)) - die("ambiguous argument '%s': both revision and filename\n" - "Use '--' to separate filenames from revisions", arg); - if (errno != ENOENT && errno != ENOTDIR) - die_errno("failed to stat '%s'", arg); + if (!check_filename(prefix, arg)) + return; + die("ambiguous argument '%s': both revision and filename\n" + "Use '--' to separate filenames from revisions", arg); } const char **get_pathspec(const char *prefix, const char **pathspec) -- cgit v0.10.2-6-g49f6 From 70c9ac2f1999b7e0c17a864235537cffe29dfea4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Oct 2009 00:13:47 -0700 Subject: DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz" When 'frotz' is not a valid object name and not a tracked filename, we used to complain and failed this command. When there is only one remote that has 'frotz' as one of its tracking branches, we can DWIM it as a request to create a local branch 'frotz' forking from the matching remote tracking branch. Signed-off-by: Junio C Hamano diff --git a/builtin-checkout.c b/builtin-checkout.c index d050c37..fb7e68a 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -572,6 +572,40 @@ static int interactive_checkout(const char *revision, const char **pathspec, return run_add_interactive(revision, "--patch=checkout", pathspec); } +struct tracking_name_data { + const char *name; + char *remote; + int unique; +}; + +static int check_tracking_name(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) +{ + struct tracking_name_data *cb = cb_data; + const char *slash; + + if (prefixcmp(refname, "refs/remotes/")) + return 0; + slash = strchr(refname + 13, '/'); + if (!slash || strcmp(slash + 1, cb->name)) + return 0; + if (cb->remote) { + cb->unique = 0; + return 0; + } + cb->remote = xstrdup(refname); + return 0; +} + +static const char *unique_tracking_name(const char *name) +{ + struct tracking_name_data cb_data = { name, NULL, 1 }; + for_each_ref(check_tracking_name, &cb_data); + if (cb_data.unique) + return cb_data.remote; + free(cb_data.remote); + return NULL; +} int cmd_checkout(int argc, const char **argv, const char *prefix) { @@ -630,8 +664,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.new_branch = argv0 + 1; } - if (opts.track == BRANCH_TRACK_UNSPECIFIED) - opts.track = git_branch_track; if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); @@ -655,6 +687,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * With no paths, if is a commit, that is to * switch to the branch or detach HEAD at it. * + * With no paths, if is _not_ a commit, no -t nor -b + * was given, and there is a tracking branch whose name is + * in one and only one remote, then this is a short-hand + * to fork local from that remote tracking branch. + * * Otherwise shall not be ambiguous. * - If it's *only* a reference, treat it like case (1). * - If it's only a path, treat it like case (2). @@ -677,7 +714,20 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (get_sha1(arg, rev)) { if (has_dash_dash) /* case (1) */ die("invalid reference: %s", arg); - goto no_reference; /* case (3 -> 2) */ + if (!patch_mode && + opts.track == BRANCH_TRACK_UNSPECIFIED && + !opts.new_branch && + !check_filename(NULL, arg) && + argc == 1) { + const char *remote = unique_tracking_name(arg); + if (!remote || get_sha1(remote, rev)) + goto no_reference; + opts.new_branch = arg; + arg = remote; + /* DWIMmed to create local branch */ + } + else + goto no_reference; } /* we can't end up being in (2) anymore, eat the argument */ @@ -715,6 +765,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) } no_reference: + + if (opts.track == BRANCH_TRACK_UNSPECIFIED) + opts.track = git_branch_track; + if (argc) { const char **pathspec = get_pathspec(prefix, argv); -- cgit v0.10.2-6-g49f6 From 3edd98ac6569252acd669c2b9cbe39d15eeb7e56 Mon Sep 17 00:00:00 2001 From: "Carlos R. Mafra" Date: Sun, 11 Oct 2009 15:32:19 +0200 Subject: Makefile: clean block-sha1/ directory instead of mozilla-sha1/ 'make clean' should remove the object files from block-sha1/ instead of the non-existent mozilla-sha1/ directory. Signed-off-by: Carlos R. Mafra Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index fea237b..42b7d60 100644 --- a/Makefile +++ b/Makefile @@ -1827,7 +1827,7 @@ distclean: clean $(RM) configure clean: - $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ + $(RM) *.o block-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) -- cgit v0.10.2-6-g49f6 From 989119d96e8d06c69b16d2021856309ec8e0a0c8 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 19 Oct 2009 00:01:19 -0400 Subject: document push's new quiet option Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index ba6a8a2..37c8895 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -138,6 +138,11 @@ useful if you write an alias or script around 'git-push'. --verbose:: Run verbosely. +-q:: +--quiet:: + Suppress all output, including the listing of updated refs, + unless an error occurs. + include::urls-remotes.txt[] OUTPUT -- cgit v0.10.2-6-g49f6 From fcb044ee5754ad2df2d217748a976ec82970eb29 Mon Sep 17 00:00:00 2001 From: Nanako Shiraishi Date: Mon, 19 Oct 2009 11:54:12 +0900 Subject: git push: remove incomplete options list from help text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'git push -h' shows usage text with incomplete list of options and then has a separate list of options that are supported. Imitate the way other commands (I looked at 'git diff' for an example) show their options. Signed-off-by: しらいし ななこ Signed-off-by: Junio C Hamano diff --git a/builtin-push.c b/builtin-push.c index 3cb1ee4..ab49b7c 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,7 +10,7 @@ #include "parse-options.h" static const char * const push_usage[] = { - "git push [--all | --mirror] [-n | --dry-run] [--porcelain] [--tags] [--receive-pack=] [--repo=] [-f | --force] [-v] [ ...]", + "git push [] [ ...]", NULL, }; -- cgit v0.10.2-6-g49f6 From 8ef4c28b8d391ee053049d2dc9a3568b86c0dd38 Mon Sep 17 00:00:00 2001 From: Nanako Shiraishi Date: Mon, 19 Oct 2009 12:57:01 +0900 Subject: git push: say that --tag can't be used with --all or --mirror in help text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This replaces an earlier patch by Björn Gustavsson, Message-ID: <4AD75029.1010109@gmail.com> Signed-off-by: しらいし ななこ Signed-off-by: Junio C Hamano diff --git a/builtin-push.c b/builtin-push.c index ab49b7c..b5cd2cd 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -181,7 +181,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL), OPT_BIT( 0 , "mirror", &flags, "mirror all refs", (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), - OPT_BOOLEAN( 0 , "tags", &tags, "push tags"), + OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror"), OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), -- cgit v0.10.2-6-g49f6 From fb423da0e5668f3945c5a3e34fe5953cde6985a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 17 Oct 2009 18:30:48 +0200 Subject: describe: load refnames before calling describe() Get rid of the static variable that was used to prevent loading all the refnames multiple times by moving that code out of describe(), simply making sure it is only run once that way. Also change the error message that is shown in case no refnames are found to not include a hash any more, as the error condition is not specific to any particular revision. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/builtin-describe.c b/builtin-describe.c index df67a73..2dcfd3d 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -180,7 +180,6 @@ static void describe(const char *arg, int last_one) unsigned char sha1[20]; struct commit *cmit, *gave_up_on = NULL; struct commit_list *list; - static int initialized = 0; struct commit_name *n; struct possible_tag all_matches[MAX_TAGS]; unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; @@ -192,14 +191,6 @@ static void describe(const char *arg, int last_one) if (!cmit) die("%s is not a valid '%s' object", arg, commit_type); - if (!initialized) { - initialized = 1; - for_each_ref(get_name, NULL); - } - - if (!found_names) - die("cannot describe '%s'", sha1_to_hex(sha1)); - n = cmit->util; if (n) { /* @@ -359,6 +350,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix) return cmd_name_rev(i + argc, args, prefix); } + for_each_ref(get_name, NULL); + if (!found_names) + die("No names found, cannot describe anything."); + if (argc == 0) { describe("HEAD", 1); } else { -- cgit v0.10.2-6-g49f6 From f6fdbb6804eac72eb0ccff191162a5911bf2b014 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 19 Oct 2009 02:49:55 -0400 Subject: cvsimport: fix relative argument filenames One of the first things that cvsimport does is chdir to the newly created git repo. This means that any filenames given to us on the command line will be looked up relative to the git repo directory. This is probably not what the user expects, so let's remember and prepend the original directory for relative filenames. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 1ad20ac..a7d215c 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -579,10 +579,21 @@ sub get_headref ($) { return $r; } +my $user_filename_prepend = ''; +sub munge_user_filename { + my $name = shift; + return File::Spec->file_name_is_absolute($name) ? + $name : + $user_filename_prepend . $name; +} + -d $git_tree or mkdir($git_tree,0777) or die "Could not create $git_tree: $!"; -chdir($git_tree); +if ($git_tree ne '.') { + $user_filename_prepend = getwd() . '/'; + chdir($git_tree); +} my $last_branch = ""; my $orig_branch = ""; @@ -644,7 +655,7 @@ unless (-d $git_dir) { -f "$git_dir/cvs-authors" and read_author_info("$git_dir/cvs-authors"); if ($opt_A) { - read_author_info($opt_A); + read_author_info(munge_user_filename($opt_A)); write_author_info("$git_dir/cvs-authors"); } @@ -679,7 +690,7 @@ unless ($opt_P) { $? == 0 or die "git-cvsimport: fatal: cvsps reported error\n"; close $cvspsfh; } else { - $cvspsfile = $opt_P; + $cvspsfile = munge_user_filename($opt_P); } open(CVS, "<$cvspsfile") or die $!; -- cgit v0.10.2-6-g49f6 From 7f98ebc8fd34107c6234cbad7b776054810b6fe1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 15 Oct 2009 22:59:41 -0700 Subject: format_commit_message(): fix function signature The format template string was declared as "const void *" for some unknown reason, even though it obviously is meant to be passed a string. Make it "const char *". Signed-off-by: Junio C Hamano diff --git a/commit.h b/commit.h index f4fc5c5..95f981a 100644 --- a/commit.h +++ b/commit.h @@ -70,7 +70,7 @@ extern char *reencode_commit_message(const struct commit *commit, const char **encoding_p); extern void get_commit_format(const char *arg, struct rev_info *); extern void format_commit_message(const struct commit *commit, - const void *format, struct strbuf *sb, + const char *format, struct strbuf *sb, enum date_mode dmode); extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*, struct strbuf *, diff --git a/pretty.c b/pretty.c index f5983f8..587101f 100644 --- a/pretty.c +++ b/pretty.c @@ -740,7 +740,7 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, } void format_commit_message(const struct commit *commit, - const void *format, struct strbuf *sb, + const char *format, struct strbuf *sb, enum date_mode dmode) { struct format_commit_context context; -- cgit v0.10.2-6-g49f6 From 7725cb5e8bfd27887ee0b984b83708eff655ec9e Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 15 Oct 2009 23:32:33 -0700 Subject: rebase -i: fix reword when using a terminal editor We don't want to use output() on git-commit --amend when rewording the commit message. This leads to confusion as the editor is run in a subshell with it's output saved away, leaving the user with a seemingly frozen terminal. Fix by removing the output part. Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index a43ee22..a1879e3 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -346,7 +346,7 @@ do_next () { mark_action_done pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" - output git commit --amend + git commit --amend ;; edit|e) comment_for_reflog edit -- cgit v0.10.2-6-g49f6 From ae0b27023018416c0bfe54823466dee67c20705a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 11 Nov 2007 14:14:15 +0000 Subject: print_wrapped_text(): allow hard newlines print_wrapped_text() will insert its own newlines. Up until now, if the text passed to it contained newlines, they would not be handled properly (the wrapping got confused after that). The strategy is to replace a single new-line with a space, but keep double new-lines so that already-wrapped text with empty lines between paragraphs will be handled properly. However, single new-line characters are only handled this way if the character after it is an alphanumeric character, as per Linus' suggestion. Signed-off-by: Johannes Schindelin diff --git a/utf8.c b/utf8.c index db706ac..2aace1d 100644 --- a/utf8.c +++ b/utf8.c @@ -310,6 +310,8 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) if (!c || isspace(c)) { if (w < width || !space) { const char *start = bol; + if (!c && text == start) + return w; if (space) start = space; else @@ -317,13 +319,25 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) fwrite(start, text - start, 1, stdout); if (!c) return w; - else if (c == '\t') - w |= 0x07; space = text; + if (c == '\t') + w |= 0x07; + else if (c == '\n') { + space++; + if (*space == '\n') { + putchar('\n'); + goto new_line; + } + else if (!isalnum(*space)) + goto new_line; + else + putchar(' '); + } w++; text++; } else { +new_line: putchar('\n'); text = bol = space + isspace(*space); space = NULL; -- cgit v0.10.2-6-g49f6 From a94410c8134581f2f11a7db838da4d8725911a3c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 10 Nov 2008 18:47:00 +0100 Subject: Add strbuf_add_wrapped_text() to utf8.[ch] The newly added function can rewrap text according to a given first-line indent, other-indent and text width. Signed-off-by: Johannes Schindelin diff --git a/utf8.c b/utf8.c index 2aace1d..da99669 100644 --- a/utf8.c +++ b/utf8.c @@ -1,4 +1,5 @@ #include "git-compat-util.h" +#include "strbuf.h" #include "utf8.h" /* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */ @@ -279,14 +280,22 @@ int is_utf8(const char *text) return 1; } -static void print_spaces(int count) +static inline void strbuf_write(struct strbuf *sb, const char *buf, int len) +{ + if (sb) + strbuf_insert(sb, sb->len, buf, len); + else + fwrite(buf, len, 1, stdout); +} + +static void print_spaces(struct strbuf *buf, int count) { static const char s[] = " "; while (count >= sizeof(s)) { - fwrite(s, sizeof(s) - 1, 1, stdout); + strbuf_write(buf, s, sizeof(s) - 1); count -= sizeof(s) - 1; } - fwrite(s, count, 1, stdout); + strbuf_write(buf, s, count); } /* @@ -295,7 +304,8 @@ static void print_spaces(int count) * If indent is negative, assume that already -indent columns have been * consumed (and no extra indent is necessary for the first line). */ -int print_wrapped_text(const char *text, int indent, int indent2, int width) +int strbuf_add_wrapped_text(struct strbuf *buf, + const char *text, int indent, int indent2, int width) { int w = indent, assume_utf8 = is_utf8(text); const char *bol = text, *space = NULL; @@ -315,8 +325,8 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) if (space) start = space; else - print_spaces(indent); - fwrite(start, text - start, 1, stdout); + print_spaces(buf, indent); + strbuf_write(buf, start, text - start); if (!c) return w; space = text; @@ -325,20 +335,20 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width) else if (c == '\n') { space++; if (*space == '\n') { - putchar('\n'); + strbuf_write(buf, "\n", 1); goto new_line; } else if (!isalnum(*space)) goto new_line; else - putchar(' '); + strbuf_write(buf, " ", 1); } w++; text++; } else { new_line: - putchar('\n'); + strbuf_write(buf, "\n", 1); text = bol = space + isspace(*space); space = NULL; w = indent = indent2; @@ -354,6 +364,11 @@ new_line: } } +int print_wrapped_text(const char *text, int indent, int indent2, int width) +{ + return strbuf_add_wrapped_text(NULL, text, indent, indent2, width); +} + int is_encoding_utf8(const char *name) { if (!name) diff --git a/utf8.h b/utf8.h index 2f1b14f..ae30ae4 100644 --- a/utf8.h +++ b/utf8.h @@ -10,6 +10,8 @@ int is_utf8(const char *text); int is_encoding_utf8(const char *name); int print_wrapped_text(const char *text, int indent, int indent2, int len); +int strbuf_add_wrapped_text(struct strbuf *buf, + const char *text, int indent, int indent2, int width); #ifndef NO_ICONV char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding); -- cgit v0.10.2-6-g49f6 From a97a74686d70a318cd802003498054cc1e8b0ae2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Oct 2009 12:21:57 +0200 Subject: Introduce commit notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit notes are blobs which are shown together with the commit message. These blobs are taken from the notes ref, which you can configure by the config variable core.notesRef, which in turn can be overridden by the environment variable GIT_NOTES_REF. The notes ref is a branch which contains "files" whose names are the names of the corresponding commits (i.e. the SHA-1). The rationale for putting this information into a ref is this: we want to be able to fetch and possibly union-merge the notes, maybe even look at the date when a note was introduced, and we want to store them efficiently together with the other objects. This patch has been improved by the following contributions: - Thomas Rast: fix core.notesRef documentation - Tor Arne Vestbø: fix printing of multi-line notes - Alex Riesen: Using char array instead of char pointer costs less BSS - Johan Herland: Plug leak when msg is good, but msglen or type causes return Signed-off-by: Johannes Schindelin Signed-off-by: Thomas Rast Signed-off-by: Tor Arne Vestbø Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano get_commit_notes(): Plug memory leak when 'if' triggers, but not because of read_sha1_file() failure diff --git a/Documentation/config.txt b/Documentation/config.txt index cd17814..57d64e4 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -454,6 +454,19 @@ On some file system/operating system combinations, this is unreliable. Set this config setting to 'rename' there; However, This will remove the check that makes sure that existing object files will not get overwritten. +core.notesRef:: + When showing commit messages, also show notes which are stored in + the given ref. This ref is expected to contain files named + after the full SHA-1 of the commit they annotate. ++ +If such a file exists in the given ref, the referenced blob is read, and +appended to the commit message, separated by a "Notes:" line. If the +given ref itself does not exist, it is not an error, but means that no +notes should be printed. ++ +This setting defaults to "refs/notes/commits", and can be overridden by +the `GIT_NOTES_REF` environment variable. + add.ignore-errors:: Tells 'git-add' to continue adding files when some files cannot be added due to indexing errors. Equivalent to the '--ignore-errors' diff --git a/Makefile b/Makefile index fea237b..9a6a729 100644 --- a/Makefile +++ b/Makefile @@ -432,6 +432,7 @@ LIB_H += ll-merge.h LIB_H += log-tree.h LIB_H += mailmap.h LIB_H += merge-recursive.h +LIB_H += notes.h LIB_H += object.h LIB_H += pack.h LIB_H += pack-refs.h @@ -516,6 +517,7 @@ LIB_OBJS += match-trees.o LIB_OBJS += merge-file.o LIB_OBJS += merge-recursive.o LIB_OBJS += name-hash.o +LIB_OBJS += notes.o LIB_OBJS += object.o LIB_OBJS += pack-check.o LIB_OBJS += pack-refs.o diff --git a/cache.h b/cache.h index a5eeead..4f0ddec 100644 --- a/cache.h +++ b/cache.h @@ -372,6 +372,8 @@ static inline enum object_type object_type(unsigned int mode) #define GITATTRIBUTES_FILE ".gitattributes" #define INFOATTRIBUTES_FILE "info/attributes" #define ATTRIBUTE_MACRO_PREFIX "[attr]" +#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF" +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" extern int is_bare_repository_cfg; extern int is_bare_repository(void); @@ -567,6 +569,8 @@ enum object_creation_mode { extern enum object_creation_mode object_creation_mode; +extern char *notes_ref_name; + extern int grafts_replace_parents; #define GIT_REPO_VERSION 0 diff --git a/commit.c b/commit.c index fedbd5e..5ade8ed 100644 --- a/commit.c +++ b/commit.c @@ -5,6 +5,7 @@ #include "utf8.h" #include "diff.h" #include "revision.h" +#include "notes.h" int save_commit_buffer = 1; diff --git a/config.c b/config.c index c644061..51f2208 100644 --- a/config.c +++ b/config.c @@ -467,6 +467,11 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.notesref")) { + notes_ref_name = xstrdup(value); + return 0; + } + if (!strcmp(var, "core.pager")) return git_config_string(&pager_program, var, value); diff --git a/environment.c b/environment.c index 5de6837..571ab56 100644 --- a/environment.c +++ b/environment.c @@ -49,6 +49,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING; #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS #endif enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; +char *notes_ref_name; int grafts_replace_parents = 1; /* Parallel index stat data preload? */ diff --git a/notes.c b/notes.c new file mode 100644 index 0000000..66379ff --- /dev/null +++ b/notes.c @@ -0,0 +1,70 @@ +#include "cache.h" +#include "commit.h" +#include "notes.h" +#include "refs.h" +#include "utf8.h" +#include "strbuf.h" + +static int initialized; + +void get_commit_notes(const struct commit *commit, struct strbuf *sb, + const char *output_encoding) +{ + static const char utf8[] = "utf-8"; + struct strbuf name = STRBUF_INIT; + unsigned char sha1[20]; + char *msg, *msg_p; + unsigned long linelen, msglen; + enum object_type type; + + if (!initialized) { + const char *env = getenv(GIT_NOTES_REF_ENVIRONMENT); + if (env) + notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); + else if (!notes_ref_name) + notes_ref_name = GIT_NOTES_DEFAULT_REF; + if (notes_ref_name && read_ref(notes_ref_name, sha1)) + notes_ref_name = NULL; + initialized = 1; + } + + if (!notes_ref_name) + return; + + strbuf_addf(&name, "%s:%s", notes_ref_name, + sha1_to_hex(commit->object.sha1)); + if (get_sha1(name.buf, sha1)) + return; + + if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen || + type != OBJ_BLOB) { + free(msg); + return; + } + + if (output_encoding && *output_encoding && + strcmp(utf8, output_encoding)) { + char *reencoded = reencode_string(msg, output_encoding, utf8); + if (reencoded) { + free(msg); + msg = reencoded; + msglen = strlen(msg); + } + } + + /* we will end the annotation by a newline anyway */ + if (msglen && msg[msglen - 1] == '\n') + msglen--; + + strbuf_addstr(sb, "\nNotes:\n"); + + for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { + linelen = strchrnul(msg_p, '\n') - msg_p; + + strbuf_addstr(sb, " "); + strbuf_add(sb, msg_p, linelen); + strbuf_addch(sb, '\n'); + } + + free(msg); +} diff --git a/notes.h b/notes.h new file mode 100644 index 0000000..79d21b6 --- /dev/null +++ b/notes.h @@ -0,0 +1,7 @@ +#ifndef NOTES_H +#define NOTES_H + +void get_commit_notes(const struct commit *commit, struct strbuf *sb, + const char *output_encoding); + +#endif diff --git a/pretty.c b/pretty.c index f5983f8..e25db81 100644 --- a/pretty.c +++ b/pretty.c @@ -6,6 +6,7 @@ #include "string-list.h" #include "mailmap.h" #include "log-tree.h" +#include "notes.h" #include "color.h" static char *user_format; @@ -975,5 +976,9 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, */ if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body) strbuf_addch(sb, '\n'); + + if (fmt != CMIT_FMT_ONELINE) + get_commit_notes(commit, sb, encoding); + free(reencoded); } -- cgit v0.10.2-6-g49f6 From 65d9fb487f36d4a12a169dc18cbbb5225337c085 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Oct 2009 12:21:58 +0200 Subject: Add a script to edit/inspect notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The script 'git notes' allows you to edit and show commit notes, by calling either git notes show or git notes edit This patch has been improved by the following contributions: - Tor Arne Vestbø: fix printing of multi-line notes - Michael J Gruber: test and handle empty notes gracefully - Thomas Rast: - only clean up message file when editing - use GIT_EDITOR and core.editor over VISUAL/EDITOR - t3301: fix confusing quoting in test for valid notes ref - t3301: use test_must_fail instead of ! - refuse to edit notes outside refs/notes/ - Junio C Hamano: tests: fix "export var=val" - Christian Couder: documentation: fix 'linkgit' macro in "git-notes.txt" - Johan Herland: minor cleanup and bugfixing in git-notes.sh (v2) Signed-off-by: Johannes Schindelin Signed-off-by: Tor Arne Vestbø Signed-off-by: Michael J Gruber Signed-off-by: Thomas Rast Signed-off-by: Christian Couder Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 51a37b1..cbafa64 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ git-mktag git-mktree git-name-rev git-mv +git-notes git-pack-redundant git-pack-objects git-pack-refs diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt new file mode 100644 index 0000000..7136016 --- /dev/null +++ b/Documentation/git-notes.txt @@ -0,0 +1,46 @@ +git-notes(1) +============ + +NAME +---- +git-notes - Add/inspect commit notes + +SYNOPSIS +-------- +[verse] +'git-notes' (edit | show) [commit] + +DESCRIPTION +----------- +This command allows you to add notes to commit messages, without +changing the commit. To discern these notes from the message stored +in the commit object, the notes are indented like the message, after +an unindented line saying "Notes:". + +To disable commit notes, you have to set the config variable +core.notesRef to the empty string. Alternatively, you can set it +to a different ref, something like "refs/notes/bugzilla". This setting +can be overridden by the environment variable "GIT_NOTES_REF". + + +SUBCOMMANDS +----------- + +edit:: + Edit the notes for a given commit (defaults to HEAD). + +show:: + Show the notes for a given commit (defaults to HEAD). + + +Author +------ +Written by Johannes Schindelin + +Documentation +------------- +Documentation by Johannes Schindelin + +GIT +--- +Part of the linkgit:git[7] suite diff --git a/Makefile b/Makefile index 9a6a729..8d7cec7 100644 --- a/Makefile +++ b/Makefile @@ -321,6 +321,7 @@ SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-mergetool--lib.sh +SCRIPT_SH += git-notes.sh SCRIPT_SH += git-parse-remote.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh diff --git a/command-list.txt b/command-list.txt index fb03a2e..4296941 100644 --- a/command-list.txt +++ b/command-list.txt @@ -74,6 +74,7 @@ git-mktag plumbingmanipulators git-mktree plumbingmanipulators git-mv mainporcelain common git-name-rev plumbinginterrogators +git-notes mainporcelain git-pack-objects plumbingmanipulators git-pack-redundant plumbinginterrogators git-pack-refs ancillarymanipulators diff --git a/git-notes.sh b/git-notes.sh new file mode 100755 index 0000000..f06c254 --- /dev/null +++ b/git-notes.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +USAGE="(edit | show) [commit]" +. git-sh-setup + +test -n "$3" && usage + +test -z "$1" && usage +ACTION="$1"; shift + +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)" +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits" + +COMMIT=$(git rev-parse --verify --default HEAD "$@") || +die "Invalid commit: $@" + +case "$ACTION" in +edit) + if [ "${GIT_NOTES_REF#refs/notes/}" = "$GIT_NOTES_REF" ]; then + die "Refusing to edit notes in $GIT_NOTES_REF (outside of refs/notes/)" + fi + + MSG_FILE="$GIT_DIR/new-notes-$COMMIT" + GIT_INDEX_FILE="$MSG_FILE.idx" + export GIT_INDEX_FILE + + trap ' + test -f "$MSG_FILE" && rm "$MSG_FILE" + test -f "$GIT_INDEX_FILE" && rm "$GIT_INDEX_FILE" + ' 0 + + GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE" + + CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ') + if [ -z "$CURRENT_HEAD" ]; then + PARENT= + else + PARENT="-p $CURRENT_HEAD" + git read-tree "$GIT_NOTES_REF" || die "Could not read index" + git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null + fi + + core_editor="$(git config core.editor)" + ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE" + + grep -v ^# < "$MSG_FILE" | git stripspace > "$MSG_FILE".processed + mv "$MSG_FILE".processed "$MSG_FILE" + if [ -s "$MSG_FILE" ]; then + BLOB=$(git hash-object -w "$MSG_FILE") || + die "Could not write into object database" + git update-index --add --cacheinfo 0644 $BLOB $COMMIT || + die "Could not write index" + else + test -z "$CURRENT_HEAD" && + die "Will not initialise with empty tree" + git update-index --force-remove $COMMIT || + die "Could not update index" + fi + + TREE=$(git write-tree) || die "Could not write tree" + NEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) || + die "Could not annotate" + git update-ref -m "Annotate $COMMIT" \ + "$GIT_NOTES_REF" $NEW_HEAD $CURRENT_HEAD +;; +show) + git rev-parse -q --verify "$GIT_NOTES_REF":$COMMIT > /dev/null || + die "No note for commit $COMMIT." + git show "$GIT_NOTES_REF":$COMMIT +;; +*) + usage +esac diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh new file mode 100755 index 0000000..73e53be --- /dev/null +++ b/t/t3301-notes.sh @@ -0,0 +1,114 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='Test commit notes' + +. ./test-lib.sh + +cat > fake_editor.sh << \EOF +echo "$MSG" > "$1" +echo "$MSG" >& 2 +EOF +chmod a+x fake_editor.sh +VISUAL=./fake_editor.sh +export VISUAL + +test_expect_success 'cannot annotate non-existing HEAD' ' + (MSG=3 && export MSG && test_must_fail git notes edit) +' + +test_expect_success setup ' + : > a1 && + git add a1 && + test_tick && + git commit -m 1st && + : > a2 && + git add a2 && + test_tick && + git commit -m 2nd +' + +test_expect_success 'need valid notes ref' ' + (MSG=1 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF && + test_must_fail git notes edit) && + (MSG=2 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF && + test_must_fail git notes show) +' + +test_expect_success 'refusing to edit in refs/heads/' ' + (MSG=1 GIT_NOTES_REF=refs/heads/bogus && + export MSG GIT_NOTES_REF && + test_must_fail git notes edit) +' + +test_expect_success 'refusing to edit in refs/remotes/' ' + (MSG=1 GIT_NOTES_REF=refs/remotes/bogus && + export MSG GIT_NOTES_REF && + test_must_fail git notes edit) +' + +# 1 indicates caught gracefully by die, 128 means git-show barked +test_expect_success 'handle empty notes gracefully' ' + git notes show ; test 1 = $? +' + +test_expect_success 'create notes' ' + git config core.notesRef refs/notes/commits && + MSG=b1 git notes edit && + test ! -f .git/new-notes && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b1 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +cat > expect << EOF +commit 268048bfb8a1fb38e703baceb8ab235421bf80c5 +Author: A U Thor +Date: Thu Apr 7 15:14:13 2005 -0700 + + 2nd + +Notes: + b1 +EOF + +test_expect_success 'show notes' ' + ! (git cat-file commit HEAD | grep b1) && + git log -1 > output && + test_cmp expect output +' +test_expect_success 'create multi-line notes (setup)' ' + : > a3 && + git add a3 && + test_tick && + git commit -m 3rd && + MSG="b3 +c3c3c3c3 +d3d3d3" git notes edit +' + +cat > expect-multiline << EOF +commit 1584215f1d29c65e99c6c6848626553fdd07fd75 +Author: A U Thor +Date: Thu Apr 7 15:15:13 2005 -0700 + + 3rd + +Notes: + b3 + c3c3c3c3 + d3d3d3 +EOF + +printf "\n" >> expect-multiline +cat expect >> expect-multiline + +test_expect_success 'show multi-line notes' ' + git log -2 > output && + test_cmp expect-multiline output +' + +test_done -- cgit v0.10.2-6-g49f6 From fd53c9eb445815696bf84c4701b9af73b5d7f50d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Oct 2009 12:21:59 +0200 Subject: Speed up git notes lookup To avoid looking up each and every commit in the notes ref's tree object, which is very expensive, speed things up by slurping the tree object's contents into a hash_map. The idea for the hashmap singleton is from David Reiss, initial benchmarking by Jeff King. Note: the implementation allows for arbitrary entries in the notes tree object, ignoring those that do not reference a valid object. This allows you to annotate arbitrary branches, or objects. This patch has been improved by the following contributions: - Junio C Hamano: fixed an obvious error in initialize_hash_map() Signed-off-by: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/notes.c b/notes.c index 66379ff..2b66723 100644 --- a/notes.c +++ b/notes.c @@ -4,15 +4,112 @@ #include "refs.h" #include "utf8.h" #include "strbuf.h" +#include "tree-walk.h" + +struct entry { + unsigned char commit_sha1[20]; + unsigned char notes_sha1[20]; +}; + +struct hash_map { + struct entry *entries; + off_t count, size; +}; static int initialized; +static struct hash_map hash_map; + +static int hash_index(struct hash_map *map, const unsigned char *sha1) +{ + int i = ((*(unsigned int *)sha1) % map->size); + + for (;;) { + unsigned char *current = map->entries[i].commit_sha1; + + if (!hashcmp(sha1, current)) + return i; + + if (is_null_sha1(current)) + return -1 - i; + + if (++i == map->size) + i = 0; + } +} + +static void add_entry(const unsigned char *commit_sha1, + const unsigned char *notes_sha1) +{ + int index; + + if (hash_map.count + 1 > hash_map.size >> 1) { + int i, old_size = hash_map.size; + struct entry *old = hash_map.entries; + + hash_map.size = old_size ? old_size << 1 : 64; + hash_map.entries = (struct entry *) + xcalloc(sizeof(struct entry), hash_map.size); + + for (i = 0; i < old_size; i++) + if (!is_null_sha1(old[i].commit_sha1)) { + index = -1 - hash_index(&hash_map, + old[i].commit_sha1); + memcpy(hash_map.entries + index, old + i, + sizeof(struct entry)); + } + free(old); + } + + index = hash_index(&hash_map, commit_sha1); + if (index < 0) { + index = -1 - index; + hash_map.count++; + } + + hashcpy(hash_map.entries[index].commit_sha1, commit_sha1); + hashcpy(hash_map.entries[index].notes_sha1, notes_sha1); +} + +static void initialize_hash_map(const char *notes_ref_name) +{ + unsigned char sha1[20], commit_sha1[20]; + unsigned mode; + struct tree_desc desc; + struct name_entry entry; + void *buf; + + if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) || + get_tree_entry(commit_sha1, "", sha1, &mode)) + return; + + buf = fill_tree_descriptor(&desc, sha1); + if (!buf) + die("Could not read %s for notes-index", sha1_to_hex(sha1)); + + while (tree_entry(&desc, &entry)) + if (!get_sha1(entry.path, commit_sha1)) + add_entry(commit_sha1, entry.sha1); + free(buf); +} + +static unsigned char *lookup_notes(const unsigned char *commit_sha1) +{ + int index; + + if (!hash_map.size) + return NULL; + + index = hash_index(&hash_map, commit_sha1); + if (index < 0) + return NULL; + return hash_map.entries[index].notes_sha1; +} void get_commit_notes(const struct commit *commit, struct strbuf *sb, const char *output_encoding) { static const char utf8[] = "utf-8"; - struct strbuf name = STRBUF_INIT; - unsigned char sha1[20]; + unsigned char *sha1; char *msg, *msg_p; unsigned long linelen, msglen; enum object_type type; @@ -23,17 +120,12 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); else if (!notes_ref_name) notes_ref_name = GIT_NOTES_DEFAULT_REF; - if (notes_ref_name && read_ref(notes_ref_name, sha1)) - notes_ref_name = NULL; + initialize_hash_map(notes_ref_name); initialized = 1; } - if (!notes_ref_name) - return; - - strbuf_addf(&name, "%s:%s", notes_ref_name, - sha1_to_hex(commit->object.sha1)); - if (get_sha1(name.buf, sha1)) + sha1 = lookup_notes(commit->object.sha1); + if (!sha1) return; if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen || -- cgit v0.10.2-6-g49f6 From a5b0c24f3e9a29f8fe496f49db82f2e3f1b687ce Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Oct 2009 12:22:00 +0200 Subject: Add an expensive test for git-notes git-notes have the potential of being pretty expensive, so test with a lot of commits. A lot. So to make things cheaper, you have to opt-in explicitely, by setting the environment variable GIT_NOTES_TIMING_TESTS. This patch has been improved by the following contributions: - Junio C Hamano: tests: fix "export var=val" Signed-off-by: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh new file mode 100755 index 0000000..0ef3e95 --- /dev/null +++ b/t/t3302-notes-index-expensive.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='Test commit notes index (expensive!)' + +. ./test-lib.sh + +test -z "$GIT_NOTES_TIMING_TESTS" && { + say Skipping timing tests + test_done + exit +} + +create_repo () { + number_of_commits=$1 + nr=0 + parent= + test -d .git || { + git init && + tree=$(git write-tree) && + while [ $nr -lt $number_of_commits ]; do + test_tick && + commit=$(echo $nr | git commit-tree $tree $parent) || + return + parent="-p $commit" + nr=$(($nr+1)) + done && + git update-ref refs/heads/master $commit && + { + GIT_INDEX_FILE=.git/temp; export GIT_INDEX_FILE; + git rev-list HEAD | cat -n | sed "s/^[ ][ ]*/ /g" | + while read nr sha1; do + blob=$(echo note $nr | git hash-object -w --stdin) && + echo $sha1 | sed "s/^/0644 $blob 0 /" + done | git update-index --index-info && + tree=$(git write-tree) && + test_tick && + commit=$(echo notes | git commit-tree $tree) && + git update-ref refs/notes/commits $commit + } && + git config core.notesRef refs/notes/commits + } +} + +test_notes () { + count=$1 && + git config core.notesRef refs/notes/commits && + git log | grep "^ " > output && + i=1 && + while [ $i -le $count ]; do + echo " $(($count-$i))" && + echo " note $i" && + i=$(($i+1)); + done > expect && + git diff expect output +} + +cat > time_notes << \EOF + mode=$1 + i=1 + while [ $i -lt $2 ]; do + case $1 in + no-notes) + GIT_NOTES_REF=non-existing; export GIT_NOTES_REF + ;; + notes) + unset GIT_NOTES_REF + ;; + esac + git log >/dev/null + i=$(($i+1)) + done +EOF + +time_notes () { + for mode in no-notes notes + do + echo $mode + /usr/bin/time sh ../time_notes $mode $1 + done +} + +for count in 10 100 1000 10000; do + + mkdir $count + (cd $count; + + test_expect_success "setup $count" "create_repo $count" + + test_expect_success 'notes work' "test_notes $count" + + test_expect_success 'notes timing' "time_notes 100" + ) +done + +test_done -- cgit v0.10.2-6-g49f6 From d9246d4303f441c0e30614cd3a97a80fbe9354b6 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:01 +0200 Subject: Teach "-m " and "-F " to "git notes edit" The "-m" and "-F" options are already the established method (in both git-commit and git-tag) to specify a commit/tag message without invoking the editor. This patch teaches "git notes edit" to respect the same options for specifying a notes message without invoking the editor. Multiple "-m" and/or "-F" options are concatenated as separate paragraphs. The patch also updates the "git notes" documentation and adds selftests for the new functionality. Unfortunately, the added selftests include a couple of lines with trailing whitespace (without these the test will fail). This may cause git to warn about "whitespace errors". This patch has been improved by the following contributions: - Thomas Rast: fix trailing whitespace in t3301 Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 7136016..94cceb1 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -8,7 +8,7 @@ git-notes - Add/inspect commit notes SYNOPSIS -------- [verse] -'git-notes' (edit | show) [commit] +'git-notes' (edit [-F | -m ] | show) [commit] DESCRIPTION ----------- @@ -33,6 +33,20 @@ show:: Show the notes for a given commit (defaults to HEAD). +OPTIONS +------- +-m :: + Use the given note message (instead of prompting). + If multiple `-m` (or `-F`) options are given, their + values are concatenated as separate paragraphs. + +-F :: + Take the note message from the given file. Use '-' to + read the note message from the standard input. + If multiple `-F` (or `-m`) options are given, their + values are concatenated as separate paragraphs. + + Author ------ Written by Johannes Schindelin diff --git a/git-notes.sh b/git-notes.sh index f06c254..e642e47 100755 --- a/git-notes.sh +++ b/git-notes.sh @@ -1,16 +1,59 @@ #!/bin/sh -USAGE="(edit | show) [commit]" +USAGE="(edit [-F | -m ] | show) [commit]" . git-sh-setup -test -n "$3" && usage - test -z "$1" && usage ACTION="$1"; shift test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)" test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits" +MESSAGE= +while test $# != 0 +do + case "$1" in + -m) + test "$ACTION" = "edit" || usage + shift + if test "$#" = "0"; then + die "error: option -m needs an argument" + else + if [ -z "$MESSAGE" ]; then + MESSAGE="$1" + else + MESSAGE="$MESSAGE + +$1" + fi + shift + fi + ;; + -F) + test "$ACTION" = "edit" || usage + shift + if test "$#" = "0"; then + die "error: option -F needs an argument" + else + if [ -z "$MESSAGE" ]; then + MESSAGE="$(cat "$1")" + else + MESSAGE="$MESSAGE + +$(cat "$1")" + fi + shift + fi + ;; + -*) + usage + ;; + *) + break + ;; + esac +done + COMMIT=$(git rev-parse --verify --default HEAD "$@") || die "Invalid commit: $@" @@ -29,19 +72,24 @@ edit) test -f "$GIT_INDEX_FILE" && rm "$GIT_INDEX_FILE" ' 0 - GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE" - CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ') if [ -z "$CURRENT_HEAD" ]; then PARENT= else PARENT="-p $CURRENT_HEAD" git read-tree "$GIT_NOTES_REF" || die "Could not read index" - git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null fi - core_editor="$(git config core.editor)" - ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE" + if [ -z "$MESSAGE" ]; then + GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE" + if [ ! -z "$CURRENT_HEAD" ]; then + git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null + fi + core_editor="$(git config core.editor)" + ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE" + else + echo "$MESSAGE" > "$MSG_FILE" + fi grep -v ^# < "$MSG_FILE" | git stripspace > "$MSG_FILE".processed mv "$MSG_FILE".processed "$MSG_FILE" diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 73e53be..1e34f48 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -110,5 +110,41 @@ test_expect_success 'show multi-line notes' ' git log -2 > output && test_cmp expect-multiline output ' +test_expect_success 'create -m and -F notes (setup)' ' + : > a4 && + git add a4 && + test_tick && + git commit -m 4th && + echo "xyzzy" > note5 && + git notes edit -m spam -F note5 -m "foo +bar +baz" +' + +whitespace=" " +cat > expect-m-and-F << EOF +commit 15023535574ded8b1a89052b32673f84cf9582b8 +Author: A U Thor +Date: Thu Apr 7 15:16:13 2005 -0700 + + 4th + +Notes: + spam +$whitespace + xyzzy +$whitespace + foo + bar + baz +EOF + +printf "\n" >> expect-m-and-F +cat expect-multiline >> expect-m-and-F + +test_expect_success 'show -m and -F notes' ' + git log -3 > output && + test_cmp expect-m-and-F output +' test_done -- cgit v0.10.2-6-g49f6 From a8dd2e7d2bd7472b4e02b07aeef795de9c74f3e7 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:02 +0200 Subject: fast-import: Add support for importing commit notes Introduce a 'notemodify' subcommand of the 'commit' command. This subcommand is similar to 'filemodify', except that no mode is supplied (all notes have mode 0644), and the path is set to the hex SHA1 of the given "comittish". This enables fast import of note objects along with their associated commits, since the notes can now be named using the mark references of their corresponding commits. The patch also includes a test case of the added functionality. Signed-off-by: Johan Herland Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index c2f483a..288032c 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -316,7 +316,7 @@ change to the project. data ('from' SP LF)? ('merge' SP LF)? - (filemodify | filedelete | filecopy | filerename | filedeleteall)* + (filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)* LF? .... @@ -339,14 +339,13 @@ commit message use a 0 length data. Commit messages are free-form and are not interpreted by Git. Currently they must be encoded in UTF-8, as fast-import does not permit other encodings to be specified. -Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename` -and `filedeleteall` commands +Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`, +`filedeleteall` and `notemodify` commands may be included to update the contents of the branch prior to creating the commit. These commands may be supplied in any order. However it is recommended that a `filedeleteall` command precede -all `filemodify`, `filecopy` and `filerename` commands in the same -commit, as `filedeleteall` -wipes the branch clean (see below). +all `filemodify`, `filecopy`, `filerename` and `notemodify` commands in +the same commit, as `filedeleteall` wipes the branch clean (see below). The `LF` after the command is optional (it used to be required). @@ -595,6 +594,40 @@ more memory per active branch (less than 1 MiB for even most large projects); so frontends that can easily obtain only the affected paths for a commit are encouraged to do so. +`notemodify` +^^^^^^^^^^^^ +Included in a `commit` command to add a new note (annotating a given +commit) or change the content of an existing note. This command has +two different means of specifying the content of the note. + +External data format:: + The data content for the note was already supplied by a prior + `blob` command. The frontend just needs to connect it to the + commit that is to be annotated. ++ +.... + 'N' SP SP LF +.... ++ +Here `` can be either a mark reference (`:`) +set by a prior `blob` command, or a full 40-byte SHA-1 of an +existing Git blob object. + +Inline data format:: + The data content for the note has not been supplied yet. + The frontend wants to supply it as part of this modify + command. ++ +.... + 'N' SP 'inline' SP LF + data +.... ++ +See below for a detailed description of the `data` command. + +In both formats `` is any of the commit specification +expressions also accepted by `from` (see above). + `mark` ~~~~~~ Arranges for fast-import to save a reference to the current object, allowing diff --git a/fast-import.c b/fast-import.c index 6faaaac..b41d29f 100644 --- a/fast-import.c +++ b/fast-import.c @@ -22,8 +22,8 @@ Format of STDIN stream: ('author' sp name sp '<' email '>' sp when lf)? 'committer' sp name sp '<' email '>' sp when lf commit_msg - ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? - ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)* + ('from' sp committish lf)? + ('merge' sp committish lf)* file_change* lf?; commit_msg ::= data; @@ -41,15 +41,18 @@ Format of STDIN stream: file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf; file_inm ::= 'M' sp mode sp 'inline' sp path_str lf data; + note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf; + note_inm ::= 'N' sp 'inline' sp committish lf + data; new_tag ::= 'tag' sp tag_str lf - 'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf + 'from' sp committish lf ('tagger' sp name sp '<' email '>' sp when lf)? tag_msg; tag_msg ::= data; reset_branch ::= 'reset' sp ref_str lf - ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? + ('from' sp committish lf)? lf?; checkpoint ::= 'checkpoint' lf @@ -88,6 +91,7 @@ Format of STDIN stream: # stream formatting is: \, " and LF. Otherwise these values # are UTF8. # + committish ::= (ref_str | hexsha1 | sha1exp_str | idnum); ref_str ::= ref; sha1exp_str ::= sha1exp; tag_str ::= tag; @@ -2006,6 +2010,80 @@ static void file_change_cr(struct branch *b, int rename) leaf.tree); } +static void note_change_n(struct branch *b) +{ + const char *p = command_buf.buf + 2; + static struct strbuf uq = STRBUF_INIT; + struct object_entry *oe = oe; + struct branch *s; + unsigned char sha1[20], commit_sha1[20]; + uint16_t inline_data = 0; + + /* or 'inline' */ + if (*p == ':') { + char *x; + oe = find_mark(strtoumax(p + 1, &x, 10)); + hashcpy(sha1, oe->sha1); + p = x; + } else if (!prefixcmp(p, "inline")) { + inline_data = 1; + p += 6; + } else { + if (get_sha1_hex(p, sha1)) + die("Invalid SHA1: %s", command_buf.buf); + oe = find_object(sha1); + p += 40; + } + if (*p++ != ' ') + die("Missing space after SHA1: %s", command_buf.buf); + + /* */ + s = lookup_branch(p); + if (s) { + hashcpy(commit_sha1, s->sha1); + } else if (*p == ':') { + uintmax_t commit_mark = strtoumax(p + 1, NULL, 10); + struct object_entry *commit_oe = find_mark(commit_mark); + if (commit_oe->type != OBJ_COMMIT) + die("Mark :%" PRIuMAX " not a commit", commit_mark); + hashcpy(commit_sha1, commit_oe->sha1); + } else if (!get_sha1(p, commit_sha1)) { + unsigned long size; + char *buf = read_object_with_reference(commit_sha1, + commit_type, &size, commit_sha1); + if (!buf || size < 46) + die("Not a valid commit: %s", p); + free(buf); + } else + die("Invalid ref name or SHA1 expression: %s", p); + + if (inline_data) { + static struct strbuf buf = STRBUF_INIT; + + if (p != uq.buf) { + strbuf_addstr(&uq, p); + p = uq.buf; + } + read_next_command(); + parse_data(&buf); + store_object(OBJ_BLOB, &buf, &last_blob, sha1, 0); + } else if (oe) { + if (oe->type != OBJ_BLOB) + die("Not a blob (actually a %s): %s", + typename(oe->type), command_buf.buf); + } else { + enum object_type type = sha1_object_info(sha1, NULL); + if (type < 0) + die("Blob not found: %s", command_buf.buf); + if (type != OBJ_BLOB) + die("Not a blob (actually a %s): %s", + typename(type), command_buf.buf); + } + + tree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1, + S_IFREG | 0644, NULL); +} + static void file_change_deleteall(struct branch *b) { release_tree_content_recursive(b->branch_tree.tree); @@ -2175,6 +2253,8 @@ static void parse_new_commit(void) file_change_cr(b, 1); else if (!prefixcmp(command_buf.buf, "C ")) file_change_cr(b, 0); + else if (!prefixcmp(command_buf.buf, "N ")) + note_change_n(b); else if (!strcmp("deleteall", command_buf.buf)) file_change_deleteall(b); else { diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 821be7c..b49815d 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -1088,4 +1088,170 @@ INPUT_END test_expect_success 'P: fail on blob mark in gitlink' ' test_must_fail git fast-import input < $GIT_COMMITTER_DATE +data < $GIT_COMMITTER_DATE +data < $GIT_COMMITTER_DATE +data < $GIT_COMMITTER_DATE +data <expect < $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +first (:3) +EOF +test_expect_success \ + 'Q: verify first commit' \ + 'git cat-file commit notes-test~2 | sed 1d >actual && + test_cmp expect actual' + +cat >expect < $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +second (:5) +EOF +test_expect_success \ + 'Q: verify second commit' \ + 'git cat-file commit notes-test^ | sed 1d >actual && + test_cmp expect actual' + +cat >expect < $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +third (:6) +EOF +test_expect_success \ + 'Q: verify third commit' \ + 'git cat-file commit notes-test | sed 1d >actual && + test_cmp expect actual' + +cat >expect < $GIT_COMMITTER_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +notes (:9) +EOF +test_expect_success \ + 'Q: verify notes commit' \ + 'git cat-file commit refs/notes/foobar | sed 1d >actual && + test_cmp expect actual' + +cat >expect.unsorted <expect +test_expect_success \ + 'Q: verify notes tree' \ + 'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual' + +echo "$note1_data" >expect +test_expect_success \ + 'Q: verify note for first commit' \ + 'git cat-file blob refs/notes/foobar:$commit1 >actual && test_cmp expect actual' + +echo "$note2_data" >expect +test_expect_success \ + 'Q: verify note for second commit' \ + 'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual' + +echo "$note3_data" >expect +test_expect_success \ + 'Q: verify note for third commit' \ + 'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual' + test_done -- cgit v0.10.2-6-g49f6 From 3ed24b6aaf35d6ca1eef2643cd0b9128eb152cda Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:03 +0200 Subject: t3302-notes-index-expensive: Speed up create_repo() Creating repos with 10/100/1000/10000 commits and notes takes a lot of time. However, using git-fast-import to do the job is a lot more efficient than using plumbing commands to do the same. This patch decreases the overall run-time of this test on my machine from ~3 to ~1 minutes. Signed-off-by: Johan Herland Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh index 0ef3e95..ee84fc4 100755 --- a/t/t3302-notes-index-expensive.sh +++ b/t/t3302-notes-index-expensive.sh @@ -16,30 +16,50 @@ test -z "$GIT_NOTES_TIMING_TESTS" && { create_repo () { number_of_commits=$1 nr=0 - parent= test -d .git || { git init && - tree=$(git write-tree) && - while [ $nr -lt $number_of_commits ]; do - test_tick && - commit=$(echo $nr | git commit-tree $tree $parent) || - return - parent="-p $commit" - nr=$(($nr+1)) - done && - git update-ref refs/heads/master $commit && - { - GIT_INDEX_FILE=.git/temp; export GIT_INDEX_FILE; - git rev-list HEAD | cat -n | sed "s/^[ ][ ]*/ /g" | - while read nr sha1; do - blob=$(echo note $nr | git hash-object -w --stdin) && - echo $sha1 | sed "s/^/0644 $blob 0 /" - done | git update-index --index-info && - tree=$(git write-tree) && + ( + while [ $nr -lt $number_of_commits ]; do + nr=$(($nr+1)) + mark=$(($nr+$nr)) + notemark=$(($mark+1)) + test_tick && + cat < $GIT_COMMITTER_DATE +data <> note_commit + done && test_tick && - commit=$(echo notes | git commit-tree $tree) && - git update-ref refs/notes/commits $commit - } && + cat < $GIT_COMMITTER_DATE +data < output && - i=1 && - while [ $i -le $count ]; do - echo " $(($count-$i))" && - echo " note $i" && - i=$(($i+1)); + i=$count && + while [ $i -gt 0 ]; do + echo " commit #$i" && + echo " note for commit #$i" && + i=$(($i-1)); done > expect && - git diff expect output + test_cmp expect output } cat > time_notes << \EOF -- cgit v0.10.2-6-g49f6 From c56fcc89b951f3e8c9240ea02676b2eef5417da6 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:04 +0200 Subject: Add flags to get_commit_notes() to control the format of the note string This patch adds the following flags to get_commit_notes() for adjusting the format of the produced note string: - NOTES_SHOW_HEADER: Print "Notes:" line before the notes contents - NOTES_INDENT: Indent notes contents by 4 spaces Suggested-by: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/notes.c b/notes.c index 2b66723..b7d79e1 100644 --- a/notes.c +++ b/notes.c @@ -106,7 +106,7 @@ static unsigned char *lookup_notes(const unsigned char *commit_sha1) } void get_commit_notes(const struct commit *commit, struct strbuf *sb, - const char *output_encoding) + const char *output_encoding, int flags) { static const char utf8[] = "utf-8"; unsigned char *sha1; @@ -148,12 +148,14 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, if (msglen && msg[msglen - 1] == '\n') msglen--; - strbuf_addstr(sb, "\nNotes:\n"); + if (flags & NOTES_SHOW_HEADER) + strbuf_addstr(sb, "\nNotes:\n"); for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { linelen = strchrnul(msg_p, '\n') - msg_p; - strbuf_addstr(sb, " "); + if (flags & NOTES_INDENT) + strbuf_addstr(sb, " "); strbuf_add(sb, msg_p, linelen); strbuf_addch(sb, '\n'); } diff --git a/notes.h b/notes.h index 79d21b6..7f3eed4 100644 --- a/notes.h +++ b/notes.h @@ -1,7 +1,10 @@ #ifndef NOTES_H #define NOTES_H +#define NOTES_SHOW_HEADER 1 +#define NOTES_INDENT 2 + void get_commit_notes(const struct commit *commit, struct strbuf *sb, - const char *output_encoding); + const char *output_encoding, int flags); #endif diff --git a/pretty.c b/pretty.c index e25db81..01eadd0 100644 --- a/pretty.c +++ b/pretty.c @@ -978,7 +978,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, strbuf_addch(sb, '\n'); if (fmt != CMIT_FMT_ONELINE) - get_commit_notes(commit, sb, encoding); + get_commit_notes(commit, sb, encoding, + NOTES_SHOW_HEADER | NOTES_INDENT); free(reencoded); } -- cgit v0.10.2-6-g49f6 From 8b208f0213ed349ecb2ab8f7bb6c5072f8011a70 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Oct 2009 12:22:05 +0200 Subject: Add '%N'-format for pretty-printing commit notes Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 2a845b1..5fb10b3 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -123,6 +123,7 @@ The placeholders are: - '%s': subject - '%f': sanitized subject line, suitable for a filename - '%b': body +- '%N': commit notes - '%Cred': switch color to red - '%Cgreen': switch color to green - '%Cblue': switch color to blue diff --git a/pretty.c b/pretty.c index 01eadd0..7f350bb 100644 --- a/pretty.c +++ b/pretty.c @@ -702,6 +702,10 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, case 'd': format_decoration(sb, commit); return 1; + case 'N': + get_commit_notes(commit, sb, git_log_output_encoding ? + git_log_output_encoding : git_commit_encoding, 0); + return 1; } /* For the rest we have to parse the commit header. */ -- cgit v0.10.2-6-g49f6 From 27d57564102a98950bf4398daeeb14a15154478f Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:06 +0200 Subject: Teach notes code to free its internal data structures on request There's no need to be rude to memory-concious callers... This patch has been improved by the following contributions: - Junio C Hamano: avoid old-style declaration Signed-off-by: Junio C Hamano Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/notes.c b/notes.c index b7d79e1..a5d888d 100644 --- a/notes.c +++ b/notes.c @@ -105,6 +105,13 @@ static unsigned char *lookup_notes(const unsigned char *commit_sha1) return hash_map.entries[index].notes_sha1; } +void free_notes(void) +{ + free(hash_map.entries); + memset(&hash_map, 0, sizeof(struct hash_map)); + initialized = 0; +} + void get_commit_notes(const struct commit *commit, struct strbuf *sb, const char *output_encoding, int flags) { diff --git a/notes.h b/notes.h index 7f3eed4..a1421e3 100644 --- a/notes.h +++ b/notes.h @@ -1,6 +1,9 @@ #ifndef NOTES_H #define NOTES_H +/* Free (and de-initialize) the internal notes tree structure */ +void free_notes(void); + #define NOTES_SHOW_HEADER 1 #define NOTES_INDENT 2 -- cgit v0.10.2-6-g49f6 From 23123aecf8418a6b0ec23378555ed78c438ae894 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:07 +0200 Subject: Teach the notes lookup code to parse notes trees with various fanout schemes The semantics used when parsing notes trees (with regards to fanout subtrees) follow Dscho's proposal fairly closely: - No concatenation/merging of notes is performed. If there are several notes objects referencing a given commit, only one of those objects are used. - If a notes object for a given commit is present in the "root" notes tree, no subtrees are consulted; the object in the root tree is used directly. - If there are more than one subtree that prefix-matches the given commit, only the subtree with the longest matching prefix is consulted. This means that if the given commit is e.g. "deadbeef", and the notes tree have subtrees "de" and "dead", then the following paths in the notes tree are searched: "deadbeef", "dead/beef". Note that "de/adbeef" is NOT searched. - Fanout directories (subtrees) must references a whole number of bytes from the SHA1 sum they subdivide. E.g. subtrees "dead" and "de" are acceptable; "d" and "dea" are not. - Multiple levels of fanout are allowed. All the above rules apply recursively. E.g. "de/adbeef" is preferred over "de/adbe/ef", etc. This patch changes the in-memory datastructure for holding parsed notes: Instead of holding all note (and subtree) entries in a hash table, a simple 16-tree structure is used instead. The tree structure consists of 16-arrays as internal nodes, and note/subtree entries as leaf nodes. The tree is traversed by indexing subsequent nibbles of the search key until a leaf node is encountered. If a subtree entry is encountered while searching for a note, the subtree is unpacked into the 16-tree structure, and the search continues into that subtree. The new algorithm performs significantly better in the cases where only a fraction of the notes need to be looked up (this is assumed to be the common case for notes lookup). The new code even performs marginally better in the worst case (where _all_ the notes are looked up). In addition to this, comes the massive performance win associated with organizing the notes tree according to some fanout scheme. Even a simple 2/38 fanout scheme is dramatically quicker to traverse (going from tens of seconds to sub-second runtimes). As for memory usage, the new code is marginally better than the old code in the worst case, but in the case of looking up only some notes from a notes tree with proper fanout, the new code uses only a small fraction of the memory needed to hold the entire notes tree. However, there is one casualty of this patch. The old notes lookup code was able to parse notes that were associated with non-SHA1s (e.g. refs). The new code requires the referenced object to be named by a SHA1 sum. Still, this is not considered a major setback, since the notes infrastructure was not originally intended to annotate objects outside the Git object database. Cc: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/notes.c b/notes.c index a5d888d..210c4b2 100644 --- a/notes.c +++ b/notes.c @@ -6,109 +6,288 @@ #include "strbuf.h" #include "tree-walk.h" -struct entry { - unsigned char commit_sha1[20]; - unsigned char notes_sha1[20]; +/* + * Use a non-balancing simple 16-tree structure with struct int_node as + * internal nodes, and struct leaf_node as leaf nodes. Each int_node has a + * 16-array of pointers to its children. + * The bottom 2 bits of each pointer is used to identify the pointer type + * - ptr & 3 == 0 - NULL pointer, assert(ptr == NULL) + * - ptr & 3 == 1 - pointer to next internal node - cast to struct int_node * + * - ptr & 3 == 2 - pointer to note entry - cast to struct leaf_node * + * - ptr & 3 == 3 - pointer to subtree entry - cast to struct leaf_node * + * + * The root node is a statically allocated struct int_node. + */ +struct int_node { + void *a[16]; }; -struct hash_map { - struct entry *entries; - off_t count, size; +/* + * Leaf nodes come in two variants, note entries and subtree entries, + * distinguished by the LSb of the leaf node pointer (see above). + * As a note entry, the key is the SHA1 of the referenced commit, and the + * value is the SHA1 of the note object. + * As a subtree entry, the key is the prefix SHA1 (w/trailing NULs) of the + * referenced commit, using the last byte of the key to store the length of + * the prefix. The value is the SHA1 of the tree object containing the notes + * subtree. + */ +struct leaf_node { + unsigned char key_sha1[20]; + unsigned char val_sha1[20]; }; -static int initialized; -static struct hash_map hash_map; +#define PTR_TYPE_NULL 0 +#define PTR_TYPE_INTERNAL 1 +#define PTR_TYPE_NOTE 2 +#define PTR_TYPE_SUBTREE 3 -static int hash_index(struct hash_map *map, const unsigned char *sha1) -{ - int i = ((*(unsigned int *)sha1) % map->size); +#define GET_PTR_TYPE(ptr) ((uintptr_t) (ptr) & 3) +#define CLR_PTR_TYPE(ptr) ((void *) ((uintptr_t) (ptr) & ~3)) +#define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type))) - for (;;) { - unsigned char *current = map->entries[i].commit_sha1; +#define GET_NIBBLE(n, sha1) (((sha1[n >> 1]) >> ((~n & 0x01) << 2)) & 0x0f) - if (!hashcmp(sha1, current)) - return i; +#define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \ + (memcmp(key_sha1, subtree_sha1, subtree_sha1[19])) - if (is_null_sha1(current)) - return -1 - i; +static struct int_node root_node; - if (++i == map->size) - i = 0; +static int initialized; + +static void load_subtree(struct leaf_node *subtree, struct int_node *node, + unsigned int n); + +/* + * To find a leaf_node: + * 1. Start at the root node, with n = 0 + * 2. Use the nth nibble of the key as an index into a: + * - If a[n] is an int_node, recurse into that node and increment n + * - If a leaf_node with matching key, return leaf_node (assert note entry) + * - If a matching subtree entry, unpack that subtree entry (and remove it); + * restart search at the current level. + * - Otherwise, we end up at a NULL pointer, or a non-matching leaf_node. + * Backtrack out of the recursion, one level at a time and check a[0]: + * - If a[0] at the current level is a matching subtree entry, unpack that + * subtree entry (and remove it); restart search at the current level. + */ +static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, + const unsigned char *key_sha1) +{ + struct leaf_node *l; + unsigned char i = GET_NIBBLE(n, key_sha1); + void *p = tree->a[i]; + + switch(GET_PTR_TYPE(p)) { + case PTR_TYPE_INTERNAL: + l = note_tree_find(CLR_PTR_TYPE(p), n + 1, key_sha1); + if (l) + return l; + break; + case PTR_TYPE_NOTE: + l = (struct leaf_node *) CLR_PTR_TYPE(p); + if (!hashcmp(key_sha1, l->key_sha1)) + return l; /* return note object matching given key */ + break; + case PTR_TYPE_SUBTREE: + l = (struct leaf_node *) CLR_PTR_TYPE(p); + if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { + /* unpack tree and resume search */ + tree->a[i] = NULL; + load_subtree(l, tree, n); + free(l); + return note_tree_find(tree, n, key_sha1); + } + break; + case PTR_TYPE_NULL: + default: + assert(!p); + break; } + + /* + * Did not find key at this (or any lower) level. + * Check if there's a matching subtree entry in tree->a[0]. + * If so, unpack tree and resume search. + */ + p = tree->a[0]; + if (GET_PTR_TYPE(p) != PTR_TYPE_SUBTREE) + return NULL; + l = (struct leaf_node *) CLR_PTR_TYPE(p); + if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { + /* unpack tree and resume search */ + tree->a[0] = NULL; + load_subtree(l, tree, n); + free(l); + return note_tree_find(tree, n, key_sha1); + } + return NULL; } -static void add_entry(const unsigned char *commit_sha1, - const unsigned char *notes_sha1) +/* + * To insert a leaf_node: + * 1. Start at the root node, with n = 0 + * 2. Use the nth nibble of the key as an index into a: + * - If a[n] is NULL, store the tweaked pointer directly into a[n] + * - If a[n] is an int_node, recurse into that node and increment n + * - If a[n] is a leaf_node: + * 1. Check if they're equal, and handle that (abort? overwrite?) + * 2. Create a new int_node, and store both leaf_nodes there + * 3. Store the new int_node into a[n]. + */ +static int note_tree_insert(struct int_node *tree, unsigned char n, + const struct leaf_node *entry, unsigned char type) { - int index; - - if (hash_map.count + 1 > hash_map.size >> 1) { - int i, old_size = hash_map.size; - struct entry *old = hash_map.entries; - - hash_map.size = old_size ? old_size << 1 : 64; - hash_map.entries = (struct entry *) - xcalloc(sizeof(struct entry), hash_map.size); - - for (i = 0; i < old_size; i++) - if (!is_null_sha1(old[i].commit_sha1)) { - index = -1 - hash_index(&hash_map, - old[i].commit_sha1); - memcpy(hash_map.entries + index, old + i, - sizeof(struct entry)); - } - free(old); + struct int_node *new_node; + const struct leaf_node *l; + int ret; + unsigned char i = GET_NIBBLE(n, entry->key_sha1); + void *p = tree->a[i]; + assert(GET_PTR_TYPE(entry) == PTR_TYPE_NULL); + switch(GET_PTR_TYPE(p)) { + case PTR_TYPE_NULL: + assert(!p); + tree->a[i] = SET_PTR_TYPE(entry, type); + return 0; + case PTR_TYPE_INTERNAL: + return note_tree_insert(CLR_PTR_TYPE(p), n + 1, entry, type); + default: + assert(GET_PTR_TYPE(p) == PTR_TYPE_NOTE || + GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE); + l = (const struct leaf_node *) CLR_PTR_TYPE(p); + if (!hashcmp(entry->key_sha1, l->key_sha1)) + return -1; /* abort insert on matching key */ + new_node = (struct int_node *) + xcalloc(sizeof(struct int_node), 1); + ret = note_tree_insert(new_node, n + 1, + CLR_PTR_TYPE(p), GET_PTR_TYPE(p)); + if (ret) { + free(new_node); + return -1; + } + tree->a[i] = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); + return note_tree_insert(new_node, n + 1, entry, type); } +} - index = hash_index(&hash_map, commit_sha1); - if (index < 0) { - index = -1 - index; - hash_map.count++; +/* Free the entire notes data contained in the given tree */ +static void note_tree_free(struct int_node *tree) +{ + unsigned int i; + for (i = 0; i < 16; i++) { + void *p = tree->a[i]; + switch(GET_PTR_TYPE(p)) { + case PTR_TYPE_INTERNAL: + note_tree_free(CLR_PTR_TYPE(p)); + /* fall through */ + case PTR_TYPE_NOTE: + case PTR_TYPE_SUBTREE: + free(CLR_PTR_TYPE(p)); + } } +} - hashcpy(hash_map.entries[index].commit_sha1, commit_sha1); - hashcpy(hash_map.entries[index].notes_sha1, notes_sha1); +/* + * Convert a partial SHA1 hex string to the corresponding partial SHA1 value. + * - hex - Partial SHA1 segment in ASCII hex format + * - hex_len - Length of above segment. Must be multiple of 2 between 0 and 40 + * - sha1 - Partial SHA1 value is written here + * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20 + * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format). + * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2). + * Pads sha1 with NULs up to sha1_len (not included in returned length). + */ +static int get_sha1_hex_segment(const char *hex, unsigned int hex_len, + unsigned char *sha1, unsigned int sha1_len) +{ + unsigned int i, len = hex_len >> 1; + if (hex_len % 2 != 0 || len > sha1_len) + return -1; + for (i = 0; i < len; i++) { + unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); + if (val & ~0xff) + return -1; + *sha1++ = val; + hex += 2; + } + for (; i < sha1_len; i++) + *sha1++ = 0; + return len; } -static void initialize_hash_map(const char *notes_ref_name) +static void load_subtree(struct leaf_node *subtree, struct int_node *node, + unsigned int n) { - unsigned char sha1[20], commit_sha1[20]; - unsigned mode; + unsigned char commit_sha1[20]; + unsigned int prefix_len; + int status; + void *buf; struct tree_desc desc; struct name_entry entry; - void *buf; + + buf = fill_tree_descriptor(&desc, subtree->val_sha1); + if (!buf) + die("Could not read %s for notes-index", + sha1_to_hex(subtree->val_sha1)); + + prefix_len = subtree->key_sha1[19]; + assert(prefix_len * 2 >= n); + memcpy(commit_sha1, subtree->key_sha1, prefix_len); + while (tree_entry(&desc, &entry)) { + int len = get_sha1_hex_segment(entry.path, strlen(entry.path), + commit_sha1 + prefix_len, 20 - prefix_len); + if (len < 0) + continue; /* entry.path is not a SHA1 sum. Skip */ + len += prefix_len; + + /* + * If commit SHA1 is complete (len == 20), assume note object + * If commit SHA1 is incomplete (len < 20), assume note subtree + */ + if (len <= 20) { + unsigned char type = PTR_TYPE_NOTE; + struct leaf_node *l = (struct leaf_node *) + xcalloc(sizeof(struct leaf_node), 1); + hashcpy(l->key_sha1, commit_sha1); + hashcpy(l->val_sha1, entry.sha1); + if (len < 20) { + l->key_sha1[19] = (unsigned char) len; + type = PTR_TYPE_SUBTREE; + } + status = note_tree_insert(node, n, l, type); + assert(!status); + } + } + free(buf); +} + +static void initialize_notes(const char *notes_ref_name) +{ + unsigned char sha1[20], commit_sha1[20]; + unsigned mode; + struct leaf_node root_tree; if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) || get_tree_entry(commit_sha1, "", sha1, &mode)) return; - buf = fill_tree_descriptor(&desc, sha1); - if (!buf) - die("Could not read %s for notes-index", sha1_to_hex(sha1)); - - while (tree_entry(&desc, &entry)) - if (!get_sha1(entry.path, commit_sha1)) - add_entry(commit_sha1, entry.sha1); - free(buf); + hashclr(root_tree.key_sha1); + hashcpy(root_tree.val_sha1, sha1); + load_subtree(&root_tree, &root_node, 0); } static unsigned char *lookup_notes(const unsigned char *commit_sha1) { - int index; - - if (!hash_map.size) - return NULL; - - index = hash_index(&hash_map, commit_sha1); - if (index < 0) - return NULL; - return hash_map.entries[index].notes_sha1; + struct leaf_node *found = note_tree_find(&root_node, 0, commit_sha1); + if (found) + return found->val_sha1; + return NULL; } void free_notes(void) { - free(hash_map.entries); - memset(&hash_map, 0, sizeof(struct hash_map)); + note_tree_free(&root_node); + memset(&root_node, 0, sizeof(struct int_node)); initialized = 0; } @@ -127,7 +306,7 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); else if (!notes_ref_name) notes_ref_name = GIT_NOTES_DEFAULT_REF; - initialize_hash_map(notes_ref_name); + initialize_notes(notes_ref_name); initialized = 1; } -- cgit v0.10.2-6-g49f6 From 0057c0917d35f9f21e01f2122e7f2b9f169a8b02 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:08 +0200 Subject: Add selftests verifying that we can parse notes trees with various fanouts Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh new file mode 100755 index 0000000..cbb9d35 --- /dev/null +++ b/t/t3303-notes-subtrees.sh @@ -0,0 +1,104 @@ +#!/bin/sh + +test_description='Test commit notes organized in subtrees' + +. ./test-lib.sh + +number_of_commits=100 + +start_note_commit () { + test_tick && + cat < $GIT_COMMITTER_DATE +data < output && + i=$number_of_commits && + while [ $i -gt 0 ]; do + echo " commit #$i" && + echo " note for commit #$i" && + i=$(($i-1)); + done > expect && + test_cmp expect output +} + +test_expect_success "setup: create $number_of_commits commits" ' + + ( + nr=0 && + while [ $nr -lt $number_of_commits ]; do + nr=$(($nr+1)) && + test_tick && + cat < $GIT_COMMITTER_DATE +data < $GIT_COMMITTER_DATE +data < Date: Fri, 9 Oct 2009 12:22:09 +0200 Subject: Refactor notes code to concatenate multiple notes annotating the same object Currently, having multiple notes referring to the same commit from various locations in the notes tree is strongly discouraged, since only one of those notes will be parsed and shown. This patch teaches the notes code to _concatenate_ multiple notes that annotate the same commit. Notes are concatenated by creating a new blob object containing the concatenation of the notes in question, and replacing them with the concatenated note in the internal notes tree structure. Getting the concatenation right requires being more proactive in unpacking subtree entries in the internal notes tree structure, so that we don't return a note prematurely (i.e. before having found all other notes that annotate the same object). As such, this patch may incur a small performance penalty. Suggested-by: Sam Vilain Re-suggested-by: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/notes.c b/notes.c index 210c4b2..50a4672 100644 --- a/notes.c +++ b/notes.c @@ -59,115 +59,196 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, unsigned int n); /* - * To find a leaf_node: + * Search the tree until the appropriate location for the given key is found: * 1. Start at the root node, with n = 0 - * 2. Use the nth nibble of the key as an index into a: - * - If a[n] is an int_node, recurse into that node and increment n - * - If a leaf_node with matching key, return leaf_node (assert note entry) + * 2. If a[0] at the current level is a matching subtree entry, unpack that + * subtree entry and remove it; restart search at the current level. + * 3. Use the nth nibble of the key as an index into a: + * - If a[n] is an int_node, recurse from #2 into that node and increment n * - If a matching subtree entry, unpack that subtree entry (and remove it); * restart search at the current level. - * - Otherwise, we end up at a NULL pointer, or a non-matching leaf_node. - * Backtrack out of the recursion, one level at a time and check a[0]: - * - If a[0] at the current level is a matching subtree entry, unpack that - * subtree entry (and remove it); restart search at the current level. + * - Otherwise, we have found one of the following: + * - a subtree entry which does not match the key + * - a note entry which may or may not match the key + * - an unused leaf node (NULL) + * In any case, set *tree and *n, and return pointer to the tree location. */ -static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, - const unsigned char *key_sha1) +static void **note_tree_search(struct int_node **tree, + unsigned char *n, const unsigned char *key_sha1) { struct leaf_node *l; - unsigned char i = GET_NIBBLE(n, key_sha1); - void *p = tree->a[i]; + unsigned char i; + void *p = (*tree)->a[0]; + if (GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE) { + l = (struct leaf_node *) CLR_PTR_TYPE(p); + if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { + /* unpack tree and resume search */ + (*tree)->a[0] = NULL; + load_subtree(l, *tree, *n); + free(l); + return note_tree_search(tree, n, key_sha1); + } + } + + i = GET_NIBBLE(*n, key_sha1); + p = (*tree)->a[i]; switch(GET_PTR_TYPE(p)) { case PTR_TYPE_INTERNAL: - l = note_tree_find(CLR_PTR_TYPE(p), n + 1, key_sha1); - if (l) - return l; - break; - case PTR_TYPE_NOTE: - l = (struct leaf_node *) CLR_PTR_TYPE(p); - if (!hashcmp(key_sha1, l->key_sha1)) - return l; /* return note object matching given key */ - break; + *tree = CLR_PTR_TYPE(p); + (*n)++; + return note_tree_search(tree, n, key_sha1); case PTR_TYPE_SUBTREE: l = (struct leaf_node *) CLR_PTR_TYPE(p); if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { /* unpack tree and resume search */ - tree->a[i] = NULL; - load_subtree(l, tree, n); + (*tree)->a[i] = NULL; + load_subtree(l, *tree, *n); free(l); - return note_tree_find(tree, n, key_sha1); + return note_tree_search(tree, n, key_sha1); } - break; - case PTR_TYPE_NULL: + /* fall through */ default: - assert(!p); - break; + return &((*tree)->a[i]); } +} - /* - * Did not find key at this (or any lower) level. - * Check if there's a matching subtree entry in tree->a[0]. - * If so, unpack tree and resume search. - */ - p = tree->a[0]; - if (GET_PTR_TYPE(p) != PTR_TYPE_SUBTREE) - return NULL; - l = (struct leaf_node *) CLR_PTR_TYPE(p); - if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { - /* unpack tree and resume search */ - tree->a[0] = NULL; - load_subtree(l, tree, n); - free(l); - return note_tree_find(tree, n, key_sha1); +/* + * To find a leaf_node: + * Search to the tree location appropriate for the given key: + * If a note entry with matching key, return the note entry, else return NULL. + */ +static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, + const unsigned char *key_sha1) +{ + void **p = note_tree_search(&tree, &n, key_sha1); + if (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) { + struct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p); + if (!hashcmp(key_sha1, l->key_sha1)) + return l; } return NULL; } +/* Create a new blob object by concatenating the two given blob objects */ +static int concatenate_notes(unsigned char *cur_sha1, + const unsigned char *new_sha1) +{ + char *cur_msg, *new_msg, *buf; + unsigned long cur_len, new_len, buf_len; + enum object_type cur_type, new_type; + int ret; + + /* read in both note blob objects */ + new_msg = read_sha1_file(new_sha1, &new_type, &new_len); + if (!new_msg || !new_len || new_type != OBJ_BLOB) { + free(new_msg); + return 0; + } + cur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len); + if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) { + free(cur_msg); + free(new_msg); + hashcpy(cur_sha1, new_sha1); + return 0; + } + + /* we will separate the notes by a newline anyway */ + if (cur_msg[cur_len - 1] == '\n') + cur_len--; + + /* concatenate cur_msg and new_msg into buf */ + buf_len = cur_len + 1 + new_len; + buf = (char *) xmalloc(buf_len); + memcpy(buf, cur_msg, cur_len); + buf[cur_len] = '\n'; + memcpy(buf + cur_len + 1, new_msg, new_len); + + free(cur_msg); + free(new_msg); + + /* create a new blob object from buf */ + ret = write_sha1_file(buf, buf_len, "blob", cur_sha1); + free(buf); + return ret; +} + /* * To insert a leaf_node: - * 1. Start at the root node, with n = 0 - * 2. Use the nth nibble of the key as an index into a: - * - If a[n] is NULL, store the tweaked pointer directly into a[n] - * - If a[n] is an int_node, recurse into that node and increment n - * - If a[n] is a leaf_node: - * 1. Check if they're equal, and handle that (abort? overwrite?) - * 2. Create a new int_node, and store both leaf_nodes there - * 3. Store the new int_node into a[n]. + * Search to the tree location appropriate for the given leaf_node's key: + * - If location is unused (NULL), store the tweaked pointer directly there + * - If location holds a note entry that matches the note-to-be-inserted, then + * concatenate the two notes. + * - If location holds a note entry that matches the subtree-to-be-inserted, + * then unpack the subtree-to-be-inserted into the location. + * - If location holds a matching subtree entry, unpack the subtree at that + * location, and restart the insert operation from that level. + * - Else, create a new int_node, holding both the node-at-location and the + * node-to-be-inserted, and store the new int_node into the location. */ -static int note_tree_insert(struct int_node *tree, unsigned char n, - const struct leaf_node *entry, unsigned char type) +static void note_tree_insert(struct int_node *tree, unsigned char n, + struct leaf_node *entry, unsigned char type) { struct int_node *new_node; - const struct leaf_node *l; - int ret; - unsigned char i = GET_NIBBLE(n, entry->key_sha1); - void *p = tree->a[i]; - assert(GET_PTR_TYPE(entry) == PTR_TYPE_NULL); - switch(GET_PTR_TYPE(p)) { + struct leaf_node *l; + void **p = note_tree_search(&tree, &n, entry->key_sha1); + + assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ + l = (struct leaf_node *) CLR_PTR_TYPE(*p); + switch(GET_PTR_TYPE(*p)) { case PTR_TYPE_NULL: - assert(!p); - tree->a[i] = SET_PTR_TYPE(entry, type); - return 0; - case PTR_TYPE_INTERNAL: - return note_tree_insert(CLR_PTR_TYPE(p), n + 1, entry, type); - default: - assert(GET_PTR_TYPE(p) == PTR_TYPE_NOTE || - GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE); - l = (const struct leaf_node *) CLR_PTR_TYPE(p); - if (!hashcmp(entry->key_sha1, l->key_sha1)) - return -1; /* abort insert on matching key */ - new_node = (struct int_node *) - xcalloc(sizeof(struct int_node), 1); - ret = note_tree_insert(new_node, n + 1, - CLR_PTR_TYPE(p), GET_PTR_TYPE(p)); - if (ret) { - free(new_node); - return -1; + assert(!*p); + *p = SET_PTR_TYPE(entry, type); + return; + case PTR_TYPE_NOTE: + switch (type) { + case PTR_TYPE_NOTE: + if (!hashcmp(l->key_sha1, entry->key_sha1)) { + /* skip concatenation if l == entry */ + if (!hashcmp(l->val_sha1, entry->val_sha1)) + return; + + if (concatenate_notes(l->val_sha1, + entry->val_sha1)) + die("failed to concatenate note %s " + "into note %s for commit %s", + sha1_to_hex(entry->val_sha1), + sha1_to_hex(l->val_sha1), + sha1_to_hex(l->key_sha1)); + free(entry); + return; + } + break; + case PTR_TYPE_SUBTREE: + if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1, + entry->key_sha1)) { + /* unpack 'entry' */ + load_subtree(entry, tree, n); + free(entry); + return; + } + break; + } + break; + case PTR_TYPE_SUBTREE: + if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) { + /* unpack 'l' and restart insert */ + *p = NULL; + load_subtree(l, tree, n); + free(l); + note_tree_insert(tree, n, entry, type); + return; } - tree->a[i] = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); - return note_tree_insert(new_node, n + 1, entry, type); + break; } + + /* non-matching leaf_node */ + assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || + GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); + new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1); + note_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p)); + *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); + note_tree_insert(new_node, n + 1, entry, type); } /* Free the entire notes data contained in the given tree */ @@ -220,7 +301,6 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, { unsigned char commit_sha1[20]; unsigned int prefix_len; - int status; void *buf; struct tree_desc desc; struct name_entry entry; @@ -254,8 +334,7 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, l->key_sha1[19] = (unsigned char) len; type = PTR_TYPE_SUBTREE; } - status = note_tree_insert(node, n, l, type); - assert(!status); + note_tree_insert(node, n, l, type); } } free(buf); -- cgit v0.10.2-6-g49f6 From a099469bbcf273e76d81573229971956b4ef0700 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 9 Oct 2009 12:22:10 +0200 Subject: Add selftests verifying concatenation of multiple notes for the same commit Also verify that multiple references to the _same_ note blob are _not_ concatenated. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh index cbb9d35..edc4bc8 100755 --- a/t/t3303-notes-subtrees.sh +++ b/t/t3303-notes-subtrees.sh @@ -101,4 +101,88 @@ test_expect_success 'verify notes in 4/36-fanout' 'verify_notes' test_expect_success 'test notes in 2/2/36-fanout' 'test_sha1_based "s|^\(..\)\(..\)|\1/\2/|"' test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes' +test_same_notes () { + ( + start_note_commit && + nr=$number_of_commits && + git rev-list refs/heads/master | + while read sha1; do + first_note_path=$(echo "$sha1" | sed "$1") + second_note_path=$(echo "$sha1" | sed "$2") + cat < output && + i=$number_of_commits && + while [ $i -gt 0 ]; do + echo " commit #$i" && + echo " first note for commit #$i" && + echo " second note for commit #$i" && + i=$(($i-1)); + done > expect && + test_cmp expect output +} + +test_expect_success 'test notes in 4/36-fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" "s|^....|&/|"' +test_expect_success 'verify notes in 4/36-fanout concatenated with 2/38-fanout' 'verify_concatenated_notes' + +test_expect_success 'test notes in 2/38-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"' +test_expect_success 'verify notes in 2/38-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' + +test_expect_success 'test notes in 4/36-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"' +test_expect_success 'verify notes in 4/36-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' + test_done -- cgit v0.10.2-6-g49f6 From 3a7cba95b74dd2460f8b9bf4f7cefa3c21d31fdd Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 19 Oct 2009 17:42:02 +0200 Subject: imap-send: remove useless uid code The imap-send code is based on code from isync, a program for syncing imap mailboxes. Because of this, it has inherited some code that makes sense for isync, but not for imap-send. In particular, when storing a message, it does one of: - if the server supports it, note the server-assigned unique identifier (UID) given to each message - otherwise, assigned a random UID and store it in the message header as X-TUID Presumably this is used in isync to be able to synchronize mailstores multiple times without duplication. But for imap-send, the values are useless; we never do anything with them and simply forget them at the end of the program. This patch removes the useless code. Not only is it nice for maintainability to get rid of dead code, but the removed code relied on the existence of /dev/urandom, which made it a portability problem for non-Unix platforms. Signed-off-by: Jeff King Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano diff --git a/imap-send.c b/imap-send.c index 3847fd1..8da7a94 100644 --- a/imap-send.c +++ b/imap-send.c @@ -123,9 +123,6 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap) return len; } -static void arc4_init(void); -static unsigned char arc4_getbyte(void); - struct imap_server_conf { char *name; char *tunnel; @@ -489,52 +486,6 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...) return ret; } -static struct { - unsigned char i, j, s[256]; -} rs; - -static void arc4_init(void) -{ - int i, fd; - unsigned char j, si, dat[128]; - - if ((fd = open("/dev/urandom", O_RDONLY)) < 0 && (fd = open("/dev/random", O_RDONLY)) < 0) { - fprintf(stderr, "Fatal: no random number source available.\n"); - exit(3); - } - if (read_in_full(fd, dat, 128) != 128) { - fprintf(stderr, "Fatal: cannot read random number source.\n"); - exit(3); - } - close(fd); - - for (i = 0; i < 256; i++) - rs.s[i] = i; - for (i = j = 0; i < 256; i++) { - si = rs.s[i]; - j += si + dat[i & 127]; - rs.s[i] = rs.s[j]; - rs.s[j] = si; - } - rs.i = rs.j = 0; - - for (i = 0; i < 256; i++) - arc4_getbyte(); -} - -static unsigned char arc4_getbyte(void) -{ - unsigned char si, sj; - - rs.i++; - si = rs.s[rs.i]; - rs.j += si; - sj = rs.s[rs.j]; - rs.s[rs.i] = sj; - rs.s[rs.j] = si; - return rs.s[(si + sj) & 0xff]; -} - static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx, struct imap_cmd_cb *cb, const char *fmt, va_list ap) @@ -1198,88 +1149,20 @@ static int imap_make_flags(int flags, char *buf) return d; } -#define TUIDL 8 - -static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid) +static int imap_store_msg(struct store *gctx, struct msg_data *data) { struct imap_store *ctx = (struct imap_store *)gctx; struct imap *imap = ctx->imap; struct imap_cmd_cb cb; - char *fmap, *buf; const char *prefix, *box; - int ret, i, j, d, len, extra, nocr; - int start, sbreak = 0, ebreak = 0; - char flagstr[128], tuid[TUIDL * 2 + 1]; + int ret, d; + char flagstr[128]; memset(&cb, 0, sizeof(cb)); - fmap = data->data; - len = data->len; - nocr = !data->crlf; - extra = 0, i = 0; - if (!CAP(UIDPLUS) && uid) { - nloop: - start = i; - while (i < len) - if (fmap[i++] == '\n') { - extra += nocr; - if (i - 2 + nocr == start) { - sbreak = ebreak = i - 2 + nocr; - goto mktid; - } - if (!memcmp(fmap + start, "X-TUID: ", 8)) { - extra -= (ebreak = i) - (sbreak = start) + nocr; - goto mktid; - } - goto nloop; - } - /* invalid message */ - free(fmap); - return DRV_MSG_BAD; - mktid: - for (j = 0; j < TUIDL; j++) - sprintf(tuid + j * 2, "%02x", arc4_getbyte()); - extra += 8 + TUIDL * 2 + 2; - } - if (nocr) - for (; i < len; i++) - if (fmap[i] == '\n') - extra++; - - cb.dlen = len + extra; - buf = cb.data = xmalloc(cb.dlen); - i = 0; - if (!CAP(UIDPLUS) && uid) { - if (nocr) { - for (; i < sbreak; i++) - if (fmap[i] == '\n') { - *buf++ = '\r'; - *buf++ = '\n'; - } else - *buf++ = fmap[i]; - } else { - memcpy(buf, fmap, sbreak); - buf += sbreak; - } - memcpy(buf, "X-TUID: ", 8); - buf += 8; - memcpy(buf, tuid, TUIDL * 2); - buf += TUIDL * 2; - *buf++ = '\r'; - *buf++ = '\n'; - i = ebreak; - } - if (nocr) { - for (; i < len; i++) - if (fmap[i] == '\n') { - *buf++ = '\r'; - *buf++ = '\n'; - } else - *buf++ = fmap[i]; - } else - memcpy(buf, fmap + i, len - i); - - free(fmap); + cb.dlen = data->len; + cb.data = xmalloc(cb.dlen); + memcpy(cb.data, data->data, data->len); d = 0; if (data->flags) { @@ -1288,26 +1171,14 @@ static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid) } flagstr[d] = 0; - if (!uid) { - box = gctx->conf->trash; - prefix = ctx->prefix; - cb.create = 1; - if (ctx->trashnc) - imap->caps = imap->rcaps & ~(1 << LITERALPLUS); - } else { - box = gctx->name; - prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix; - cb.create = 0; - } - cb.ctx = uid; + box = gctx->name; + prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix; + cb.create = 0; ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr); imap->caps = imap->rcaps; if (ret != DRV_OK) return ret; - if (!uid) - ctx->trashnc = 0; - else - gctx->count++; + gctx->count++; return DRV_OK; } @@ -1483,7 +1354,6 @@ int main(int argc, char **argv) { struct msg_data all_msgs, msg; struct store *ctx = NULL; - int uid = 0; int ofs = 0; int r; int total, n = 0; @@ -1491,9 +1361,6 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); - /* init the random number generator */ - arc4_init(); - setup_git_directory_gently(&nongit_ok); git_config(git_imap_config, NULL); @@ -1540,7 +1407,7 @@ int main(int argc, char **argv) break; if (server.use_html) wrap_in_html(&msg); - r = imap_store_msg(ctx, &msg, &uid); + r = imap_store_msg(ctx, &msg); if (r != DRV_OK) break; n++; -- cgit v0.10.2-6-g49f6 From 7a7796e9a7cd8f789fa964acbb1eec5efd05436a Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 19 Oct 2009 17:42:03 +0200 Subject: imap-send: use separate read and write fds This is a patch that enables us to use the run-command API, which is supported on Windows. Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano diff --git a/imap-send.c b/imap-send.c index 8da7a94..7216453 100644 --- a/imap-send.c +++ b/imap-send.c @@ -151,7 +151,7 @@ struct imap_list { }; struct imap_socket { - int fd; + int fd[2]; SSL *ssl; }; @@ -301,8 +301,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve ssl_socket_perror("SSL_new"); return -1; } - if (!SSL_set_fd(sock->ssl, sock->fd)) { - ssl_socket_perror("SSL_set_fd"); + if (!SSL_set_rfd(sock->ssl, sock->fd[0])) { + ssl_socket_perror("SSL_set_rfd"); + return -1; + } + if (!SSL_set_wfd(sock->ssl, sock->fd[1])) { + ssl_socket_perror("SSL_set_wfd"); return -1; } @@ -324,11 +328,12 @@ static int socket_read(struct imap_socket *sock, char *buf, int len) n = SSL_read(sock->ssl, buf, len); else #endif - n = xread(sock->fd, buf, len); + n = xread(sock->fd[0], buf, len); if (n <= 0) { socket_perror("read", sock, n); - close(sock->fd); - sock->fd = -1; + close(sock->fd[0]); + close(sock->fd[1]); + sock->fd[0] = sock->fd[1] = -1; } return n; } @@ -341,11 +346,12 @@ static int socket_write(struct imap_socket *sock, const char *buf, int len) n = SSL_write(sock->ssl, buf, len); else #endif - n = write_in_full(sock->fd, buf, len); + n = write_in_full(sock->fd[1], buf, len); if (n != len) { socket_perror("write", sock, n); - close(sock->fd); - sock->fd = -1; + close(sock->fd[0]); + close(sock->fd[1]); + sock->fd[0] = sock->fd[1] = -1; } return n; } @@ -358,7 +364,8 @@ static void socket_shutdown(struct imap_socket *sock) SSL_free(sock->ssl); } #endif - close(sock->fd); + close(sock->fd[0]); + close(sock->fd[1]); } /* simple line buffering */ @@ -911,7 +918,7 @@ static void imap_close_server(struct imap_store *ictx) { struct imap *imap = ictx->imap; - if (imap->buf.sock.fd != -1) { + if (imap->buf.sock.fd[0] != -1) { imap_exec(ictx, NULL, "LOGOUT"); socket_shutdown(&imap->buf.sock); } @@ -939,7 +946,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) ctx = xcalloc(sizeof(*ctx), 1); ctx->imap = imap = xcalloc(sizeof(*imap), 1); - imap->buf.sock.fd = -1; + imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1; imap->in_progress_append = &imap->in_progress; /* open connection to IMAP server */ @@ -966,7 +973,8 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) close(a[0]); - imap->buf.sock.fd = a[1]; + imap->buf.sock.fd[0] = a[1]; + imap->buf.sock.fd[1] = dup(a[1]); imap_info("ok\n"); } else { @@ -1043,7 +1051,8 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) goto bail; } - imap->buf.sock.fd = s; + imap->buf.sock.fd[0] = s; + imap->buf.sock.fd[1] = dup(s); if (srvc->use_ssl && ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) { -- cgit v0.10.2-6-g49f6 From c94d2dd0807328b1ee4aa8353382caa45bc24055 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 19 Oct 2009 17:42:04 +0200 Subject: imap-send: use run-command API for tunneling Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano diff --git a/imap-send.c b/imap-send.c index 7216453..72ed640 100644 --- a/imap-send.c +++ b/imap-send.c @@ -24,6 +24,7 @@ #include "cache.h" #include "exec_cmd.h" +#include "run-command.h" #ifdef NO_OPENSSL typedef void *SSL; #endif @@ -940,8 +941,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) struct imap_store *ctx; struct imap *imap; char *arg, *rsp; - int s = -1, a[2], preauth; - pid_t pid; + int s = -1, preauth; ctx = xcalloc(sizeof(*ctx), 1); @@ -952,29 +952,24 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) /* open connection to IMAP server */ if (srvc->tunnel) { - imap_info("Starting tunnel '%s'... ", srvc->tunnel); + const char *argv[4]; + struct child_process tunnel = {0}; - if (socketpair(PF_UNIX, SOCK_STREAM, 0, a)) { - perror("socketpair"); - exit(1); - } + imap_info("Starting tunnel '%s'... ", srvc->tunnel); - pid = fork(); - if (pid < 0) - _exit(127); - if (!pid) { - if (dup2(a[0], 0) == -1 || dup2(a[0], 1) == -1) - _exit(127); - close(a[0]); - close(a[1]); - execl("/bin/sh", "sh", "-c", srvc->tunnel, NULL); - _exit(127); - } + argv[0] = "sh"; + argv[1] = "-c"; + argv[2] = srvc->tunnel; + argv[3] = NULL; - close(a[0]); + tunnel.argv = argv; + tunnel.in = -1; + tunnel.out = -1; + if (start_command(&tunnel)) + die("cannot start proxy %s", argv[0]); - imap->buf.sock.fd[0] = a[1]; - imap->buf.sock.fd[1] = dup(a[1]); + imap->buf.sock.fd[0] = tunnel.out; + imap->buf.sock.fd[1] = tunnel.in; imap_info("ok\n"); } else { -- cgit v0.10.2-6-g49f6 From d23b1ecf11ee48cd9e266ada06f1b8298b917e92 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 19 Oct 2009 17:42:05 +0200 Subject: imap-send: fix compilation-error on Windows mmsystem.h (included from windows.h) defines DRV_OK to 1. To avoid an error due to DRV_OK redefenition, this patch undefines the old definition (i.e the one from mmsystem.h) before defining DRV_OK. Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano diff --git a/imap-send.c b/imap-send.c index 72ed640..69e6142 100644 --- a/imap-send.c +++ b/imap-send.c @@ -94,6 +94,7 @@ struct msg_data { unsigned int crlf:1; }; +#undef DRV_OK #define DRV_OK 0 #define DRV_MSG_BAD -1 #define DRV_BOX_BAD -2 -- cgit v0.10.2-6-g49f6 From f9a88b70f94917f95e247a0e6e4d37f163fb41e3 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 19 Oct 2009 17:42:06 +0200 Subject: imap-send: build imap-send on Windows Since the POSIX-specific tunneling code has been replaced by the run-command API (and a compile-error has been cleaned away), we can now enable imap-send on Windows builds. Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 12defd4..13980a5 100644 --- a/Makefile +++ b/Makefile @@ -350,6 +350,7 @@ EXTRA_PROGRAMS = PROGRAMS += $(EXTRA_PROGRAMS) PROGRAMS += git-fast-import$X PROGRAMS += git-hash-object$X +PROGRAMS += git-imap-send$X PROGRAMS += git-index-pack$X PROGRAMS += git-merge-index$X PROGRAMS += git-merge-tree$X @@ -1056,7 +1057,6 @@ EXTLIBS += -lz ifndef NO_POSIX_ONLY_PROGRAMS PROGRAMS += git-daemon$X - PROGRAMS += git-imap-send$X endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl -- cgit v0.10.2-6-g49f6 From 514213bf72cb61725e42440aaf8dd53fa4c33a74 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 19 Oct 2009 17:42:07 +0200 Subject: mingw: wrap SSL_set_(w|r)fd to call _get_osfhandle SSL_set_fd (and friends) expects a OS file handle on Windows, not a file descriptor as on UNIX(-ish). This patch makes the Windows version of SSL_set_fd behave like the UNIX versions, by calling _get_osfhandle on it's input. Signed-off-by: Erik Faye-Lund Acked-by: Johannes Sixt Signed-off-by: Junio C Hamano diff --git a/compat/mingw.h b/compat/mingw.h index 5b5258b..6907345 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -124,6 +124,27 @@ static inline int waitpid(pid_t pid, int *status, unsigned options) return -1; } +#ifndef NO_OPENSSL +#include +static inline int mingw_SSL_set_fd(SSL *ssl, int fd) +{ + return SSL_set_fd(ssl, _get_osfhandle(fd)); +} +#define SSL_set_fd mingw_SSL_set_fd + +static inline int mingw_SSL_set_rfd(SSL *ssl, int fd) +{ + return SSL_set_rfd(ssl, _get_osfhandle(fd)); +} +#define SSL_set_rfd mingw_SSL_set_rfd + +static inline int mingw_SSL_set_wfd(SSL *ssl, int fd) +{ + return SSL_set_wfd(ssl, _get_osfhandle(fd)); +} +#define SSL_set_wfd mingw_SSL_set_wfd +#endif + /* * implementations of missing functions */ -- cgit v0.10.2-6-g49f6 From dd2e794a214350711db46c4e08d9b19188a7d63a Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 19 Oct 2009 17:48:08 +0200 Subject: Refactor pretty_print_commit arguments into a struct pretty_print_commit() has a bunch of rarely-used arguments, and introducing more of them requires yet another update of all the call sites. Refactor most of them into a struct to make future extensions easier. The ones that stay "plain" arguments were chosen on the grounds that all callers put real arguments there, whereas some callers have 0/NULL for all arguments that were factored into the struct. We declare the struct 'const' to ensure none of the callers are bitten by the changed (no longer call-by-value) semantics. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano diff --git a/archive.c b/archive.c index 0cc79d2..55b2732 100644 --- a/archive.c +++ b/archive.c @@ -31,6 +31,8 @@ static void format_subst(const struct commit *commit, { char *to_free = NULL; struct strbuf fmt = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; if (src == buf->buf) to_free = strbuf_detach(buf, NULL); @@ -48,7 +50,7 @@ static void format_subst(const struct commit *commit, strbuf_add(&fmt, b + 8, c - b - 8); strbuf_add(buf, src, b - src); - format_commit_message(commit, fmt.buf, buf, DATE_NORMAL); + format_commit_message(commit, fmt.buf, buf, &ctx); len -= c + 1 - src; src = c + 1; } diff --git a/builtin-branch.c b/builtin-branch.c index 9f57992..05e876e 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -387,8 +387,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, commit = item->commit; if (commit && !parse_commit(commit)) { + struct pretty_print_context ctx = {0}; pretty_print_commit(CMIT_FMT_ONELINE, commit, - &subject, 0, NULL, NULL, 0, 0); + &subject, &ctx); sub = subject.buf; } diff --git a/builtin-checkout.c b/builtin-checkout.c index d050c37..075a49f 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -302,8 +302,9 @@ static void show_local_changes(struct object *head) static void describe_detached_head(char *msg, struct commit *commit) { struct strbuf sb = STRBUF_INIT; + struct pretty_print_context ctx = {0}; parse_commit(commit); - pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, NULL, NULL, 0, 0); + pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx); fprintf(stderr, "%s %s... %s\n", msg, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); strbuf_release(&sb); diff --git a/builtin-commit.c b/builtin-commit.c index 200ffda..13edeee 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -684,8 +684,10 @@ static const char *find_author_by_nickname(const char *name) prepare_revision_walk(&revs); commit = get_revision(&revs); if (commit) { + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; strbuf_release(&buf); - format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL); + format_commit_message(commit, "%an <%ae>", &buf, &ctx); return strbuf_detach(&buf, NULL); } die("No existing author found with '%s'", name); @@ -942,8 +944,10 @@ static void print_summary(const char *prefix, const unsigned char *sha1) initial_commit ? " (root-commit)" : ""); if (!log_tree_commit(&rev, commit)) { + struct pretty_print_context ctx = {0}; struct strbuf buf = STRBUF_INIT; - format_commit_message(commit, format + 7, &buf, DATE_NORMAL); + ctx.date_mode = DATE_NORMAL; + format_commit_message(commit, format + 7, &buf, &ctx); printf("%s\n", buf.buf); strbuf_release(&buf); } diff --git a/builtin-log.c b/builtin-log.c index 25e21ed..207a361 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -1304,8 +1304,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) if (verbose) { struct strbuf buf = STRBUF_INIT; + struct pretty_print_context ctx = {0}; pretty_print_commit(CMIT_FMT_ONELINE, commit, - &buf, 0, NULL, NULL, 0, 0); + &buf, &ctx); printf("%c %s %s\n", sign, sha1_to_hex(commit->object.sha1), buf.buf); strbuf_release(&buf); diff --git a/builtin-merge.c b/builtin-merge.c index b6b8428..c69a305 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -264,6 +264,7 @@ static void squash_message(void) struct strbuf out = STRBUF_INIT; struct commit_list *j; int fd; + struct pretty_print_context ctx = {0}; printf("Squash commit -- not updating HEAD\n"); fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); @@ -285,13 +286,15 @@ static void squash_message(void) if (prepare_revision_walk(&rev)) die("revision walk setup failed"); + ctx.abbrev = rev.abbrev; + ctx.date_mode = rev.date_mode; + strbuf_addstr(&out, "Squashed commit of the following:\n"); while ((commit = get_revision(&rev)) != NULL) { strbuf_addch(&out, '\n'); strbuf_addf(&out, "commit %s\n", sha1_to_hex(commit->object.sha1)); - pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev, - NULL, NULL, rev.date_mode, 0); + pretty_print_commit(rev.commit_format, commit, &out, &ctx); } if (write(fd, out.buf, out.len) < 0) die_errno("Writing SQUASH_MSG"); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 4ba1c12..42cc8d8 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -96,9 +96,10 @@ static void show_commit(struct commit *commit, void *data) if (revs->verbose_header && commit->buffer) { struct strbuf buf = STRBUF_INIT; - pretty_print_commit(revs->commit_format, commit, - &buf, revs->abbrev, NULL, NULL, - revs->date_mode, 0); + struct pretty_print_context ctx = {0}; + ctx.abbrev = revs->abbrev; + ctx.date_mode = revs->date_mode; + pretty_print_commit(revs->commit_format, commit, &buf, &ctx); if (revs->graph) { if (buf.len) { if (revs->commit_format != CMIT_FMT_ONELINE) diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 4d4a3c8..8aa63c7 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -158,9 +158,12 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) sha1_to_hex(commit->object.sha1)); if (log->user_format) { struct strbuf buf = STRBUF_INIT; - - pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf, - DEFAULT_ABBREV, "", "", DATE_NORMAL, 0); + struct pretty_print_context ctx = {0}; + ctx.abbrev = DEFAULT_ABBREV; + ctx.subject = ""; + ctx.after_subject = ""; + ctx.date_mode = DATE_NORMAL; + pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf, &ctx); insert_one_record(log, author, buf.buf); strbuf_release(&buf); return; diff --git a/builtin-show-branch.c b/builtin-show-branch.c index be95930..9f13caa 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -293,8 +293,8 @@ static void show_one_commit(struct commit *commit, int no_name) struct commit_name *name = commit->util; if (commit->object.parsed) { - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &pretty, 0, NULL, NULL, 0, 0); + struct pretty_print_context ctx = {0}; + pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx); pretty_str = pretty.buf; } if (!prefixcmp(pretty_str, "[PATCH] ")) diff --git a/commit.h b/commit.h index f4fc5c5..011766d 100644 --- a/commit.h +++ b/commit.h @@ -63,6 +63,15 @@ enum cmit_fmt { CMIT_FMT_UNSPECIFIED, }; +struct pretty_print_context +{ + int abbrev; + const char *subject; + const char *after_subject; + enum date_mode date_mode; + int need_8bit_cte; +}; + extern int non_ascii(int); extern int has_non_ascii(const char *text); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ @@ -71,12 +80,10 @@ extern char *reencode_commit_message(const struct commit *commit, extern void get_commit_format(const char *arg, struct rev_info *); extern void format_commit_message(const struct commit *commit, const void *format, struct strbuf *sb, - enum date_mode dmode); -extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*, - struct strbuf *, - int abbrev, const char *subject, - const char *after_subject, enum date_mode, - int need_8bit_cte); + const struct pretty_print_context *context); +extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, + struct strbuf *sb, + const struct pretty_print_context *context); void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, const char *line, enum date_mode dmode, const char *encoding); diff --git a/log-tree.c b/log-tree.c index f7d54f2..1675035 100644 --- a/log-tree.c +++ b/log-tree.c @@ -179,8 +179,10 @@ void get_patch_filename(struct commit *commit, int nr, const char *suffix, strbuf_addf(buf, commit ? "%04d-" : "%d", nr); if (commit) { int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len; + struct pretty_print_context ctx = {0}; + ctx.date_mode = DATE_NORMAL; - format_commit_message(commit, "%f", buf, DATE_NORMAL); + format_commit_message(commit, "%f", buf, &ctx); if (max_len < buf->len) strbuf_setlen(buf, max_len); strbuf_addstr(buf, suffix); @@ -277,10 +279,9 @@ void show_log(struct rev_info *opt) struct strbuf msgbuf = STRBUF_INIT; struct log_info *log = opt->loginfo; struct commit *commit = log->commit, *parent = log->parent; - int abbrev = opt->diffopt.abbrev; int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40; - const char *subject = NULL, *extra_headers = opt->extra_headers; - int need_8bit_cte = 0; + const char *extra_headers = opt->extra_headers; + struct pretty_print_context ctx = {0}; opt->loginfo = NULL; if (!opt->verbose_header) { @@ -347,8 +348,8 @@ void show_log(struct rev_info *opt) */ if (opt->commit_format == CMIT_FMT_EMAIL) { - log_write_email_headers(opt, commit, &subject, &extra_headers, - &need_8bit_cte); + log_write_email_headers(opt, commit, &ctx.subject, &extra_headers, + &ctx.need_8bit_cte); } else if (opt->commit_format != CMIT_FMT_USERFORMAT) { fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout); if (opt->commit_format != CMIT_FMT_ONELINE) @@ -405,11 +406,12 @@ void show_log(struct rev_info *opt) /* * And then the pretty-printed message itself */ - if (need_8bit_cte >= 0) - need_8bit_cte = has_non_ascii(opt->add_signoff); - pretty_print_commit(opt->commit_format, commit, &msgbuf, - abbrev, subject, extra_headers, opt->date_mode, - need_8bit_cte); + if (ctx.need_8bit_cte >= 0) + ctx.need_8bit_cte = has_non_ascii(opt->add_signoff); + ctx.date_mode = opt->date_mode; + ctx.abbrev = opt->diffopt.abbrev; + ctx.after_subject = extra_headers; + pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx); if (opt->add_signoff) append_signoff(&msgbuf, opt->add_signoff); diff --git a/pretty.c b/pretty.c index f5983f8..d6d57eb 100644 --- a/pretty.c +++ b/pretty.c @@ -442,7 +442,7 @@ struct chunk { struct format_commit_context { const struct commit *commit; - enum date_mode dmode; + const struct pretty_print_context *pretty_ctx; unsigned commit_header_parsed:1; unsigned commit_message_parsed:1; @@ -711,11 +711,11 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, case 'a': /* author ... */ return format_person_part(sb, placeholder[1], msg + c->author.off, c->author.len, - c->dmode); + c->pretty_ctx->date_mode); case 'c': /* committer ... */ return format_person_part(sb, placeholder[1], msg + c->committer.off, c->committer.len, - c->dmode); + c->pretty_ctx->date_mode); case 'e': /* encoding */ strbuf_add(sb, msg + c->encoding.off, c->encoding.len); return 1; @@ -741,13 +741,13 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, void format_commit_message(const struct commit *commit, const void *format, struct strbuf *sb, - enum date_mode dmode) + const struct pretty_print_context *pretty_ctx) { struct format_commit_context context; memset(&context, 0, sizeof(context)); context.commit = commit; - context.dmode = dmode; + context.pretty_ctx = pretty_ctx; strbuf_expand(sb, format, format_commit_item, &context); } @@ -900,18 +900,18 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding } void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, - struct strbuf *sb, int abbrev, - const char *subject, const char *after_subject, - enum date_mode dmode, int need_8bit_cte) + struct strbuf *sb, + const struct pretty_print_context *context) { unsigned long beginning_of_body; int indent = 4; const char *msg = commit->buffer; char *reencoded; const char *encoding; + int need_8bit_cte = context->need_8bit_cte; if (fmt == CMIT_FMT_USERFORMAT) { - format_commit_message(commit, user_format, sb, dmode); + format_commit_message(commit, user_format, sb, context); return; } @@ -946,8 +946,9 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, } } - pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb); - if (fmt != CMIT_FMT_ONELINE && !subject) { + pp_header(fmt, context->abbrev, context->date_mode, encoding, + commit, &msg, sb); + if (fmt != CMIT_FMT_ONELINE && !context->subject) { strbuf_addch(sb, '\n'); } @@ -956,8 +957,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, /* These formats treat the title line specially. */ if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) - pp_title_line(fmt, &msg, sb, subject, - after_subject, encoding, need_8bit_cte); + pp_title_line(fmt, &msg, sb, context->subject, + context->after_subject, encoding, need_8bit_cte); beginning_of_body = sb->len; if (fmt != CMIT_FMT_ONELINE) -- cgit v0.10.2-6-g49f6 From 72b103fec7af967d295410c2fd3899bc6e8386e2 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 19 Oct 2009 17:48:09 +0200 Subject: reflog-walk: refactor the branch@{num} formatting We'll use the same output in an upcoming commit, so refactor its formatting (which was duplicated anyway) into a separate function. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano diff --git a/reflog-walk.c b/reflog-walk.c index 5623ea6..596bafe 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -241,36 +241,46 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) commit->object.flags &= ~(ADDED | SEEN | SHOWN); } -void show_reflog_message(struct reflog_walk_info *info, int oneline, +void get_reflog_selector(struct strbuf *sb, + struct reflog_walk_info *reflog_info, + enum date_mode dmode) +{ + struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; + struct reflog_info *info; + + if (!commit_reflog) + return; + + strbuf_addf(sb, "%s@{", commit_reflog->reflogs->ref); + if (commit_reflog->flag || dmode) { + info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; + strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode)); + } else { + strbuf_addf(sb, "%d", commit_reflog->reflogs->nr + - 2 - commit_reflog->recno); + } + + strbuf_addch(sb, '}'); +} + +void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, enum date_mode dmode) { - if (info && info->last_commit_reflog) { - struct commit_reflog *commit_reflog = info->last_commit_reflog; + if (reflog_info && reflog_info->last_commit_reflog) { + struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; struct reflog_info *info; + struct strbuf selector = STRBUF_INIT; info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; + get_reflog_selector(&selector, reflog_info, dmode); if (oneline) { - printf("%s@{", commit_reflog->reflogs->ref); - if (commit_reflog->flag || dmode) - printf("%s", show_date(info->timestamp, - info->tz, - dmode)); - else - printf("%d", commit_reflog->reflogs->nr - - 2 - commit_reflog->recno); - printf("}: %s", info->message); + printf("%s: %s", selector.buf, info->message); } else { - printf("Reflog: %s@{", commit_reflog->reflogs->ref); - if (commit_reflog->flag || dmode) - printf("%s", show_date(info->timestamp, - info->tz, - dmode)); - else - printf("%d", commit_reflog->reflogs->nr - - 2 - commit_reflog->recno); - printf("} (%s)\nReflog message: %s", - info->email, info->message); + printf("Reflog: %s (%s)\nReflog message: %s", + selector.buf, info->email, info->message); } + + strbuf_release(&selector); } } -- cgit v0.10.2-6-g49f6 From 8f8f5476cd6542387d435c242752404cf144005f Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 19 Oct 2009 17:48:10 +0200 Subject: Introduce new pretty formats %g[sdD] for reflog information Add three new --pretty=format escapes: %gD long reflog descriptor (e.g. refs/stash@{0}) %gd short reflog descriptor (e.g. stash@{0}) %gs reflog message This is achieved by passing down the reflog info, if any, inside the pretty_print_context struct. We use the newly refactored get_reflog_selector(), and give it some extra functionality to extract a shortened ref. The shortening is cached inside the commit_reflogs struct; the only allocation of it happens in read_complete_reflog(), where it is initialised to 0. Also add another helper get_reflog_message() for the message extraction. Note that the --format="%h %gD: %gs" tests may not work in real repositories, as the --pretty formatter doesn't know to leave away the ": " on the last commit in an incomplete (because git-gc removed the old part) reflog. This equivalence is nevertheless the main goal of this patch. Thanks to Jeff King for reviews, the %gd testcase and documentation. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 2a845b1..38b9904 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -123,6 +123,9 @@ The placeholders are: - '%s': subject - '%f': sanitized subject line, suitable for a filename - '%b': body +- '%gD': reflog selector, e.g., `refs/stash@\{1\}` +- '%gd': shortened reflog selector, e.g., `stash@\{1\}` +- '%gs': reflog subject - '%Cred': switch color to red - '%Cgreen': switch color to green - '%Cblue': switch color to blue @@ -132,6 +135,12 @@ The placeholders are: - '%n': newline - '%x00': print a byte from a hex code +NOTE: Some placeholders may depend on other options given to the +revision traversal engine. For example, the `%g*` reflog options will +insert an empty string unless we are traversing reflog entries (e.g., by +`git log -g`). The `%d` placeholder will use the "short" decoration +format if `--decorate` was not already provided on the command line. + * 'tformat:' + The 'tformat:' format works exactly like 'format:', except that it diff --git a/commit.h b/commit.h index 011766d..15cb649 100644 --- a/commit.h +++ b/commit.h @@ -70,6 +70,7 @@ struct pretty_print_context const char *after_subject; enum date_mode date_mode; int need_8bit_cte; + struct reflog_walk_info *reflog_info; }; extern int non_ascii(int); diff --git a/log-tree.c b/log-tree.c index 1675035..0fdf159 100644 --- a/log-tree.c +++ b/log-tree.c @@ -411,6 +411,7 @@ void show_log(struct rev_info *opt) ctx.date_mode = opt->date_mode; ctx.abbrev = opt->diffopt.abbrev; ctx.after_subject = extra_headers; + ctx.reflog_info = opt->reflog_info; pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx); if (opt->add_signoff) diff --git a/pretty.c b/pretty.c index d6d57eb..fc65fca 100644 --- a/pretty.c +++ b/pretty.c @@ -7,6 +7,7 @@ #include "mailmap.h" #include "log-tree.h" #include "color.h" +#include "reflog-walk.h" static char *user_format; @@ -701,6 +702,22 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, case 'd': format_decoration(sb, commit); return 1; + case 'g': /* reflog info */ + switch(placeholder[1]) { + case 'd': /* reflog selector */ + case 'D': + if (c->pretty_ctx->reflog_info) + get_reflog_selector(sb, + c->pretty_ctx->reflog_info, + c->pretty_ctx->date_mode, + (placeholder[1] == 'd')); + return 2; + case 's': /* reflog message */ + if (c->pretty_ctx->reflog_info) + get_reflog_message(sb, c->pretty_ctx->reflog_info); + return 2; + } + return 0; /* unknown %g placeholder */ } /* For the rest we have to parse the commit header. */ diff --git a/reflog-walk.c b/reflog-walk.c index 596bafe..caba4f7 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -8,6 +8,7 @@ struct complete_reflogs { char *ref; + const char *short_ref; struct reflog_info { unsigned char osha1[20], nsha1[20]; char *email; @@ -243,15 +244,26 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) void get_reflog_selector(struct strbuf *sb, struct reflog_walk_info *reflog_info, - enum date_mode dmode) + enum date_mode dmode, + int shorten) { struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; struct reflog_info *info; + const char *printed_ref; if (!commit_reflog) return; - strbuf_addf(sb, "%s@{", commit_reflog->reflogs->ref); + if (shorten) { + if (!commit_reflog->reflogs->short_ref) + commit_reflog->reflogs->short_ref + = shorten_unambiguous_ref(commit_reflog->reflogs->ref, 0); + printed_ref = commit_reflog->reflogs->short_ref; + } else { + printed_ref = commit_reflog->reflogs->ref; + } + + strbuf_addf(sb, "%s@{", printed_ref); if (commit_reflog->flag || dmode) { info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; strbuf_addstr(sb, show_date(info->timestamp, info->tz, dmode)); @@ -263,6 +275,23 @@ void get_reflog_selector(struct strbuf *sb, strbuf_addch(sb, '}'); } +void get_reflog_message(struct strbuf *sb, + struct reflog_walk_info *reflog_info) +{ + struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; + struct reflog_info *info; + size_t len; + + if (!commit_reflog) + return; + + info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; + len = strlen(info->message); + if (len > 0) + len--; /* strip away trailing newline */ + strbuf_add(sb, info->message, len); +} + void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, enum date_mode dmode) { @@ -272,7 +301,7 @@ void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, struct strbuf selector = STRBUF_INIT; info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; - get_reflog_selector(&selector, reflog_info, dmode); + get_reflog_selector(&selector, reflog_info, dmode, 0); if (oneline) { printf("%s: %s", selector.buf, info->message); } diff --git a/reflog-walk.h b/reflog-walk.h index 74c9096..7bd2cd4 100644 --- a/reflog-walk.h +++ b/reflog-walk.h @@ -3,6 +3,8 @@ #include "cache.h" +struct reflog_walk_info; + extern void init_reflog_walk(struct reflog_walk_info** info); extern int add_reflog_for_walk(struct reflog_walk_info *info, struct commit *commit, const char *name); @@ -10,5 +12,11 @@ extern void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit); extern void show_reflog_message(struct reflog_walk_info *info, int, enum date_mode); +extern void get_reflog_message(struct strbuf *sb, + struct reflog_walk_info *reflog_info); +extern void get_reflog_selector(struct strbuf *sb, + struct reflog_walk_info *reflog_info, + enum date_mode dmode, + int shorten); #endif diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 59d1f62..7f61ab0 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -162,4 +162,22 @@ test_expect_success 'empty email' ' } ' +test_expect_success '"%h %gD: %gs" is same as git-reflog' ' + git reflog >expect && + git log -g --format="%h %gD: %gs" >actual && + test_cmp expect actual +' + +test_expect_success '"%h %gD: %gs" is same as git-reflog (with date)' ' + git reflog --date=raw >expect && + git log -g --format="%h %gD: %gs" --date=raw >actual && + test_cmp expect actual +' + +test_expect_success '%gd shortens ref name' ' + echo "master@{0}" >expect.gd-short && + git log -g -1 --format=%gd refs/heads/master >actual.gd-short && + test_cmp expect.gd-short actual.gd-short +' + test_done -- cgit v0.10.2-6-g49f6 From 391c53bdcd7bbce366eaef7288afb948525ed3e8 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 19 Oct 2009 17:48:11 +0200 Subject: stash list: use new %g formats instead of sed With the new formats, we can rewrite 'git stash list' in terms of an appropriate pretty format, instead of hand-editing with sed. This has the advantage that it obeys the normal settings for git-log, notably the pager. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano diff --git a/git-stash.sh b/git-stash.sh index 4febbbf..f8847c1 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -205,8 +205,7 @@ have_stash () { list_stash () { have_stash || return 0 - git log --no-color --pretty=oneline -g "$@" $ref_stash -- | - sed -n -e 's/^[.0-9a-f]* refs\///p' + git log --format="%gd: %gs" -g "$@" $ref_stash -- } show_stash () { -- cgit v0.10.2-6-g49f6 From b7b10385a84c741a4fe219807c9511f69403640a Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 19 Oct 2009 17:48:12 +0200 Subject: stash list: drop the default limit of 10 stashes 'git stash list' had an undocumented limit of 10 stashes, unless other git-log arguments were specified. This surprised at least one user, but possibly served to cut the output below a screenful without using a pager. Since the last commit, 'git stash list' will fire up a pager according to the same rules as the 'git log' it calls, so we can drop the limit. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index fafe728..3f14b72 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -78,8 +78,7 @@ stash@{1}: On master: 9cc0589... Add git-stash ---------------------------------------------------------------- + The command takes options applicable to the 'git-log' -command to control what is shown and how. If no options are set, the -default is `-n 10`. See linkgit:git-log[1]. +command to control what is shown and how. See linkgit:git-log[1]. show []:: diff --git a/git-stash.sh b/git-stash.sh index f8847c1..f796c2f 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -382,11 +382,6 @@ test -n "$seen_non_option" || set "save" "$@" case "$1" in list) shift - if test $# = 0 - then - set x -n 10 - shift - fi list_stash "$@" ;; show) -- cgit v0.10.2-6-g49f6 From 752c0c24926aacbceca0d27de6ad22cbb7dd0709 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Oct 2009 14:38:32 +0200 Subject: Add the --submodule option to the diff option family When you use the option --submodule=log you can see the submodule summaries inlined in the diff, instead of not-quite-helpful SHA-1 pairs. The format imitates what "git submodule summary" shows. To do that, /.git/objects/ is added to the alternate object databases (if that directory exists). This option was requested by Jens Lehmann at the GitTogether in Berlin. Signed-off-by: Johannes Schindelin Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 9276fae..e26b847 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -87,6 +87,13 @@ endif::git-format-patch[] Show only names and status of changed files. See the description of the `--diff-filter` option on what the status letters mean. +--submodule[=]:: + Chose the output format for submodule differences. can be one of + 'short' and 'log'. 'short' just shows pairs of commit names, this format + is used when this option is not given. 'log' is the default value for this + option and lists the commits in that commit range like the 'summary' + option of linkgit:git-submodule[1] does. + --color:: Show colored diff. diff --git a/Makefile b/Makefile index 12defd4..efade29 100644 --- a/Makefile +++ b/Makefile @@ -448,6 +448,7 @@ LIB_H += sideband.h LIB_H += sigchain.h LIB_H += strbuf.h LIB_H += string-list.h +LIB_H += submodule.h LIB_H += tag.h LIB_H += transport.h LIB_H += tree.h @@ -546,6 +547,7 @@ LIB_OBJS += sideband.o LIB_OBJS += sigchain.o LIB_OBJS += strbuf.o LIB_OBJS += string-list.o +LIB_OBJS += submodule.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += trace.o diff --git a/diff.c b/diff.c index e1be189..6c63b87 100644 --- a/diff.c +++ b/diff.c @@ -13,6 +13,7 @@ #include "utf8.h" #include "userdiff.h" #include "sigchain.h" +#include "submodule.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -1453,6 +1454,17 @@ static void builtin_diff(const char *name_a, const char *a_prefix, *b_prefix; const char *textconv_one = NULL, *textconv_two = NULL; + if (DIFF_OPT_TST(o, SUBMODULE_LOG) && + (!one->mode || S_ISGITLINK(one->mode)) && + (!two->mode || S_ISGITLINK(two->mode))) { + const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); + const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); + show_submodule_summary(o->file, one ? one->path : two->path, + one->sha1, two->sha1, + del, add, reset); + return; + } + if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) { textconv_one = get_textconv(one); textconv_two = get_textconv(two); @@ -2640,6 +2652,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, ALLOW_TEXTCONV); else if (!strcmp(arg, "--ignore-submodules")) DIFF_OPT_SET(options, IGNORE_SUBMODULES); + else if (!strcmp(arg, "--submodule")) + DIFF_OPT_SET(options, SUBMODULE_LOG); + else if (!prefixcmp(arg, "--submodule=")) { + if (!strcmp(arg + 12, "log")) + DIFF_OPT_SET(options, SUBMODULE_LOG); + } /* misc options */ else if (!strcmp(arg, "-z")) diff --git a/diff.h b/diff.h index 6616877..2740421 100644 --- a/diff.h +++ b/diff.h @@ -66,6 +66,9 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_DIRSTAT_CUMULATIVE (1 << 19) #define DIFF_OPT_DIRSTAT_BY_FILE (1 << 20) #define DIFF_OPT_ALLOW_TEXTCONV (1 << 21) + +#define DIFF_OPT_SUBMODULE_LOG (1 << 23) + #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) #define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag) diff --git a/submodule.c b/submodule.c new file mode 100644 index 0000000..d5fce7a --- /dev/null +++ b/submodule.c @@ -0,0 +1,113 @@ +#include "cache.h" +#include "submodule.h" +#include "dir.h" +#include "diff.h" +#include "commit.h" +#include "revision.h" + +int add_submodule_odb(const char *path) +{ + struct strbuf objects_directory = STRBUF_INIT; + struct alternate_object_database *alt_odb; + + strbuf_addf(&objects_directory, "%s/.git/objects/", path); + if (!is_directory(objects_directory.buf)) + return -1; + + /* avoid adding it twice */ + for (alt_odb = alt_odb_list; alt_odb; alt_odb = alt_odb->next) + if (alt_odb->name - alt_odb->base == objects_directory.len && + !strncmp(alt_odb->base, objects_directory.buf, + objects_directory.len)) + return 0; + + alt_odb = xmalloc(objects_directory.len + 42 + sizeof(*alt_odb)); + alt_odb->next = alt_odb_list; + strcpy(alt_odb->base, objects_directory.buf); + alt_odb->name = alt_odb->base + objects_directory.len; + alt_odb->name[2] = '/'; + alt_odb->name[40] = '\0'; + alt_odb->name[41] = '\0'; + alt_odb_list = alt_odb; + prepare_alt_odb(); + return 0; +} + +void show_submodule_summary(FILE *f, const char *path, + unsigned char one[20], unsigned char two[20], + const char *del, const char *add, const char *reset) +{ + struct rev_info rev; + struct commit *commit, *left = left, *right; + struct commit_list *merge_bases, *list; + const char *message = NULL; + struct strbuf sb = STRBUF_INIT; + static const char *format = " %m %s"; + int fast_forward = 0, fast_backward = 0; + + if (is_null_sha1(two)) + message = "(submodule deleted)"; + else if (add_submodule_odb(path)) + message = "(not checked out)"; + else if (is_null_sha1(one)) + message = "(new submodule)"; + else if (!(left = lookup_commit_reference(one)) || + !(right = lookup_commit_reference(two))) + message = "(commits not present)"; + + if (!message) { + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, NULL); + rev.left_right = 1; + rev.first_parent_only = 1; + left->object.flags |= SYMMETRIC_LEFT; + add_pending_object(&rev, &left->object, path); + add_pending_object(&rev, &right->object, path); + merge_bases = get_merge_bases(left, right, 1); + if (merge_bases) { + if (merge_bases->item == left) + fast_forward = 1; + else if (merge_bases->item == right) + fast_backward = 1; + } + for (list = merge_bases; list; list = list->next) { + list->item->object.flags |= UNINTERESTING; + add_pending_object(&rev, &list->item->object, + sha1_to_hex(list->item->object.sha1)); + } + if (prepare_revision_walk(&rev)) + message = "(revision walker failed)"; + } + + strbuf_addf(&sb, "Submodule %s %s..", path, + find_unique_abbrev(one, DEFAULT_ABBREV)); + if (!fast_backward && !fast_forward) + strbuf_addch(&sb, '.'); + strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV)); + if (message) + strbuf_addf(&sb, " %s\n", message); + else + strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : ""); + fwrite(sb.buf, sb.len, 1, f); + + if (!message) { + while ((commit = get_revision(&rev))) { + strbuf_setlen(&sb, 0); + if (commit->object.flags & SYMMETRIC_LEFT) { + if (del) + strbuf_addstr(&sb, del); + } + else if (add) + strbuf_addstr(&sb, add); + format_commit_message(commit, format, &sb, + rev.date_mode); + if (reset) + strbuf_addstr(&sb, reset); + strbuf_addch(&sb, '\n'); + fprintf(f, "%s", sb.buf); + } + clear_commit_marks(left, ~0); + clear_commit_marks(right, ~0); + } + strbuf_release(&sb); +} diff --git a/submodule.h b/submodule.h new file mode 100644 index 0000000..4c0269d --- /dev/null +++ b/submodule.h @@ -0,0 +1,8 @@ +#ifndef SUBMODULE_H +#define SUBMODULE_H + +void show_submodule_summary(FILE *f, const char *path, + unsigned char one[20], unsigned char two[20], + const char *del, const char *add, const char *reset); + +#endif -- cgit v0.10.2-6-g49f6 From 3ed0b11e7e9a8a35956d2c8c39c51c8d8f5eac23 Mon Sep 17 00:00:00 2001 From: Matt Kraai Date: Mon, 19 Oct 2009 22:22:25 -0700 Subject: Documentation/git-gc.txt: change "references" to "reference" Signed-off-by: Matt Kraai Signed-off-by: Junio C Hamano diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 1f6df6a..4cd9cdf 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -120,7 +120,7 @@ Notes particular, it will keep not only objects referenced by your current set of branches and tags, but also objects referenced by the index, remote tracking branches, refs saved by 'git-filter-branch' in -refs/original/, or reflogs (which may references commits in branches +refs/original/, or reflogs (which may reference commits in branches that were later amended or rewound). If you are expecting some objects to be collected and they aren't, check -- cgit v0.10.2-6-g49f6 From e133d65c3e3d7f5f869edb2d6205af68af0fe38f Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 15 Oct 2009 21:14:59 -0700 Subject: gitweb: linkify author/committer names with search It's nice to search for an author by merely clicking on their name in gitweb. This is usually faster than selecting the name, copying the selection, pasting it into the search box, selecting between author/committer and then hitting enter. Linkify the avatar icon in log/shortlog view because the icon is directly adjacent to the name and thus more related. The same is not true when in commit/tag view where the icon is farther away. Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 8f68fe3..27405e6 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -32,6 +32,10 @@ img.avatar { vertical-align: middle; } +a.list img.avatar { + border-style: none; +} + div.page_header { height: 25px; padding: 8px; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 4b21ad2..c160a56 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1594,6 +1594,29 @@ sub git_get_avatar { } } +sub format_search_author { + my ($author, $searchtype, $displaytext) = @_; + my $have_search = gitweb_check_feature('search'); + + if ($have_search) { + my $performed = ""; + if ($searchtype eq 'author') { + $performed = "authored"; + } elsif ($searchtype eq 'committer') { + $performed = "committed"; + } + + return $cgi->a({-href => href(action=>"search", hash=>$hash, + searchtext=>$author, + searchtype=>$searchtype), class=>"list", + title=>"Search for commits $performed by $author"}, + $displaytext); + + } else { + return $displaytext; + } +} + # format the author name of the given commit with the given tag # the author name is chopped and escaped according to the other # optional parameters (see chop_str). @@ -1602,8 +1625,10 @@ sub format_author_html { my $co = shift; my $author = chop_and_escape_str($co->{'author_name'}, @_); return "<$tag class=\"author\">" . - git_get_avatar($co->{'author_email'}, -pad_after => 1) . - $author . ""; + format_search_author($co->{'author_name'}, "author", + git_get_avatar($co->{'author_email'}, -pad_after => 1) . + $author) . + ""; } # format git diff header line, i.e. "diff --(git|combined|cc) ..." @@ -3372,10 +3397,11 @@ sub git_print_authorship { my $co = shift; my %opts = @_; my $tag = $opts{-tag} || 'div'; + my $author = $co->{'author_name'}; my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); print "<$tag class=\"author_date\">" . - esc_html($co->{'author_name'}) . + format_search_author($author, "author", esc_html($author)) . " [$ad{'rfc2822'}"; print_local_time(%ad) if ($opts{-localtime}); print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1) @@ -3394,8 +3420,12 @@ sub git_print_authorship_rows { @people = ('author', 'committer') unless @people; foreach my $who (@people) { my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"}); - print "$who" . esc_html($co->{$who}) . "" . - "" . + print "$who" . + format_search_author($co->{"${who}_name"}, $who, + esc_html($co->{"${who}_name"})) . " " . + format_search_author($co->{"${who}_email"}, $who, + esc_html("<" . $co->{"${who}_email"} . ">")) . + "" . git_get_avatar($co->{"${who}_email"}, -size => 'double') . "\n" . "" . -- cgit v0.10.2-6-g49f6 From f29cd3938dbe9300dd75096d8ee2ad6311cc00af Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Oct 2009 11:46:55 -0700 Subject: fsck: default to "git fsck --full" Linus and other git developers from the early days trained their fingers to type the command, every once in a while even without thinking, to check the consistency of the repository back when the lower core part of the git was still being developed. Developers who wanted to make sure that git correctly dealt with packfiles could deliberately trigger their creation and checked them after they were created carefully, but loose objects are the ones that are written by various commands from random codepaths. It made some technical sense to have a mode that checked only loose objects from the debugging point of view for that reason. Even for git developers, there no longer is any reason to type "git fsck" every five minutes these days, worried that some newly created objects might be corrupt due to recent change to git. The reason we did not make "--full" the default is probably we trust our filesystems a bit too much. At least, we trusted filesystems more than we trusted the lower core part of git that was under development. Once a packfile is created and we always use it read-only, there didn't seem to be much point in suspecting that the underlying filesystems or disks may corrupt them in such a way that is not caught by the SHA-1 checksum over the entire packfile and per object checksum. That trust in the filesystems might have been a good tradeoff between fsck performance and reliability on platforms git was initially developed on and for, but it may not be true anymore as we run on many more platforms these days. Signed-off-by: Junio C Hamano diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes-1.6.6.txt index 5f1fecb..fa0e11a 100644 --- a/Documentation/RelNotes-1.6.6.txt +++ b/Documentation/RelNotes-1.6.6.txt @@ -1,6 +1,14 @@ GIT v1.6.6 Release Notes ======================== +In this release, "git fsck" defaults to "git fsck --full" and checks +packfiles, and because of this it will take much longer to complete +than before. If you prefer a quicker check only on loose objects (the +old default), you can say "git fsck --no-full". This has been +supported by 1.5.4 and newer versions of git, so it is safe to write +it in your script even if you use slightly older git on some of your +machines. + In git 1.7.0, which is planned to be the release after 1.6.6, "git push" into a branch that is currently checked out will be refused by default. @@ -38,6 +46,9 @@ Updates since v1.6.5 (usability, bells and whistles) + * "git fsck" by default checks the packfiles (i.e. "--full" is the + default); you can turn it off with "git fsck --no-full". + * "git log --decorate" shows the location of HEAD as well. (developers) diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 287c4fc..6fe9484 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] - [--full] [--strict] [--verbose] [--lost-found] [*] + [--[no-]full] [--strict] [--verbose] [--lost-found] [*] DESCRIPTION ----------- @@ -52,7 +52,8 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless or $GIT_DIR/objects/info/alternates, and in packed git archives found in $GIT_DIR/objects/pack and corresponding pack subdirectories in alternate - object pools. + object pools. This is now default; you can turn it off + with --no-full. --strict:: Enable more strict checking, namely to catch a file mode diff --git a/builtin-fsck.c b/builtin-fsck.c index c58b0e3..2d88e45 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -19,7 +19,7 @@ static int show_root; static int show_tags; static int show_unreachable; static int include_reflogs = 1; -static int check_full; +static int check_full = 1; static int check_strict; static int keep_cache_objects; static unsigned char head_sha1[20]; -- cgit v0.10.2-6-g49f6 From 66bcb6a68f8759ab7a7b5ca47085ab1c1402062e Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Wed, 21 Oct 2009 13:35:15 +0200 Subject: Remove a left-over file from t/t5100 This mbox file must have been added by accident in e9fe804 (git-mailinfo: Fix getting the subject from the in-body [PATCH] line, 2008-07-14). Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano diff --git a/t/t5100/0010 b/t/t5100/0010 deleted file mode 100644 index f5892c9..0000000 --- a/t/t5100/0010 +++ /dev/null @@ -1,35 +0,0 @@ -From b9704a518e21158433baa2cc2d591fea687967f6 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= -Date: Thu, 10 Jul 2008 23:41:33 +0200 -Subject: Re: discussion that lead to this patch -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -[PATCH] git-mailinfo: Fix getting the subject from the body - -"Subject: " isn't in the static array "header", and thus -memcmp("Subject: ", header[i], 7) will never match. - -Signed-off-by: Lukas Sandström -Signed-off-by: Junio C Hamano ---- - builtin-mailinfo.c | 2 +- - 1 files changed, 1 insertions(+), 1 deletions(-) - -diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c -index 962aa34..2d1520f 100644 ---- a/builtin-mailinfo.c -+++ b/builtin-mailinfo.c -@@ -334,7 +334,7 @@ static int check_header(char *line, unsigned linesize, char **hdr_data, int over - return 1; - if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { - for (i = 0; header[i]; i++) { -- if (!memcmp("Subject: ", header[i], 9)) { -+ if (!memcmp("Subject", header[i], 7)) { - if (! handle_header(line, hdr_data[i], 0)) { - return 1; - } --- -1.5.6.2.455.g1efb2 - -- cgit v0.10.2-6-g49f6 From 814a9bfee93d6b7beefa4a6079d1acf4d4166724 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Wed, 21 Oct 2009 13:35:16 +0200 Subject: Mark files in t/t5100 as UTF-8 This enables gitk to show the patch text with correct glyphs if the locale is not UTF-8. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano diff --git a/t/t5100/.gitattributes b/t/t5100/.gitattributes new file mode 100644 index 0000000..c93f514 --- /dev/null +++ b/t/t5100/.gitattributes @@ -0,0 +1,4 @@ +msg* encoding=UTF-8 +info* encoding=UTF-8 +rfc2047-info-* encoding=UTF-8 +sample.mbox encoding=UTF-8 -- cgit v0.10.2-6-g49f6 From 975457f185acd7c1f96ccff67cd5dc8dcb7908a0 Mon Sep 17 00:00:00 2001 From: Nasser Grainawi Date: Wed, 21 Oct 2009 14:06:21 -0700 Subject: Document `delta` attribute in "git help attributes". Signed-off-by: Junio C Hamano diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 1195e83..1f472ce 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -560,6 +560,16 @@ in the file. E.g. the string `$Format:%H$` will be replaced by the commit hash. +Packing objects +~~~~~~~~~~~~~~~ + +`delta` +^^^^^^^ + +Delta compression will not be attempted for blobs for paths with the +attribute `delta` set to false. + + Viewing files in GUI tools ~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v0.10.2-6-g49f6 From d504f6975d34025ed3b5478b657789410b52cdb1 Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Wed, 21 Oct 2009 19:21:23 +0200 Subject: modernize fetch/merge/pull examples The "git pull" documentation has examples which follow an outdated style. Update the examples to use "git merge" where appropriate and move the examples to the corresponding manpages. Furthermore, - show that pull is equivalent to fetch and merge, which is still a frequently asked question, - explain the default fetch refspec. Signed-off-by: Clemens Buchacher Signed-off-by: Junio C Hamano diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index d3164c5..f2483d6 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -37,6 +37,35 @@ include::pull-fetch-param.txt[] include::urls-remotes.txt[] + +EXAMPLES +-------- + +* Update the remote-tracking branches: ++ +------------------------------------------------ +$ git fetch origin +------------------------------------------------ ++ +The above command copies all branches from the remote refs/heads/ +namespace and stores them to the local refs/remotes/origin/ namespace, +unless the branch..fetch option is used to specify a non-default +refspec. + +* Using refspecs explicitly: ++ +------------------------------------------------ +$ git fetch origin +pu:pu maint:tmp +------------------------------------------------ ++ +This updates (or creates, as necessary) branches `pu` and `tmp` in +the local repository by fetching from the branches (respectively) +`pu` and `maint` from the remote repository. ++ +The `pu` branch will be updated even if it is does not fast-forward, +because it is prefixed with a plus sign; `tmp` will not be. + + SEE ALSO -------- linkgit:git-pull[1] diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index d05f324..e886c2e 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -212,6 +212,39 @@ You can work through the conflict with a number of tools: common ancestor, 'git show :2:filename' shows the HEAD version and 'git show :3:filename' shows the remote version. + +EXAMPLES +-------- + +* Merge branches `fixes` and `enhancements` on top of + the current branch, making an octopus merge: ++ +------------------------------------------------ +$ git merge fixes enhancements +------------------------------------------------ + +* Merge branch `obsolete` into the current branch, using `ours` + merge strategy: ++ +------------------------------------------------ +$ git merge -s ours obsolete +------------------------------------------------ + +* Merge branch `maint` into the current branch, but do not make + a new commit automatically: ++ +------------------------------------------------ +$ git merge --no-commit maint +------------------------------------------------ ++ +This can be used when you want to include further changes to the +merge, or want to write your own merge commit message. ++ +You should refrain from abusing this option to sneak substantial +changes into a merge commit. Small fixups like bumping +release/version name would be acceptable. + + SEE ALSO -------- linkgit:git-fmt-merge-msg[1], linkgit:git-pull[1], diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 7578623..de2bcd6 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -131,54 +131,13 @@ $ git pull origin next ------------------------------------------------ + This leaves a copy of `next` temporarily in FETCH_HEAD, but -does not update any remote-tracking branches. - -* Bundle local branch `fixes` and `enhancements` on top of - the current branch, making an Octopus merge: -+ ------------------------------------------------- -$ git pull . fixes enhancements ------------------------------------------------- -+ -This `git pull .` syntax is equivalent to `git merge`. - -* Merge local branch `obsolete` into the current branch, using `ours` - merge strategy: -+ ------------------------------------------------- -$ git pull -s ours . obsolete ------------------------------------------------- - -* Merge local branch `maint` into the current branch, but do not make - a commit automatically: +does not update any remote-tracking branches. Using remote-tracking +branches, the same can be done by invoking fetch and merge: + ------------------------------------------------ -$ git pull --no-commit . maint +$ git fetch origin +$ git merge origin/next ------------------------------------------------ -+ -This can be used when you want to include further changes to the -merge, or want to write your own merge commit message. -+ -You should refrain from abusing this option to sneak substantial -changes into a merge commit. Small fixups like bumping -release/version name would be acceptable. - -* Command line pull of multiple branches from one repository: -+ ------------------------------------------------- -$ git checkout master -$ git fetch origin +pu:pu maint:tmp -$ git pull . tmp ------------------------------------------------- -+ -This updates (or creates, as necessary) branches `pu` and `tmp` in -the local repository by fetching from the branches (respectively) -`pu` and `maint` from the remote repository. -+ -The `pu` branch will be updated even if it is does not fast-forward; -the others will not be. -+ -The final command then merges the newly fetched `tmp` into master. If you tried a pull which resulted in a complex conflicts and -- cgit v0.10.2-6-g49f6 From 6b276e19fa71fabe64039cf004aba908d7083e82 Mon Sep 17 00:00:00 2001 From: Jari Aalto Date: Wed, 21 Oct 2009 23:07:49 +0300 Subject: Documentation/fetch-options.txt: order options alphabetically git-fetch.{1,html} will be helped with this patch Signed-off-by: Jari Aalto Signed-off-by: Junio C Hamano diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 5eb2b0e..2886874 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -1,25 +1,13 @@ -ifndef::git-pull[] --q:: ---quiet:: - Pass --quiet to git-fetch-pack and silence any other internally - used git commands. - --v:: ---verbose:: - Be verbose. -endif::git-pull[] - -a:: --append:: Append ref names and object names of fetched refs to the existing contents of `.git/FETCH_HEAD`. Without this option old data in `.git/FETCH_HEAD` will be overwritten. ---upload-pack :: - When given, and the repository to fetch from is handled - by 'git-fetch-pack', '--exec=' is passed to - the command to specify non-default path for the command - run on the other end. +--depth=:: + Deepen the history of a 'shallow' repository created by + `git clone` with `--depth=` option (see linkgit:git-clone[1]) + by the specified number of commits. -f:: --force:: @@ -29,6 +17,10 @@ endif::git-pull[] fetches is a descendant of ``. This option overrides that check. +-k:: +--keep:: + Keep downloaded pack. + ifdef::git-pull[] --no-tags:: endif::git-pull[] @@ -49,10 +41,6 @@ endif::git-pull[] flag lets all tags and their associated objects be downloaded. --k:: ---keep:: - Keep downloaded pack. - -u:: --update-head-ok:: By default 'git-fetch' refuses to update the head which @@ -62,7 +50,19 @@ endif::git-pull[] implementing your own Porcelain you are not supposed to use it. ---depth=:: - Deepen the history of a 'shallow' repository created by - `git clone` with `--depth=` option (see linkgit:git-clone[1]) - by the specified number of commits. +--upload-pack :: + When given, and the repository to fetch from is handled + by 'git-fetch-pack', '--exec=' is passed to + the command to specify non-default path for the command + run on the other end. + +ifndef::git-pull[] +-q:: +--quiet:: + Pass --quiet to git-fetch-pack and silence any other internally + used git commands. + +-v:: +--verbose:: + Be verbose. +endif::git-pull[] -- cgit v0.10.2-6-g49f6 From 46148dd7ea41de10fc784c247924f73ddb21121b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Oct 2009 00:49:23 -0700 Subject: git checkout --no-guess Porcelains may want to make sure their calls to "git checkout" will reliably fail regardless of the presense of random remote tracking branches by the new DWIMmery introduced. Luckily all existing in-tree callers have extra checks to make sure they feed local branch name when they want to switch, or they explicitly ask to detach HEAD at the given commit, so there is no need to add this option for them. As this is strictly script-only option, do not even bother to document it, and do bother to hide it from "git checkout -h". Signed-off-by: Junio C Hamano diff --git a/builtin-checkout.c b/builtin-checkout.c index fb7e68a..da04eed 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -616,6 +616,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) struct tree *source_tree = NULL; char *conflict_style = NULL; int patch_mode = 0; + int dwim_new_local_branch = 1; struct option options[] = { OPT__QUIET(&opts.quiet), OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), @@ -631,6 +632,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), + { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL, + "second guess 'git checkout no-such-branch'", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_END(), }; int has_dash_dash; @@ -715,6 +719,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (has_dash_dash) /* case (1) */ die("invalid reference: %s", arg); if (!patch_mode && + dwim_new_local_branch && opts.track == BRANCH_TRACK_UNSPECIFIED && !opts.new_branch && !check_filename(NULL, arg) && -- cgit v0.10.2-6-g49f6 From dad5f89fc568639c43bdbf344411757adf39d636 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Oct 2009 15:26:10 -0700 Subject: gc --auto --quiet: make the notice a bit less verboase When "gc --auto --quiet" decides there is something to do, it tells the user what it is doing, as it is going to make the user wait for a bit. But the message was a bit too long. Signed-off-by: Junio C Hamano diff --git a/builtin-gc.c b/builtin-gc.c index 7d3e9cc..093517e 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -216,10 +216,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix) */ if (!need_to_gc()) return 0; - fprintf(stderr, "Auto packing your repository for optimum " - "performance. You may also\n" - "run \"git gc\" manually. See " - "\"git help gc\" for more information.\n"); + fprintf(stderr, + "Auto packing the repository for optimum performance.%s\n", + quiet + ? "" + : (" You may also\n" + "run \"git gc\" manually. See " + "\"git help gc\" for more information.")); } else append_option(argv_repack, prune_expire && !strcmp(prune_expire, "now") -- cgit v0.10.2-6-g49f6 From 77e3efbf43a1060049121350cc3ae8c208e77d57 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Oct 2009 14:56:40 -0700 Subject: receive-pack: run "gc --auto --quiet" and optionally "update-server-info" Introduce two new configuration variables, receive.autogc (defaults to true) and receive.updateserverinfo (defaults to false). When these are set, receive-pack runs "gc --auto --quiet" and "update-server-info" respectively after it finishes receiving data from "git push" and updating refs. Signed-off-by: Junio C Hamano Acked-by: Nicolas Pitre diff --git a/Documentation/config.txt b/Documentation/config.txt index cd17814..ba6ed10 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1320,6 +1320,11 @@ rebase.stat:: Whether to show a diffstat of what changed upstream since the last rebase. False by default. +receive.autogc:: + By default, git-receive-pack will run "git-gc --auto" after + receiving data from git-push and updating refs. You can stop + it by setting this variable to false. + receive.fsckObjects:: If it is set to true, git-receive-pack will check all received objects. It will abort in the case of a malformed object or a @@ -1355,6 +1360,10 @@ receive.denyNonFastForwards:: even if that push is forced. This configuration variable is set when initializing a shared repository. +receive.updateserverinfo:: + If set to true, git-receive-pack will run git-update-server-info + after receiving data from git-push and updating refs. + remote..url:: The URL of a remote repository. See linkgit:git-fetch[1] or linkgit:git-push[1]. diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index b771fe9..e8bde02 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -28,6 +28,8 @@ static int transfer_unpack_limit = -1; static int unpack_limit = 100; static int report_status; static int prefer_ofs_delta = 1; +static int auto_update_server_info; +static int auto_gc = 1; static const char *head_name; static char *capabilities_to_send; @@ -88,6 +90,16 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.updateserverinfo") == 0) { + auto_update_server_info = git_config_bool(var, value); + return 0; + } + + if (strcmp(var, "receive.autogc") == 0) { + auto_gc = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -672,6 +684,14 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) report(unpack_status); run_receive_hook(post_receive_hook); run_update_post_hook(commands); + if (auto_gc) { + const char *argv_gc_auto[] = { + "gc", "--auto", "--quiet", NULL, + }; + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + } + if (auto_update_server_info) + update_server_info(0); } return 0; } -- cgit v0.10.2-6-g49f6 From 2a94552887ecbaa8b848a92eb607e032d77c8a2c Mon Sep 17 00:00:00 2001 From: Ingmar Vanhassel Date: Tue, 20 Oct 2009 12:29:32 +0200 Subject: import-tars: Add support for tarballs compressed with lzma, xz Also handle the extensions .tlz and .txz, aliases for .tar.lzma and .tar.xz respectively. Signed-off-by: Ingmar Vanhassel Liked-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index 7001862..95438e1 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -20,7 +20,7 @@ use Getopt::Long; my $metaext = ''; -die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,Z}\n" +die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,lzma,xz,Z}\n" unless GetOptions('metainfo=s' => \$metaext) && @ARGV; my $branch_name = 'import-tars'; @@ -49,6 +49,9 @@ foreach my $tar_file (@ARGV) } elsif ($tar_name =~ s/\.tar\.Z$//) { open(I, '-|', 'uncompress', '-c', $tar_file) or die "Unable to uncompress -c $tar_file: $!\n"; + } elsif ($tar_name =~ s/\.(tar\.(lzma|xz)|(tlz|txz))$//) { + open(I, '-|', 'xz', '-dc', $tar_file) + or die "Unable to xz -dc $tar_file: $!\n"; } elsif ($tar_name =~ s/\.tar$//) { open(I, $tar_file) or die "Unable to open $tar_file: $!\n"; } else { -- cgit v0.10.2-6-g49f6 From 550c66f3f9c8b2106ecca19c267c6e7e019e3cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Tue, 20 Oct 2009 22:38:38 +0200 Subject: git-clone.txt: Fix grammar and formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing definite article ("the") in several places. Change "note to..." to "note for...", since "note to" means that that the note is addressed to someone (source: Google search). Change "progressbar" to "progress bar" (source: Wikipedia). Format git commands, options, and file names consistently using back quotes (i.e. a fixed font in the resulting HTML document). Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 5ebcba1..7e7d9fc 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -39,7 +39,7 @@ OPTIONS --local:: -l:: When the repository to clone from is on a local machine, - this flag bypasses normal "git aware" transport + this flag bypasses the normal "git aware" transport mechanism and clones the repository by making a copy of HEAD and everything under objects and refs directories. The files under `.git/objects/` directory are hardlinked @@ -60,7 +60,7 @@ OPTIONS -s:: When the repository to clone is on the local machine, instead of using hard links, automatically setup - .git/objects/info/alternates to share the objects + `.git/objects/info/alternates` to share the objects with the source repository. The resulting repository starts out without any object of its own. + @@ -69,7 +69,7 @@ it unless you understand what it does. If you clone your repository using this option and then delete branches (or use any other git command that makes any existing commit unreferenced) in the source repository, some objects may become unreferenced (or dangling). -These objects may be removed by normal git operations (such as 'git-commit') +These objects may be removed by normal git operations (such as `git commit`) which automatically call `git gc --auto`. (See linkgit:git-gc[1].) If these objects are removed and were referenced by the cloned repository, then the cloned repository will become corrupt. @@ -86,13 +86,13 @@ objects from the source repository into a pack in the cloned repository. --reference :: If the reference repository is on the local machine, - automatically setup .git/objects/info/alternates to + automatically setup `.git/objects/info/alternates` to obtain objects from the reference repository. Using an already existing repository as an alternate will require fewer objects to be copied from the repository being cloned, reducing network and local storage costs. + -*NOTE*: see NOTE to --shared option. +*NOTE*: see the NOTE for the `--shared` option. --quiet:: -q:: @@ -101,7 +101,7 @@ objects from the source repository into a pack in the cloned repository. --verbose:: -v:: - Display the progressbar, even in case the standard output is not + Display the progress bar, even in case the standard output is not a terminal. --no-checkout:: @@ -121,17 +121,17 @@ objects from the source repository into a pack in the cloned repository. configuration variables are created. --mirror:: - Set up a mirror of the remote repository. This implies --bare. + Set up a mirror of the remote repository. This implies `--bare`. --origin :: -o :: - Instead of using the remote name 'origin' to keep track - of the upstream repository, use . + Instead of using the remote name `origin` to keep track + of the upstream repository, use ``. --branch :: -b :: Instead of pointing the newly created HEAD to the branch pointed - to by the cloned repository's HEAD, point to branch + to by the cloned repository's HEAD, point to `` branch instead. In a non-bare repository, this is the branch that will be checked out. @@ -158,7 +158,7 @@ objects from the source repository into a pack in the cloned repository. --recursive:: After the clone is created, initialize all submodules within, using their default settings. This is equivalent to running - 'git submodule update --init --recursive' immediately after + `git submodule update --init --recursive` immediately after the clone is finished. This option is ignored if the cloned repository does not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`, or `--mirror` is given) @@ -171,8 +171,8 @@ objects from the source repository into a pack in the cloned repository. :: The name of a new directory to clone into. The "humanish" part of the source repository is used if no directory is - explicitly given ("repo" for "/path/to/repo.git" and "foo" - for "host.xz:foo/.git"). Cloning into an existing directory + explicitly given (`repo` for `/path/to/repo.git` and `foo` + for `host.xz:foo/.git`). Cloning into an existing directory is only allowed if the directory is empty. :git-clone: 1 -- cgit v0.10.2-6-g49f6 From a5ca8367c223b154b485ea51dc8c97201498caa4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Oct 2009 22:06:28 -0500 Subject: blame: make sure that the last line ends in an LF This is convenient when parsing multiple the blame of multiple files, for example: git ls-files -z --exclude-standard -- "*.[ch]" | xargs --null -n 1 git blame -p > output and then analyzing the 'output' file using a seperate script. Currently the parsing is difficult when not all files have a newline at EOF, this patch ensures that even such files have a newline at the end of the blame output. Signed-off-by: Sverre Rabbelier CC: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-blame.c b/builtin-blame.c index 7512773..dd16b22 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1604,6 +1604,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) } while (ch != '\n' && cp < sb->final_buf + sb->final_buf_size); } + + if (sb->final_buf_size && cp[-1] != '\n') + putchar('\n'); } static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) @@ -1667,6 +1670,9 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) } while (ch != '\n' && cp < sb->final_buf + sb->final_buf_size); } + + if (sb->final_buf_size && cp[-1] != '\n') + putchar('\n'); } static void output(struct scoreboard *sb, int option) -- cgit v0.10.2-6-g49f6 From 39eea7bdd943a103deadfd4c4fd204bbbf111219 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Oct 2009 23:06:14 -0700 Subject: Fix incorrect error check while reading deflated pack data The loop in get_size_from_delta() feeds a deflated delta data from the pack stream _until_ we get inflated result of 20 bytes[*] or we reach the end of stream. Side note. This magic number 20 does not have anything to do with the size of the hash we use, but comes from 1a3b55c (reduce delta head inflated size, 2006-10-18). The loop reads like this: do { in = use_pack(); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); curpos += stream.next_in - in; } while ((st == Z_OK || st == Z_BUF_ERROR) && stream.total_out < sizeof(delta_head)); This git_inflate() can return: - Z_STREAM_END, if use_pack() fed it enough input and the delta itself was smaller than 20 bytes; - Z_OK, when some progress has been made; - Z_BUF_ERROR, if no progress is possible, because we either ran out of input (due to corrupt pack), or we ran out of output before we saw the end of the stream. The fix b3118bd (sha1_file: Fix infinite loop when pack is corrupted, 2009-10-14) attempted was against a corruption that appears to be a valid stream that produces a result larger than the output buffer, but we are not even trying to read the stream to the end in this loop. If avail_out becomes zero, total_out will be the same as sizeof(delta_head) so the loop will terminate without the "fix". There is no fix from b3118bd needed for this loop, in other words. The loop in unpack_compressed_entry() is quite a different story. It feeds a deflated stream (either delta or base) and allows the stream to produce output up to what we expect but no more. do { in = use_pack(); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); curpos += stream.next_in - in; } while (st == Z_OK || st == Z_BUF_ERROR) This _does_ risk falling into an endless interation, as we can exhaust avail_out if the length we expect is smaller than what the stream wants to produce (due to pack corruption). In such a case, avail_out will become zero and inflate() will return Z_BUF_ERROR, while avail_in may (or may not) be zero. But this is not a right fix: do { in = use_pack(); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); + if (st == Z_BUF_ERROR && (stream.avail_in || !stream.avail_out) + break; /* wants more input??? */ curpos += stream.next_in - in; } while (st == Z_OK || st == Z_BUF_ERROR) as Z_BUF_ERROR from inflate() may be telling us that avail_in has also run out before reading the end of stream marker. In such a case, both avail_in and avail_out would be zero, and the loop should iterate to allow the end of stream marker to be seen by inflate from the input stream. The right fix for this loop is likely to be to increment the initial avail_out by one (we allocate one extra byte to terminate it with NUL anyway, so there is no risk to overrun the buffer), and break out if we see that avail_out has become zero, in order to detect that the stream wants to produce more than what we expect. After the loop, we have a check that exactly tests this condition: if ((st != Z_STREAM_END) || stream.total_out != size) { free(buffer); return NULL; } So here is a patch (without my previous botched attempts) to fix this issue. The first hunk reverts the corresponding hunk from b3118bd, and the second hunk is the same fix proposed earlier. Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 4cc8939..63981fb 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1357,8 +1357,6 @@ unsigned long get_size_from_delta(struct packed_git *p, in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); - if (st == Z_BUF_ERROR && (stream.avail_in || !stream.avail_out)) - break; curpos += stream.next_in - in; } while ((st == Z_OK || st == Z_BUF_ERROR) && stream.total_out < sizeof(delta_head)); @@ -1589,15 +1587,15 @@ static void *unpack_compressed_entry(struct packed_git *p, buffer[size] = 0; memset(&stream, 0, sizeof(stream)); stream.next_out = buffer; - stream.avail_out = size; + stream.avail_out = size + 1; git_inflate_init(&stream); do { in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; st = git_inflate(&stream, Z_FINISH); - if (st == Z_BUF_ERROR && (stream.avail_in || !stream.avail_out)) - break; + if (!stream.avail_out) + break; /* the payload is larger than it should be */ curpos += stream.next_in - in; } while (st == Z_OK || st == Z_BUF_ERROR); git_inflate_end(&stream); -- cgit v0.10.2-6-g49f6 From 3f7a9b5ad128151e6eb0b45266779e63e4b2287a Mon Sep 17 00:00:00 2001 From: Jari Aalto Date: Thu, 22 Oct 2009 17:14:57 +0300 Subject: Documentation/git-pull.txt: Add subtitles above included option files Signed-off-by: Jari Aalto Signed-off-by: Junio C Hamano diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 7578623..51534dd 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -26,6 +26,10 @@ Also note that options meant for 'git-pull' itself and underlying OPTIONS ------- + +Options related to merging +~~~~~~~~~~~~~~~~~~~~~~~~~~ + include::merge-options.txt[] :git-pull: 1 @@ -47,6 +51,9 @@ unless you have read linkgit:git-rebase[1] carefully. --no-rebase:: Override earlier --rebase. +Options related to fetching +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + include::fetch-options.txt[] include::pull-fetch-param.txt[] -- cgit v0.10.2-6-g49f6 From 7c85d27429784c181ca633fb1465a9c651dbce8a Mon Sep 17 00:00:00 2001 From: Jari Aalto Date: Thu, 22 Oct 2009 22:25:20 +0300 Subject: Documentation/merge-options.txt: order options in alphabetical groups Signed-off-by: Jari Aalto Signed-off-by: Junio C Hamano diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index adadf8e..48d04a5 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -1,43 +1,42 @@ --q:: ---quiet:: - Operate quietly. - --v:: ---verbose:: - Be verbose. - ---stat:: - Show a diffstat at the end of the merge. The diffstat is also - controlled by the configuration option merge.stat. - --n:: ---no-stat:: - Do not show a diffstat at the end of the merge. +--commit:: +--no-commit:: + Perform the merge and commit the result. This option can + be used to override --no-commit. ++ +With --no-commit perform the merge but pretend the merge +failed and do not autocommit, to give the user a chance to +inspect and further tweak the merge result before committing. ---summary:: ---no-summary:: - Synonyms to --stat and --no-stat; these are deprecated and will be - removed in the future. +--ff:: +--no-ff:: + Do not generate a merge commit if the merge resolved as + a fast-forward, only update the branch pointer. This is + the default behavior of git-merge. ++ +With --no-ff Generate a merge commit even if the merge +resolved as a fast-forward. --log:: +--no-log:: In addition to branch names, populate the log message with one-line descriptions from the actual commits that are being merged. ++ +With --no-log do not list one-line descriptions from the +actual commits being merged. ---no-log:: - Do not list one-line descriptions from the actual commits being - merged. - ---no-commit:: - Perform the merge but pretend the merge failed and do - not autocommit, to give the user a chance to inspect and - further tweak the merge result before committing. ---commit:: - Perform the merge and commit the result. This option can - be used to override --no-commit. +--stat:: +-n:: +--no-stat:: + Show a diffstat at the end of the merge. The diffstat is also + controlled by the configuration option merge.stat. ++ +With -n or --no-stat do not show a diffstat at the end of the +merge. --squash:: +--no-squash:: Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit or @@ -46,19 +45,9 @@ commit. This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus). - ---no-squash:: - Perform the merge and commit the result. This option can - be used to override --squash. - ---no-ff:: - Generate a merge commit even if the merge resolved as a - fast-forward. - ---ff:: - Do not generate a merge commit if the merge resolved as - a fast-forward, only update the branch pointer. This is - the default behavior of git-merge. ++ +With --no-squash perform the merge and commit the result. This +option can be used to override --squash. -s :: --strategy=:: @@ -67,3 +56,16 @@ If there is no `-s` option, a built-in list of strategies is used instead ('git-merge-recursive' when merging a single head, 'git-merge-octopus' otherwise). + +--summary:: +--no-summary:: + Synonyms to --stat and --no-stat; these are deprecated and will be + removed in the future. + +-q:: +--quiet:: + Operate quietly. + +-v:: +--verbose:: + Be verbose. -- cgit v0.10.2-6-g49f6 From 204d363f5a05bba0bdeb13f96a08d5078dcee820 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Thu, 22 Oct 2009 10:19:06 +0200 Subject: Quote ' as \(aq in manpages The docbook/xmlto toolchain insists on quoting ' as \'. This does achieve the quoting goal, but modern 'man' implementations turn the apostrophe into a unicode "proper" apostrophe (given the right circumstances), breaking code examples in many of our manpages. Quote them as \(aq instead, which is an "apostrophe quote" as per the groff_char manpage. Unfortunately, as Anders Kaseorg kindly pointed out, this is not portable beyond groff, so we add an extra Makefile variable GNU_ROFF which you need to enable to get the new quoting. Thanks also to Miklos Vajna for documentation. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano diff --git a/Documentation/Makefile b/Documentation/Makefile index 7a8037f..7614844 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -103,6 +103,14 @@ ifdef DOCBOOK_SUPPRESS_SP XMLTO_EXTRA += -m manpage-suppress-sp.xsl endif +# If your target system uses GNU groff, it may try to render +# apostrophes as a "pretty" apostrophe using unicode. This breaks +# cut&paste, so you should set GNU_ROFF to force them to be ASCII +# apostrophes. Unfortunately does not work with non-GNU roff. +ifdef GNU_ROFF +XMLTO_EXTRA += -m manpage-quote-apos.xsl +endif + SHELL_PATH ?= $(SHELL) # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) diff --git a/Documentation/manpage-quote-apos.xsl b/Documentation/manpage-quote-apos.xsl new file mode 100644 index 0000000..aeb8839 --- /dev/null +++ b/Documentation/manpage-quote-apos.xsl @@ -0,0 +1,16 @@ + + + + + + + + ' + \(aq + + + + diff --git a/Makefile b/Makefile index f88ed3e..7974717 100644 --- a/Makefile +++ b/Makefile @@ -142,6 +142,10 @@ all:: # # Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72. # +# Define GNU_ROFF if your target system uses GNU groff. This forces +# apostrophes to be ASCII so that cut&pasting examples to the shell +# will work. +# # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's # MakeMaker (e.g. using ActiveState under Cygwin). # -- cgit v0.10.2-6-g49f6 From 00d3947366a50a06da40989a1fd1e3f99885a4c3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Oct 2009 23:40:35 -0700 Subject: Teach --wrap to only indent without wrapping When a zero or negative width is given to "shortlog -w,," and --format=%[wrap(w,in1,in2)...%], just indent the text by in1 without wrapping. Signed-off-by: Junio C Hamano diff --git a/utf8.c b/utf8.c index da99669..5c18f0c 100644 --- a/utf8.c +++ b/utf8.c @@ -310,6 +310,19 @@ int strbuf_add_wrapped_text(struct strbuf *buf, int w = indent, assume_utf8 = is_utf8(text); const char *bol = text, *space = NULL; + if (width <= 0) { + /* just indent */ + while (*text) { + const char *eol = strchrnul(text, '\n'); + if (*eol == '\n') + eol++; + print_spaces(buf, indent); + strbuf_write(buf, text, eol-text); + text = eol; + } + return 1; + } + if (indent < 0) { w = -indent; space = text; -- cgit v0.10.2-6-g49f6 From 02edd56b84f00c1a88c5602f5608033d4bc1cbff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 17 Oct 2009 23:04:19 +0200 Subject: Implement wrap format %w() as if it is a mode switch I always considered line wrapping to be more similar to a colour, i.e. a state that one can change and that is applied to all following text until the next state change, except that it's always reset at the end of the format string. Here's a patch to implement this behaviour, using Dscho's strbuf_add_wrapped_text() Signed-off-by: Junio C Hamano diff --git a/pretty.c b/pretty.c index 587101f..91be0ce 100644 --- a/pretty.c +++ b/pretty.c @@ -445,6 +445,7 @@ struct format_commit_context { enum date_mode dmode; unsigned commit_header_parsed:1; unsigned commit_message_parsed:1; + size_t width, indent1, indent2; /* These offsets are relative to the start of the commit message. */ struct chunk author; @@ -458,6 +459,7 @@ struct format_commit_context { struct chunk abbrev_commit_hash; struct chunk abbrev_tree_hash; struct chunk abbrev_parent_hashes; + size_t wrap_start; }; static int add_again(struct strbuf *sb, struct chunk *chunk) @@ -595,6 +597,35 @@ static void format_decoration(struct strbuf *sb, const struct commit *commit) strbuf_addch(sb, ')'); } +static void strbuf_wrap(struct strbuf *sb, size_t pos, + size_t width, size_t indent1, size_t indent2) +{ + struct strbuf tmp = STRBUF_INIT; + + if (pos) + strbuf_add(&tmp, sb->buf, pos); + strbuf_add_wrapped_text(&tmp, sb->buf + pos, + (int) indent1, (int) indent2, (int) width); + strbuf_swap(&tmp, sb); + strbuf_release(&tmp); +} + +static void rewrap_message_tail(struct strbuf *sb, + struct format_commit_context *c, + size_t new_width, size_t new_indent1, + size_t new_indent2) +{ + if (c->width == new_width && c->indent1 == new_indent1 && + c->indent2 == new_indent2) + return; + if (c->wrap_start && c->wrap_start < sb->len) + strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2); + c->wrap_start = sb->len; + c->width = new_width; + c->indent1 = new_indent1; + c->indent2 = new_indent2; +} + static size_t format_commit_item(struct strbuf *sb, const char *placeholder, void *context) { @@ -645,6 +676,30 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, return 3; } else return 0; + case 'w': + if (placeholder[1] == '(') { + unsigned long width = 0, indent1 = 0, indent2 = 0; + char *next; + const char *start = placeholder + 2; + const char *end = strchr(start, ')'); + if (!end) + return 0; + if (end > start) { + width = strtoul(start, &next, 10); + if (*next == ',') { + indent1 = strtoul(next + 1, &next, 10); + if (*next == ',') { + indent2 = strtoul(next + 1, + &next, 10); + } + } + if (*next != ')') + return 0; + } + rewrap_message_tail(sb, c, width, indent1, indent2); + return end - placeholder + 1; + } else + return 0; } /* these depend on the commit */ @@ -748,7 +803,9 @@ void format_commit_message(const struct commit *commit, memset(&context, 0, sizeof(context)); context.commit = commit; context.dmode = dmode; + context.wrap_start = sb->len; strbuf_expand(sb, format, format_commit_item, &context); + rewrap_message_tail(sb, &context, 0, 0, 0); } static void pp_header(enum cmit_fmt fmt, -- cgit v0.10.2-6-g49f6 From 4192e1cd02365c63f1155fc547e4ef2163050c59 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Wed, 21 Oct 2009 19:04:50 +0200 Subject: mingw: enable OpenSSL Since we have OpenSSL in msysgit now, enable it to support SSL encryption for imap-send. Signed-off-by: Erik Faye-Lund Acked-by: Johannes Sixt Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 13980a5..6a197f3 100644 --- a/Makefile +++ b/Makefile @@ -933,7 +933,7 @@ else ifneq (,$(findstring MINGW,$(uname_S))) pathsep = ; NO_PREAD = YesPlease - NO_OPENSSL = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease -- cgit v0.10.2-6-g49f6 From c36e16385be363a11abaa4a43edd5a2b2b2dff81 Mon Sep 17 00:00:00 2001 From: Marius Storm-Olsen Date: Wed, 21 Oct 2009 19:04:51 +0200 Subject: MSVC: Enable OpenSSL, and translate -lcrypto We don't use crypto, but rather require libeay32 and ssleay32. handle it in both the Makefile msvc linker script, and the buildsystem generator. Signed-off-by: Marius Storm-Olsen Signed-off-by: Erik Faye-Lund Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 6a197f3..5403fad 100644 --- a/Makefile +++ b/Makefile @@ -881,7 +881,7 @@ ifdef MSVC GIT_VERSION := $(GIT_VERSION).MSVC pathsep = ; NO_PREAD = YesPlease - NO_OPENSSL = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl index 0ffd59f..fce1e24 100644 --- a/compat/vcbuild/scripts/clink.pl +++ b/compat/vcbuild/scripts/clink.pl @@ -29,6 +29,9 @@ while (@ARGV) { push(@args, "zlib.lib"); } elsif ("$arg" eq "-liconv") { push(@args, "iconv.lib"); + } elsif ("$arg" eq "-lcrypto") { + push(@args, "libeay32.lib"); + push(@args, "ssleay32.lib"); } elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") { $arg =~ s/^-L/-LIBPATH:/; push(@args, $arg); diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl index 20bd061..d506717 100644 --- a/contrib/buildsystems/engine.pl +++ b/contrib/buildsystems/engine.pl @@ -315,6 +315,9 @@ sub handleLinkLine $appout = shift @parts; } elsif ("$part" eq "-lz") { push(@libs, "zlib.lib"); + } elsif ("$part" eq "-lcrypto") { + push(@libs, "libeay32.lib"); + push(@libs, "ssleay32.lib"); } elsif ($part =~ /^-/) { push(@lflags, $part); } elsif ($part =~ /\.(a|lib)$/) { -- cgit v0.10.2-6-g49f6 From 9bccfcdbff3b11e11a69d05c50c8bc1a9a6dbb5f Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Thu, 22 Oct 2009 20:26:29 +0200 Subject: Windows: use BLK_SHA1 again Since NO_OPENSSL is no longer defined on Windows, BLK_SHA1 is not defined anymore implicitly. Define it explicitly. As a nice side-effect, we no longer link against libcrypto.dll, which has non-trivial startup costs because it depends on 6 otherwise unneeded DLLs. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 5403fad..f666d2f 100644 --- a/Makefile +++ b/Makefile @@ -911,6 +911,7 @@ ifdef MSVC NO_REGEX = YesPlease NO_CURL = YesPlease NO_PTHREADS = YesPlease + BLK_SHA1 = YesPlease CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl @@ -960,6 +961,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) UNRELIABLE_FSTAT = UnfortunatelyYes OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease + BLK_SHA1 = YesPlease COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o -- cgit v0.10.2-6-g49f6 From 024ab976fffdc1d78b6d0b15e261b52f3a5d594b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Oct 2009 11:42:39 -0700 Subject: Do not fail "describe --always" in a tag-less repository This fixes a regression introduce by d68dc34 (git-describe: Die early if there are no possible descriptions, 2009-08-06). Signed-off-by: Junio C Hamano diff --git a/builtin-describe.c b/builtin-describe.c index df67a73..7542b57 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -197,7 +197,7 @@ static void describe(const char *arg, int last_one) for_each_ref(get_name, NULL); } - if (!found_names) + if (!found_names && !always) die("cannot describe '%s'", sha1_to_hex(sha1)); n = cmit->util; diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 8c7e081..f5a1b61 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -34,6 +34,8 @@ test_expect_success setup ' echo one >file && git add file && git commit -m initial && one=$(git rev-parse HEAD) && + git describe --always HEAD && + test_tick && echo two >file && git add file && git commit -m second && two=$(git rev-parse HEAD) && -- cgit v0.10.2-6-g49f6 From 86140d56c150aa17e6c98dde4ba669516f3c302a Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Fri, 23 Oct 2009 13:25:33 +0200 Subject: add tests for git diff --submodule Copied from the submodule summary test and changed to reflect the differences in the output of git diff --submodule. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano diff --git a/t/t4041-diff-submodule.sh b/t/t4041-diff-submodule.sh new file mode 100755 index 0000000..5bb4fed --- /dev/null +++ b/t/t4041-diff-submodule.sh @@ -0,0 +1,260 @@ +#!/bin/sh +# +# Copyright (c) 2009 Jens Lehmann, based on t7401 by Ping Yin +# + +test_description='Support for verbose submodule differences in git diff + +This test tries to verify the sanity of the --submodule option of git diff. +' + +. ./test-lib.sh + +add_file () { + sm=$1 + shift + owd=$(pwd) + cd "$sm" + for name; do + echo "$name" > "$name" && + git add "$name" && + test_tick && + git commit -m "Add $name" + done >/dev/null + git rev-parse --verify HEAD | cut -c1-7 + cd "$owd" +} +commit_file () { + test_tick && + git commit "$@" -m "Commit $*" >/dev/null +} + +test_create_repo sm1 && +add_file . foo >/dev/null + +head1=$(add_file sm1 foo1 foo2) + +test_expect_success 'added submodule' " + git add sm1 && + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 0000000...$head1 (new submodule) +EOF +" + +commit_file sm1 && +head2=$(add_file sm1 foo3) + +test_expect_success 'modified submodule(forward)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head1..$head2: + > Add foo3 +EOF +" + +test_expect_success 'modified submodule(forward)' " + git diff --submodule=log >actual && + diff actual - <<-EOF +Submodule sm1 $head1..$head2: + > Add foo3 +EOF +" + +test_expect_success 'modified submodule(forward) --submodule' " + git diff --submodule >actual && + diff actual - <<-EOF +Submodule sm1 $head1..$head2: + > Add foo3 +EOF +" + +fullhead1=$(cd sm1; git rev-list --max-count=1 $head1) +fullhead2=$(cd sm1; git rev-list --max-count=1 $head2) +test_expect_success 'modified submodule(forward) --submodule=short' " + git diff --submodule=short >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +index $head1..$head2 160000 +--- a/sm1 ++++ b/sm1 +@@ -1 +1 @@ +-Subproject commit $fullhead1 ++Subproject commit $fullhead2 +EOF +" + +commit_file sm1 && +cd sm1 && +git reset --hard HEAD~2 >/dev/null && +head3=$(git rev-parse --verify HEAD | cut -c1-7) && +cd .. + +test_expect_success 'modified submodule(backward)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head2..$head3 (rewind): + < Add foo3 + < Add foo2 +EOF +" + +head4=$(add_file sm1 foo4 foo5) && +head4_full=$(GIT_DIR=sm1/.git git rev-parse --verify HEAD) +test_expect_success 'modified submodule(backward and forward)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head2...$head4: + > Add foo5 + > Add foo4 + < Add foo3 + < Add foo2 +EOF +" + +commit_file sm1 && +mv sm1 sm1-bak && +echo sm1 >sm1 && +head5=$(git hash-object sm1 | cut -c1-7) && +git add sm1 && +rm -f sm1 && +mv sm1-bak sm1 + +test_expect_success 'typechanged submodule(submodule->blob), --cached' " + git diff --submodule=log --cached >actual && + diff actual - <<-EOF +Submodule sm1 41fbea9...0000000 (submodule deleted) +diff --git a/sm1 b/sm1 +new file mode 100644 +index 0000000..9da5fb8 +--- /dev/null ++++ b/sm1 +@@ -0,0 +1 @@ ++sm1 +EOF +" + +test_expect_success 'typechanged submodule(submodule->blob)' " + git diff --submodule=log >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +deleted file mode 100644 +index 9da5fb8..0000000 +--- a/sm1 ++++ /dev/null +@@ -1 +0,0 @@ +-sm1 +Submodule sm1 0000000...$head4 (new submodule) +EOF +" + +rm -rf sm1 && +git checkout-index sm1 +test_expect_success 'typechanged submodule(submodule->blob)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head4...0000000 (submodule deleted) +diff --git a/sm1 b/sm1 +new file mode 100644 +index 0000000..$head5 +--- /dev/null ++++ b/sm1 +@@ -0,0 +1 @@ ++sm1 +EOF +" + +rm -f sm1 && +test_create_repo sm1 && +head6=$(add_file sm1 foo6 foo7) +fullhead6=$(cd sm1; git rev-list --max-count=1 $head6) +test_expect_success 'nonexistent commit' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head4...$head6 (commits not present) +EOF +" + +commit_file +test_expect_success 'typechanged submodule(blob->submodule)' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +deleted file mode 100644 +index $head5..0000000 +--- a/sm1 ++++ /dev/null +@@ -1 +0,0 @@ +-sm1 +Submodule sm1 0000000...$head6 (new submodule) +EOF +" + +commit_file sm1 && +rm -rf sm1 +test_expect_success 'deleted submodule' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +EOF +" + +test_create_repo sm2 && +head7=$(add_file sm2 foo8 foo9) && +git add sm2 + +test_expect_success 'multiple submodules' " + git diff-index -p --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +test_expect_success 'path filter' " + git diff-index -p --submodule=log HEAD sm2 >actual && + diff actual - <<-EOF +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +commit_file sm2 +test_expect_success 'given commit' " + git diff-index -p --submodule=log HEAD^ >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +test_expect_success 'given commit --submodule' " + git diff-index -p --submodule HEAD^ >actual && + diff actual - <<-EOF +Submodule sm1 $head6...0000000 (submodule deleted) +Submodule sm2 0000000...$head7 (new submodule) +EOF +" + +fullhead7=$(cd sm2; git rev-list --max-count=1 $head7) + +test_expect_success 'given commit --submodule=short' " + git diff-index -p --submodule=short HEAD^ >actual && + diff actual - <<-EOF +diff --git a/sm1 b/sm1 +deleted file mode 160000 +index $head6..0000000 +--- a/sm1 ++++ /dev/null +@@ -1 +0,0 @@ +-Subproject commit $fullhead6 +diff --git a/sm2 b/sm2 +new file mode 160000 +index 0000000..$head7 +--- /dev/null ++++ b/sm2 +@@ -0,0 +1 @@ ++Subproject commit $fullhead7 +EOF +" + +test_done -- cgit v0.10.2-6-g49f6 From 2cf6b4bfec068cf24bdc3e4d46ad233091471a02 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Oct 2009 22:38:44 -0700 Subject: Fix list of released versions in the toc document diff --git a/Documentation/git.txt b/Documentation/git.txt index d11c5c1..8519928 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,7 +43,7 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.5/git.html[documentation for release 1.6.5] +* link:v1.6.5.1/git.html[documentation for release 1.6.5.1] * release notes for link:RelNotes-1.6.5.1.txt[1.6.5.1], -- cgit v0.10.2-6-g49f6 From 36e561064e83d3f890f8fa128b3ca4e0d7691f10 Mon Sep 17 00:00:00 2001 From: Andreas Schwab Date: Sat, 24 Oct 2009 15:06:57 +0200 Subject: Work around option parsing bug in the busybox tar implementation The first argument of the tar command is interpreted as a bundle of letters specifying the mode of operation and additional options, with any option arguments taken from subsequent words on the command line as needed. The implementation of tar in busybox treats this bundle as if preceded by a dash and then parses it by getopt rules, which mishandles 'tar xfo -'. Use 'tar xof -' instead to work this around. Signed-off-by: Andreas Schwab Signed-off-by: Junio C Hamano diff --git a/templates/Makefile b/templates/Makefile index a12c6e2..408f013 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -50,4 +50,4 @@ clean: install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(template_instdir_SQ)' (cd blt && $(TAR) cf - .) | \ - (cd '$(DESTDIR_SQ)$(template_instdir_SQ)' && umask 022 && $(TAR) xfo -) + (cd '$(DESTDIR_SQ)$(template_instdir_SQ)' && umask 022 && $(TAR) xof -) -- cgit v0.10.2-6-g49f6 From 3319df6f3aa3271124847e2c91c7e6d0be961bc6 Mon Sep 17 00:00:00 2001 From: Markus Heidelberg Date: Sun, 25 Oct 2009 01:39:19 +0200 Subject: t7800-difftool: fix the effectless GIT_DIFFTOOL_PROMPT test GIT_DIFFTOOL_PROMPT doesn't have any effect if overridden with --prompt. Signed-off-by: Markus Heidelberg Signed-off-by: Junio C Hamano diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index ebdccf9..fff6a6d 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -136,7 +136,7 @@ test_expect_success 'GIT_DIFFTOOL_PROMPT variable' ' GIT_DIFFTOOL_PROMPT=true && export GIT_DIFFTOOL_PROMPT && - prompt=$(echo | git difftool --prompt branch | tail -1) && + prompt=$(echo | git difftool branch | tail -1) && prompt_given "$prompt" && restore_test_defaults -- cgit v0.10.2-6-g49f6 From a75d7b54097ef0d0945cbe673a9940d6c561f95c Mon Sep 17 00:00:00 2001 From: Felipe Contreras Date: Sat, 24 Oct 2009 11:31:32 +0300 Subject: Use 'fast-forward' all over the place It's a compound word. Signed-off-by: Felipe Contreras Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index cd17814..52bbafb 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1351,7 +1351,7 @@ receive.denyCurrentBranch:: receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is - not a fast forward. Use this to prevent such an update via a push, + not a fast-forward. Use this to prevent such an update via a push, even if that push is forced. This configuration variable is set when initializing a shared repository. diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt index aef383e..ddf7a18 100644 --- a/Documentation/git-http-push.txt +++ b/Documentation/git-http-push.txt @@ -82,11 +82,11 @@ destination side. Without '--force', the ref is stored at the remote only if does not exist, or is a proper subset (i.e. an -ancestor) of . This check, known as "fast forward check", +ancestor) of . This check, known as "fast-forward check", is performed in order to avoid accidentally overwriting the remote ref and lose other peoples' commits from there. -With '--force', the fast forward check is disabled for all refs. +With '--force', the fast-forward check is disabled for all refs. Optionally, a parameter can be prefixed with a plus '+' sign to disable the fast-forward check only on that ref. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index ba6a8a2..1a9d886 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -50,9 +50,9 @@ updated. + The object referenced by is used to update the reference on the remote side, but by default this is only allowed if the -update can fast forward . By having the optional leading `{plus}`, +update can fast-forward . By having the optional leading `{plus}`, you can tell git to update the ref even when the update is not a -fast forward. This does *not* attempt to merge into . See +fast-forward. This does *not* attempt to merge into . See EXAMPLES below for details. + `tag ` means the same as `refs/tags/:refs/tags/`. @@ -60,7 +60,7 @@ EXAMPLES below for details. Pushing an empty allows you to delete the ref from the remote repository. + -The special refspec `:` (or `{plus}:` to allow non-fast forward updates) +The special refspec `:` (or `{plus}:` to allow non-fast-forward updates) directs git to push "matching" branches: for every branch that exists on the local side, the remote side is updated if a branch of the same name already exists on the remote side. This is the default operation mode @@ -171,10 +171,10 @@ summary:: For a successfully pushed ref, the summary shows the old and new values of the ref in a form suitable for using as an argument to `git log` (this is `..` in most cases, and - `...` for forced non-fast forward updates). For a + `...` for forced non-fast-forward updates). For a failed update, more details are given for the failure. The string `rejected` indicates that git did not try to send the - ref at all (typically because it is not a fast forward). The + ref at all (typically because it is not a fast-forward). The string `remote rejected` indicates that the remote end refused the update; this rejection is typically caused by a hook on the remote side. The string `remote failure` indicates that the @@ -342,9 +342,9 @@ git push origin :experimental:: git push origin {plus}dev:master:: Update the origin repository's master branch with the dev branch, - allowing non-fast forward updates. *This can leave unreferenced + allowing non-fast-forward updates. *This can leave unreferenced commits dangling in the origin repository.* Consider the - following situation, where a fast forward is not possible: + following situation, where a fast-forward is not possible: + ---- o---o---o---A---B origin/master diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 4a932b0..a10ce4b 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -144,7 +144,7 @@ Two Tree Merge Typically, this is invoked as `git read-tree -m $H $M`, where $H is the head commit of the current repository, and $M is the head of a foreign tree, which is simply ahead of $H (i.e. we are in a -fast forward situation). +fast-forward situation). When two trees are specified, the user is telling 'git-read-tree' the following: diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index 514f03c..cb5f405 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -20,7 +20,7 @@ The UI for the protocol is on the 'git-send-pack' side, and the program pair is meant to be used to push updates to remote repository. For pull operations, see linkgit:git-fetch-pack[1]. -The command allows for creation and fast forwarding of sha1 refs +The command allows for creation and fast-forwarding of sha1 refs (heads/tags) on the remote end (strictly speaking, it is the local end 'git-receive-pack' runs, but to the user who is sitting at the send-pack end, it is updating the remote. Confused?) diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 469cf6d..2d27e40 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -150,7 +150,7 @@ Automatic merge failed; fix conflicts and then commit the result. $ git reset --hard <2> $ git pull . topic/branch <3> Updating from 41223... to 13134... -Fast forward +Fast-forward $ git reset --hard ORIG_HEAD <4> ------------ + @@ -161,7 +161,7 @@ right now, so you decide to do that later. which is a synonym for "git reset --hard HEAD" clears the mess from the index file and the working tree. <3> Merge a topic branch into the current branch, which resulted -in a fast forward. +in a fast-forward. <4> But you decided that the topic branch is not ready for public consumption yet. "pull" or "merge" always leaves the original tip of the current branch in ORIG_HEAD, so resetting hard to it diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 3998218..5a04c6e 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -105,11 +105,11 @@ name. See linkgit:git-rev-parse[1]. Without '--force', the ref is stored at the remote only if does not exist, or is a proper subset (i.e. an -ancestor) of . This check, known as "fast forward check", +ancestor) of . This check, known as "fast-forward check", is performed in order to avoid accidentally overwriting the remote ref and lose other peoples' commits from there. -With '--force', the fast forward check is disabled for all refs. +With '--force', the fast-forward check is disabled for all refs. Optionally, a parameter can be prefixed with a plus '+' sign to disable the fast-forward check only on that ref. diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index b3640c4..253ef62 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -993,7 +993,7 @@ would be different) ---------------- Updating from ae3a2da... to a80b4aa.... -Fast forward (no commit created; -m option ignored) +Fast-forward (no commit created; -m option ignored) example | 1 + hello | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) @@ -1003,7 +1003,7 @@ Because your branch did not contain anything more than what had already been merged into the `master` branch, the merge operation did not actually do a merge. Instead, it just updated the top of the tree of your branch to that of the `master` branch. This is -often called 'fast forward' merge. +often called 'fast-forward' merge. You can run `gitk \--all` again to see how the commit ancestry looks like, or run 'show-branch', which tells you this. diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 06e0f31..4cc3d13 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -229,7 +229,7 @@ from updating that ref. This hook can be used to prevent 'forced' update on certain refs by making sure that the object name is a commit object that is a descendant of the commit object named by the old object name. -That is, to enforce a "fast forward only" policy. +That is, to enforce a "fast-forward only" policy. It could also be used to log the old..new status. However, it does not know the entire set of branches, so it would end up diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 43d84d1..1f029f8 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -124,7 +124,7 @@ to point at the new commit. An evil merge is a <> that introduces changes that do not appear in any <>. -[[def_fast_forward]]fast forward:: +[[def_fast_forward]]fast-forward:: A fast-forward is a special type of <> where you have a <> and you are "merging" another <>'s changes that happen to be a descendant of what @@ -220,7 +220,7 @@ to point at the new commit. conflict, manual intervention may be required to complete the merge. + -As a noun: unless it is a <>, a +As a noun: unless it is a <>, a successful merge results in the creation of a new <> representing the result of the merge, and having as <> the tips of the merged <>. diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt index 4357e26..d527b30 100644 --- a/Documentation/howto/maintain-git.txt +++ b/Documentation/howto/maintain-git.txt @@ -59,7 +59,7 @@ The policy. not yet pass the criteria set for 'next'. - The tips of 'master', 'maint' and 'next' branches will always - fast forward, to allow people to build their own + fast-forward, to allow people to build their own customization on top of them. - Usually 'master' contains all of 'maint', 'next' contains all diff --git a/Documentation/howto/revert-branch-rebase.txt b/Documentation/howto/revert-branch-rebase.txt index e70d8a3..8c32da6 100644 --- a/Documentation/howto/revert-branch-rebase.txt +++ b/Documentation/howto/revert-branch-rebase.txt @@ -85,7 +85,7 @@ Fortunately I did not have to; what I have in the current branch ------------------------------------------------ $ git checkout master -$ git merge revert-c99 ;# this should be a fast forward +$ git merge revert-c99 ;# this should be a fast-forward Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c... cache.h | 8 ++++---- commit.c | 2 +- @@ -95,7 +95,7 @@ Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c... 5 files changed, 8 insertions(+), 8 deletions(-) ------------------------------------------------ -There is no need to redo the test at this point. We fast forwarded +There is no need to redo the test at this point. We fast-forwarded and we know 'master' matches 'revert-c99' exactly. In fact: ------------------------------------------------ diff --git a/Documentation/howto/update-hook-example.txt b/Documentation/howto/update-hook-example.txt index 697d918..b7f8d41 100644 --- a/Documentation/howto/update-hook-example.txt +++ b/Documentation/howto/update-hook-example.txt @@ -76,7 +76,7 @@ case "$1" in if expr "$2" : '0*$' >/dev/null; then info "The branch '$1' is new..." else - # updating -- make sure it is a fast forward + # updating -- make sure it is a fast-forward mb=$(git-merge-base "$2" "$3") case "$mb,$2" in "$2,$mb") info "Update is fast-forward" ;; diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt index f9811f2..44d9363 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -11,9 +11,9 @@ + The remote ref that matches is fetched, and if is not empty string, the local -ref that matches it is fast forwarded using . +ref that matches it is fast-forwarded using . If the optional plus `+` is used, the local ref -is updated even if it does not result in a fast forward +is updated even if it does not result in a fast-forward update. + [NOTE] diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 67ebffa..269ec47 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1384,7 +1384,7 @@ were merged. However, if the current branch is a descendant of the other--so every commit present in the one is already contained in the other--then git -just performs a "fast forward"; the head of the current branch is moved +just performs a "fast-forward"; the head of the current branch is moved forward to point at the head of the merged-in branch, without any new commits being created. @@ -1719,7 +1719,7 @@ producing a default commit message documenting the branch and repository that you pulled from. (But note that no such commit will be created in the case of a -<>; instead, your branch will just be +<>; instead, your branch will just be updated to point to the latest commit from the upstream branch.) The `git pull` command can also be given "." as the "remote" repository, @@ -1943,7 +1943,7 @@ $ git push ssh://yourserver.com/~you/proj.git master ------------------------------------------------- As with `git fetch`, `git push` will complain if this does not result in a -<>; see the following section for details on +<>; see the following section for details on handling this case. Note that the target of a "push" is normally a @@ -1976,7 +1976,7 @@ details. What to do when a push fails ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If a push would not result in a <> of the +If a push would not result in a <> of the remote branch, then it will fail with an error like: ------------------------------------------------- @@ -2115,7 +2115,7 @@ $ git checkout release && git pull Important note! If you have any local changes in these branches, then this merge will create a commit object in the history (with no local -changes git will simply do a "Fast forward" merge). Many people dislike +changes git will simply do a "fast-forward" merge). Many people dislike the "noise" that this creates in the Linux history, so you should avoid doing this capriciously in the "release" branch, as these noisy commits will become part of the permanent history when you ask Linus to pull @@ -2729,9 +2729,9 @@ In the previous example, when updating an existing branch, "git fetch" checks to make sure that the most recent commit on the remote branch is a descendant of the most recent commit on your copy of the branch before updating your copy of the branch to point at the new -commit. Git calls this process a <>. +commit. Git calls this process a <>. -A fast forward looks something like this: +A fast-forward looks something like this: ................................................ o--o--o--o <-- old head of the branch diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 3dbdf7a..cd10dbc 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -97,21 +97,21 @@ static int update_local_ref(const char *name, strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); if (in_merge_bases(current, &updated, 1)) { - fprintf(stderr, "* %s: fast forward to %s\n", + fprintf(stderr, "* %s: fast-forward to %s\n", name, note); fprintf(stderr, " old..new: %s..%s\n", oldh, newh); - return update_ref_env("fast forward", name, sha1_new, sha1_old); + return update_ref_env("fast-forward", name, sha1_new, sha1_old); } if (!force) { fprintf(stderr, - "* %s: not updating to non-fast forward %s\n", + "* %s: not updating to non-fast-forward %s\n", name, note); fprintf(stderr, " old...new: %s...%s\n", oldh, newh); return 1; } fprintf(stderr, - "* %s: forcing update to non-fast forward %s\n", + "* %s: forcing update to non-fast-forward %s\n", name, note); fprintf(stderr, " old...new: %s...%s\n", oldh, newh); return update_ref_env("forced-update", name, sha1_new, sha1_old); diff --git a/builtin-fetch.c b/builtin-fetch.c index cb48c57..6303aa0 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -269,7 +269,7 @@ static int update_local_ref(struct ref *ref, strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); strcat(quickref, ".."); strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); - r = s_update_ref("fast forward", ref, 1); + r = s_update_ref("fast-forward", ref, 1); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref, r ? " (unable to update local ref)" : ""); @@ -287,7 +287,7 @@ static int update_local_ref(struct ref *ref, r ? "unable to update local ref" : "forced update"); return r; } else { - sprintf(display, "! %-*s %-*s -> %s (non fast forward)", + sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)", SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, pretty_ref); return 1; diff --git a/builtin-merge.c b/builtin-merge.c index b6b8428..a595b8b 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -166,7 +166,7 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "commit", &option_commit, "perform a commit if the merge succeeds (default)"), OPT_BOOLEAN(0, "ff", &allow_fast_forward, - "allow fast forward (default)"), + "allow fast-forward (default)"), OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", "merge strategy to use", option_parse_strategy), OPT_CALLBACK('m', "message", &merge_msg, "message", @@ -1013,7 +1013,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) hex, find_unique_abbrev(remoteheads->item->object.sha1, DEFAULT_ABBREV)); - strbuf_addstr(&msg, "Fast forward"); + strbuf_addstr(&msg, "Fast-forward"); if (have_message) strbuf_addstr(&msg, " (no commit created; -m option ignored)"); @@ -1031,12 +1031,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } else if (!remoteheads->next && common->next) ; /* - * We are not doing octopus and not fast forward. Need + * We are not doing octopus and not fast-forward. Need * a real merge. */ else if (!remoteheads->next && !common->next && option_commit) { /* - * We are not doing octopus, not fast forward, and have + * We are not doing octopus, not fast-forward, and have * only one common. */ refresh_cache(REFRESH_QUIET); diff --git a/builtin-push.c b/builtin-push.c index 3cb1ee4..e883574 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -159,7 +159,7 @@ static int do_push(const char *repo, int flags) error("failed to push some refs to '%s'", url[i]); if (nonfastforward && advice_push_nonfastforward) { printf("To prevent you from losing history, non-fast-forward updates were rejected\n" - "Merge the remote changes before pushing again. See the 'non-fast forward'\n" + "Merge the remote changes before pushing again. See the 'non-fast-forward'\n" "section of 'git push --help' for details.\n"); } errs++; diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index b771fe9..fea8fcd 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -329,9 +329,9 @@ static const char *update(struct command *cmd) break; free_commit_list(bases); if (!ent) { - error("denying non-fast forward %s" + error("denying non-fast-forward %s" " (you should pull first)", name); - return "non-fast forward"; + return "non-fast-forward"; } } if (run_update_hook(cmd)) { diff --git a/builtin-remote.c b/builtin-remote.c index 0777dd7..9aafc19 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -953,7 +953,7 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data) status = "up to date"; break; case PUSH_STATUS_FASTFORWARD: - status = "fast forwardable"; + status = "fast-forwardable"; break; case PUSH_STATUS_OUTOFDATE: status = "local out of date"; diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 37e528e..37acad5 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -246,7 +246,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count) break; case REF_STATUS_REJECT_NONFASTFORWARD: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast forward"); + "non-fast-forward"); break; case REF_STATUS_REMOTE_REJECT: print_ref_status('!', "[remote rejected]", ref, diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh index e9588ee..500635f 100755 --- a/contrib/examples/git-merge.sh +++ b/contrib/examples/git-merge.sh @@ -14,7 +14,7 @@ summary (synonym to --stat) log add list of one-line log to merge commit message squash create a single commit instead of doing a merge commit perform a commit if the merge succeeds (default) -ff allow fast forward (default) +ff allow fast-forward (default) s,strategy= merge strategy to use m,message= message to be used for the merge commit (if any) " @@ -353,7 +353,7 @@ t,1,"$head",*) # Again the most common case of merging one remote. echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)" git update-index --refresh 2>/dev/null - msg="Fast forward" + msg="Fast-forward" if test -n "$have_message" then msg="$msg (no commit created; -m option ignored)" @@ -365,11 +365,11 @@ t,1,"$head",*) exit 0 ;; ?,1,?*"$LF"?*,*) - # We are not doing octopus and not fast forward. Need a + # We are not doing octopus and not fast-forward. Need a # real merge. ;; ?,1,*,) - # We are not doing octopus, not fast forward, and have only + # We are not doing octopus, not fast-forward, and have only # one common. git update-index --refresh 2>/dev/null case "$allow_trivial_merge" in diff --git a/contrib/examples/git-resolve.sh b/contrib/examples/git-resolve.sh index 0ee1bd8..8f98142 100755 --- a/contrib/examples/git-resolve.sh +++ b/contrib/examples/git-resolve.sh @@ -48,7 +48,7 @@ case "$common" in "$head") echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $merge)" git read-tree -u -m $head $merge || exit 1 - git update-ref -m "resolve $merge_name: Fast forward" \ + git update-ref -m "resolve $merge_name: Fast-forward" \ HEAD "$merge" "$head" git diff-tree -p $head $merge | git apply --stat dropheads diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 2a66063..58a35c8 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -315,8 +315,8 @@ generate_update_branch_email() # "remotes/" will be ignored as well. # List all of the revisions that were removed by this update, in a - # fast forward update, this list will be empty, because rev-list O - # ^N is empty. For a non fast forward, O ^N is the list of removed + # fast-forward update, this list will be empty, because rev-list O + # ^N is empty. For a non-fast-forward, O ^N is the list of removed # revisions fast_forward="" rev="" @@ -411,7 +411,7 @@ generate_update_branch_email() # revision because the base is effectively a random revision at this # point - the user will be interested in what this revision changed # - including the undoing of previous revisions in the case of - # non-fast forward updates. + # non-fast-forward updates. echo "" echo "Summary of changes:" git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh index 1dadbb4..825c52c 100755 --- a/git-merge-octopus.sh +++ b/git-merge-octopus.sh @@ -81,7 +81,7 @@ do # tree as the intermediate result of the merge. # We still need to count this as part of the parent set. - echo "Fast forwarding to: $SHA1" + echo "Fast-forwarding to: $SHA1" git read-tree -u -m $head $SHA1 || exit MRC=$SHA1 MRT=$(git write-tree) continue diff --git a/git-pull.sh b/git-pull.sh index fc78592..f36eb3e 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -171,7 +171,7 @@ then # First update the working tree to match $curr_head. echo >&2 "Warning: fetch updated the current branch head." - echo >&2 "Warning: fast forwarding your working tree from" + echo >&2 "Warning: fast-forwarding your working tree from" echo >&2 "Warning: commit $orig_head." git update-index -q --refresh git read-tree -u -m "$orig_head" "$curr_head" || diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 23ded48..c898788 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -168,7 +168,7 @@ pick_one () { output git reset --hard $sha1 test "a$1" = a-n && output git reset --soft $current_sha1 sha1=$(git rev-parse --short $sha1) - output warn Fast forward to $sha1 + output warn Fast-forward to $sha1 else output git cherry-pick "$@" fi @@ -248,9 +248,9 @@ pick_one_preserving_merges () { done case $fast_forward in t) - output warn "Fast forward to $sha1" + output warn "Fast-forward to $sha1" output git reset --hard $sha1 || - die "Cannot fast forward to $sha1" + die "Cannot fast-forward to $sha1" ;; f) first_parent=$(expr "$new_parents" : ' \([^ ]*\)') diff --git a/git-rebase.sh b/git-rebase.sh index 6ec155c..6830e16 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -496,7 +496,7 @@ then fi # If the $onto is a proper descendant of the tip of the branch, then -# we just fast forwarded. +# we just fast-forwarded. if test "$mb" = "$branch" then say "Fast-forwarded $branch_name to $onto_name." diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 271bc4e..c2d408b 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -5,7 +5,7 @@ test_description='Two way merge with read-tree -m $H $M -This test tries two-way merge (aka fast forward with carry forward). +This test tries two-way merge (aka fast-forward with carry forward). There is the head (called H) and another commit (called M), which is simply ahead of H. The index and the work tree contains a state that @@ -51,7 +51,7 @@ check_cache_at () { } cat >bozbar-old <<\EOF -This is a sample file used in two-way fast forward merge +This is a sample file used in two-way fast-forward merge tests. Its second line ends with a magic word bozbar which will be modified by the merged head to gnusto. It has some extra lines so that external tools can @@ -300,7 +300,7 @@ test_expect_success \ echo gnusto gnusto >bozbar && if read_tree_twoway $treeH $treeM; then false; else :; fi' -# This fails with straight two-way fast forward. +# This fails with straight two-way fast-forward. test_expect_success \ '22 - local change cache updated.' \ 'rm -f .git/index && diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 852ccb5..220b6a3 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -158,7 +158,7 @@ cat > test/expect << EOF another master Local refs configured for 'git push': - ahead forces to master (fast forwardable) + ahead forces to master (fast-forwardable) master pushes to another (up to date) EOF diff --git a/t/t5518-fetch-exit-status.sh b/t/t5518-fetch-exit-status.sh index c6bc65f..c2060bb 100755 --- a/t/t5518-fetch-exit-status.sh +++ b/t/t5518-fetch-exit-status.sh @@ -22,7 +22,7 @@ test_expect_success setup ' git commit -a -m next ' -test_expect_success 'non fast forward fetch' ' +test_expect_success 'non-fast-forward fetch' ' test_must_fail git fetch . master:side diff --git a/t/t6028-merge-up-to-date.sh b/t/t6028-merge-up-to-date.sh index f8f3e3f..a91644e 100755 --- a/t/t6028-merge-up-to-date.sh +++ b/t/t6028-merge-up-to-date.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='merge fast forward and up to date' +test_description='merge fast-forward and up to date' . ./test-lib.sh diff --git a/transport.c b/transport.c index 644a30a..d81a42a 100644 --- a/transport.c +++ b/transport.c @@ -668,7 +668,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i break; case REF_STATUS_REJECT_NONFASTFORWARD: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast forward", porcelain); + "non-fast-forward", porcelain); break; case REF_STATUS_REMOTE_REJECT: print_ref_status('!', "[remote rejected]", ref, diff --git a/unpack-trees.c b/unpack-trees.c index 720f7a1..157d5d0 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -895,7 +895,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o) * Two-way merge. * * The rule is to "carry forward" what is in the index without losing - * information across a "fast forward", favoring a successful merge + * information across a "fast-forward", favoring a successful merge * over a merge failure when it makes sense. For details of the * "carry forward" rule, please see . * -- cgit v0.10.2-6-g49f6 From 6c0efa2ac07f275c1edd11ae6646a647d7f19f36 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Oct 2009 18:37:56 -0700 Subject: GIT 1.6.5.2 Signed-off-by: Junio C Hamano diff --git a/Documentation/RelNotes-1.6.5.2.txt b/Documentation/RelNotes-1.6.5.2.txt new file mode 100644 index 0000000..aa7ccce --- /dev/null +++ b/Documentation/RelNotes-1.6.5.2.txt @@ -0,0 +1,19 @@ +GIT v1.6.5.2 Release Notes +========================== + +Fixes since v1.6.5.1 +-------------------- + + * Installation of templates triggered a bug in busybox when using tar + implementation from it. + + * "git add -i" incorrectly ignored paths that are already in the index + if they matched .gitignore patterns. + + * "git describe --always" should have produced some output even there + were no tags in the repository, but it didn't. + + * "git ls-files" when showing tracked files incorrectly paid attention + to the exclude patterns. + +Other minor documentation updates are included. diff --git a/Documentation/git.txt b/Documentation/git.txt index 8519928..e3fe970 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.5.1/git.html[documentation for release 1.6.5.1] +* link:v1.6.5.2/git.html[documentation for release 1.6.5.2] * release notes for + link:RelNotes-1.6.5.2.txt[1.6.5.2], link:RelNotes-1.6.5.1.txt[1.6.5.1], link:RelNotes-1.6.5.txt[1.6.5]. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index d11e43c..09cb354 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.5.1 +DEF_VER=v1.6.5.2 LF=' ' diff --git a/RelNotes b/RelNotes index dda06d1..1cb2571 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.6.5.1.txt \ No newline at end of file +Documentation/RelNotes-1.6.5.2.txt \ No newline at end of file -- cgit v0.10.2-6-g49f6 From 610f99ec7d22b5750f12350f67c1341d6be8030f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Oct 2009 18:41:09 -0700 Subject: Update draft release notes to 1.6.6 Signed-off-by: Junio C Hamano diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes-1.6.6.txt index fa0e11a..abf34e6 100644 --- a/Documentation/RelNotes-1.6.6.txt +++ b/Documentation/RelNotes-1.6.6.txt @@ -46,11 +46,28 @@ Updates since v1.6.5 (usability, bells and whistles) + * The object replace mechanism can be bypassed with --no-replace-objects + global option given to the "git" program. + * "git fsck" by default checks the packfiles (i.e. "--full" is the default); you can turn it off with "git fsck --no-full". + * import-tars contributed fast-import frontend learned more types of + compressed tarballs. + + * "git instaweb" knows how to talk with mod_cgid to apache2. + * "git log --decorate" shows the location of HEAD as well. + * "git rebase -i" learned "reword" that acts like "edit" but immediately + starts an editor to tweak the log message without returning control to + the shell, which is done by "edit" to give an opportunity to tweak the + contents. + + * Author names shown in gitweb output are links to search commits by the + author. + + (developers) Fixes since v1.6.5 @@ -69,3 +86,23 @@ release, unless otherwise noted. whitespace attribute). The 'trailing-space' whitespace error class has become a short-hand to cover both of these and there is no behaviour change for existing set-ups. + + * "git cvsimport" did not work well when it is fed filenames from the + command line and is not started at the top of the work tree. We should + backport this by merging f6fdbb6 (cvsimport: fix relative argument + filenames, 2009-10-19). + + * The way gitweb escapes its CGI parameters were broken especially when + the parameter was a UTF-8 string. We may want to backport this to + 1.6.5.X series by merging 452e225 (gitweb: fix esc_param, 2009-10-13). + + * gitweb used to show 'patch' link for merge commits but the output from + it is not usable to feed "git am" with. We may want to backport this + to 1.6.5.X series by merging 1655c98 (gitweb: Do not show 'patch' link + for merge commits, 2009-10-09). + +--- +exec >/var/tmp/1 +echo O=$(git describe master) +O=v1.6.5.2-73-g9b12444 +git shortlog --no-merges $O..master --not maint -- cgit v0.10.2-6-g49f6 From 2aae905f23f79f004625346d057e5be7a81dbcd4 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 25 Oct 2009 15:15:22 -0400 Subject: push: always load default config This is needed because we want to use the advice.pushnonfastforward variable. Previously, we would load the config on demand only when we needed to look at push.default. Which meant that "git push" would load it, but "git push remote" would not, leading to differing behavior. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin-push.c b/builtin-push.c index 3cb1ee4..8a19f10 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -66,7 +66,6 @@ static void setup_push_tracking(void) static void setup_default_push_refspecs(void) { - git_config(git_default_config, NULL); switch (push_default) { default: case PUSH_DEFAULT_MATCHING: @@ -173,7 +172,6 @@ int cmd_push(int argc, const char **argv, const char *prefix) int tags = 0; int rc; const char *repo = NULL; /* default repository */ - struct option options[] = { OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET), OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE), @@ -191,6 +189,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_END() }; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (tags) -- cgit v0.10.2-6-g49f6 From ffd5c8e457f8139ee89006083d97d47e17a066b8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 22 Oct 2009 23:39:04 -0700 Subject: git svn: fix fetch where glob is on the top-level URL In cases where the top-level URL we're tracking is the path we glob against, we can once again track odd repositories that keep branches/tags at the top level. This regression was introduced in commit 6f5748e14cc5bb0a836b649fb8e2d6a5eb166f1d. Thanks to Daniel Cordero for the original bug report and bisection. Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index eb4b75a..56af221 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1765,7 +1765,7 @@ sub read_all_remotes { my $use_svm_props = eval { command_oneline(qw/config --bool svn.useSvmProps/) }; $use_svm_props = $use_svm_props eq 'true' if $use_svm_props; - my $svn_refspec = qr{\s*/?(.*?)\s*:\s*(.+?)\s*}; + my $svn_refspec = qr{\s*(.*?)\s*:\s*(.+?)\s*}; foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { if (m!^(.+)\.fetch=$svn_refspec$!) { my ($remote, $local_ref, $remote_ref) = ($1, $2, $3); @@ -1979,7 +1979,7 @@ sub find_ref { my ($ref_id) = @_; foreach (command(qw/config -l/)) { next unless m!^svn-remote\.(.+)\.fetch= - \s*/?(.*?)\s*:\s*(.+?)\s*$!x; + \s*(.*?)\s*:\s*(.+?)\s*$!x; my ($repo_id, $path, $ref) = ($1, $2, $3); if ($ref eq $ref_id) { $path = '' if ($path =~ m#^\./?#); -- cgit v0.10.2-6-g49f6 From cb74a0ca61278b97d9e12af9ed9f7040ab13f507 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Tue, 20 Oct 2009 15:41:59 +1300 Subject: git-svn: add test data for SVK merge, with script. Dump generated with SVK 2.0.2 and SVN 1.5.1 (on lenny amd64). Signed-off-by: Sam Vilain Acked-by: Eric Wong diff --git a/t/t9150/make-svk-dump b/t/t9150/make-svk-dump new file mode 100644 index 0000000..2242f14 --- /dev/null +++ b/t/t9150/make-svk-dump @@ -0,0 +1,57 @@ +#!/bin/sh +# +# this script sets up a Subversion repository for Makefile in the +# first ever git merge, as if it were done with svk. +# + +set -e + +svk depotmap foo ~/.svk/foo +svk co /foo/ foo +cd foo +mkdir trunk +mkdir branches +svk add trunk branches +svk commit -m "Setup trunk and branches" +cd trunk + +git cat-file blob 6683463e:Makefile > Makefile +svk add Makefile + +svk commit -m "ancestor" +cd .. +svk cp trunk branches/left + +svk commit -m "make left branch" +cd branches/left/ + +git cat-file blob 5873b67e:Makefile > Makefile +svk commit -m "left update 1" + +cd ../../trunk +git cat-file blob 75118b13:Makefile > Makefile +svk commit -m "trunk update" + +cd ../branches/left +git cat-file blob b5039db6:Makefile > Makefile +svk commit -m "left update 2" + +cd ../../trunk +svk sm /foo/branches/left +# in theory we could delete the "left" branch here, but it's not +# required so don't do it, in case people start getting ideas ;) +svk commit -m "merge branch 'left' into 'trunk'" + +git cat-file blob b51ad431:Makefile > Makefile + +svk diff Makefile && echo "Hey! No differences, magic" + +cd ../.. + +svnadmin dump ~/.svk/foo > svk-merge.dump + +svk co -d foo +rm -rf foo +svk depotmap -d /foo/ +rm -rf ~/.svk/foo + diff --git a/t/t9150/svk-merge.dump b/t/t9150/svk-merge.dump new file mode 100644 index 0000000..42f70db --- /dev/null +++ b/t/t9150/svk-merge.dump @@ -0,0 +1,616 @@ +SVN-fs-dump-format-version: 2 + +UUID: b48289b2-9c08-4d72-af37-0358a40b9c15 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-10-19T23:44:03.722969Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 123 +Content-length: 123 + +K 7 +svn:log +V 24 +Setup trunk and branches +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:04.927533Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 106 +Content-length: 106 + +K 7 +svn:log +V 8 +ancestor +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:05.835585Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2401 +Text-content-md5: bfd8ff778d1492dc6758567373176a89 +Content-length: 2411 + +PROPS-END +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 3 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 16 +make left branch +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:06.719737Z +PROPS-END + +Node-path: branches/left +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk + + +Revision-number: 4 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:07.167666Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2465 +Text-content-md5: 16e38d9753b061731650561ce01b1195 +Content-length: 2465 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 5 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 12 +trunk update +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:07.619633Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2521 +Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0 +Content-length: 2521 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 6 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 2 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:08.067554Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2593 +Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba +Content-length: 2593 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 7 +Prop-content-length: 131 +Content-length: 131 + +K 7 +svn:log +V 32 +merge branch 'left' into 'trunk' +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-19T23:44:08.971801Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 83 +Content-length: 83 + +K 9 +svk:merge +V 53 +b48289b2-9c08-4d72-af37-0358a40b9c15:/branches/left:6 +PROPS-END + + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2713 +Text-content-md5: 0afbe34f244cd662b1f97d708c687f90 +Content-length: 2713 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + -- cgit v0.10.2-6-g49f6 From a5e9c7dfe4732b560ab5dfc75e718bfda2027990 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Tue, 20 Oct 2009 15:42:00 +1300 Subject: git-svn: allow test setup script to support PERL env. var Possibly the 'perl' in the PATH is not the one to be used for the tests; let PERL set in the environment select it. Signed-off-by: Sam Vilain Acked-by: Eric Wong diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index fd8631f..0f7f35c 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -16,6 +16,7 @@ fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree +PERL=${PERL:-perl} svn >/dev/null 2>&1 if test $? -ne 1 @@ -29,7 +30,7 @@ export svnrepo svnconf=$PWD/svnconf export svnconf -perl -w -e " +$PERL -w -e " use SVN::Core; use SVN::Repos; \$SVN::Core::VERSION gt '1.1.0' or exit(42); @@ -130,7 +131,7 @@ stop_httpd () { } convert_to_rev_db () { - perl -w -- - "$@" <<\EOF + $PERL -w -- - "$@" <<\EOF use strict; @ARGV == 2 or die "Usage: convert_to_rev_db "; open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]"; -- cgit v0.10.2-6-g49f6 From f1264bd65451a68e87e0f99c9601cfa1b3244db6 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Tue, 20 Oct 2009 15:42:01 +1300 Subject: git-svn: convert SVK merge tickets to extra parents SVK is a simple case to start with, as its idea of merge parents matches git's one. When a svk:merge ticket is encountered, check each of the listed merged revisions to see if they are in the history of this commit; if not, then we have encountered a merge - record it. [ew: minor formatting cleanups] Signed-off-by: Sam Vilain Acked-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index 56af221..1b918ae 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2878,14 +2878,64 @@ sub check_author { $author; } +sub find_extra_svk_parents { + my ($self, $ed, $tickets, $parents) = @_; + # aha! svk:merge property changed... + my @tickets = split "\n", $tickets; + my @known_parents; + for my $ticket ( @tickets ) { + my ($uuid, $path, $rev) = split /:/, $ticket; + if ( $uuid eq $self->ra_uuid ) { + my $url = $self->rewrite_root || $self->{url}; + my $repos_root = $url; + my $branch_from = $path; + $branch_from =~ s{^/}{}; + my $gs = $self->other_gs($repos_root."/".$branch_from, + $url, + $branch_from, + $rev, + $self->{ref_id}); + if ( my $commit = $gs->rev_map_get($rev, $uuid) ) { + # wahey! we found it, but it might be + # an old one (!) + push @known_parents, $commit; + } + } + } + for my $parent ( @known_parents ) { + my @cmd = ('rev-list', $parent, map { "^$_" } @$parents ); + my ($msg_fh, $ctx) = command_output_pipe(@cmd); + my $new; + while ( <$msg_fh> ) { + $new=1;last; + } + command_close_pipe($msg_fh, $ctx); + if ( $new ) { + print STDERR + "Found merge parent (svk:merge ticket): $parent\n"; + push @$parents, $parent; + } + } +} + sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); + my @parents = @$parents; + my $ps = $ed->{path_strip} || ""; + for my $path ( grep { m/$ps/ } %{$ed->{dir_prop}} ) { + my $props = $ed->{dir_prop}{$path}; + if ( $props->{"svk:merge"} ) { + $self->find_extra_svk_parents + ($ed, $props->{"svk:merge"}, \@parents); + } + } + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; print $un "r$rev\n" or croak $!; print $un $_, "\n" foreach @$untracked; - my %log_entry = ( parents => $parents || [], revision => $rev, + my %log_entry = ( parents => \@parents, revision => $rev, log => ''); my $headrev; diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh new file mode 100644 index 0000000..8000c34 --- /dev/null +++ b/t/t9150-svk-mergetickets.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# +# Copyright (c) 2007 Sam Vilain +# + +test_description='git-svn svk merge tickets' + +. ./lib-git-svn.sh + +test_expect_success 'load svk depot' " + svnadmin load -q '$rawsvnrepo' < '../t9150/svk-merge.dump' && + git svn init --minimize-url -R svkmerge \ + -T trunk -b branches '$svnrepo' && + git svn fetch --all + " + +uuid=b48289b2-9c08-4d72-af37-0358a40b9c15 + +test_expect_success 'svk merges were represented coming in' " + [ `git-cat-file commit HEAD | grep parent | wc -l` -eq 2 ] + " + +test_done -- cgit v0.10.2-6-g49f6 From ce62683096d144749c77e557ecd21c91b840b0f1 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Tue, 20 Oct 2009 15:42:02 +1300 Subject: git-svn: add test data for SVN 1.5+ merge, with script. Dump generated with SVN 1.5.1 (on lenny amd64). This test should hopefully cover all but a few intermediate versions of the svnmerge.py script. Signed-off-by: Sam Vilain Acked-by: Eric Wong diff --git a/t/t9151/.gitignore b/t/t9151/.gitignore new file mode 100644 index 0000000..587c37d --- /dev/null +++ b/t/t9151/.gitignore @@ -0,0 +1,2 @@ +foo +foo.svn diff --git a/t/t9151/make-svnmerge-dump b/t/t9151/make-svnmerge-dump new file mode 100644 index 0000000..e35d64d --- /dev/null +++ b/t/t9151/make-svnmerge-dump @@ -0,0 +1,73 @@ +#!/bin/sh +# +# this script sets up a Subversion repository for Makefile in the +# first ever git merge, as if it were done with svnmerge (SVN 1.5+) +# + +rm -rf foo.svn foo +set -e + +mkdir foo.svn +svnadmin create foo.svn +svn co file://`pwd`/foo.svn foo + +cd foo +mkdir trunk +mkdir branches +svn add trunk branches +svn commit -m "Setup trunk and branches" +cd trunk + +git cat-file blob 6683463e:Makefile > Makefile +svn add Makefile + +echo "Committing ANCESTOR" +svn commit -m "ancestor" +cd .. +svn cp trunk branches/left + +echo "Committing BRANCH POINT" +svn commit -m "make left branch" +cd branches/left/ + +#$sm init +#svn commit -m "init svnmerge" + +git cat-file blob 5873b67e:Makefile > Makefile +echo "Committing BRANCH UPDATE 1" +svn commit -m "left update 1" +cd ../.. + +cd trunk +git cat-file blob 75118b13:Makefile > Makefile +echo "Committing TRUNK UPDATE" +svn commit -m "trunk update" + +cd ../branches/left +git cat-file blob ff5ebe39:Makefile > Makefile +echo "Committing BRANCH UPDATE 2" +svn commit -m "left update 2" + +git cat-file blob b5039db6:Makefile > Makefile +echo "Committing BRANCH UPDATE 3" +svn commit -m "left update 3" + +# merge to trunk + +cd ../.. +svn update +cd trunk + +svn merge ../branches/left --accept postpone + +git cat-file blob b51ad431:Makefile > Makefile + +svn resolved Makefile + +svn commit -m "Merge trunk" + +cd ../.. + +svnadmin dump foo.svn > svn-mergeinfo.dump + +rm -rf foo foo.svn diff --git a/t/t9151/svn-mergeinfo.dump b/t/t9151/svn-mergeinfo.dump new file mode 100644 index 0000000..2153187 --- /dev/null +++ b/t/t9151/svn-mergeinfo.dump @@ -0,0 +1,736 @@ +SVN-fs-dump-format-version: 2 + +UUID: 1ce241d1-ba54-4eb9-bded-03057fe48a33 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-10-20T01:33:37.692723Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 123 +Content-length: 123 + +K 7 +svn:log +V 24 +Setup trunk and branches +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:38.159933Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 106 +Content-length: 106 + +K 7 +svn:log +V 8 +ancestor +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:39.160059Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2401 +Text-content-md5: bfd8ff778d1492dc6758567373176a89 +Content-length: 2411 + +PROPS-END +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 3 +Prop-content-length: 115 +Content-length: 115 + +K 7 +svn:log +V 16 +make left branch +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:41.148192Z +PROPS-END + +Node-path: branches/left +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk +Prop-content-length: 34 +Content-length: 34 + +K 13 +svn:mergeinfo +V 0 + +PROPS-END + + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/Makefile +Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89 + + +Revision-number: 4 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 1 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:42.148773Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2465 +Text-content-md5: 16e38d9753b061731650561ce01b1195 +Content-length: 2465 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 5 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 12 +trunk update +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:43.159959Z +PROPS-END + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2521 +Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0 +Content-length: 2521 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 6 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 2 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:44.164175Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2529 +Text-content-md5: f6b197cc3f2e89a83e545d4bb003de73 +Content-length: 2529 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 7 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 13 +left update 3 +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:45.144214Z +PROPS-END + +Node-path: branches/left/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2593 +Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba +Content-length: 2593 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + +Revision-number: 8 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 11 +Merge trunk +K 10 +svn:author +V 4 +samv +K 8 +svn:date +V 27 +2009-10-20T01:33:48.176135Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 53 +Content-length: 53 + +K 13 +svn:mergeinfo +V 18 +/branches/left:2-7 +PROPS-END + + +Node-path: trunk/Makefile +Node-kind: file +Node-action: change +Text-content-length: 2713 +Text-content-md5: 0afbe34f244cd662b1f97d708c687f90 +Content-length: 2713 + +# -DCOLLISION_CHECK if you believe that SHA1's +# 1461501637330902918203684832716283019655932542976 hashes do not give you +# enough guarantees about no collisions between objects ever hapenning. +# +# -DNSEC if you want git to care about sub-second file mtimes and ctimes. +# Note that you need some new glibc (at least >2.2.4) for this, and it will +# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly +# break unless your underlying filesystem supports those sub-second times +# (my ext3 doesn't). +CFLAGS=-g -O3 -Wall + +CC=gcc + + +PROG= update-cache show-diff init-db write-tree read-tree commit-tree \ + cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \ + check-files ls-tree merge-base merge-cache + +all: $(PROG) + +install: $(PROG) + install $(PROG) $(HOME)/bin/ + +LIBS= -lssl -lz + +init-db: init-db.o + +update-cache: update-cache.o read-cache.o + $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS) + +show-diff: show-diff.o read-cache.o + $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS) + +write-tree: write-tree.o read-cache.o + $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS) + +read-tree: read-tree.o read-cache.o + $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS) + +commit-tree: commit-tree.o read-cache.o + $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS) + +cat-file: cat-file.o read-cache.o + $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS) + +fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +checkout-cache: checkout-cache.o read-cache.o + $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS) + +diff-tree: diff-tree.o read-cache.o + $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS) + +rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +show-files: show-files.o read-cache.o + $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS) + +check-files: check-files.o read-cache.o + $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS) + +ls-tree: ls-tree.o read-cache.o + $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS) + +merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o + $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS) + +merge-cache: merge-cache.o read-cache.o + $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS) + +read-cache.o: cache.h +show-diff.o: cache.h + +clean: + rm -f *.o $(PROG) + +backup: clean + cd .. ; tar czvf dircache.tar.gz dir-cache + + -- cgit v0.10.2-6-g49f6 From dff589ef949e97cbec5363b105ba27f6e85594fb Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Tue, 20 Oct 2009 15:42:03 +1300 Subject: git-svn: convert SVN 1.5+ / svnmerge.py svn:mergeinfo props to parents This feature is long overdue; convert SVN's merge representation to git's as revisions are imported. This works by converting the list of revisions in each line of the svn:mergeinfo into git revision ranges, and then checking the latest of each of these revision ranges for A) being new and B) now being completely merged. Signed-off-by: Sam Vilain Acked-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index 1b918ae..6a3b501 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2918,6 +2918,93 @@ sub find_extra_svk_parents { } } +# note: this function should only be called if the various dirprops +# have actually changed +sub find_extra_svn_parents { + my ($self, $ed, $mergeinfo, $parents) = @_; + # aha! svk:merge property changed... + + # We first search for merged tips which are not in our + # history. Then, we figure out which git revisions are in + # that tip, but not this revision. If all of those revisions + # are now marked as merge, we can add the tip as a parent. + my @merges = split "\n", $mergeinfo; + my @merge_tips; + my @merged_commit_ranges; + my $url = $self->rewrite_root || $self->{url}; + for my $merge ( @merges ) { + my ($source, $revs) = split ":", $merge; + my $path = $source; + $path =~ s{^/}{}; + my $gs = Git::SVN->find_by_url($url.$source, $url, $path); + if ( !$gs ) { + warn "Couldn't find revmap for $url$source\n"; + next; + } + my @ranges = split ",", $revs; + my ($tip, $tip_commit); + # find the tip + for my $range ( @ranges ) { + my ($bottom, $top) = split "-", $range; + $top ||= $bottom; + my $bottom_commit = + $gs->rev_map_get($bottom, $self->ra_uuid) || + $gs->rev_map_get($bottom+1, $self->ra_uuid); + my $top_commit = + $gs->rev_map_get($top, $self->ra_uuid); + + unless ($top_commit and $bottom_commit) { + warn "W:unknown path/rev in svn:mergeinfo " + ."dirprop: $source:$range\n"; + next; + } + + push @merged_commit_ranges, + "$bottom_commit..$top_commit"; + + if ( !defined $tip or $top > $tip ) { + $tip = $top; + $tip_commit = $top_commit; + } + } + unless (!$tip_commit or + grep { $_ eq $tip_commit } @$parents ) { + push @merge_tips, $tip_commit; + } else { + push @merge_tips, undef; + } + } + for my $merge_tip ( @merge_tips ) { + my $spec = shift @merges; + next unless $merge_tip; + my @cmd = ('rev-list', "-1", $merge_tip, + "--not", @$parents ); + my ($msg_fh, $ctx) = command_output_pipe(@cmd); + my $new; + while ( <$msg_fh> ) { + $new=1;last; + } + command_close_pipe($msg_fh, $ctx); + if ( $new ) { + push @cmd, @merged_commit_ranges; + my ($msg_fh, $ctx) = command_output_pipe(@cmd); + my $unmerged; + while ( <$msg_fh> ) { + $unmerged=1;last; + } + command_close_pipe($msg_fh, $ctx); + if ( $unmerged ) { + warn "W:svn cherry-pick ignored ($spec)\n"; + } else { + warn + "Found merge parent (svn:mergeinfo prop): ", + $merge_tip, "\n"; + push @$parents, $merge_tip; + } + } + } +} + sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); @@ -2930,6 +3017,12 @@ sub make_log_entry { $self->find_extra_svk_parents ($ed, $props->{"svk:merge"}, \@parents); } + if ( $props->{"svn:mergeinfo"} ) { + $self->find_extra_svn_parents + ($ed, + $props->{"svn:mergeinfo"}, + \@parents); + } } open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh new file mode 100644 index 0000000..7eb36e5 --- /dev/null +++ b/t/t9151-svn-mergeinfo.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Copyright (c) 2007, 2009 Sam Vilain +# + +test_description='git-svn svn mergeinfo properties' + +. ./lib-git-svn.sh + +test_expect_success 'load svn dump' " + svnadmin load -q '$rawsvnrepo' < '../t9151/svn-mergeinfo.dump' && + git svn init --minimize-url -R svnmerge \ + -T trunk -b branches '$svnrepo' && + git svn fetch --all + " + +test_expect_success 'svn merges were represented coming in' " + [ `git cat-file commit HEAD | grep parent | wc -l` -eq 2 ] + " + +test_done -- cgit v0.10.2-6-g49f6 From acb9108c19e40b0477b8c828d1c5054665abbaf4 Mon Sep 17 00:00:00 2001 From: Vietor Liu Date: Fri, 16 Oct 2009 17:41:26 +0800 Subject: git-gui: adjust the minimum height of diff pane for shorter screen height When the main window is maximized, if the screen height is shorter (e.g. Netbook screen 1024x600), both the partial commit pane and the status bar are hidden. The diff pane is resizable, so that it can use less vertical height, allowing the overall window to be shorter and still display both the entire commit pane and status bar. Signed-off-by: Vietor Liu Signed-off-by: Shawn O. Pearce diff --git a/git-gui.sh b/git-gui.sh index 09b2720..037a1f2 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3083,7 +3083,7 @@ frame .vpane.lower.diff.body set ui_diff .vpane.lower.diff.body.t text $ui_diff -background white -foreground black \ -borderwidth 0 \ - -width 80 -height 15 -wrap none \ + -width 80 -height 5 -wrap none \ -font font_diff \ -xscrollcommand {.vpane.lower.diff.body.sbx set} \ -yscrollcommand {.vpane.lower.diff.body.sby set} \ -- cgit v0.10.2-6-g49f6 From 9f67d2e8279e1885ef2b4681c19cef8534259783 Mon Sep 17 00:00:00 2001 From: Jean Privat Date: Wed, 21 Oct 2009 09:35:22 -0400 Subject: Teach "git describe" --dirty option With the --dirty option, git describe works on HEAD but append s"-dirty" iff the contents of the work tree differs from HEAD. E.g. $ git describe --dirty v1.6.5-15-gc274db7 $ echo >> Makefile $ git describe --dirty v1.6.5-15-gc274db7-dirty The --dirty option can also be used to specify what is appended, instead of the default string "-dirty". $ git describe --dirty=.mod v1.6.5-15-gc274db7.mod Many build scripts use `git describe` to produce a version number based on the description of HEAD (on which the work tree is based) + saying that if the build contains uncommitted changes. This patch helps the writing of such scripts since `git describe --dirty` does directly the intended thing. Three possiblities were considered while discussing this new feature: 1. Describe the work tree by default and describe HEAD only if "HEAD" is explicitly specified Pro: does the right thing by default (both for users and for scripts) Pro: other git commands that works on the work tree by default Con: breaks existing scripts used by the Linux kernel and other projects 2. Use --worktree instead of --dirty Pro: does what it says: "git describe --worktree" describes the work tree Con: other commands do not require a --worktree option when working on the work tree (it often is the default mode for them) Con: unusable with an optional value: "git describe --worktree=.mod" is quite unintuitive. 3. Use --dirty as in this patch Pro: makes sense to specify an optional value (what the dirty mark is) Pro: does not have any of the big cons of previous alternatives * does not break scripts * is not inconsistent with other git commands This patch takes the third approach. Signed-off-by: Jean Privat Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index b231dbb..5253d86 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -9,6 +9,7 @@ git-describe - Show the most recent tag that is reachable from a commit SYNOPSIS -------- 'git describe' [--all] [--tags] [--contains] [--abbrev=] ... +'git describe' [--all] [--tags] [--contains] [--abbrev=] --dirty[=] DESCRIPTION ----------- @@ -27,6 +28,11 @@ OPTIONS ...:: Committish object names to describe. +--dirty[=]:: + Describe the working tree. + It means describe HEAD and appends (`-dirty` by + default) if the working tree is dirty. + --all:: Instead of using only the annotated tags, use any ref found in `.git/refs/`. This option enables matching diff --git a/builtin-describe.c b/builtin-describe.c index df67a73..7dbbee3 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -5,12 +5,14 @@ #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" +#include "diff.h" #define SEEN (1u<<0) #define MAX_TAGS (FLAG_BITS - 1) static const char * const describe_usage[] = { "git describe [options] *", + "git describe [options] --dirty", NULL }; @@ -23,6 +25,13 @@ static int max_candidates = 10; static int found_names; static const char *pattern; static int always; +static const char *dirty; + +/* diff-index command arguments to check if working tree is dirty. */ +static const char *diff_index_args[] = { + "diff-index", "--quiet", "HEAD", "--", NULL +}; + struct commit_name { struct tag *tag; @@ -208,6 +217,8 @@ static void describe(const char *arg, int last_one) display_name(n); if (longformat) show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1); + if (dirty) + printf("%s", dirty); printf("\n"); return; } @@ -265,7 +276,10 @@ static void describe(const char *arg, int last_one) if (!match_cnt) { const unsigned char *sha1 = cmit->object.sha1; if (always) { - printf("%s\n", find_unique_abbrev(sha1, abbrev)); + printf("%s", find_unique_abbrev(sha1, abbrev)); + if (dirty) + printf("%s", dirty); + printf("\n"); return; } die("cannot describe '%s'", sha1_to_hex(sha1)); @@ -300,6 +314,8 @@ static void describe(const char *arg, int last_one) display_name(all_matches[0].name); if (abbrev) show_suffix(all_matches[0].depth, cmit->object.sha1); + if (dirty) + printf("%s", dirty); printf("\n"); if (!last_one) @@ -324,6 +340,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix) "only consider tags matching "), OPT_BOOLEAN(0, "always", &always, "show abbreviated commit object as fallback"), + {OPTION_STRING, 0, "dirty", &dirty, "mark", + "append on dirty working tree (default: \"-dirty\")", + PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"}, OPT_END(), }; @@ -360,7 +379,11 @@ int cmd_describe(int argc, const char **argv, const char *prefix) } if (argc == 0) { + if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix)) + dirty = NULL; describe("HEAD", 1); + } else if (dirty) { + die("--dirty is incompatible with committishes"); } else { while (argc-- > 0) { describe(*argv++, argc == 0); diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 8c7e081..100c4d9 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -123,6 +123,20 @@ test_expect_success 'rename tag Q back to A' ' test_expect_success 'pack tag refs' 'git pack-refs' check_describe A-* HEAD +check_describe "A-*[0-9a-f]" --dirty + +test_expect_success 'set-up dirty work tree' ' + echo >>file +' + +check_describe "A-*[0-9a-f]-dirty" --dirty + +check_describe "A-*[0-9a-f].mod" --dirty=.mod + +test_expect_success 'describe --dirty HEAD' ' + test_must_fail git describe --dirty HEAD +' + test_expect_success 'set-up matching pattern tests' ' git tag -a -m test-annotated test-annotated && echo >>file && -- cgit v0.10.2-6-g49f6 From c591d5f311e0c2c0708b91bf5e05bd1c36da658a Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 26 Oct 2009 21:10:24 -0400 Subject: gitignore: root most patterns at the top-level directory Our gitignore doesn't use a preceding "/" to root its patterns in the top of the repository. This means that if you add a file or directory called "git" (for example) inside a subdirectory, it will be erroneously ignored. This patch was done mechanically with "s/^[^*]/\/&/" with one exception: instead of ignoring gitk-wish, we should gitk-git/gitk-wish (arguably, this should be done in gitk-git/.gitignore, but because that is a subtree merge from elsewhere, this is easier). Acked-by: Sverre Rabbelier Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 51a37b1..289c3d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,184 +1,184 @@ -GIT-BUILD-OPTIONS -GIT-CFLAGS -GIT-GUI-VARS -GIT-VERSION-FILE -git -git-add -git-add--interactive -git-am -git-annotate -git-apply -git-archimport -git-archive -git-bisect -git-bisect--helper -git-blame -git-branch -git-bundle -git-cat-file -git-check-attr -git-check-ref-format -git-checkout -git-checkout-index -git-cherry -git-cherry-pick -git-clean -git-clone -git-commit -git-commit-tree -git-config -git-count-objects -git-cvsexportcommit -git-cvsimport -git-cvsserver -git-daemon -git-diff -git-diff-files -git-diff-index -git-diff-tree -git-difftool -git-difftool--helper -git-describe -git-fast-export -git-fast-import -git-fetch -git-fetch--tool -git-fetch-pack -git-filter-branch -git-fmt-merge-msg -git-for-each-ref -git-format-patch -git-fsck -git-fsck-objects -git-gc -git-get-tar-commit-id -git-grep -git-hash-object -git-help -git-http-fetch -git-http-push -git-imap-send -git-index-pack -git-init -git-init-db -git-instaweb -git-log -git-lost-found -git-ls-files -git-ls-remote -git-ls-tree -git-mailinfo -git-mailsplit -git-merge -git-merge-base -git-merge-index -git-merge-file -git-merge-tree -git-merge-octopus -git-merge-one-file -git-merge-ours -git-merge-recursive -git-merge-resolve -git-merge-subtree -git-mergetool -git-mergetool--lib -git-mktag -git-mktree -git-name-rev -git-mv -git-pack-redundant -git-pack-objects -git-pack-refs -git-parse-remote -git-patch-id -git-peek-remote -git-prune -git-prune-packed -git-pull -git-push -git-quiltimport -git-read-tree -git-rebase -git-rebase--interactive -git-receive-pack -git-reflog -git-relink -git-remote -git-remote-curl -git-repack -git-replace -git-repo-config -git-request-pull -git-rerere -git-reset -git-rev-list -git-rev-parse -git-revert -git-rm -git-send-email -git-send-pack -git-sh-setup -git-shell -git-shortlog -git-show -git-show-branch -git-show-index -git-show-ref -git-stage -git-stash -git-status -git-stripspace -git-submodule -git-svn -git-symbolic-ref -git-tag -git-tar-tree -git-unpack-file -git-unpack-objects -git-update-index -git-update-ref -git-update-server-info -git-upload-archive -git-upload-pack -git-var -git-verify-pack -git-verify-tag -git-web--browse -git-whatchanged -git-write-tree -git-core-*/?* -gitk-wish -gitweb/gitweb.cgi -test-chmtime -test-ctype -test-date -test-delta -test-dump-cache-tree -test-genrandom -test-match-trees -test-parse-options -test-path-utils -test-sha1 -test-sigchain -common-cmds.h +/GIT-BUILD-OPTIONS +/GIT-CFLAGS +/GIT-GUI-VARS +/GIT-VERSION-FILE +/git +/git-add +/git-add--interactive +/git-am +/git-annotate +/git-apply +/git-archimport +/git-archive +/git-bisect +/git-bisect--helper +/git-blame +/git-branch +/git-bundle +/git-cat-file +/git-check-attr +/git-check-ref-format +/git-checkout +/git-checkout-index +/git-cherry +/git-cherry-pick +/git-clean +/git-clone +/git-commit +/git-commit-tree +/git-config +/git-count-objects +/git-cvsexportcommit +/git-cvsimport +/git-cvsserver +/git-daemon +/git-diff +/git-diff-files +/git-diff-index +/git-diff-tree +/git-difftool +/git-difftool--helper +/git-describe +/git-fast-export +/git-fast-import +/git-fetch +/git-fetch--tool +/git-fetch-pack +/git-filter-branch +/git-fmt-merge-msg +/git-for-each-ref +/git-format-patch +/git-fsck +/git-fsck-objects +/git-gc +/git-get-tar-commit-id +/git-grep +/git-hash-object +/git-help +/git-http-fetch +/git-http-push +/git-imap-send +/git-index-pack +/git-init +/git-init-db +/git-instaweb +/git-log +/git-lost-found +/git-ls-files +/git-ls-remote +/git-ls-tree +/git-mailinfo +/git-mailsplit +/git-merge +/git-merge-base +/git-merge-index +/git-merge-file +/git-merge-tree +/git-merge-octopus +/git-merge-one-file +/git-merge-ours +/git-merge-recursive +/git-merge-resolve +/git-merge-subtree +/git-mergetool +/git-mergetool--lib +/git-mktag +/git-mktree +/git-name-rev +/git-mv +/git-pack-redundant +/git-pack-objects +/git-pack-refs +/git-parse-remote +/git-patch-id +/git-peek-remote +/git-prune +/git-prune-packed +/git-pull +/git-push +/git-quiltimport +/git-read-tree +/git-rebase +/git-rebase--interactive +/git-receive-pack +/git-reflog +/git-relink +/git-remote +/git-remote-curl +/git-repack +/git-replace +/git-repo-config +/git-request-pull +/git-rerere +/git-reset +/git-rev-list +/git-rev-parse +/git-revert +/git-rm +/git-send-email +/git-send-pack +/git-sh-setup +/git-shell +/git-shortlog +/git-show +/git-show-branch +/git-show-index +/git-show-ref +/git-stage +/git-stash +/git-status +/git-stripspace +/git-submodule +/git-svn +/git-symbolic-ref +/git-tag +/git-tar-tree +/git-unpack-file +/git-unpack-objects +/git-update-index +/git-update-ref +/git-update-server-info +/git-upload-archive +/git-upload-pack +/git-var +/git-verify-pack +/git-verify-tag +/git-web--browse +/git-whatchanged +/git-write-tree +/git-core-*/?* +/gitk-git/gitk-wish +/gitweb/gitweb.cgi +/test-chmtime +/test-ctype +/test-date +/test-delta +/test-dump-cache-tree +/test-genrandom +/test-match-trees +/test-parse-options +/test-path-utils +/test-sha1 +/test-sigchain +/common-cmds.h *.tar.gz *.dsc *.deb -git.spec +/git.spec *.exe *.[aos] *.py[co] -config.mak -autom4te.cache -config.cache -config.log -config.status -config.mak.autogen -config.mak.append -configure -tags -TAGS -cscope* +/config.mak +/autom4te.cache +/config.cache +/config.log +/config.status +/config.mak.autogen +/config.mak.append +/configure +/tags +/TAGS +/cscope* *.obj *.lib *.sln @@ -188,5 +188,5 @@ cscope* *.user *.idb *.pdb -Debug/ -Release/ +/Debug/ +/Release/ -- cgit v0.10.2-6-g49f6 From a29aa47da79cfd0ef7ee0ef423e7e5a9a3cf07bd Mon Sep 17 00:00:00 2001 From: Gerrit Pape Date: Tue, 27 Oct 2009 13:31:33 +0000 Subject: help -i: properly error out if no info viewer can be found With this commit, git help -i prints an error message and exits non-zero instead of being silent and exit code 0. Reported by Trent W. Buck through http://bugs.debian.org/537664 Signed-off-by: Gerrit Pape Signed-off-by: Junio C Hamano diff --git a/builtin-help.c b/builtin-help.c index e1eba77..e1ade8e 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -372,6 +372,7 @@ static void show_info_page(const char *git_cmd) const char *page = cmd_to_page(git_cmd); setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); execlp("info", "info", "gitman", page, NULL); + die("no info viewer handled the request"); } static void get_html_page_path(struct strbuf *page_path, const char *page) -- cgit v0.10.2-6-g49f6 From f1be316ada93158507c315ee7948bb9e6007eb91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kr=C3=BCger?= Date: Tue, 27 Oct 2009 15:58:14 +0100 Subject: rebase -i: more graceful handling of invalid commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, when there is an invalid command, the rest of the line is still treated as if the command had been valid, i.e. rebase -i attempts to produce a patch, using the next argument as a SHA1 name. If there is no next argument or an invalid one, very confusing error messages appear (the line was '.'; path to git-rebase-todo substituted): Unknown command: . fatal: ambiguous argument 'Please fix this in the file $somefile.': unknown revision or path not in the working tree. Use '--' to separate paths from revisions fatal: Not a valid object name Please fix this in the file $somefile. fatal: bad revision 'Please fix this in the file $somefile.' Instead, verify the validity of the remaining line and error out earlier if necessary. Signed-off-by: Jan Krüger Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 23ded48..9b1e899 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -408,7 +408,12 @@ do_next () { ;; *) warn "Unknown command: $command $sha1 $rest" - die_with_patch $sha1 "Please fix this in the file $TODO." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "Please fix this in the file $TODO." + else + die "Please fix this in the file $TODO." + fi ;; esac test -s "$TODO" && return -- cgit v0.10.2-6-g49f6 From 24ab81ae4d12c81076da256b3f9cdde45277f126 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 27 Oct 2009 20:52:57 -0400 Subject: add-interactive: handle deletion of empty files Usually we show deletion as a big hunk deleting all of the file's text. However, for files with no content, the diff shows just the 'deleted file mode ...' line. This patch cause "add -p" (and related commands) to recognize that line and explicitly ask about deleting the file. We only add the "stage this deletion" hunk for empty files, since other files will already ask about the big content deletion hunk. We could also change those files to simply display "stage this deletion", but showing the actual deleted content is probably what an interactive user wants. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 392efb9..35f4ef1 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -731,14 +731,17 @@ sub parse_diff_header { my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' }; my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' }; + my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' }; for (my $i = 0; $i < @{$src->{TEXT}}; $i++) { - my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? - $mode : $head; + my $dest = + $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode : + $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion : + $head; push @{$dest->{TEXT}}, $src->{TEXT}->[$i]; push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i]; } - return ($head, $mode); + return ($head, $mode, $deletion); } sub hunk_splittable { @@ -1206,7 +1209,7 @@ sub patch_update_file { my ($ix, $num); my $path = shift; my ($head, @hunk) = parse_diff($path); - ($head, my $mode) = parse_diff_header($head); + ($head, my $mode, my $deletion) = parse_diff_header($head); for (@{$head->{DISPLAY}}) { print; } @@ -1214,6 +1217,9 @@ sub patch_update_file { if (@{$mode->{TEXT}}) { unshift @hunk, $mode; } + if (@{$deletion->{TEXT}} && !@hunk) { + @hunk = ($deletion); + } $num = scalar @hunk; $ix = 0; @@ -1267,7 +1273,9 @@ sub patch_update_file { print; } print colored $prompt_color, $patch_mode_flavour{VERB}, - ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'), + ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : + $hunk[$ix]{TYPE} eq 'deletion' ? ' deletion' : + ' this hunk'), $patch_mode_flavour{TARGET}, " [y,n,q,a,d,/$other,?]? "; my $line = prompt_single_character; diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 62fd65e..aa5909b 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -214,4 +214,21 @@ test_expect_success 'add first line works' ' test_cmp expected diff ' +cat >expected < empty && + git add empty && + git commit -m empty && + rm empty && + echo y | git add -p empty && + git diff --cached >diff && + test_cmp expected diff +' + test_done -- cgit v0.10.2-6-g49f6 From 73cf0822b2a4ffa7ad559d1f0772e39718fc7776 Mon Sep 17 00:00:00 2001 From: Julian Phillips Date: Sun, 25 Oct 2009 21:28:11 +0000 Subject: remote: Make ref_remove_duplicates faster for large numbers of refs The ref_remove_duplicates function was very slow at dealing with very large numbers of refs. This is because it was using a linear search through all remaining refs to find any duplicates of the current ref. Rewriting it to use a string list to keep track of which refs have already been seen and removing duplicates when they are found is much more efficient. Signed-off-by: Julian Phillips Signed-off-by: Junio C Hamano diff --git a/remote.c b/remote.c index 73d33f2..4f9f0cc 100644 --- a/remote.c +++ b/remote.c @@ -6,6 +6,7 @@ #include "revision.h" #include "dir.h" #include "tag.h" +#include "string-list.h" static struct refspec s_tag_refspec = { 0, @@ -734,29 +735,31 @@ int for_each_remote(each_remote_fn fn, void *priv) void ref_remove_duplicates(struct ref *ref_map) { - struct ref **posn; - struct ref *next; - for (; ref_map; ref_map = ref_map->next) { + struct string_list refs = { NULL, 0, 0, 0 }; + struct string_list_item *item = NULL; + struct ref *prev = NULL, *next = NULL; + for (; ref_map; prev = ref_map, ref_map = next) { + next = ref_map->next; if (!ref_map->peer_ref) continue; - posn = &ref_map->next; - while (*posn) { - if ((*posn)->peer_ref && - !strcmp((*posn)->peer_ref->name, - ref_map->peer_ref->name)) { - if (strcmp((*posn)->name, ref_map->name)) - die("%s tracks both %s and %s", - ref_map->peer_ref->name, - (*posn)->name, ref_map->name); - next = (*posn)->next; - free((*posn)->peer_ref); - free(*posn); - *posn = next; - } else { - posn = &(*posn)->next; - } + + item = string_list_lookup(ref_map->peer_ref->name, &refs); + if (item) { + if (strcmp(((struct ref *)item->util)->name, + ref_map->name)) + die("%s tracks both %s and %s", + ref_map->peer_ref->name, + ((struct ref *)item->util)->name, + ref_map->name); + prev->next = ref_map->next; + free(ref_map->peer_ref); + free(ref_map); } + + item = string_list_insert(ref_map->peer_ref->name, &refs); + item->util = ref_map; } + string_list_clear(&refs, 0); } int remote_has_url(struct remote *remote, const char *url) -- cgit v0.10.2-6-g49f6 From b1a01e1c0762d117da7dac009b773f310479be12 Mon Sep 17 00:00:00 2001 From: Julian Phillips Date: Sun, 25 Oct 2009 21:28:12 +0000 Subject: fetch: Speed up fetch of large numbers of refs When there are large numbers of refs, calling read_ref for each ref is inefficent (and infact downright slow) - so instead use for_each_ref to build up a string list of all the refs that we currently have, which significantly improves the volume. Signed-off-by: Julian Phillips Signed-off-by: Junio C Hamano diff --git a/builtin-fetch.c b/builtin-fetch.c index a35a6f8..5c7465c 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -489,7 +489,8 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { struct string_list *list = (struct string_list *)cbdata; - string_list_insert(refname, list); + struct string_list_item *item = string_list_insert(refname, list); + item->util = (void *)sha1; return 0; } @@ -615,9 +616,14 @@ static void check_not_current_branch(struct ref *ref_map) static int do_fetch(struct transport *transport, struct refspec *refs, int ref_count) { + struct string_list existing_refs = { NULL, 0, 0, 0 }; + struct string_list_item *peer_item = NULL; struct ref *ref_map; struct ref *rm; int autotags = (transport->remote->fetch_tags == 1); + + for_each_ref(add_existing, &existing_refs); + if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET) tags = TAGS_SET; if (transport->remote->fetch_tags == -1) @@ -640,8 +646,13 @@ static int do_fetch(struct transport *transport, check_not_current_branch(ref_map); for (rm = ref_map; rm; rm = rm->next) { - if (rm->peer_ref) - read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1); + if (rm->peer_ref) { + peer_item = string_list_lookup(rm->peer_ref->name, + &existing_refs); + if (peer_item) + hashcpy(rm->peer_ref->old_sha1, + peer_item->util); + } } if (tags == TAGS_DEFAULT && autotags) -- cgit v0.10.2-6-g49f6 From ade2ca0ca9f8335be300d5538e1ca9cb3594ae64 Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Tue, 27 Oct 2009 12:23:33 +0100 Subject: Do not try to remove directories when removing old links When building Git with MSVC on Windows, directories named after the Git alias are created for the output files, e.g. there is a "git-merge-index" directory next to the "git-merge-index.exe" executable in the build root. Previously, "make all" just checked if "git-merge-index" and "git-merge-index.exe" are the same file, and if not, tried to remove "git-merge-index". This fails in the case of "git-merge-index" being a directory, which is why this is checked now. Signed-off-by: Sebastian Schuberth Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 42b7d60..268aede 100644 --- a/Makefile +++ b/Makefile @@ -1375,7 +1375,7 @@ SHELL = $(SHELL_PATH) all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS ifneq (,$X) - $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$p' -ef '$p$X' || $(RM) '$p';) + $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';) endif all:: -- cgit v0.10.2-6-g49f6 From 93cf50a412e3ed988cbce07e2ba3a80841d3884b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Sat, 17 Oct 2009 11:33:38 +0200 Subject: bash: complete more options for 'git rebase' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete all long options for 'git rebase' except --no-verify (probably used very seldom) and the long options corresponding to -v, -q, and -f. Signed-off-by: Björn Gustavsson Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index d3fec32..7c7318c 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1323,8 +1323,18 @@ _git_rebase () fi __git_complete_strategy && return case "$cur" in + --whitespace=*) + __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" + return + ;; --*) - __gitcomp "--onto --merge --strategy --interactive" + __gitcomp " + --onto --merge --strategy --interactive + --preserve-merges --stat --no-stat + --committer-date-is-author-date --ignore-date + --ignore-whitespace --whitespace= + " + return esac __gitcomp "$(__git_refs)" -- cgit v0.10.2-6-g49f6 From 7c3baa9abf0abe15f068d95688bbc27e73a69042 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Sep 2009 12:22:36 +0200 Subject: help -a: do not unnecessarily look for a repository Although 'git help -a' actually doesn't need to be run inside a git repository and uses no repository-specific information, it looks for a git directory. On 'git ' the bash completion runs 'git help -a' and unnecessary searching for a git directory can be annoying in auto-mount environments. With this commit, 'git help' no longer searches for a repository when run with the -a option. Reported by Vincent Danjean through http://bugs.debian.org/539273 Signed-off-by: Johannes Schindelin Signed-off-by: Gerrit Pape Signed-off-by: Junio C Hamano diff --git a/builtin-help.c b/builtin-help.c index e1ade8e..ca08519 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -417,9 +417,6 @@ int cmd_help(int argc, const char **argv, const char *prefix) const char *alias; load_command_list("git-", &main_cmds, &other_cmds); - setup_git_directory_gently(&nongit); - git_config(git_help_config, NULL); - argc = parse_options(argc, argv, prefix, builtin_help_options, builtin_help_usage, 0); @@ -430,6 +427,9 @@ int cmd_help(int argc, const char **argv, const char *prefix) return 0; } + setup_git_directory_gently(&nongit); + git_config(git_help_config, NULL); + if (!argv[0]) { printf("usage: %s\n\n", git_usage_string); list_common_cmds_help(); -- cgit v0.10.2-6-g49f6 From f7ad96cfaaec3b32458ca69d44c50d1397d11d38 Mon Sep 17 00:00:00 2001 From: Markus Heidelberg Date: Wed, 28 Oct 2009 10:45:38 +0100 Subject: bash completion: difftool accepts the same options as diff So complete refs, files after the double-dash and some diff options that make sense for difftool. Signed-off-by: Markus Heidelberg Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 7c7318c..e3ddecc 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -958,6 +958,8 @@ __git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff _git_difftool () { + __git_has_doubledash && return + local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --tool=*) @@ -965,11 +967,15 @@ _git_difftool () return ;; --*) - __gitcomp "--tool=" + __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex + --base --ours --theirs + --no-renames --diff-filter= --find-copies-harder + --relative --ignore-submodules + --tool=" return ;; esac - COMPREPLY=() + __git_complete_file } __git_fetch_options=" -- cgit v0.10.2-6-g49f6 From c1e01b0c5106451770ae77a62f52acfe814c3326 Mon Sep 17 00:00:00 2001 From: David Brown Date: Wed, 28 Oct 2009 10:13:44 -0700 Subject: commit: More generous accepting of RFC-2822 footer lines. 'git commit -s' will insert a blank line before the Signed-off-by line at the end of the message, unless this last line is a Signed-off-by line itself. Common use has other trailing lines at the ends of commit text, in the style of RFC2822 headers. Be more generous in considering lines to be part of this footer. If the last paragraph of the commit message reasonably resembles RFC-2822 formatted lines, don't insert that blank line. The new Signed-off-by line is still only suppressed when the author's existing Signed-off-by is the last line of the message. Signed-off-by: David Brown Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 200ffda..c395cbf 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -414,6 +414,47 @@ static void determine_author_info(void) author_date = date; } +static int ends_rfc2822_footer(struct strbuf *sb) +{ + int ch; + int hit = 0; + int i, j, k; + int len = sb->len; + int first = 1; + const char *buf = sb->buf; + + for (i = len - 1; i > 0; i--) { + if (hit && buf[i] == '\n') + break; + hit = (buf[i] == '\n'); + } + + while (i < len - 1 && buf[i] == '\n') + i++; + + for (; i < len; i = k) { + for (k = i; k < len && buf[k] != '\n'; k++) + ; /* do nothing */ + k++; + + if ((buf[k] == ' ' || buf[k] == '\t') && !first) + continue; + + first = 0; + + for (j = 0; i + j < len; j++) { + ch = buf[i + j]; + if (ch == ':') + break; + if (isalnum(ch) || + (ch == '-')) + continue; + return 0; + } + } + return 1; +} + static int prepare_to_commit(const char *index_file, const char *prefix, struct wt_status *s) { @@ -489,7 +530,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) ; /* do nothing */ if (prefixcmp(sb.buf + i, sob.buf)) { - if (prefixcmp(sb.buf + i, sign_off_header)) + if (!ends_rfc2822_footer(&sb)) strbuf_addch(&sb, '\n'); strbuf_addbuf(&sb, &sob); } diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index e2ef532..d2de576 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -247,6 +247,47 @@ $existing" && ' +test_expect_success 'signoff gap' ' + + echo 3 >positive && + git add positive && + alt="Alt-RFC-822-Header: Value" && + git commit -s -m "welcome + +$alt" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo welcome + echo + echo $alt + git var GIT_COMMITTER_IDENT | + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'signoff gap 2' ' + + echo 4 >positive && + git add positive && + alt="fixed: 34" && + git commit -s -m "welcome + +We have now +$alt" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo welcome + echo + echo We have now + echo $alt + echo + git var GIT_COMMITTER_IDENT | + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + ) >expected && + test_cmp expected actual +' + test_expect_success 'multiple -m' ' >negative && -- cgit v0.10.2-6-g49f6 From ad3f9a71a8200418e1da59b9712a8fde3f8c4c08 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 27 Oct 2009 11:28:07 -0700 Subject: Add '--bisect' revision machinery argument I personally use "git bisect visualize" all the time when I bisect, but it turns out that that is not a very flexible model. Sometimes I want to do bisection based on all commits (no pathname limiting), but then visualize the current bisection tree with just a few pathnames because I _suspect_ those pathnames are involved in the problem but am not totally sure about them. And at other times, I want to use other revision parsing logic, none of which is available with "git bisect visualize". So this adds "--bisect" as a revision parsing argument, and as a result it just works with all the normal logging tools. So now I can just do gitk --bisect --simplify-by-decoration filename-here etc. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 4ba1c12..32bf033 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -319,6 +319,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) memset(&info, 0, sizeof(info)); info.revs = &revs; + if (revs.bisect) + bisect_list = 1; quiet = DIFF_OPT_TST(&revs.diffopt, QUIET); for (i = 1 ; i < argc; i++) { diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 45bead6..9526aaf 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -180,6 +180,12 @@ static int show_reference(const char *refname, const unsigned char *sha1, int fl return 0; } +static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + show_rev(REVERSED, sha1, refname); + return 0; +} + static void show_datestring(const char *flag, const char *datestr) { static char buffer[100]; @@ -548,6 +554,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_ref(show_reference, NULL); continue; } + if (!strcmp(arg, "--bisect")) { + for_each_ref_in("refs/bisect/bad", show_reference, NULL); + for_each_ref_in("refs/bisect/good", anti_reference, NULL); + continue; + } if (!strcmp(arg, "--branches")) { for_each_branch_ref(show_reference, NULL); continue; diff --git a/revision.c b/revision.c index 9fc4e8d..a36c0d9 100644 --- a/revision.c +++ b/revision.c @@ -994,7 +994,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") || !strcmp(arg, "--tags") || !strcmp(arg, "--remotes") || !strcmp(arg, "--reflog") || !strcmp(arg, "--not") || - !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk")) + !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") || + !strcmp(arg, "--bisect")) { unkv[(*unkc)++] = arg; return 1; @@ -1218,6 +1219,16 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, ctx->argc -= n; } +static int for_each_bad_bisect_ref(each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in("refs/bisect/bad", fn, cb_data); +} + +static int for_each_good_bisect_ref(each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in("refs/bisect/good", fn, cb_data); +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -1259,6 +1270,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch handle_refs(revs, flags, for_each_branch_ref); continue; } + if (!strcmp(arg, "--bisect")) { + handle_refs(revs, flags, for_each_bad_bisect_ref); + handle_refs(revs, flags ^ UNINTERESTING, for_each_good_bisect_ref); + revs->bisect = 1; + continue; + } if (!strcmp(arg, "--tags")) { handle_refs(revs, flags, for_each_tag_ref); continue; diff --git a/revision.h b/revision.h index b6421a6..921656a 100644 --- a/revision.h +++ b/revision.h @@ -63,6 +63,7 @@ struct rev_info { reverse:1, reverse_output_stage:1, cherry_pick:1, + bisect:1, first_parent_only:1; /* Diff flags */ -- cgit v0.10.2-6-g49f6 From 4d23660e79dbbb7e2ae37cb7193166d085a78502 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Wed, 28 Oct 2009 23:10:06 +0100 Subject: describe: when failing, tell the user about options that work Users seem to call git-describe without reading the manpage, and then wonder why it doesn't work with unannotated tags by default. Make a minimal effort towards seeing if there would have been unannotated tags, and tell the user. Specifically, we say that --tags could work if we found any unannotated tags. If not, we say that --always would have given results. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano diff --git a/builtin-describe.c b/builtin-describe.c index eaa8a9d..504d9b1 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -96,8 +96,6 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void if (!all) { if (!prio) return 0; - if (!tags && prio < 2) - return 0; } add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1); return 0; @@ -184,6 +182,7 @@ static void describe(const char *arg, int last_one) struct possible_tag all_matches[MAX_TAGS]; unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; unsigned long seen_commits = 0; + unsigned int unannotated_cnt = 0; if (get_sha1(arg, sha1)) die("Not a valid object name %s", arg); @@ -217,7 +216,9 @@ static void describe(const char *arg, int last_one) seen_commits++; n = c->util; if (n) { - if (match_cnt < max_candidates) { + if (!tags && !all && n->prio < 2) { + unannotated_cnt++; + } else if (match_cnt < max_candidates) { struct possible_tag *t = &all_matches[match_cnt++]; t->name = n; t->depth = seen_commits - 1; @@ -259,7 +260,14 @@ static void describe(const char *arg, int last_one) printf("%s\n", find_unique_abbrev(sha1, abbrev)); return; } - die("cannot describe '%s'", sha1_to_hex(sha1)); + if (unannotated_cnt) + die("No annotated tags can describe '%s'.\n" + "However, there were unannotated tags: try --tags.", + sha1_to_hex(sha1)); + else + die("No tags can describe '%s'.\n" + "Try --always, or create some tags.", + sha1_to_hex(sha1)); } qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt); -- cgit v0.10.2-6-g49f6 From c8998b4823cbccd6bd49c2034e242ae7d5873eae Mon Sep 17 00:00:00 2001 From: Scott Chacon Date: Wed, 28 Oct 2009 14:39:32 -0700 Subject: mergetool--lib: add p4merge as a pre-configured mergetool option Add p4merge to the set of built-in diff/merge tools, and update bash completion and documentation. Signed-off-by: Scott Chacon Signed-off-by: Junio C Hamano diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 96a6c51..8e9aed6 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -31,7 +31,7 @@ OPTIONS Use the diff tool specified by . Valid merge tools are: kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, - ecmerge, diffuse, opendiff and araxis. + ecmerge, diffuse, opendiff, p4merge and araxis. + If a diff tool is not specified, 'git-difftool' will use the configuration variable `diff.tool`. If the diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 68ed6c0..4a6f7f3 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -27,7 +27,7 @@ OPTIONS Use the merge resolution program specified by . Valid merge tools are: kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, - diffuse, tortoisemerge, opendiff and araxis. + diffuse, tortoisemerge, opendiff, p4merge and araxis. + If a merge resolution program is not specified, 'git-mergetool' will use the configuration variable `merge.tool`. If the diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index c0f96e7..a403155 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -23,7 +23,7 @@ merge.tool:: Controls which merge resolution program is used by linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3", "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", - "diffuse", "ecmerge", "tortoisemerge", "araxis", and + "diffuse", "ecmerge", "tortoisemerge", "p4merge", "araxis" and "opendiff". Any other value is treated is custom merge tool and there must be a corresponding mergetool..cmd option. diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e3ddecc..2a9a889 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -953,7 +953,7 @@ _git_diff () } __git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff - tkdiff vimdiff gvimdiff xxdiff araxis + tkdiff vimdiff gvimdiff xxdiff araxis p4merge " _git_difftool () diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index bfb01f7..f7c571e 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -46,7 +46,7 @@ check_unchanged () { valid_tool () { case "$1" in kdiff3 | tkdiff | xxdiff | meld | opendiff | \ - emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis) + emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis | p4merge) ;; # happy tortoisemerge) if ! merge_mode; then @@ -130,6 +130,19 @@ run_merge_tool () { "$merge_tool_path" "$LOCAL" "$REMOTE" fi ;; + p4merge) + if merge_mode; then + touch "$BACKUP" + if $base_present; then + "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED" + else + "$merge_tool_path" "$LOCAL" "$LOCAL" "$REMOTE" "$MERGED" + fi + check_unchanged + else + "$merge_tool_path" "$LOCAL" "$REMOTE" + fi + ;; meld) if merge_mode; then touch "$BACKUP" @@ -323,7 +336,7 @@ guess_merge_tool () { else tools="opendiff kdiff3 tkdiff xxdiff meld $tools" fi - tools="$tools gvimdiff diffuse ecmerge araxis" + tools="$tools gvimdiff diffuse ecmerge p4merge araxis" fi if echo "${VISUAL:-$EDITOR}" | grep emacs > /dev/null 2>&1; then # $EDITOR is emacs so add emerge as a candidate -- cgit v0.10.2-6-g49f6 From 0fcabdeb52b79775173d009ccc179db104dfbb66 Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Mon, 19 Oct 2009 18:37:05 +0200 Subject: Use faster byte swapping when compiling with MSVC When compiling with MSVC on x86-compatible, use an intrinsic for byte swapping. In contrast to the GCC path, we do not prefer inline assembly here as it is not supported for the x64 platform. Signed-off-by: Sebastian Schuberth Signed-off-by: Junio C Hamano diff --git a/compat/bswap.h b/compat/bswap.h index 5cc4acb..279e0b4 100644 --- a/compat/bswap.h +++ b/compat/bswap.h @@ -28,6 +28,16 @@ static inline uint32_t default_swab32(uint32_t val) } \ __res; }) +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + +#include + +#define bswap32(x) _byteswap_ulong(x) + +#endif + +#ifdef bswap32 + #undef ntohl #undef htonl #define ntohl(x) bswap32(x) -- cgit v0.10.2-6-g49f6 From 3fe9747b94ca81d29894debdb371cff3b1d89af1 Mon Sep 17 00:00:00 2001 From: Sebastian Schuberth Date: Mon, 19 Oct 2009 18:40:47 +0200 Subject: Make the MSVC projects use PDB/IDB files named after the project Instead of having all PDB files for all projects named "vc90.pdb", name them after the respective project to make the relation more clear (and to avoid name clashes when copying files around). Signed-off-by: Sebastian Schuberth Acked-by: Marius Storm-Olsen Signed-off-by: Junio C Hamano diff --git a/contrib/buildsystems/Generators/Vcproj.pm b/contrib/buildsystems/Generators/Vcproj.pm index be94ba1..cfa74ad 100644 --- a/contrib/buildsystems/Generators/Vcproj.pm +++ b/contrib/buildsystems/Generators/Vcproj.pm @@ -178,6 +178,7 @@ sub createLibProject { MinimalRebuild="true" RuntimeLibrary="1" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> @@ -244,6 +245,7 @@ sub createLibProject { RuntimeLibrary="0" EnableFunctionLevelLinking="true" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> @@ -401,6 +403,7 @@ sub createAppProject { MinimalRebuild="true" RuntimeLibrary="1" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> @@ -472,6 +475,7 @@ sub createAppProject { RuntimeLibrary="0" EnableFunctionLevelLinking="true" UsePrecompiledHeader="0" + ProgramDataBaseFileName="\$(IntDir)\\\$(TargetName).pdb" WarningLevel="3" DebugInformationFormat="3" /> -- cgit v0.10.2-6-g49f6 From 168eff3c802a47a4e8a97b2ec70d86e5f605a012 Mon Sep 17 00:00:00 2001 From: Markus Heidelberg Date: Wed, 28 Oct 2009 13:24:30 +0100 Subject: t4034-diff-words: add a test for word diff without context Signed-off-by: Markus Heidelberg Signed-off-by: Junio C Hamano diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 4508eff..82240cf 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -68,6 +68,26 @@ cat > expect <<\EOF index 330b04f..5ed8eff 100644 --- a/pre +++ b/post +@@ -1 +1 @@ +h(4)h(4),hh[44] +@@ -3,0 +4,4 @@ a = b + c + +aa = a + +aeff = aeff * ( aaa ) +EOF + +test_expect_failure 'word diff without context' ' + + word_diff --color-words --unified=0 + +' + +cat > expect <<\EOF +diff --git a/pre b/post +index 330b04f..5ed8eff 100644 +--- a/pre ++++ b/post @@ -1,3 +1,7 @@ h(4),hh[44] -- cgit v0.10.2-6-g49f6 From a4ca1465ec8afee798bf8f11d727179ca3da64a9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 29 Oct 2009 11:45:03 +0100 Subject: diff --color-words -U0: fix the location of hunk headers Colored word diff without context lines firstly printed all the hunk headers among each other and then printed the diff. This was due to the code relying on getting at least one context line at the end of each hunk, where the colored words would be flushed (it is done that way to be able to ignore rewrapped lines). Noticed by Markus Heidelberg. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index e1be189..b7ecfe3 100644 --- a/diff.c +++ b/diff.c @@ -656,6 +656,12 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) for (i = 0; i < len && line[i] == '@'; i++) ; if (2 <= i && i < len && line[i] == ' ') { + /* flush --color-words even for --unified=0 */ + if (ecbdata->diff_words && + (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size)) + diff_words_show(ecbdata->diff_words); + ecbdata->nparents = i - 1; len = sane_truncate_line(ecbdata, line, len); emit_line(ecbdata->file, diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 82240cf..21db6e9 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -77,7 +77,7 @@ cat > expect <<\EOF aeff = aeff * ( aaa ) EOF -test_expect_failure 'word diff without context' ' +test_expect_success 'word diff without context' ' word_diff --color-words --unified=0 -- cgit v0.10.2-6-g49f6 From 0cc5691a8b05a7eabdeef520c94b1bb3bcac7874 Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Fri, 30 Oct 2009 18:20:28 +0100 Subject: Don't create the $GIT_DIR/branches directory on init Git itself does not even look at this directory. Any tools that actually needs it should create it itself. Signed-off-by: Robin Rosenberg Signed-off-by: Junio C Hamano diff --git a/templates/branches-- b/templates/branches-- deleted file mode 100644 index fae8870..0000000 --- a/templates/branches-- +++ /dev/null @@ -1 +0,0 @@ -: this is just to ensure the directory exists. -- cgit v0.10.2-6-g49f6 From 500348aa6859e436a890f5f5a7e0eeea8ef6c1de Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 30 Oct 2009 15:05:52 -0400 Subject: ls-files: unbreak "ls-files -i" Commit b5227d8 changed the behavior of "ls-files" with respect to includes, but accidentally broke the "-i" option The original behavior was: 1. if no "-i" is given, cull all results according to --exclude* 2. if "-i" is given, show the inverse of (1) The broken behavior was: 1. if no "-i" is given: a. for "-o", cull results according to --exclude* b. for index files, always show all 2. if "-i" is given: a. for "-o", shows the inverse of (1a) b. for index files, always show all The fixed behavior keeps the new (1b) behavior introduced by b5227d8, but fixes the (2b) behavior to show only ignored files, not all files. This patch also tweaks the documentation. The original text was somewhat obscure in the first place, but it is also now inaccurate (the relationship between (1b) and (2b) is not quite a "reverse"). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 057a021..8985915 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -48,8 +48,10 @@ OPTIONS -i:: --ignored:: - Show ignored files in the output. - Note that this also reverses any exclude list present. + Show only ignored files in the output. When showing files in the + index, print only those matched by an exclude pattern. When + showing "other" files, show only those matched by an exclude + pattern. -s:: --stage:: diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 16a1370..e458a49 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -175,6 +175,10 @@ static void show_files(struct dir_struct *dir, const char *prefix) if (show_cached | show_stage) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; + int dtype = ce_to_dtype(ce); + if (dir->flags & DIR_SHOW_IGNORED && + !excluded(dir, ce->name, &dtype)) + continue; if (show_unmerged && !ce_stage(ce)) continue; if (ce->ce_flags & CE_UPDATE) @@ -187,6 +191,10 @@ static void show_files(struct dir_struct *dir, const char *prefix) struct cache_entry *ce = active_cache[i]; struct stat st; int err; + int dtype = ce_to_dtype(ce); + if (dir->flags & DIR_SHOW_IGNORED && + !excluded(dir, ce->name, &dtype)) + continue; if (ce->ce_flags & CE_UPDATE) continue; err = lstat(ce->name, &st); diff --git a/t/t3003-ls-files-exclude.sh b/t/t3003-ls-files-exclude.sh index fc1e379..d5ec333 100755 --- a/t/t3003-ls-files-exclude.sh +++ b/t/t3003-ls-files-exclude.sh @@ -29,4 +29,12 @@ test_expect_success 'add file to gitignore' ' ' check_all_output +test_expect_success 'ls-files -i lists only tracked-but-ignored files' ' + echo content >other-file && + git add other-file && + echo file >expect && + git ls-files -i --exclude-standard >output && + test_cmp expect output +' + test_done -- cgit v0.10.2-6-g49f6 From 492cf3f72f9d8a0308cc42a9ffc00ef782c88a20 Mon Sep 17 00:00:00 2001 From: Gisle Aas Date: Thu, 29 Oct 2009 22:29:35 +0100 Subject: More precise description of 'git describe --abbrev' Also adds a note about why the output in the examples might give different output today. Signed-off-by: Gisle Aas Signed-off-by: Junio C Hamano diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index b231dbb..e9dbca7 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -44,7 +44,9 @@ OPTIONS --abbrev=:: Instead of using the default 7 hexadecimal digits as the - abbreviated object name, use digits. + abbreviated object name, use digits, or as many digits + as needed to form a unique object name. An of 0 + will suppress long format, only showing the closest tag. --candidates=:: Instead of considering only the 10 most recent tags as @@ -68,8 +70,8 @@ OPTIONS This is useful when you want to see parts of the commit object name in "describe" output, even when the commit in question happens to be a tagged version. Instead of just emitting the tag name, it will - describe such a commit as v1.2-0-deadbeef (0th commit since tag v1.2 - that points at object deadbeef....). + describe such a commit as v1.2-0-gdeadbee (0th commit since tag v1.2 + that points at object deadbee....). --match :: Only consider tags matching the given pattern (can be used to avoid @@ -108,7 +110,7 @@ the output shows the reference path as well: [torvalds@g5 git]$ git describe --all --abbrev=4 v1.0.5^2 tags/v1.0.0-21-g975b - [torvalds@g5 git]$ git describe --all HEAD^ + [torvalds@g5 git]$ git describe --all --abbrev=4 HEAD^ heads/lt/describe-7-g975b With --abbrev set to 0, the command can be used to find the @@ -117,6 +119,13 @@ closest tagname without any suffix: [torvalds@g5 git]$ git describe --abbrev=0 v1.0.5^2 tags/v1.0.0 +Note that the suffix you get if you type these commands today may be +longer than what Linus saw above when he ran this command, as your +git repository may have new commits whose object names begin with +975b that did not exist back then, and "-g975b" suffix alone may not +be sufficient to disambiguate these commits. + + SEARCH STRATEGY --------------- -- cgit v0.10.2-6-g49f6 From ab3d175f87767c377cd65a06d96c50a316a28e30 Mon Sep 17 00:00:00 2001 From: Michael J Gruber Date: Thu, 29 Oct 2009 16:26:20 +0100 Subject: Make t9150 and t9151 test scripts executable so that they can be run individually as (cd t && ./t9150-svk-mergetickets.sh) etc. just like all other test scripts. Signed-off-by: Michael J Gruber Signed-off-by: Junio C Hamano diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh old mode 100644 new mode 100755 diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh old mode 100644 new mode 100755 -- cgit v0.10.2-6-g49f6 From 15c6bf0df4d9f060b5cae7a9a899dc3601c54353 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 30 Oct 2009 16:10:17 -0400 Subject: t915{0,1}: use $TEST_DIRECTORY Because --root can put our trash directories elsewhere, using ".." may not always work. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh index 8000c34..dd0c2ba 100755 --- a/t/t9150-svk-mergetickets.sh +++ b/t/t9150-svk-mergetickets.sh @@ -8,7 +8,8 @@ test_description='git-svn svk merge tickets' . ./lib-git-svn.sh test_expect_success 'load svk depot' " - svnadmin load -q '$rawsvnrepo' < '../t9150/svk-merge.dump' && + svnadmin load -q '$rawsvnrepo' \ + < '$TEST_DIRECTORY/t9150/svk-merge.dump' && git svn init --minimize-url -R svkmerge \ -T trunk -b branches '$svnrepo' && git svn fetch --all diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh index 7eb36e5..9bee516 100755 --- a/t/t9151-svn-mergeinfo.sh +++ b/t/t9151-svn-mergeinfo.sh @@ -8,7 +8,8 @@ test_description='git-svn svn mergeinfo properties' . ./lib-git-svn.sh test_expect_success 'load svn dump' " - svnadmin load -q '$rawsvnrepo' < '../t9151/svn-mergeinfo.dump' && + svnadmin load -q '$rawsvnrepo' \ + < '$TEST_DIRECTORY/t9151/svn-mergeinfo.dump' && git svn init --minimize-url -R svnmerge \ -T trunk -b branches '$svnrepo' && git svn fetch --all -- cgit v0.10.2-6-g49f6 From f740cc25298e2a0eb7c561c40854ed568c7de657 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 30 Oct 2009 11:04:53 -0400 Subject: push: fix typo in usage Missing ")". Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin-push.c b/builtin-push.c index b5cd2cd..8631c06 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -181,7 +181,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL), OPT_BIT( 0 , "mirror", &flags, "mirror all refs", (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), - OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror"), + OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"), OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), -- cgit v0.10.2-6-g49f6 From ebc9d420566de32dbadada5a4f700db2d974853c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Oct 2009 15:15:36 -0700 Subject: clone: fix help on options Fix incorrect description of --recursive, and stop listing the historical synonym --naked that is not advertised anywhere. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin-clone.c b/builtin-clone.c index 5762a6f..0a4f612 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -51,7 +51,9 @@ static struct option builtin_clone_options[] = { OPT_BOOLEAN('n', "no-checkout", &option_no_checkout, "don't create a checkout"), OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"), - OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"), + { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL, + "create a bare repository", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_BOOLEAN(0, "mirror", &option_mirror, "create a mirror repository (implies bare)"), OPT_BOOLEAN('l', "local", &option_local, @@ -61,7 +63,7 @@ static struct option builtin_clone_options[] = { OPT_BOOLEAN('s', "shared", &option_shared, "setup as shared repository"), OPT_BOOLEAN(0, "recursive", &option_recursive, - "setup as shared repository"), + "initialize submodules in the clone"), OPT_STRING(0, "template", &option_template, "path", "path the template repository"), OPT_STRING(0, "reference", &option_reference, "repo", -- cgit v0.10.2-6-g49f6 From d52dc4b10b2f78dc24ea05e88ddc25ee0f46491e Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Thu, 29 Oct 2009 03:10:30 -0500 Subject: clone: detect extra arguments If git clone is given more than two non-option arguments, it silently throws away all but the first one. Complain instead. Discovered by comparing the new builtin clone to the old git-clone.sh. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-clone.c b/builtin-clone.c index 0a4f612..caf3025 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -379,8 +379,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); + if (argc > 2) + usage_msg_opt("Too many arguments.", + builtin_clone_usage, builtin_clone_options); + if (argc == 0) - die("You must specify a repository to clone."); + usage_msg_opt("You must specify a repository to clone.", + builtin_clone_usage, builtin_clone_options); if (option_mirror) option_bare = 1; -- cgit v0.10.2-6-g49f6 From 134748353b2a71a34f899c9b1326ccf7ae082412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Thu, 29 Oct 2009 23:08:31 +0100 Subject: Teach 'git merge' and 'git pull' the option --ff-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For convenience in scripts and aliases, add the option --ff-only to only allow fast-forwards (and up-to-date, despite the name). Disallow combining --ff-only and --no-ff, since they flatly contradict each other. Allow all other options to be combined with --ff-only (i.e. do not add any code to handle them specially), including the following options: * --strategy (one or more): As long as the chosen merge strategy results in up-to-date or fast-forward, the command will succeed. * --squash: I cannot imagine why anyone would want to squash commits only if fast-forward is possible, but I also see no reason why it should not be allowed. * --message: The message will always be ignored, but I see no need to explicitly disallow providing a redundant message. Acknowledgements: I did look at Yuval Kogman's earlier patch (107768 in gmane), mainly as shortcut to find my way in the code, but I did not copy anything directly. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index adadf8e..27a9a84 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -60,6 +60,11 @@ a fast-forward, only update the branch pointer. This is the default behavior of git-merge. +--ff-only:: + Refuse to merge and exit with a non-zero status unless the + current `HEAD` is already up-to-date or the merge can be + resolved as a fast-forward. + -s :: --strategy=:: Use the given merge strategy; can be supplied more than diff --git a/builtin-merge.c b/builtin-merge.c index b6b8428..5e8c4b5 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -43,6 +43,7 @@ static const char * const builtin_merge_usage[] = { static int show_diffstat = 1, option_log, squash; static int option_commit = 1, allow_fast_forward = 1; +static int fast_forward_only; static int allow_trivial = 1, have_message; static struct strbuf merge_msg; static struct commit_list *remoteheads; @@ -167,6 +168,8 @@ static struct option builtin_merge_options[] = { "perform a commit if the merge succeeds (default)"), OPT_BOOLEAN(0, "ff", &allow_fast_forward, "allow fast forward (default)"), + OPT_BOOLEAN(0, "ff-only", &fast_forward_only, + "abort if fast forward is not possible"), OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", "merge strategy to use", option_parse_strategy), OPT_CALLBACK('m', "message", &merge_msg, "message", @@ -874,6 +877,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) option_commit = 0; } + if (!allow_fast_forward && fast_forward_only) + die("You cannot combine --no-ff with --ff-only."); + if (!argc) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1040,7 +1046,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * only one common. */ refresh_cache(REFRESH_QUIET); - if (allow_trivial) { + if (allow_trivial && !fast_forward_only) { /* See if it is really trivial. */ git_committer_info(IDENT_ERROR_ON_NO_NAME); printf("Trying really trivial in-index merge...\n"); @@ -1079,6 +1085,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } } + if (fast_forward_only) + die("Not possible to fast forward, aborting."); + /* We are going to make a new commit. */ git_committer_info(IDENT_ERROR_ON_NO_NAME); diff --git a/git-pull.sh b/git-pull.sh index fc78592..37f3d93 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -16,7 +16,8 @@ cd_to_toplevel test -z "$(git ls-files -u)" || die "You are in the middle of a conflicted merge." -strategy_args= diffstat= no_commit= squash= no_ff= log_arg= verbosity= +strategy_args= diffstat= no_commit= squash= no_ff= ff_only= +log_arg= verbosity= curr_branch=$(git symbolic-ref -q HEAD) curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||") rebase=$(git config --bool branch.$curr_branch_short.rebase) @@ -45,6 +46,8 @@ do no_ff=--ff ;; --no-ff) no_ff=--no-ff ;; + --ff-only) + ff_only=--ff-only ;; -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ --strateg=*|--strategy=*|\ -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) @@ -215,5 +218,5 @@ merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit test true = "$rebase" && exec git-rebase $diffstat $strategy_args --onto $merge_head \ ${oldremoteref:-$merge_head} -exec git-merge $diffstat $no_commit $squash $no_ff $log_arg $strategy_args \ +exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args \ "$merge_name" HEAD $merge_head $verbosity diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index e5b210b..57f6d2b 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -243,6 +243,16 @@ test_expect_success 'merge c0 with c1' ' test_debug 'gitk --all' +test_expect_success 'merge c0 with c1 with --ff-only' ' + git reset --hard c0 && + git merge --ff-only c1 && + git merge --ff-only HEAD c0 c1 && + verify_merge file result.1 && + verify_head "$c1" +' + +test_debug 'gitk --all' + test_expect_success 'merge c1 with c2' ' git reset --hard c1 && test_tick && @@ -263,6 +273,14 @@ test_expect_success 'merge c1 with c2 and c3' ' test_debug 'gitk --all' +test_expect_success 'failing merges with --ff-only' ' + git reset --hard c1 && + test_tick && + test_must_fail git merge --ff-only c2 && + test_must_fail git merge --ff-only c3 && + test_must_fail git merge --ff-only c2 c3 +' + test_expect_success 'merge c0 with c1 (no-commit)' ' git reset --hard c0 && git merge --no-commit c1 && @@ -303,6 +321,17 @@ test_expect_success 'merge c0 with c1 (squash)' ' test_debug 'gitk --all' +test_expect_success 'merge c0 with c1 (squash, ff-only)' ' + git reset --hard c0 && + git merge --squash --ff-only c1 && + verify_merge file result.1 && + verify_head $c0 && + verify_no_mergehead && + verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message" +' + +test_debug 'gitk --all' + test_expect_success 'merge c1 with c2 (squash)' ' git reset --hard c1 && git merge --squash c2 && @@ -314,6 +343,13 @@ test_expect_success 'merge c1 with c2 (squash)' ' test_debug 'gitk --all' +test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' ' + git reset --hard c1 && + test_must_fail git merge --squash --ff-only c2 +' + +test_debug 'gitk --all' + test_expect_success 'merge c1 with c2 and c3 (squash)' ' git reset --hard c1 && git merge --squash c2 c3 && @@ -432,6 +468,11 @@ test_expect_success 'combining --squash and --no-ff is refused' ' test_must_fail git merge --no-ff --squash c1 ' +test_expect_success 'combining --ff-only and --no-ff is refused' ' + test_must_fail git merge --ff-only --no-ff c1 && + test_must_fail git merge --no-ff --ff-only c1 +' + test_expect_success 'merge c0 with c1 (ff overrides no-ff)' ' git reset --hard c0 && git config branch.master.mergeoptions "--no-ff" && -- cgit v0.10.2-6-g49f6 From 46e09f310567b680c03151e048bf2b7e847611e2 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 29 Oct 2009 23:07:41 +0100 Subject: t/gitweb-lib.sh: Split gitweb output into headers and body Save HTTP headers into gitweb.headers, and the body of message into gitweb.body in gitweb_run() Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index 8452532..32b841d 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -52,10 +52,14 @@ gitweb_run () { rm -f gitweb.log && perl -- "$SCRIPT_NAME" \ >gitweb.output 2>gitweb.log && + sed -e '/^\r$/q' gitweb.headers && + sed -e '1,/^\r$/d' gitweb.body && if grep '^[[]' gitweb.log >/dev/null 2>&1; then false; else true; fi # gitweb.log is left for debugging - # gitweb.output is used to parse http output + # gitweb.output is used to parse HTTP output + # gitweb.headers contains only HTTP headers + # gitweb.body contains body of message, without headers } . ./test-lib.sh -- cgit v0.10.2-6-g49f6 From eab58f1e8e5ef86b5075ce6dfcd6d3f1b3b888b3 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Fri, 30 Oct 2009 20:24:04 -0500 Subject: Handle more shell metacharacters in editor names Pass the editor name to the shell if it contains any susv3 shell special character (globs, redirections, variable substitutions, escapes, etc). This way, the meaning of some characters will not meaninglessly change when others are added, and git commands implemented in C and in shell scripts will interpret editor names in the same way. This does not make the GIT_EDITOR setting any more expressive, since one could always use single quotes to force the editor to be passed to the shell. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/editor.c b/editor.c index 4d469d0..941c0b2 100644 --- a/editor.c +++ b/editor.c @@ -28,7 +28,7 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en const char *args[6]; struct strbuf arg0 = STRBUF_INIT; - if (strcspn(editor, "$ \t'") != len) { + if (strcspn(editor, "|&;<>()$`\\\"' \t\n*?[#~=%") != len) { /* there are specials */ strbuf_addf(&arg0, "%s \"$@\"", editor); args[i++] = "sh"; -- cgit v0.10.2-6-g49f6 From 609621a4ad8144e56df6e5f811eb037661967f49 Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 30 Oct 2009 17:47:20 -0700 Subject: http-push: fix check condition on http.c::finish_http_pack_request() Check that http.c::finish_http_pack_request() returns 0 (for success). Signed-off-by: Tay Ray Chuan Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/http-push.c b/http-push.c index 00e83dc..cc5d4b8 100644 --- a/http-push.c +++ b/http-push.c @@ -604,7 +604,7 @@ static void finish_request(struct transfer_request *request) preq = (struct http_pack_request *)request->userData; if (preq) { - if (finish_http_pack_request(preq) > 0) + if (finish_http_pack_request(preq) == 0) fail = 0; release_http_pack_request(preq); } -- cgit v0.10.2-6-g49f6 From f5615d246770a2796e60b06dd5e17f5e79d5dd0c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:21 -0700 Subject: pkt-line: Add strbuf based functions These routines help to work with pkt-line values inside of a strbuf, permitting simple formatting of buffered network messages. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/pkt-line.c b/pkt-line.c index b691abe..bd603f8 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -42,17 +42,19 @@ void packet_flush(int fd) safe_write(fd, "0000", 4); } +void packet_buf_flush(struct strbuf *buf) +{ + strbuf_add(buf, "0000", 4); +} + #define hex(a) (hexchar[(a) & 15]) -void packet_write(int fd, const char *fmt, ...) +static char buffer[1000]; +static unsigned format_packet(const char *fmt, va_list args) { - static char buffer[1000]; static char hexchar[] = "0123456789abcdef"; - va_list args; unsigned n; - va_start(args, fmt); n = vsnprintf(buffer + 4, sizeof(buffer) - 4, fmt, args); - va_end(args); if (n >= sizeof(buffer)-4) die("protocol error: impossibly long line"); n += 4; @@ -60,9 +62,31 @@ void packet_write(int fd, const char *fmt, ...) buffer[1] = hex(n >> 8); buffer[2] = hex(n >> 4); buffer[3] = hex(n); + return n; +} + +void packet_write(int fd, const char *fmt, ...) +{ + va_list args; + unsigned n; + + va_start(args, fmt); + n = format_packet(fmt, args); + va_end(args); safe_write(fd, buffer, n); } +void packet_buf_write(struct strbuf *buf, const char *fmt, ...) +{ + va_list args; + unsigned n; + + va_start(args, fmt); + n = format_packet(fmt, args); + va_end(args); + strbuf_add(buf, buffer, n); +} + static void safe_read(int fd, void *buffer, unsigned size) { ssize_t ret = read_in_full(fd, buffer, size); @@ -72,15 +96,11 @@ static void safe_read(int fd, void *buffer, unsigned size) die("The remote end hung up unexpectedly"); } -int packet_read_line(int fd, char *buffer, unsigned size) +static int packet_length(const char *linelen) { int n; - unsigned len; - char linelen[4]; - - safe_read(fd, linelen, 4); + int len = 0; - len = 0; for (n = 0; n < 4; n++) { unsigned char c = linelen[n]; len <<= 4; @@ -96,8 +116,20 @@ int packet_read_line(int fd, char *buffer, unsigned size) len += c - 'A' + 10; continue; } - die("protocol error: bad line length character"); + return -1; } + return len; +} + +int packet_read_line(int fd, char *buffer, unsigned size) +{ + int len; + char linelen[4]; + + safe_read(fd, linelen, 4); + len = packet_length(linelen); + if (len < 0) + die("protocol error: bad line length character"); if (!len) return 0; len -= 4; @@ -107,3 +139,31 @@ int packet_read_line(int fd, char *buffer, unsigned size) buffer[len] = 0; return len; } + +int packet_get_line(struct strbuf *out, + char **src_buf, size_t *src_len) +{ + int len; + + if (*src_len < 4) + return -1; + len = packet_length(*src_buf); + if (len < 0) + return -1; + if (!len) { + *src_buf += 4; + *src_len -= 4; + return 0; + } + if (*src_len < len) + return -2; + + *src_buf += 4; + *src_len -= 4; + len -= 4; + + strbuf_add(out, *src_buf, len); + *src_buf += len; + *src_len -= len; + return len; +} diff --git a/pkt-line.h b/pkt-line.h index 9df653f..1e5dcfe 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -2,14 +2,18 @@ #define PKTLINE_H #include "git-compat-util.h" +#include "strbuf.h" /* * Silly packetized line writing interface */ void packet_flush(int fd); void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +void packet_buf_flush(struct strbuf *buf); +void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); int packet_read_line(int fd, char *buffer, unsigned size); +int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); ssize_t safe_write(int, const void *, ssize_t); #endif -- cgit v0.10.2-6-g49f6 From 743c4b7b0fa5422d1bb5dc59c6cb919a881dd98b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:22 -0700 Subject: pkt-line: Make packet_read_line easier to debug When there is an error parsing the 4 byte length component we now display it as part of the die message, this may hint as to what data was misunderstood by the application. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/pkt-line.c b/pkt-line.c index bd603f8..295ba2b 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -129,7 +129,7 @@ int packet_read_line(int fd, char *buffer, unsigned size) safe_read(fd, linelen, 4); len = packet_length(linelen); if (len < 0) - die("protocol error: bad line length character"); + die("protocol error: bad line length character: %.4s", linelen); if (!len) return 0; len -= 4; -- cgit v0.10.2-6-g49f6 From edace6f02eeae6f4a06ed1e4f6308703523d8535 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:23 -0700 Subject: fetch-pack: Use a strbuf to compose the want list This change is being offered as a refactoring to make later commits in the smart HTTP series easier. By changing the enabled capabilities to be formatted in a strbuf it is easier to add a new capability to the set of supported capabilities. By formatting the want portion of the request into a strbuf and writing it as a whole block we can later decide to hold onto the req_buf (instead of releasing it) to recycle in stateless communications. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 629735f..783c2b0 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -165,6 +165,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, const unsigned char *sha1; unsigned in_vain = 0; int got_continue = 0; + struct strbuf req_buf = STRBUF_INIT; if (marked) for_each_ref(clear_marks, NULL); @@ -175,6 +176,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, fetching = 0; for ( ; refs ; refs = refs->next) { unsigned char *remote = refs->old_sha1; + const char *remote_hex; struct object *o; /* @@ -192,27 +194,36 @@ static int find_common(int fd[2], unsigned char *result_sha1, continue; } - if (!fetching) - packet_write(fd[1], "want %s%s%s%s%s%s%s%s\n", - sha1_to_hex(remote), - (multi_ack ? " multi_ack" : ""), - (use_sideband == 2 ? " side-band-64k" : ""), - (use_sideband == 1 ? " side-band" : ""), - (args.use_thin_pack ? " thin-pack" : ""), - (args.no_progress ? " no-progress" : ""), - (args.include_tag ? " include-tag" : ""), - (prefer_ofs_delta ? " ofs-delta" : "")); - else - packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); + remote_hex = sha1_to_hex(remote); + if (!fetching) { + struct strbuf c = STRBUF_INIT; + if (multi_ack) strbuf_addstr(&c, " multi_ack"); + if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); + if (use_sideband == 1) strbuf_addstr(&c, " side-band"); + if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack"); + if (args.no_progress) strbuf_addstr(&c, " no-progress"); + if (args.include_tag) strbuf_addstr(&c, " include-tag"); + if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta"); + packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf); + strbuf_release(&c); + } else + packet_buf_write(&req_buf, "want %s\n", remote_hex); fetching++; } + + if (!fetching) { + strbuf_release(&req_buf); + packet_flush(fd[1]); + return 1; + } + if (is_repository_shallow()) - write_shallow_commits(fd[1], 1); + write_shallow_commits(&req_buf, 1); if (args.depth > 0) - packet_write(fd[1], "deepen %d", args.depth); - packet_flush(fd[1]); - if (!fetching) - return 1; + packet_buf_write(&req_buf, "deepen %d", args.depth); + packet_buf_flush(&req_buf); + + safe_write(fd[1], req_buf.buf, req_buf.len); if (args.depth > 0) { char line[1024]; @@ -296,6 +307,8 @@ done: multi_ack = 0; flushes++; } + strbuf_release(&req_buf); + while (flushes || multi_ack) { int ack = get_ack(fd[0], result_sha1); if (ack) { @@ -809,6 +822,7 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, if (args.depth > 0) { struct cache_time mtime; + struct strbuf sb = STRBUF_INIT; char *shallow = git_path("shallow"); int fd; @@ -826,12 +840,14 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, fd = hold_lock_file_for_update(&lock, shallow, LOCK_DIE_ON_ERROR); - if (!write_shallow_commits(fd, 0)) { + if (!write_shallow_commits(&sb, 0) + || write_in_full(fd, sb.buf, sb.len) != sb.len) { unlink_or_warn(shallow); rollback_lock_file(&lock); } else { commit_lock_file(&lock); } + strbuf_release(&sb); } reprepare_packed_git(); diff --git a/commit.c b/commit.c index fedbd5e..471efb05 100644 --- a/commit.c +++ b/commit.c @@ -199,7 +199,7 @@ struct commit_graft *lookup_commit_graft(const unsigned char *sha1) return commit_graft[pos]; } -int write_shallow_commits(int fd, int use_pack_protocol) +int write_shallow_commits(struct strbuf *out, int use_pack_protocol) { int i, count = 0; for (i = 0; i < commit_graft_nr; i++) @@ -208,12 +208,10 @@ int write_shallow_commits(int fd, int use_pack_protocol) sha1_to_hex(commit_graft[i]->sha1); count++; if (use_pack_protocol) - packet_write(fd, "shallow %s", hex); + packet_buf_write(out, "shallow %s", hex); else { - if (write_in_full(fd, hex, 40) != 40) - break; - if (write_str_in_full(fd, "\n") != 1) - break; + strbuf_addstr(out, hex); + strbuf_addch(out, '\n'); } } return count; diff --git a/commit.h b/commit.h index f4fc5c5..817c75c 100644 --- a/commit.h +++ b/commit.h @@ -131,7 +131,7 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); extern int register_shallow(const unsigned char *sha1); extern int unregister_shallow(const unsigned char *sha1); -extern int write_shallow_commits(int fd, int use_pack_protocol); +extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol); extern int is_repository_shallow(void); extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); -- cgit v0.10.2-6-g49f6 From 28754ab5f0ce9b4f6ca1641c3e10e2c68bd9b3fc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:24 -0700 Subject: Move "get_ack()" back to fetch-pack In 41cb7488 Linus moved this function to connect.c for reuse inside of the git-clone-pack command. That was 2005, but in 2006 Junio retired git-clone-pack in commit efc7fa53. Since then the only caller has been fetch-pack. Since this ACK/NAK exchange is only used by the fetch-pack/upload-pack protocol we should move it back to be a private detail of fetch-pack. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 783c2b0..7c09d46 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -157,6 +157,27 @@ static const unsigned char *get_rev(void) return commit->object.sha1; } +static int get_ack(int fd, unsigned char *result_sha1) +{ + static char line[1000]; + int len = packet_read_line(fd, line, sizeof(line)); + + if (!len) + die("git fetch-pack: expected ACK/NAK, got EOF"); + if (line[len-1] == '\n') + line[--len] = 0; + if (!strcmp(line, "NAK")) + return 0; + if (!prefixcmp(line, "ACK ")) { + if (!get_sha1_hex(line+4, result_sha1)) { + if (strstr(line+45, "continue")) + return 2; + return 1; + } + } + die("git fetch_pack: expected ACK/NAK, got '%s'", line); +} + static int find_common(int fd[2], unsigned char *result_sha1, struct ref *refs) { diff --git a/cache.h b/cache.h index a5eeead..4e283be 100644 --- a/cache.h +++ b/cache.h @@ -856,7 +856,6 @@ extern struct ref *find_ref_by_name(const struct ref *list, const char *name); extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); extern int finish_connect(struct child_process *conn); extern int path_match(const char *path, int nr, char **match); -extern int get_ack(int fd, unsigned char *result_sha1); struct extra_have_objects { int nr, alloc; unsigned char (*array)[20]; diff --git a/connect.c b/connect.c index 7945e38..839a103 100644 --- a/connect.c +++ b/connect.c @@ -107,27 +107,6 @@ int server_supports(const char *feature) strstr(server_capabilities, feature) != NULL; } -int get_ack(int fd, unsigned char *result_sha1) -{ - static char line[1000]; - int len = packet_read_line(fd, line, sizeof(line)); - - if (!len) - die("git fetch-pack: expected ACK/NAK, got EOF"); - if (line[len-1] == '\n') - line[--len] = 0; - if (!strcmp(line, "NAK")) - return 0; - if (!prefixcmp(line, "ACK ")) { - if (!get_sha1_hex(line+4, result_sha1)) { - if (strstr(line+45, "continue")) - return 2; - return 1; - } - } - die("git fetch_pack: expected ACK/NAK, got '%s'", line); -} - int path_match(const char *path, int nr, char **match) { int i; -- cgit v0.10.2-6-g49f6 From 78affc49ded26700450d25168534af02b29a6c61 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:25 -0700 Subject: Add multi_ack_detailed capability to fetch-pack/upload-pack When multi_ack_detailed is enabled the ACK continue messages returned by the remote upload-pack are broken out to describe the different states within the peer. This permits the client to better understand the server's in-memory state. The fetch-pack/upload-pack protocol now looks like: NAK --------------------------------- Always sent in response to "done" if there was no common base selected from the "have" lines (or no have lines were sent). * no multi_ack or multi_ack_detailed: Sent when the client has sent a pkt-line flush ("0000") and the server has not yet found a common base object. * either multi_ack or multi_ack_detailed: Always sent in response to a pkt-line flush. ACK %s ----------------------------------- * no multi_ack or multi_ack_detailed: Sent in response to "have" when the object exists on the remote side and is therefore an object in common between the peers. The argument is the SHA-1 of the common object. * either multi_ack or multi_ack_detailed: Sent in response to "done" if there are common objects. The argument is the last SHA-1 determined to be common. ACK %s continue ----------------------------------- * multi_ack only: Sent in response to "have". The remote side wants the client to consider this object as common, and immediately stop transmitting additional "have" lines for objects that are reachable from it. The reason the client should stop is not given, but is one of the two cases below available under multi_ack_detailed. ACK %s common ----------------------------------- * multi_ack_detailed only: Sent in response to "have". Both sides have this object. Like with "ACK %s continue" above the client should stop sending have lines reachable for objects from the argument. ACK %s ready ----------------------------------- * multi_ack_detailed only: Sent in response to "have". The client should stop transmitting objects which are reachable from the argument, and send "done" soon to get the objects. If the remote side has the specified object, it should first send an "ACK %s common" message prior to sending "ACK %s ready". Clients may still submit additional "have" lines if there are more side branches for the client to explore that might be added to the common set and reduce the number of objects to transfer. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 7c09d46..615f549 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -157,7 +157,15 @@ static const unsigned char *get_rev(void) return commit->object.sha1; } -static int get_ack(int fd, unsigned char *result_sha1) +enum ack_type { + NAK = 0, + ACK, + ACK_continue, + ACK_common, + ACK_ready +}; + +static enum ack_type get_ack(int fd, unsigned char *result_sha1) { static char line[1000]; int len = packet_read_line(fd, line, sizeof(line)); @@ -167,12 +175,16 @@ static int get_ack(int fd, unsigned char *result_sha1) if (line[len-1] == '\n') line[--len] = 0; if (!strcmp(line, "NAK")) - return 0; + return NAK; if (!prefixcmp(line, "ACK ")) { if (!get_sha1_hex(line+4, result_sha1)) { if (strstr(line+45, "continue")) - return 2; - return 1; + return ACK_continue; + if (strstr(line+45, "common")) + return ACK_common; + if (strstr(line+45, "ready")) + return ACK_ready; + return ACK; } } die("git fetch_pack: expected ACK/NAK, got '%s'", line); @@ -218,7 +230,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, remote_hex = sha1_to_hex(remote); if (!fetching) { struct strbuf c = STRBUF_INIT; - if (multi_ack) strbuf_addstr(&c, " multi_ack"); + if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); + if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); if (use_sideband == 1) strbuf_addstr(&c, " side-band"); if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack"); @@ -298,18 +311,23 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (args.verbose && ack) fprintf(stderr, "got ack %d %s\n", ack, sha1_to_hex(result_sha1)); - if (ack == 1) { + switch (ack) { + case ACK: flushes = 0; multi_ack = 0; retval = 0; goto done; - } else if (ack == 2) { + case ACK_common: + case ACK_ready: + case ACK_continue: { struct commit *commit = lookup_commit(result_sha1); mark_common(commit, 0, 1); retval = 0; in_vain = 0; got_continue = 1; + break; + } } } while (ack); flushes--; @@ -336,7 +354,7 @@ done: if (args.verbose) fprintf(stderr, "got ack (%d) %s\n", ack, sha1_to_hex(result_sha1)); - if (ack == 1) + if (ack == ACK) return 0; multi_ack = 1; continue; @@ -618,7 +636,12 @@ static struct ref *do_fetch_pack(int fd[2], if (is_repository_shallow() && !server_supports("shallow")) die("Server does not support shallow clients"); - if (server_supports("multi_ack")) { + if (server_supports("multi_ack_detailed")) { + if (args.verbose) + fprintf(stderr, "Server supports multi_ack_detailed\n"); + multi_ack = 2; + } + else if (server_supports("multi_ack")) { if (args.verbose) fprintf(stderr, "Server supports multi_ack\n"); multi_ack = 1; diff --git a/upload-pack.c b/upload-pack.c index 38ddac2..f1dc3a3 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -498,7 +498,7 @@ static int get_common_commits(void) { static char line[1000]; unsigned char sha1[20]; - char hex[41], last_hex[41]; + char last_hex[41]; save_commit_buffer = 0; @@ -515,19 +515,22 @@ static int get_common_commits(void) if (!prefixcmp(line, "have ")) { switch (got_sha1(line+5, sha1)) { case -1: /* they have what we do not */ - if (multi_ack && ok_to_give_up()) - packet_write(1, "ACK %s continue\n", - sha1_to_hex(sha1)); + if (multi_ack && ok_to_give_up()) { + const char *hex = sha1_to_hex(sha1); + if (multi_ack == 2) + packet_write(1, "ACK %s ready\n", hex); + else + packet_write(1, "ACK %s continue\n", hex); + } break; default: - memcpy(hex, sha1_to_hex(sha1), 41); - if (multi_ack) { - const char *msg = "ACK %s continue\n"; - packet_write(1, msg, hex); - memcpy(last_hex, hex, 41); - } + memcpy(last_hex, sha1_to_hex(sha1), 41); + if (multi_ack == 2) + packet_write(1, "ACK %s common\n", last_hex); + else if (multi_ack) + packet_write(1, "ACK %s continue\n", last_hex); else if (have_obj.nr == 1) - packet_write(1, "ACK %s\n", hex); + packet_write(1, "ACK %s\n", last_hex); break; } continue; @@ -587,7 +590,9 @@ static void receive_needs(void) get_sha1_hex(line+5, sha1_buf)) die("git upload-pack: protocol error, " "expected to get sha, not '%s'", line); - if (strstr(line+45, "multi_ack")) + if (strstr(line+45, "multi_ack_detailed")) + multi_ack = 2; + else if (strstr(line+45, "multi_ack")) multi_ack = 1; if (strstr(line+45, "thin-pack")) use_thin_pack = 1; @@ -681,7 +686,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo { static const char *capabilities = "multi_ack thin-pack side-band" " side-band-64k ofs-delta shallow no-progress" - " include-tag"; + " include-tag multi_ack_detailed"; struct object *o = parse_object(sha1); if (!o) -- cgit v0.10.2-6-g49f6 From 37a8768f83d5932ca66202f9cc4977c20b022e17 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:26 -0700 Subject: remote-curl: Refactor walker initialization We will need the walker, url and remote in other functions as the code grows larger to support smart HTTP. Extract this out into a set of globals we can easily reference once configured. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano diff --git a/remote-curl.c b/remote-curl.c index 2faf1c6..478f3ea 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -5,7 +5,17 @@ #include "http.h" #include "exec_cmd.h" -static struct ref *get_refs(struct walker *walker, const char *url) +static struct remote *remote; +static const char *url; +static struct walker *walker; + +static void init_walker(void) +{ + if (!walker) + walker = get_http_walker(url, remote); +} + +static struct ref *get_refs(void) { struct strbuf buffer = STRBUF_INIT; char *data, *start, *mid; @@ -21,6 +31,7 @@ static struct ref *get_refs(struct walker *walker, const char *url) refs_url = xmalloc(strlen(url) + 11); sprintf(refs_url, "%s/info/refs", url); + init_walker(); http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); switch (http_ret) { case HTTP_OK: @@ -78,10 +89,7 @@ static struct ref *get_refs(struct walker *walker, const char *url) int main(int argc, const char **argv) { - struct remote *remote; struct strbuf buf = STRBUF_INIT; - const char *url; - struct walker *walker = NULL; git_extract_argv0_path(argv[0]); setup_git_directory(); @@ -103,8 +111,7 @@ int main(int argc, const char **argv) break; if (!prefixcmp(buf.buf, "fetch ")) { char *obj = buf.buf + strlen("fetch "); - if (!walker) - walker = get_http_walker(url, remote); + init_walker(); walker->get_all = 1; walker->get_tree = 1; walker->get_history = 1; @@ -115,11 +122,8 @@ int main(int argc, const char **argv) printf("\n"); fflush(stdout); } else if (!strcmp(buf.buf, "list")) { - struct ref *refs; + struct ref *refs = get_refs(); struct ref *posn; - if (!walker) - walker = get_http_walker(url, remote); - refs = get_refs(walker, url); for (posn = refs; posn; posn = posn->next) { if (posn->symref) printf("@%s %s\n", posn->symref, posn->name); -- cgit v0.10.2-6-g49f6 From cff7123c11aa2b1a849a46028d60b4bc0ab54c51 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:27 -0700 Subject: fetch: Allow transport -v -v -v to set verbosity to 3 Helpers might want a higher level of verbosity than just +1 (the porcelain default setting) and +2 (-v -v). Expand the field to allow verbosity in the range -1..3. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano diff --git a/builtin-fetch.c b/builtin-fetch.c index cb48c57..52a9a42 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -665,7 +665,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) transport = transport_get(remote, remote->url[0]); if (verbosity >= 2) - transport->verbose = 1; + transport->verbose = verbosity <= 3 ? verbosity : 3; if (verbosity < 0) transport->verbose = -1; if (upload_pack) diff --git a/transport.h b/transport.h index c14da6f..e4e6177 100644 --- a/transport.h +++ b/transport.h @@ -25,7 +25,7 @@ struct transport { int (*disconnect)(struct transport *connection); char *pack_lockfile; - signed verbose : 2; + signed verbose : 3; /* Force progress even if the output is not a tty */ unsigned progress : 1; }; -- cgit v0.10.2-6-g49f6 From 292ce46b60e2c12450c5c21044acf9c41bd837df Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:28 -0700 Subject: remote-helpers: Fetch more than one ref in a batch Some network protocols (e.g. native git://) are able to fetch more than one ref at a time and reduce the overall transfer cost by combining the requests into a single exchange. Instead of feeding each fetch request one at a time to the helper, feed all of them at once so the helper can decide whether or not it should batch them. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 173ee23..e44d821 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -36,10 +36,16 @@ Commands are given by the caller on the helper's standard input, one per line. complete list, outputs a blank line. 'fetch' :: - Fetches the given object, writing the necessary objects to the - database. Outputs a blank line when the fetch is - complete. Only objects which were reported in the ref list - with a sha1 may be fetched this way. + Fetches the given object, writing the necessary objects + to the database. Fetch commands are sent in a batch, one + per line, and the batch is terminated with a blank line. + Outputs a single blank line when all fetch commands in the + same batch are complete. Only objects which were reported + in the ref list with a sha1 may be fetched this way. ++ +Optionally may output a 'lock ' line indicating a file under +GIT_DIR/objects/pack which is keeping a pack until refs can be +suitably updated. + Supported if the helper has the "fetch" capability. diff --git a/remote-curl.c b/remote-curl.c index 478f3ea..22cd5c5 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -87,6 +87,81 @@ static struct ref *get_refs(void) return refs; } +static int fetch_dumb(int nr_heads, struct ref **to_fetch) +{ + char **targets = xmalloc(nr_heads * sizeof(char*)); + int ret, i; + + for (i = 0; i < nr_heads; i++) + targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); + + init_walker(); + walker->get_all = 1; + walker->get_tree = 1; + walker->get_history = 1; + walker->get_verbosely = 0; + walker->get_recover = 0; + ret = walker_fetch(walker, nr_heads, targets, NULL, NULL); + + for (i = 0; i < nr_heads; i++) + free(targets[i]); + free(targets); + + return ret ? error("Fetch failed.") : 0; +} + +static void parse_fetch(struct strbuf *buf) +{ + struct ref **to_fetch = NULL; + struct ref *list_head = NULL; + struct ref **list = &list_head; + int alloc_heads = 0, nr_heads = 0; + + do { + if (!prefixcmp(buf->buf, "fetch ")) { + char *p = buf->buf + strlen("fetch "); + char *name; + struct ref *ref; + unsigned char old_sha1[20]; + + if (strlen(p) < 40 || get_sha1_hex(p, old_sha1)) + die("protocol error: expected sha/ref, got %s'", p); + if (p[40] == ' ') + name = p + 41; + else if (!p[40]) + name = ""; + else + die("protocol error: expected sha/ref, got %s'", p); + + ref = alloc_ref(name); + hashcpy(ref->old_sha1, old_sha1); + + *list = ref; + list = &ref->next; + + ALLOC_GROW(to_fetch, nr_heads + 1, alloc_heads); + to_fetch[nr_heads++] = ref; + } + else + die("http transport does not support %s", buf->buf); + + strbuf_reset(buf); + if (strbuf_getline(buf, stdin, '\n') == EOF) + return; + if (!*buf->buf) + break; + } while (1); + + if (fetch_dumb(nr_heads, to_fetch)) + exit(128); /* error already reported */ + free_refs(list_head); + free(to_fetch); + + printf("\n"); + fflush(stdout); + strbuf_reset(buf); +} + int main(int argc, const char **argv) { struct strbuf buf = STRBUF_INIT; @@ -110,17 +185,8 @@ int main(int argc, const char **argv) if (strbuf_getline(&buf, stdin, '\n') == EOF) break; if (!prefixcmp(buf.buf, "fetch ")) { - char *obj = buf.buf + strlen("fetch "); - init_walker(); - walker->get_all = 1; - walker->get_tree = 1; - walker->get_history = 1; - walker->get_verbosely = 0; - walker->get_recover = 0; - if (walker_fetch(walker, 1, &obj, NULL, NULL)) - die("Fetch failed."); - printf("\n"); - fflush(stdout); + parse_fetch(&buf); + } else if (!strcmp(buf.buf, "list")) { struct ref *refs = get_refs(); struct ref *posn; diff --git a/transport-helper.c b/transport-helper.c index f57e84c..9de3408 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -10,6 +10,7 @@ struct helper_data { const char *name; struct child_process *helper; + FILE *out; unsigned fetch : 1; }; @@ -18,7 +19,6 @@ static struct child_process *get_helper(struct transport *transport) struct helper_data *data = transport->data; struct strbuf buf = STRBUF_INIT; struct child_process *helper; - FILE *file; if (data->helper) return data->helper; @@ -39,9 +39,9 @@ static struct child_process *get_helper(struct transport *transport) write_str_in_full(helper->in, "capabilities\n"); - file = xfdopen(helper->out, "r"); + data->out = xfdopen(helper->out, "r"); while (1) { - if (strbuf_getline(&buf, file, '\n') == EOF) + if (strbuf_getline(&buf, data->out, '\n') == EOF) exit(128); /* child died, message supplied already */ if (!*buf.buf) @@ -58,6 +58,7 @@ static int disconnect_helper(struct transport *transport) if (data->helper) { write_str_in_full(data->helper->in, "\n"); close(data->helper->in); + fclose(data->out); finish_command(data->helper); free((char *)data->helper->argv[0]); free(data->helper->argv); @@ -70,8 +71,7 @@ static int disconnect_helper(struct transport *transport) static int fetch_with_fetch(struct transport *transport, int nr_heads, const struct ref **to_fetch) { - struct child_process *helper = get_helper(transport); - FILE *file = xfdopen(helper->out, "r"); + struct helper_data *data = transport->data; int i; struct strbuf buf = STRBUF_INIT; @@ -82,12 +82,30 @@ static int fetch_with_fetch(struct transport *transport, strbuf_addf(&buf, "fetch %s %s\n", sha1_to_hex(posn->old_sha1), posn->name); - write_in_full(helper->in, buf.buf, buf.len); - strbuf_reset(&buf); + } - if (strbuf_getline(&buf, file, '\n') == EOF) + strbuf_addch(&buf, '\n'); + if (write_in_full(data->helper->in, buf.buf, buf.len) != buf.len) + die_errno("cannot send fetch to %s", data->name); + + while (1) { + strbuf_reset(&buf); + if (strbuf_getline(&buf, data->out, '\n') == EOF) exit(128); /* child died, message supplied already */ + + if (!prefixcmp(buf.buf, "lock ")) { + const char *name = buf.buf + 5; + if (transport->pack_lockfile) + warning("%s also locked %s", data->name, name); + else + transport->pack_lockfile = xstrdup(name); + } + else if (!buf.len) + break; + else + warning("%s unexpectedly said: '%s'", data->name, buf.buf); } + strbuf_release(&buf); return 0; } @@ -113,21 +131,20 @@ static int fetch(struct transport *transport, static struct ref *get_refs_list(struct transport *transport, int for_push) { + struct helper_data *data = transport->data; struct child_process *helper; struct ref *ret = NULL; struct ref **tail = &ret; struct ref *posn; struct strbuf buf = STRBUF_INIT; - FILE *file; helper = get_helper(transport); write_str_in_full(helper->in, "list\n"); - file = xfdopen(helper->out, "r"); while (1) { char *eov, *eon; - if (strbuf_getline(&buf, file, '\n') == EOF) + if (strbuf_getline(&buf, data->out, '\n') == EOF) exit(128); /* child died, message supplied already */ if (!*buf.buf) -- cgit v0.10.2-6-g49f6 From ef08ef9ea0a271e5be5844408d2496a946d6e8d9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:29 -0700 Subject: remote-helpers: Support custom transport options Some transports, like the native pack transport implemented by fetch-pack, support useful features like depth or include tags. These should be exposed if the underlying helper knows how to use them. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index e44d821..1133f04 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -35,6 +35,16 @@ Commands are given by the caller on the helper's standard input, one per line. the name; unrecognized attributes are ignored. After the complete list, outputs a blank line. +'option' :: + Set the transport helper option to . Outputs a + single line containing one of 'ok' (option successfully set), + 'unsupported' (option not recognized) or 'error ' + (option is supported but is not correct + for it). Options should be set before other commands, + and may how those commands behave. ++ +Supported if the helper has the "option" capability. + 'fetch' :: Fetches the given object, writing the necessary objects to the database. Fetch commands are sent in a batch, one @@ -63,11 +73,39 @@ CAPABILITIES 'fetch':: This helper supports the 'fetch' command. +'option':: + This helper supports the option command. + REF LIST ATTRIBUTES ------------------- None are defined yet, but the caller must accept any which are supplied. +OPTIONS +------- +'option verbosity' :: + Change the level of messages displayed by the helper. + When N is 0 the end-user has asked the process to be + quiet, and the helper should produce only error output. + N of 1 is the default level of verbosity, higher values + of N correspond to the number of -v flags passed on the + command line. + +'option progress' \{'true'|'false'\}:: + Enable (or disable) progress messages displayed by the + transport helper during a command. + +'option depth' :: + Deepen the history of a shallow repository. + +'option followtags' \{'true'|'false'\}:: + If enabled the helper should automatically fetch annotated + tag objects if the object the tag points at was transferred + during the fetch command. If the tag is not fetched by + the helper a second fetch command will usually be sent to + ask for the tag specifically. Some helpers may be able to + use this option to avoid a second network connection. + Documentation ------------- Documentation by Daniel Barkalow. diff --git a/remote-curl.c b/remote-curl.c index 22cd5c5..0951f11 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -9,12 +9,61 @@ static struct remote *remote; static const char *url; static struct walker *walker; +struct options { + int verbosity; + unsigned long depth; + unsigned progress : 1, + followtags : 1; +}; +static struct options options; + static void init_walker(void) { if (!walker) walker = get_http_walker(url, remote); } +static int set_option(const char *name, const char *value) +{ + if (!strcmp(name, "verbosity")) { + char *end; + int v = strtol(value, &end, 10); + if (value == end || *end) + return -1; + options.verbosity = v; + return 0; + } + else if (!strcmp(name, "progress")) { + if (!strcmp(value, "true")) + options.progress = 1; + else if (!strcmp(value, "false")) + options.progress = 0; + else + return -1; + return 1 /* TODO implement later */; + } + else if (!strcmp(name, "depth")) { + char *end; + unsigned long v = strtoul(value, &end, 10); + if (value == end || *end) + return -1; + options.depth = v; + return 1 /* TODO implement later */; + } + else if (!strcmp(name, "followtags")) { + if (!strcmp(value, "true")) + options.followtags = 1; + else if (!strcmp(value, "false")) + options.followtags = 0; + else + return -1; + return 1 /* TODO implement later */; + } + else { + return 1 /* unsupported */; + } +} + static struct ref *get_refs(void) { struct strbuf buffer = STRBUF_INIT; @@ -99,7 +148,7 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) walker->get_all = 1; walker->get_tree = 1; walker->get_history = 1; - walker->get_verbosely = 0; + walker->get_verbosely = options.verbosity >= 3; walker->get_recover = 0; ret = walker_fetch(walker, nr_heads, targets, NULL, NULL); @@ -173,6 +222,9 @@ int main(int argc, const char **argv) return 1; } + options.verbosity = 1; + options.progress = !!isatty(2); + remote = remote_get(argv[1]); if (argc > 2) { @@ -198,8 +250,28 @@ int main(int argc, const char **argv) } printf("\n"); fflush(stdout); + } else if (!prefixcmp(buf.buf, "option ")) { + char *name = buf.buf + strlen("option "); + char *value = strchr(name, ' '); + int result; + + if (value) + *value++ = '\0'; + else + value = "true"; + + result = set_option(name, value); + if (!result) + printf("ok\n"); + else if (result < 0) + printf("error invalid value\n"); + else + printf("unsupported\n"); + fflush(stdout); + } else if (!strcmp(buf.buf, "capabilities")) { printf("fetch\n"); + printf("option\n"); printf("\n"); fflush(stdout); } else { diff --git a/transport-helper.c b/transport-helper.c index 9de3408..577abc6 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -5,13 +5,15 @@ #include "commit.h" #include "diff.h" #include "revision.h" +#include "quote.h" struct helper_data { const char *name; struct child_process *helper; FILE *out; - unsigned fetch : 1; + unsigned fetch : 1, + option : 1; }; static struct child_process *get_helper(struct transport *transport) @@ -48,6 +50,8 @@ static struct child_process *get_helper(struct transport *transport) break; if (!strcmp(buf.buf, "fetch")) data->fetch = 1; + if (!strcmp(buf.buf, "option")) + data->option = 1; } return data->helper; } @@ -65,9 +69,88 @@ static int disconnect_helper(struct transport *transport) free(data->helper); data->helper = NULL; } + free(data); return 0; } +static const char *unsupported_options[] = { + TRANS_OPT_UPLOADPACK, + TRANS_OPT_RECEIVEPACK, + TRANS_OPT_THIN, + TRANS_OPT_KEEP + }; +static const char *boolean_options[] = { + TRANS_OPT_THIN, + TRANS_OPT_KEEP, + TRANS_OPT_FOLLOWTAGS + }; + +static int set_helper_option(struct transport *transport, + const char *name, const char *value) +{ + struct helper_data *data = transport->data; + struct child_process *helper = get_helper(transport); + struct strbuf buf = STRBUF_INIT; + int i, ret, is_bool = 0; + + if (!data->option) + return 1; + + for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) { + if (!strcmp(name, unsupported_options[i])) + return 1; + } + + for (i = 0; i < ARRAY_SIZE(boolean_options); i++) { + if (!strcmp(name, boolean_options[i])) { + is_bool = 1; + break; + } + } + + strbuf_addf(&buf, "option %s ", name); + if (is_bool) + strbuf_addstr(&buf, value ? "true" : "false"); + else + quote_c_style(value, &buf, NULL, 0); + strbuf_addch(&buf, '\n'); + + if (write_in_full(helper->in, buf.buf, buf.len) != buf.len) + die_errno("cannot send option to %s", data->name); + + strbuf_reset(&buf); + if (strbuf_getline(&buf, data->out, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!strcmp(buf.buf, "ok")) + ret = 0; + else if (!prefixcmp(buf.buf, "error")) { + ret = -1; + } else if (!strcmp(buf.buf, "unsupported")) + ret = 1; + else { + warning("%s unexpectedly said: '%s'", data->name, buf.buf); + ret = 1; + } + strbuf_release(&buf); + return ret; +} + +static void standard_options(struct transport *t) +{ + char buf[16]; + int n; + int v = t->verbose; + int no_progress = v < 0 || (!t->progress && !isatty(1)); + + set_helper_option(t, "progress", !no_progress ? "true" : "false"); + + n = snprintf(buf, sizeof(buf), "%d", v + 1); + if (n >= sizeof(buf)) + die("impossibly large verbosity value"); + set_helper_option(t, "verbosity", buf); +} + static int fetch_with_fetch(struct transport *transport, int nr_heads, const struct ref **to_fetch) { @@ -75,6 +158,8 @@ static int fetch_with_fetch(struct transport *transport, int i; struct strbuf buf = STRBUF_INIT; + standard_options(transport); + for (i = 0; i < nr_heads; i++) { const struct ref *posn = to_fetch[i]; if (posn->status & REF_STATUS_UPTODATE) @@ -178,6 +263,7 @@ int transport_helper_init(struct transport *transport, const char *name) data->name = name; transport->data = data; + transport->set_option = set_helper_option; transport->get_refs_list = get_refs_list; transport->fetch = fetch; transport->disconnect = disconnect_helper; -- cgit v0.10.2-6-g49f6 From ae4efe195752c27cb25fca9451852c0f4eebdb28 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:30 -0700 Subject: Move WebDAV HTTP push under remote-curl The remote helper interface now supports the push capability, which can be used to ask the implementation to push one or more specs to the remote repository. For remote-curl we implement this by calling the existing WebDAV based git-http-push executable. Internally the helper interface uses the push_refs transport hook so that the complexity of the refspec parsing and matching can be reused between remote implementations. When possible however the helper protocol uses source ref name rather than the source SHA-1, thereby allowing the helper to access this name if it is useful. >From Clemens Buchacher : update http tests according to remote-curl capabilities o Pushing packed refs is now fixed. o The transport helper fails if refs are already up-to-date. Add a test for that. o The transport helper will notice if refs are already up-to-date. We therefore need to update server info in the unpacked-refs test. o The transport helper will purge deleted branches automatically. o Use a variable ($ORIG_HEAD) instead of full SHA-1 name. Signed-off-by: Tay Ray Chuan Signed-off-by: Clemens Buchacher Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow CC: Mike Hommey Signed-off-by: Junio C Hamano diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 1133f04..8beb42d 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -34,6 +34,10 @@ Commands are given by the caller on the helper's standard input, one per line. value of the ref. A space-separated list of attributes follows the name; unrecognized attributes are ignored. After the complete list, outputs a blank line. ++ +If 'push' is supported this may be called as 'list for-push' +to obtain the current refs prior to sending one or more 'push' +commands to the helper. 'option' :: Set the transport helper option to . Outputs a @@ -59,6 +63,22 @@ suitably updated. + Supported if the helper has the "fetch" capability. +'push' +::: + Pushes the given commit or branch locally to the + remote branch described by . A batch sequence of + one or more push commands is terminated with a blank line. ++ +Zero or more protocol options may be entered after the last 'push' +command, before the batch's terminating blank line. ++ +When the push is complete, outputs one or more 'ok ' or +'error ?' lines to indicate success or failure of +each pushed ref. The status report output is terminated by +a blank line. The option field may be quoted in a C +style string if it contains an LF. ++ +Supported if the helper has the "push" capability. + If a fatal error occurs, the program writes the error message to stderr and exits. The caller should expect that a suitable error message has been printed if the child closes the connection without @@ -76,10 +96,16 @@ CAPABILITIES 'option':: This helper supports the option command. +'push':: + This helper supports the 'push' command. + REF LIST ATTRIBUTES ------------------- -None are defined yet, but the caller must accept any which are supplied. +'for-push':: + The caller wants to use the ref list to prepare push + commands. A helper might chose to acquire the ref list by + opening a different type of connection to the destination. OPTIONS ------- @@ -106,6 +132,11 @@ OPTIONS ask for the tag specifically. Some helpers may be able to use this option to avoid a second network connection. +'option dry-run' \{'true'|'false'\}: + If true, pretend the operation completed successfully, + but don't actually change any repository data. For most + helpers this only applies to the 'push', if supported. + Documentation ------------- Documentation by Daniel Barkalow. diff --git a/http-push.c b/http-push.c index cc5d4b8..f10803a 100644 --- a/http-push.c +++ b/http-push.c @@ -78,6 +78,7 @@ static int push_verbosely; static int push_all = MATCH_REFS_NONE; static int force_all; static int dry_run; +static int helper_status; static struct object_list *objects; @@ -1813,6 +1814,10 @@ int main(int argc, char **argv) dry_run = 1; continue; } + if (!strcmp(arg, "--helper-status")) { + helper_status = 1; + continue; + } if (!strcmp(arg, "--verbose")) { push_verbosely = 1; http_is_verbose = 1; @@ -1911,9 +1916,12 @@ int main(int argc, char **argv) /* Remove a remote branch if -d or -D was specified */ if (delete_branch) { - if (delete_remote_branch(refspec[0], force_delete) == -1) + if (delete_remote_branch(refspec[0], force_delete) == -1) { fprintf(stderr, "Unable to delete remote branch %s\n", refspec[0]); + if (helper_status) + printf("error %s cannot remove\n", refspec[0]); + } goto cleanup; } @@ -1925,6 +1933,8 @@ int main(int argc, char **argv) } if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n"); + if (helper_status) + printf("error null no match\n"); rc = 0; goto cleanup; } @@ -1942,8 +1952,12 @@ int main(int argc, char **argv) if (is_null_sha1(ref->peer_ref->new_sha1)) { if (delete_remote_branch(ref->name, 1) == -1) { error("Could not remove %s", ref->name); + if (helper_status) + printf("error %s cannot remove\n", ref->name); rc = -4; } + else if (helper_status) + printf("ok %s\n", ref->name); new_refs++; continue; } @@ -1951,6 +1965,8 @@ int main(int argc, char **argv) if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (push_verbosely || 1) fprintf(stderr, "'%s': up-to-date\n", ref->name); + if (helper_status) + printf("ok %s up to date\n", ref->name); continue; } @@ -1974,6 +1990,8 @@ int main(int argc, char **argv) "need to pull first?", ref->name, ref->peer_ref->name); + if (helper_status) + printf("error %s non-fast forward\n", ref->name); rc = -2; continue; } @@ -1987,14 +2005,19 @@ int main(int argc, char **argv) if (strcmp(ref->name, ref->peer_ref->name)) fprintf(stderr, " using '%s'", ref->peer_ref->name); fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex); - if (dry_run) + if (dry_run) { + if (helper_status) + printf("ok %s\n", ref->name); continue; + } /* Lock remote branch ref */ ref_lock = lock_remote(ref->name, LOCK_TIME); if (ref_lock == NULL) { fprintf(stderr, "Unable to lock remote branch %s\n", ref->name); + if (helper_status) + printf("error %s lock error\n", ref->name); rc = 1; continue; } @@ -2045,6 +2068,8 @@ int main(int argc, char **argv) if (!rc) fprintf(stderr, " done\n"); + if (helper_status) + printf("%s %s\n", !rc ? "ok" : "error", ref->name); unlock_remote(ref_lock); check_locks(); } diff --git a/remote-curl.c b/remote-curl.c index 0951f11..5c9dd97 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -4,6 +4,7 @@ #include "walker.h" #include "http.h" #include "exec_cmd.h" +#include "run-command.h" static struct remote *remote; static const char *url; @@ -13,7 +14,8 @@ struct options { int verbosity; unsigned long depth; unsigned progress : 1, - followtags : 1; + followtags : 1, + dry_run : 1; }; static struct options options; @@ -59,6 +61,15 @@ static int set_option(const char *name, const char *value) return -1; return 1 /* TODO implement later */; } + else if (!strcmp(name, "dry-run")) { + if (!strcmp(value, "true")) + options.dry_run = 1; + else if (!strcmp(value, "false")) + options.dry_run = 0; + else + return -1; + return 0; + } else { return 1 /* unsupported */; } @@ -136,6 +147,20 @@ static struct ref *get_refs(void) return refs; } +static void output_refs(struct ref *refs) +{ + struct ref *posn; + for (posn = refs; posn; posn = posn->next) { + if (posn->symref) + printf("@%s %s\n", posn->symref, posn->name); + else + printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name); + } + printf("\n"); + fflush(stdout); + free_refs(refs); +} + static int fetch_dumb(int nr_heads, struct ref **to_fetch) { char **targets = xmalloc(nr_heads * sizeof(char*)); @@ -211,6 +236,58 @@ static void parse_fetch(struct strbuf *buf) strbuf_reset(buf); } +static int push_dav(int nr_spec, char **specs) +{ + const char **argv = xmalloc((10 + nr_spec) * sizeof(char*)); + int argc = 0, i; + + argv[argc++] = "http-push"; + argv[argc++] = "--helper-status"; + if (options.dry_run) + argv[argc++] = "--dry-run"; + if (options.verbosity > 1) + argv[argc++] = "--verbose"; + argv[argc++] = url; + for (i = 0; i < nr_spec; i++) + argv[argc++] = specs[i]; + argv[argc++] = NULL; + + if (run_command_v_opt(argv, RUN_GIT_CMD)) + die("git-%s failed", argv[0]); + free(argv); + return 0; +} + +static void parse_push(struct strbuf *buf) +{ + char **specs = NULL; + int alloc_spec = 0, nr_spec = 0, i; + + do { + if (!prefixcmp(buf->buf, "push ")) { + ALLOC_GROW(specs, nr_spec + 1, alloc_spec); + specs[nr_spec++] = xstrdup(buf->buf + 5); + } + else + die("http transport does not support %s", buf->buf); + + strbuf_reset(buf); + if (strbuf_getline(buf, stdin, '\n') == EOF) + return; + if (!*buf->buf) + break; + } while (1); + + if (push_dav(nr_spec, specs)) + exit(128); /* error already reported */ + for (i = 0; i < nr_spec; i++) + free(specs[i]); + free(specs); + + printf("\n"); + fflush(stdout); +} + int main(int argc, const char **argv) { struct strbuf buf = STRBUF_INIT; @@ -239,17 +316,12 @@ int main(int argc, const char **argv) if (!prefixcmp(buf.buf, "fetch ")) { parse_fetch(&buf); - } else if (!strcmp(buf.buf, "list")) { - struct ref *refs = get_refs(); - struct ref *posn; - for (posn = refs; posn; posn = posn->next) { - if (posn->symref) - printf("@%s %s\n", posn->symref, posn->name); - else - printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name); - } - printf("\n"); - fflush(stdout); + } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) { + output_refs(get_refs()); + + } else if (!prefixcmp(buf.buf, "push ")) { + parse_push(&buf); + } else if (!prefixcmp(buf.buf, "option ")) { char *name = buf.buf + strlen("option "); char *value = strchr(name, ' '); @@ -272,6 +344,7 @@ int main(int argc, const char **argv) } else if (!strcmp(buf.buf, "capabilities")) { printf("fetch\n"); printf("option\n"); + printf("push\n"); printf("\n"); fflush(stdout); } else { diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index f4a2cf6..09edd23 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -36,6 +36,7 @@ test_expect_success 'setup remote repository' ' cd test_repo.git && git --bare update-server-info && mv hooks/post-update.sample hooks/post-update && + ORIG_HEAD=$(git rev-parse --verify HEAD) && cd - && mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH" ' @@ -45,7 +46,7 @@ test_expect_success 'clone remote repository' ' git clone $HTTPD_URL/test_repo.git test_repo_clone ' -test_expect_failure 'push to remote repository with packed refs' ' +test_expect_success 'push to remote repository with packed refs' ' cd "$ROOT_PATH"/test_repo_clone && : >path2 && git add path2 && @@ -57,11 +58,15 @@ test_expect_failure 'push to remote repository with packed refs' ' test $HEAD = $(git rev-parse --verify HEAD)) ' -test_expect_success ' push to remote repository with unpacked refs' ' +test_expect_failure 'push already up-to-date' ' + git push +' + +test_expect_success 'push to remote repository with unpacked refs' ' (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && rm packed-refs && - git update-ref refs/heads/master \ - 0c973ae9bd51902a28466f3850b543fa66a6aaf4) && + git update-ref refs/heads/master $ORIG_HEAD && + git --bare update-server-info) && git push && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && test $HEAD = $(git rev-parse --verify HEAD)) @@ -113,7 +118,6 @@ test_expect_success 'create and delete remote branch' ' git push origin dev && git fetch && git push origin :dev && - git branch -d -r origin/dev && git fetch && test_must_fail git show-ref --verify refs/remotes/origin/dev ' diff --git a/transport-helper.c b/transport-helper.c index 577abc6..16c6641 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -1,6 +1,6 @@ #include "cache.h" #include "transport.h" - +#include "quote.h" #include "run-command.h" #include "commit.h" #include "diff.h" @@ -13,7 +13,8 @@ struct helper_data struct child_process *helper; FILE *out; unsigned fetch : 1, - option : 1; + option : 1, + push : 1; }; static struct child_process *get_helper(struct transport *transport) @@ -52,6 +53,8 @@ static struct child_process *get_helper(struct transport *transport) data->fetch = 1; if (!strcmp(buf.buf, "option")) data->option = 1; + if (!strcmp(buf.buf, "push")) + data->push = 1; } return data->helper; } @@ -214,6 +217,130 @@ static int fetch(struct transport *transport, return -1; } +static int push_refs(struct transport *transport, + struct ref *remote_refs, int flags) +{ + int force_all = flags & TRANSPORT_PUSH_FORCE; + int mirror = flags & TRANSPORT_PUSH_MIRROR; + struct helper_data *data = transport->data; + struct strbuf buf = STRBUF_INIT; + struct child_process *helper; + struct ref *ref; + + if (!remote_refs) + return 0; + + helper = get_helper(transport); + if (!data->push) + return 1; + + for (ref = remote_refs; ref; ref = ref->next) { + if (ref->peer_ref) + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); + else if (!mirror) + continue; + + ref->deletion = is_null_sha1(ref->new_sha1); + if (!ref->deletion && + !hashcmp(ref->old_sha1, ref->new_sha1)) { + ref->status = REF_STATUS_UPTODATE; + continue; + } + + if (force_all) + ref->force = 1; + + strbuf_addstr(&buf, "push "); + if (!ref->deletion) { + if (ref->force) + strbuf_addch(&buf, '+'); + if (ref->peer_ref) + strbuf_addstr(&buf, ref->peer_ref->name); + else + strbuf_addstr(&buf, sha1_to_hex(ref->new_sha1)); + } + strbuf_addch(&buf, ':'); + strbuf_addstr(&buf, ref->name); + strbuf_addch(&buf, '\n'); + } + + transport->verbose = flags & TRANSPORT_PUSH_VERBOSE ? 1 : 0; + standard_options(transport); + + if (flags & TRANSPORT_PUSH_DRY_RUN) { + if (set_helper_option(transport, "dry-run", "true") != 0) + die("helper %s does not support dry-run", data->name); + } + + strbuf_addch(&buf, '\n'); + if (write_in_full(helper->in, buf.buf, buf.len) != buf.len) + exit(128); + + ref = remote_refs; + while (1) { + char *refname, *msg; + int status; + + strbuf_reset(&buf); + if (strbuf_getline(&buf, data->out, '\n') == EOF) + exit(128); /* child died, message supplied already */ + if (!buf.len) + break; + + if (!prefixcmp(buf.buf, "ok ")) { + status = REF_STATUS_OK; + refname = buf.buf + 3; + } else if (!prefixcmp(buf.buf, "error ")) { + status = REF_STATUS_REMOTE_REJECT; + refname = buf.buf + 6; + } else + die("expected ok/error, helper said '%s'\n", buf.buf); + + msg = strchr(refname, ' '); + if (msg) { + struct strbuf msg_buf = STRBUF_INIT; + const char *end; + + *msg++ = '\0'; + if (!unquote_c_style(&msg_buf, msg, &end)) + msg = strbuf_detach(&msg_buf, NULL); + else + msg = xstrdup(msg); + strbuf_release(&msg_buf); + + if (!strcmp(msg, "no match")) { + status = REF_STATUS_NONE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "up to date")) { + status = REF_STATUS_UPTODATE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "non-fast forward")) { + status = REF_STATUS_REJECT_NONFASTFORWARD; + free(msg); + msg = NULL; + } + } + + if (ref) + ref = find_ref_by_name(ref, refname); + if (!ref) + ref = find_ref_by_name(remote_refs, refname); + if (!ref) { + warning("helper reported unexpected status of %s", refname); + continue; + } + + ref->status = status; + ref->remote_status = msg; + } + strbuf_release(&buf); + return 0; +} + static struct ref *get_refs_list(struct transport *transport, int for_push) { struct helper_data *data = transport->data; @@ -225,7 +352,10 @@ static struct ref *get_refs_list(struct transport *transport, int for_push) helper = get_helper(transport); - write_str_in_full(helper->in, "list\n"); + if (data->push && for_push) + write_str_in_full(helper->in, "list for-push\n"); + else + write_str_in_full(helper->in, "list\n"); while (1) { char *eov, *eon; @@ -266,6 +396,7 @@ int transport_helper_init(struct transport *transport, const char *name) transport->set_option = set_helper_option; transport->get_refs_list = get_refs_list; transport->fetch = fetch; + transport->push_refs = push_refs; transport->disconnect = disconnect_helper; return 0; } diff --git a/transport.c b/transport.c index 644a30a..6d9652d 100644 --- a/transport.c +++ b/transport.c @@ -349,35 +349,6 @@ static int rsync_transport_push(struct transport *transport, return result; } -#ifndef NO_CURL -static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) -{ - const char **argv; - int argc; - - if (flags & TRANSPORT_PUSH_MIRROR) - return error("http transport does not support mirror mode"); - - argv = xmalloc((refspec_nr + 12) * sizeof(char *)); - argv[0] = "http-push"; - argc = 1; - if (flags & TRANSPORT_PUSH_ALL) - argv[argc++] = "--all"; - if (flags & TRANSPORT_PUSH_FORCE) - argv[argc++] = "--force"; - if (flags & TRANSPORT_PUSH_DRY_RUN) - argv[argc++] = "--dry-run"; - if (flags & TRANSPORT_PUSH_VERBOSE) - argv[argc++] = "--verbose"; - argv[argc++] = transport->url; - while (refspec_nr--) - argv[argc++] = *refspec++; - argv[argc] = NULL; - return !!run_command_v_opt(argv, RUN_GIT_CMD); -} - -#endif - struct bundle_transport_data { int fd; struct bundle_header header; @@ -826,8 +797,6 @@ struct transport *transport_get(struct remote *remote, const char *url) transport_helper_init(ret, "curl"); #ifdef NO_CURL error("git was compiled without libcurl support."); -#else - ret->push = curl_transport_push; #endif } else if (is_local(url) && is_file(url)) { -- cgit v0.10.2-6-g49f6 From d8f67d205eb98ea6dab915c29122158054218128 Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Fri, 30 Oct 2009 17:47:31 -0700 Subject: remote-helpers: return successfully if everything up-to-date Signed-off-by: Clemens Buchacher Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index 09edd23..2ece661 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -58,7 +58,7 @@ test_expect_success 'push to remote repository with packed refs' ' test $HEAD = $(git rev-parse --verify HEAD)) ' -test_expect_failure 'push already up-to-date' ' +test_expect_success 'push already up-to-date' ' git push ' diff --git a/transport-helper.c b/transport-helper.c index 16c6641..5078c71 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -263,6 +263,8 @@ static int push_refs(struct transport *transport, strbuf_addstr(&buf, ref->name); strbuf_addch(&buf, '\n'); } + if (buf.len == 0) + return 0; transport->verbose = flags & TRANSPORT_PUSH_VERBOSE ? 1 : 0; standard_options(transport); -- cgit v0.10.2-6-g49f6 From c8a58ac5a52b0850fbca87898d1c6aa44cf5626f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 31 Oct 2009 11:16:50 -0700 Subject: Revert "Don't create the $GIT_DIR/branches directory on init" This reverts commit 0cc5691a8b05a7eabdeef520c94b1bb3bcac7874. There is not enough justification for doing this. We do not update things in .git/branches and .git/remotes anymore, but still do read information from there and will keep doing so. Besides, this breaks quite a lot of tests in t55?? series. diff --git a/templates/branches-- b/templates/branches-- new file mode 100644 index 0000000..fae8870 --- /dev/null +++ b/templates/branches-- @@ -0,0 +1 @@ +: this is just to ensure the directory exists. -- cgit v0.10.2-6-g49f6 From 76fd28283f7eeea246a06994edd43ab60e59d853 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 30 Oct 2009 10:09:06 -0700 Subject: diff --color-words: bit of clean-up When we introduced the "word diff" mode, we could have done one of three things: * change fn_out_consume() to "this is called every time a line worth of diff becomes ready from the lower-level diff routine. This function knows two sets of helpers (one for line-oriented diff, another for word diff), and each set has various functions to be called at certain places (e.g. hunk header, context, ...). The function's role is to inspect the incoming line, and dispatch appropriate helpers to produce either line- or word- oriented diff output." * introduce fn_out_consume_word_diff() that is "this is called every time a line worth of diff becomes ready from the lower-level diff routine, and here is what we do to prepare word oriented diff using that line." without touching fn_out_consume() at all. * Do neither of the above, and keep fn_out_consume() to "this is called every time a line worth of diff becomes ready from the lower-level diff routine, and here is what we do to output line oriented diff using that line." but sprinkle a handful of 'are we in word-diff mode? if so do this totally different thing' at random places. This patch is to at least abstract the details of "this totally different thing" out from the main codepath, in order to improve readability. We can later refactor it by introducing fn_out_consume_word_diff(), taking the second route above, but that is a separate topic. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index b7ecfe3..8c66e4a 100644 --- a/diff.c +++ b/diff.c @@ -541,14 +541,18 @@ struct emit_callback { FILE *file; }; +/* In "color-words" mode, show word-diff of words accumulated in the buffer */ +static void diff_words_flush(struct emit_callback *ecbdata) +{ + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); +} + static void free_diff_words_data(struct emit_callback *ecbdata) { if (ecbdata->diff_words) { - /* flush buffers */ - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); - + diff_words_flush(ecbdata); free (ecbdata->diff_words->minus.text.ptr); free (ecbdata->diff_words->minus.orig); free (ecbdata->diff_words->plus.text.ptr); @@ -656,12 +660,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) for (i = 0; i < len && line[i] == '@'; i++) ; if (2 <= i && i < len && line[i] == ' ') { - /* flush --color-words even for --unified=0 */ - if (ecbdata->diff_words && - (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size)) - diff_words_show(ecbdata->diff_words); - + if (ecbdata->diff_words) + diff_words_flush(ecbdata); ecbdata->nparents = i - 1; len = sane_truncate_line(ecbdata, line, len); emit_line(ecbdata->file, @@ -691,9 +691,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) &ecbdata->diff_words->plus); return; } - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); + diff_words_flush(ecbdata); line++; len--; emit_line(ecbdata->file, plain, reset, line, len); -- cgit v0.10.2-6-g49f6 From 1e380ddcd29f1e25bfb58b7068fdafb038c8dd9a Mon Sep 17 00:00:00 2001 From: Vietor Liu Date: Sat, 31 Oct 2009 14:36:03 +0800 Subject: imap-send.c: fix compiler warnings for OpenSSL 1.0 The openssl/CHANGES file says: Let the TLSv1_method() etc. functions return a 'const' SSL_METHOD pointer and make the SSL_METHOD parameter in SSL_CTX_new, SSL_CTX_set_ssl_version and SSL_set_ssl_method 'const'. In older versions, unqualified pointers were used, so we unfortunately cannot unconditionally update the type of the variable we use. Signed-off-by: Vietor Liu Signed-off-by: Junio C Hamano diff --git a/imap-send.c b/imap-send.c index 3847fd1..f805c6e 100644 --- a/imap-send.c +++ b/imap-send.c @@ -273,7 +273,11 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve fprintf(stderr, "SSL requested but SSL support not compiled in\n"); return -1; #else +#if (OPENSSL_VERSION_NUMBER >= 0x10000000L) + const SSL_METHOD *meth; +#else SSL_METHOD *meth; +#endif SSL_CTX *ctx; int ret; -- cgit v0.10.2-6-g49f6 From 754571261a87064434441dc7978153df52a874e7 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Mon, 2 Nov 2009 02:09:05 +0300 Subject: Makefile: add compat/bswap.h to LIB_H Starting with commit 51ea55190b6e72c77c96754c1bf2f149a4714848, git-compat-util.h includes compat/bswap.h Signed-off-by: Dmitry V. Levin Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 268aede..3bca8d4 100644 --- a/Makefile +++ b/Makefile @@ -412,6 +412,7 @@ LIB_H += builtin.h LIB_H += cache.h LIB_H += cache-tree.h LIB_H += commit.h +LIB_H += compat/bswap.h LIB_H += compat/cygwin.h LIB_H += compat/mingw.h LIB_H += csum-file.h -- cgit v0.10.2-6-g49f6 From 74de2781130822c2d7948f77270c2898caf1bf66 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Mon, 2 Nov 2009 01:30:05 -0800 Subject: t1402: Make test executable Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh old mode 100644 new mode 100755 -- cgit v0.10.2-6-g49f6 From c1d45cf7b0c1953eed72a3018b5e557dbcd538e0 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Tue, 3 Nov 2009 21:38:51 -0500 Subject: Require a struct remote in transport_get() cmd_ls_remote() was calling transport_get() with a NULL remote and a non-NULL url in the case where it was run outside a git repository. This involved a bunch of ill-tested special cases. Instead, simply get the struct remote for the URL with remote_get(), which works fine outside a git repository, and can also take global options into account. This fixes a tiny and obscure bug where "git ls-remote" without a repo didn't support global url.*.insteadOf, even though "git clone" and "git ls-remote" in any repo did. Also, enforce that all callers provide a struct remote to transport_get(). Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c index 78a88f7..b5bad0c 100644 --- a/builtin-ls-remote.c +++ b/builtin-ls-remote.c @@ -86,10 +86,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) pattern[j - i] = p; } } - remote = nongit ? NULL : remote_get(dest); - if (remote && !remote->url_nr) + remote = remote_get(dest); + if (!remote->url_nr) die("remote %s has no configured URL", dest); - transport = transport_get(remote, remote ? remote->url[0] : dest); + transport = transport_get(remote, remote->url[0]); if (uploadpack != NULL) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); diff --git a/transport.c b/transport.c index 644a30a..298dc46 100644 --- a/transport.c +++ b/transport.c @@ -812,6 +812,9 @@ struct transport *transport_get(struct remote *remote, const char *url) { struct transport *ret = xcalloc(1, sizeof(*ret)); + if (!remote) + die("No remote provided to transport_get()"); + ret->remote = remote; ret->url = url; @@ -849,10 +852,10 @@ struct transport *transport_get(struct remote *remote, const char *url) data->thin = 1; data->conn = NULL; data->uploadpack = "git-upload-pack"; - if (remote && remote->uploadpack) + if (remote->uploadpack) data->uploadpack = remote->uploadpack; data->receivepack = "git-receive-pack"; - if (remote && remote->receivepack) + if (remote->receivepack) data->receivepack = remote->receivepack; } -- cgit v0.10.2-6-g49f6 From a45d3d7effdfb4b6b9fe75c9206a79d3cdcb3922 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Tue, 3 Nov 2009 21:52:35 -0500 Subject: Allow curl helper to work without a local repository It's okay to use the curl helper without a local repository, so long as you don't use "fetch". There aren't any git programs that would try to use it, and it doesn't make sense to try it (since there's nowhere to write the results), but we may as well be clear. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano diff --git a/remote-curl.c b/remote-curl.c index 2faf1c6..ebdab36 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -82,9 +82,10 @@ int main(int argc, const char **argv) struct strbuf buf = STRBUF_INIT; const char *url; struct walker *walker = NULL; + int nongit; git_extract_argv0_path(argv[0]); - setup_git_directory(); + setup_git_directory_gently(&nongit); if (argc < 2) { fprintf(stderr, "Remote needed\n"); return 1; @@ -103,6 +104,8 @@ int main(int argc, const char **argv) break; if (!prefixcmp(buf.buf, "fetch ")) { char *obj = buf.buf + strlen("fetch "); + if (nongit) + die("Fetch attempted without a local repo"); if (!walker) walker = get_http_walker(url, remote); walker->get_all = 1; -- cgit v0.10.2-6-g49f6 From d79d9e13375c43b8774b193ce725ac6c05b3865e Mon Sep 17 00:00:00 2001 From: Ben Walton Date: Wed, 4 Nov 2009 13:05:59 -0500 Subject: configure: add macro to set arbitrary make variables Add macro GIT_PARSE_WITH_SET_MAKE_VAR to configure.ac to allow --with style options that set values for variables used during the make process. Arguments are the $name part of --with-$name, the name of the variable to set in the Makefile (config.mak.autogen) and the help text for the option. Signed-off-by: Ben Walton Signed-off-by: Junio C Hamano diff --git a/configure.ac b/configure.ac index b09b8e4..c48e713 100644 --- a/configure.ac +++ b/configure.ac @@ -68,6 +68,26 @@ else \ GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \ fi \ ])# GIT_PARSE_WITH +# +# GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT) +# --------------------- +# Set VAR to the value specied by --with-WITHNAME. +# No verification of arguments is performed, but warnings are issued +# if either 'yes' or 'no' is specified. +# HELP_TEXT is presented when --help is called. +# This is a direct way to allow setting variables in the Makefile. +AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR], +[AC_ARG_WITH([$1], + [AS_HELP_STRING([--with-$1=VALUE], $3)], + if test -n "$withval"; then \ + if test "$withval" = "yes" -o "$withval" = "no"; then \ + AC_MSG_WARN([You likely do not want either 'yes' or 'no' as] + [a value for $1 ($2). Maybe you do...?]); \ + fi; \ + \ + AC_MSG_NOTICE([Setting $2 to $withval]); \ + GIT_CONF_APPEND_LINE($2=$withval); \ + fi)])# GIT_PARSE_WITH_SET_MAKE_VAR dnl dnl GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE) -- cgit v0.10.2-6-g49f6 From 5ca5377da017311a08f625e32c58a0d118e7fda6 Mon Sep 17 00:00:00 2001 From: Ben Walton Date: Wed, 4 Nov 2009 13:06:00 -0500 Subject: configure: add settings for gitconfig, editor and pager Use the new GIT_PARSE_WITH_SET_MAKE_VAR macro to allow configuration settings for ETC_GITCONFIG, DEFAULT_PAGER and DEFAULT_EDITOR. Signed-off-by: Ben Walton Signed-off-by: Junio C Hamano diff --git a/configure.ac b/configure.ac index c48e713..4625b86 100644 --- a/configure.ac +++ b/configure.ac @@ -247,6 +247,29 @@ GIT_PARSE_WITH(iconv)) # change being considered an inode change from the update-index perspective. # +# Allow user to set ETC_GITCONFIG variable +GIT_PARSE_WITH_SET_MAKE_VAR(gitconfig, ETC_GITCONFIG, + Use VALUE instead of /etc/gitconfig as the + global git configuration file. + If VALUE is not fully qualified it will be interpretted + as a path relative to the computed prefix at runtime.) + +# +# Allow user to set the default pager +GIT_PARSE_WITH_SET_MAKE_VAR(pager, DEFAULT_PAGER, + Use VALUE as the fall-back pager instead of 'less'. + This is used by things like 'git log' when the user + does not specify a pager to use through alternate + methods. eg: /usr/bin/pager) +# +# Allow user to set the default editor +GIT_PARSE_WITH_SET_MAKE_VAR(editor, DEFAULT_EDITOR, + Use VALUE as the fall-back editor instead of 'vi'. + This is used by things like 'git commit' when the user + does not specify a preferred editor through other + methods. eg: /usr/bin/editor) + +# # Define SHELL_PATH to provide path to shell. GIT_ARG_SET_PATH(shell) # -- cgit v0.10.2-6-g49f6 From 0a565de4a50032339d3786d366e70912a4dcf572 Mon Sep 17 00:00:00 2001 From: Gisle Aas Date: Wed, 4 Nov 2009 22:57:46 +0100 Subject: Fix documentation grammar typo Introduced in 492cf3f (More precise description of 'git describe --abbrev', 2009-10-29) Signed-off-by: Gisle Aas Signed-off-by: Junio C Hamano diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index e9dbca7..2f97916 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -120,7 +120,7 @@ closest tagname without any suffix: tags/v1.0.0 Note that the suffix you get if you type these commands today may be -longer than what Linus saw above when he ran this command, as your +longer than what Linus saw above when he ran these commands, as your git repository may have new commits whose object names begin with 975b that did not exist back then, and "-g975b" suffix alone may not be sufficient to disambiguate these commits. -- cgit v0.10.2-6-g49f6 From 2f4038ab337e55017d4ff21ddbae9427544ca02c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:32 -0700 Subject: Git-aware CGI to provide dumb HTTP transport The git-http-backend CGI can be configured into any Apache server using ScriptAlias, such as with the following configuration: LoadModule cgi_module /usr/libexec/apache2/mod_cgi.so LoadModule alias_module /usr/libexec/apache2/mod_alias.so ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ Repositories are accessed via the translated PATH_INFO. The CGI is backwards compatible with the dumb client, allowing all older HTTP clients to continue to download repositories which are managed by the CGI. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 51a37b1..353d22f 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ git-get-tar-commit-id git-grep git-hash-object git-help +git-http-backend git-http-fetch git-http-push git-imap-send diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt new file mode 100644 index 0000000..867675f --- /dev/null +++ b/Documentation/git-http-backend.txt @@ -0,0 +1,105 @@ +git-http-backend(1) +=================== + +NAME +---- +git-http-backend - Server side implementation of Git over HTTP + +SYNOPSIS +-------- +[verse] +'git-http-backend' + +DESCRIPTION +----------- +A simple CGI program to serve the contents of a Git repository to Git +clients accessing the repository over http:// and https:// protocols. + +By default, only the `upload-pack` service is enabled, which serves +'git-fetch-pack' and 'git-ls-remote' clients, which are invoked from +'git-fetch', 'git-pull', and 'git-clone'. + +This is ideally suited for read-only updates, i.e., pulling from +git repositories. + +URL TRANSLATION +--------------- +'git-http-backend' relies on the invoking web server to perform +URL to path translation, and store the repository path into the +PATH_TRANSLATED environment variable. Most web servers will do +this translation automatically, resolving the suffix after the +CGI name relative to the server's document root. + +EXAMPLES +-------- + +Apache 2.x:: + To serve all Git repositories contained within the '/git/' + subdirectory of the DocumentRoot, ensure mod_cgi and + mod_alias are enabled, and create a ScriptAlias to the CGI: ++ +---------------------------------------------------------------- +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/ + + + Options None + + + Options ExecCGI + +---------------------------------------------------------------- ++ +To require authentication for reads, use a Directory +directive around the repository, or one of its parent directories: ++ +---------------------------------------------------------------- + + AuthType Basic + AuthName "Private Git Access" + Require group committers + ... + +---------------------------------------------------------------- + +Accelerated static Apache 2.x:: + Similar to the above, but Apache can be used to return static + files that are stored on disk. On many systems this may + be more efficient as Apache can ask the kernel to copy the + file contents from the file system directly to the network: ++ +---------------------------------------------------------------- +DocumentRoot /var/www + +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/ +Alias /git_static/ /var/www/git/ + +RewriteEngine on +RewriteRule ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /git_static/$1 [PT] +RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.pack)$ /git_static/$1 [PT] +RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.idx)$ /git_static/$1 [PT] +---------------------------------------------------------------- + + +ENVIRONMENT +----------- +'git-http-backend' relies upon the CGI environment variables set +by the invoking web server, including: + +* PATH_TRANSLATED +* REMOTE_USER +* REMOTE_ADDR +* CONTENT_TYPE +* QUERY_STRING +* REQUEST_METHOD + +Author +------ +Written by Shawn O. Pearce . + +Documentation +-------------- +Documentation by Shawn O. Pearce . + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Makefile b/Makefile index fea237b..271c290 100644 --- a/Makefile +++ b/Makefile @@ -365,6 +365,7 @@ PROGRAMS += git-show-index$X PROGRAMS += git-unpack-file$X PROGRAMS += git-upload-pack$X PROGRAMS += git-var$X +PROGRAMS += git-http-backend$X # List built-in command $C whose implementation cmd_$C() is not in # builtin-$C.o but is linked in as part of some other command. diff --git a/http-backend.c b/http-backend.c new file mode 100644 index 0000000..22bec56 --- /dev/null +++ b/http-backend.c @@ -0,0 +1,289 @@ +#include "cache.h" +#include "refs.h" +#include "pkt-line.h" +#include "object.h" +#include "tag.h" +#include "exec_cmd.h" + +static const char content_type[] = "Content-Type"; +static const char content_length[] = "Content-Length"; +static const char last_modified[] = "Last-Modified"; + +static void format_write(int fd, const char *fmt, ...) +{ + static char buffer[1024]; + + va_list args; + unsigned n; + + va_start(args, fmt); + n = vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + if (n >= sizeof(buffer)) + die("protocol error: impossibly long line"); + + safe_write(fd, buffer, n); +} + +static void http_status(unsigned code, const char *msg) +{ + format_write(1, "Status: %u %s\r\n", code, msg); +} + +static void hdr_str(const char *name, const char *value) +{ + format_write(1, "%s: %s\r\n", name, value); +} + +static void hdr_int(const char *name, size_t value) +{ + format_write(1, "%s: %" PRIuMAX "\r\n", name, value); +} + +static void hdr_date(const char *name, unsigned long when) +{ + const char *value = show_date(when, 0, DATE_RFC2822); + hdr_str(name, value); +} + +static void hdr_nocache(void) +{ + hdr_str("Expires", "Fri, 01 Jan 1980 00:00:00 GMT"); + hdr_str("Pragma", "no-cache"); + hdr_str("Cache-Control", "no-cache, max-age=0, must-revalidate"); +} + +static void hdr_cache_forever(void) +{ + unsigned long now = time(NULL); + hdr_date("Date", now); + hdr_date("Expires", now + 31536000); + hdr_str("Cache-Control", "public, max-age=31536000"); +} + +static void end_headers(void) +{ + safe_write(1, "\r\n", 2); +} + +static NORETURN void not_found(const char *err, ...) +{ + va_list params; + + http_status(404, "Not Found"); + hdr_nocache(); + end_headers(); + + va_start(params, err); + if (err && *err) + vfprintf(stderr, err, params); + va_end(params); + exit(0); +} + +static void send_strbuf(const char *type, struct strbuf *buf) +{ + hdr_int(content_length, buf->len); + hdr_str(content_type, type); + end_headers(); + safe_write(1, buf->buf, buf->len); +} + +static void send_file(const char *the_type, const char *name) +{ + const char *p = git_path("%s", name); + size_t buf_alloc = 8192; + char *buf = xmalloc(buf_alloc); + int fd; + struct stat sb; + size_t size; + + fd = open(p, O_RDONLY); + if (fd < 0) + not_found("Cannot open '%s': %s", p, strerror(errno)); + if (fstat(fd, &sb) < 0) + die_errno("Cannot stat '%s'", p); + + size = xsize_t(sb.st_size); + + hdr_int(content_length, size); + hdr_str(content_type, the_type); + hdr_date(last_modified, sb.st_mtime); + end_headers(); + + while (size) { + ssize_t n = xread(fd, buf, buf_alloc); + if (n < 0) + die_errno("Cannot read '%s'", p); + if (!n) + break; + safe_write(1, buf, n); + } + close(fd); + free(buf); +} + +static void get_text_file(char *name) +{ + hdr_nocache(); + send_file("text/plain", name); +} + +static void get_loose_object(char *name) +{ + hdr_cache_forever(); + send_file("application/x-git-loose-object", name); +} + +static void get_pack_file(char *name) +{ + hdr_cache_forever(); + send_file("application/x-git-packed-objects", name); +} + +static void get_idx_file(char *name) +{ + hdr_cache_forever(); + send_file("application/x-git-packed-objects-toc", name); +} + +static int show_text_ref(const char *name, const unsigned char *sha1, + int flag, void *cb_data) +{ + struct strbuf *buf = cb_data; + struct object *o = parse_object(sha1); + if (!o) + return 0; + + strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name); + if (o->type == OBJ_TAG) { + o = deref_tag(o, name, 0); + if (!o) + return 0; + strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name); + } + return 0; +} + +static void get_info_refs(char *arg) +{ + struct strbuf buf = STRBUF_INIT; + + for_each_ref(show_text_ref, &buf); + hdr_nocache(); + send_strbuf("text/plain", &buf); + strbuf_release(&buf); +} + +static void get_info_packs(char *arg) +{ + size_t objdirlen = strlen(get_object_directory()); + struct strbuf buf = STRBUF_INIT; + struct packed_git *p; + size_t cnt = 0; + + prepare_packed_git(); + for (p = packed_git; p; p = p->next) { + if (p->pack_local) + cnt++; + } + + strbuf_grow(&buf, cnt * 53 + 2); + for (p = packed_git; p; p = p->next) { + if (p->pack_local) + strbuf_addf(&buf, "P %s\n", p->pack_name + objdirlen + 6); + } + strbuf_addch(&buf, '\n'); + + hdr_nocache(); + send_strbuf("text/plain; charset=utf-8", &buf); + strbuf_release(&buf); +} + +static NORETURN void die_webcgi(const char *err, va_list params) +{ + char buffer[1000]; + + http_status(500, "Internal Server Error"); + hdr_nocache(); + end_headers(); + + vsnprintf(buffer, sizeof(buffer), err, params); + fprintf(stderr, "fatal: %s\n", buffer); + exit(0); +} + +static struct service_cmd { + const char *method; + const char *pattern; + void (*imp)(char *); +} services[] = { + {"GET", "/HEAD$", get_text_file}, + {"GET", "/info/refs$", get_info_refs}, + {"GET", "/objects/info/alternates$", get_text_file}, + {"GET", "/objects/info/http-alternates$", get_text_file}, + {"GET", "/objects/info/packs$", get_info_packs}, + {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object}, + {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file}, + {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file} +}; + +int main(int argc, char **argv) +{ + char *method = getenv("REQUEST_METHOD"); + char *dir = getenv("PATH_TRANSLATED"); + struct service_cmd *cmd = NULL; + char *cmd_arg = NULL; + int i; + + git_extract_argv0_path(argv[0]); + set_die_routine(die_webcgi); + + if (!method) + die("No REQUEST_METHOD from server"); + if (!strcmp(method, "HEAD")) + method = "GET"; + if (!dir) + die("No PATH_TRANSLATED from server"); + + for (i = 0; i < ARRAY_SIZE(services); i++) { + struct service_cmd *c = &services[i]; + regex_t re; + regmatch_t out[1]; + + if (regcomp(&re, c->pattern, REG_EXTENDED)) + die("Bogus regex in service table: %s", c->pattern); + if (!regexec(&re, dir, 1, out, 0)) { + size_t n = out[0].rm_eo - out[0].rm_so; + + if (strcmp(method, c->method)) { + const char *proto = getenv("SERVER_PROTOCOL"); + if (proto && !strcmp(proto, "HTTP/1.1")) + http_status(405, "Method Not Allowed"); + else + http_status(400, "Bad Request"); + hdr_nocache(); + end_headers(); + return 0; + } + + cmd = c; + cmd_arg = xmalloc(n); + strncpy(cmd_arg, dir + out[0].rm_so + 1, n); + cmd_arg[n] = '\0'; + dir[out[0].rm_so] = 0; + break; + } + regfree(&re); + } + + if (!cmd) + not_found("Request not supported: '%s'", dir); + + setup_path(); + if (!enter_repo(dir, 0)) + not_found("Not a git repository: '%s'", dir); + + cmd->imp(cmd_arg); + return 0; +} -- cgit v0.10.2-6-g49f6 From 42526b478e369d7e8c9a95186ad87fae9930eea5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:33 -0700 Subject: Add stateless RPC options to upload-pack, receive-pack When --stateless-rpc is passed as a command line parameter to upload-pack or receive-pack the programs now assume they may perform only a single read-write cycle with stdin and stdout. This fits with the HTTP POST request processing model where a program may read the request, write a response, and must exit. When --advertise-refs is passed as a command line parameter only the initial ref advertisement is output, and the program exits immediately. This fits with the HTTP GET request model, where no request content is received but a response must be produced. HTTP headers and/or environment are not processed here, but instead are assumed to be handled by the program invoking either service backend. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index b771fe9..70ff8c5 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -615,6 +615,8 @@ static void add_alternate_refs(void) int cmd_receive_pack(int argc, const char **argv, const char *prefix) { + int advertise_refs = 0; + int stateless_rpc = 0; int i; char *dir = NULL; @@ -623,7 +625,15 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) const char *arg = *argv++; if (*arg == '-') { - /* Do flag handling here */ + if (!strcmp(arg, "--advertise-refs")) { + advertise_refs = 1; + continue; + } + if (!strcmp(arg, "--stateless-rpc")) { + stateless_rpc = 1; + continue; + } + usage(receive_pack_usage); } if (dir) @@ -652,12 +662,16 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) " report-status delete-refs ofs-delta " : " report-status delete-refs "; - add_alternate_refs(); - write_head_info(); - clear_extra_refs(); + if (advertise_refs || !stateless_rpc) { + add_alternate_refs(); + write_head_info(); + clear_extra_refs(); - /* EOF */ - packet_flush(1); + /* EOF */ + packet_flush(1); + } + if (advertise_refs) + return 0; read_head_info(); if (commands) { diff --git a/upload-pack.c b/upload-pack.c index f1dc3a3..70badcf 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -39,6 +39,8 @@ static unsigned int timeout; */ static int use_sideband; static int debug_fd; +static int advertise_refs; +static int stateless_rpc; static void reset_timeout(void) { @@ -509,6 +511,8 @@ static int get_common_commits(void) if (!len) { if (have_obj.nr == 0 || multi_ack) packet_write(1, "NAK\n"); + if (stateless_rpc) + exit(0); continue; } strip(line, len); @@ -710,12 +714,32 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo return 0; } +static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + struct object *o = parse_object(sha1); + if (!o) + die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1)); + if (!(o->flags & OUR_REF)) { + o->flags |= OUR_REF; + nr_our_refs++; + } + return 0; +} + static void upload_pack(void) { - reset_timeout(); - head_ref(send_ref, NULL); - for_each_ref(send_ref, NULL); - packet_flush(1); + if (advertise_refs || !stateless_rpc) { + reset_timeout(); + head_ref(send_ref, NULL); + for_each_ref(send_ref, NULL); + packet_flush(1); + } else { + head_ref(mark_our_ref, NULL); + for_each_ref(mark_our_ref, NULL); + } + if (advertise_refs) + return; + receive_needs(); if (want_obj.nr) { get_common_commits(); @@ -737,6 +761,14 @@ int main(int argc, char **argv) if (arg[0] != '-') break; + if (!strcmp(arg, "--advertise-refs")) { + advertise_refs = 1; + continue; + } + if (!strcmp(arg, "--stateless-rpc")) { + stateless_rpc = 1; + continue; + } if (!strcmp(arg, "--strict")) { strict = 1; continue; -- cgit v0.10.2-6-g49f6 From 556cfa3b6d316074d41cd73c4659402fdb6207c8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:34 -0700 Subject: Smart fetch and push over HTTP: server side Requests for $GIT_URL/git-receive-pack and $GIT_URL/git-upload-pack are forwarded to the corresponding backend process by directly executing it and leaving stdin and stdout connected to the invoking web server. Prior to starting the backend process the HTTP response headers are sent, thereby freeing the backend from needing to know about the HTTP protocol. Requests that are encoded with Content-Encoding: gzip are automatically inflated before being streamed into the backend. This is primarily useful for the git-upload-pack backend, which receives highly repetitive text data from clients that easily compresses to 50% of its original size. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index 867675f..022a243 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -22,6 +22,23 @@ By default, only the `upload-pack` service is enabled, which serves This is ideally suited for read-only updates, i.e., pulling from git repositories. +SERVICES +-------- +These services can be enabled/disabled using the per-repository +configuration file: + +http.uploadpack:: + This serves 'git-fetch-pack' and 'git-ls-remote' clients. + It is enabled by default, but a repository can disable it + by setting this configuration item to `false`. + +http.receivepack:: + This serves 'git-send-pack' clients, allowing push. It is + disabled by default for anonymous users, and enabled by + default for users authenticated by the web server. It can be + disabled by setting this item to `false`, or enabled for all + users, including anonymous users, by setting it to `true`. + URL TRANSLATION --------------- 'git-http-backend' relies on the invoking web server to perform @@ -49,7 +66,19 @@ ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/ ---------------------------------------------------------------- + -To require authentication for reads, use a Directory +To enable anonymous read access but authenticated write access, +require authorization with a LocationMatch directive: ++ +---------------------------------------------------------------- + + AuthType Basic + AuthName "Git Access" + Require group committers + ... + +---------------------------------------------------------------- ++ +To require authentication for both reads and writes, use a Directory directive around the repository, or one of its parent directories: + ---------------------------------------------------------------- @@ -92,6 +121,14 @@ by the invoking web server, including: * QUERY_STRING * REQUEST_METHOD +The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and +GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}', +ensuring that any reflogs created by 'git-receive-pack' contain some +identifying information of the remote user who performed the push. + +All CGI environment variables are available to each of the hooks +invoked by the 'git-receive-pack'. + Author ------ Written by Shawn O. Pearce . diff --git a/http-backend.c b/http-backend.c index 22bec56..bfce520 100644 --- a/http-backend.c +++ b/http-backend.c @@ -4,11 +4,109 @@ #include "object.h" #include "tag.h" #include "exec_cmd.h" +#include "run-command.h" +#include "string-list.h" static const char content_type[] = "Content-Type"; static const char content_length[] = "Content-Length"; static const char last_modified[] = "Last-Modified"; +static struct string_list *query_params; + +struct rpc_service { + const char *name; + const char *config_name; + signed enabled : 2; +}; + +static struct rpc_service rpc_service[] = { + { "upload-pack", "uploadpack", 1 }, + { "receive-pack", "receivepack", -1 }, +}; + +static int decode_char(const char *q) +{ + int i; + unsigned char val = 0; + for (i = 0; i < 2; i++) { + unsigned char c = *q++; + val <<= 4; + if (c >= '0' && c <= '9') + val += c - '0'; + else if (c >= 'a' && c <= 'f') + val += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val += c - 'A' + 10; + else + return -1; + } + return val; +} + +static char *decode_parameter(const char **query, int is_name) +{ + const char *q = *query; + struct strbuf out; + + strbuf_init(&out, 16); + do { + unsigned char c = *q; + + if (!c) + break; + if (c == '&' || (is_name && c == '=')) { + q++; + break; + } + + if (c == '%') { + int val = decode_char(q + 1); + if (0 <= val) { + strbuf_addch(&out, val); + q += 3; + continue; + } + } + + if (c == '+') + strbuf_addch(&out, ' '); + else + strbuf_addch(&out, c); + q++; + } while (1); + *query = q; + return strbuf_detach(&out, NULL); +} + +static struct string_list *get_parameters(void) +{ + if (!query_params) { + const char *query = getenv("QUERY_STRING"); + + query_params = xcalloc(1, sizeof(*query_params)); + while (query && *query) { + char *name = decode_parameter(&query, 1); + char *value = decode_parameter(&query, 0); + struct string_list_item *i; + + i = string_list_lookup(name, query_params); + if (!i) + i = string_list_insert(name, query_params); + else + free(i->util); + i->util = value; + } + } + return query_params; +} + +static const char *get_parameter(const char *name) +{ + struct string_list_item *i; + i = string_list_lookup(name, get_parameters()); + return i ? i->util : NULL; +} + static void format_write(int fd, const char *fmt, ...) { static char buffer[1024]; @@ -81,6 +179,21 @@ static NORETURN void not_found(const char *err, ...) exit(0); } +static NORETURN void forbidden(const char *err, ...) +{ + va_list params; + + http_status(403, "Forbidden"); + hdr_nocache(); + end_headers(); + + va_start(params, err); + if (err && *err) + vfprintf(stderr, err, params); + va_end(params); + exit(0); +} + static void send_strbuf(const char *type, struct strbuf *buf) { hdr_int(content_length, buf->len); @@ -147,6 +260,145 @@ static void get_idx_file(char *name) send_file("application/x-git-packed-objects-toc", name); } +static int http_config(const char *var, const char *value, void *cb) +{ + struct rpc_service *svc = cb; + + if (!prefixcmp(var, "http.") && + !strcmp(var + 5, svc->config_name)) { + svc->enabled = git_config_bool(var, value); + return 0; + } + + /* we are not interested in parsing any other configuration here */ + return 0; +} + +static struct rpc_service *select_service(const char *name) +{ + struct rpc_service *svc = NULL; + int i; + + if (prefixcmp(name, "git-")) + forbidden("Unsupported service: '%s'", name); + + for (i = 0; i < ARRAY_SIZE(rpc_service); i++) { + struct rpc_service *s = &rpc_service[i]; + if (!strcmp(s->name, name + 4)) { + svc = s; + break; + } + } + + if (!svc) + forbidden("Unsupported service: '%s'", name); + + git_config(http_config, svc); + if (svc->enabled < 0) { + const char *user = getenv("REMOTE_USER"); + svc->enabled = (user && *user) ? 1 : 0; + } + if (!svc->enabled) + forbidden("Service not enabled: '%s'", svc->name); + return svc; +} + +static void inflate_request(const char *prog_name, int out) +{ + z_stream stream; + unsigned char in_buf[8192]; + unsigned char out_buf[8192]; + unsigned long cnt = 0; + int ret; + + memset(&stream, 0, sizeof(stream)); + ret = inflateInit2(&stream, (15 + 16)); + if (ret != Z_OK) + die("cannot start zlib inflater, zlib err %d", ret); + + while (1) { + ssize_t n = xread(0, in_buf, sizeof(in_buf)); + if (n <= 0) + die("request ended in the middle of the gzip stream"); + + stream.next_in = in_buf; + stream.avail_in = n; + + while (0 < stream.avail_in) { + int ret; + + stream.next_out = out_buf; + stream.avail_out = sizeof(out_buf); + + ret = inflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) + die("zlib error inflating request, result %d", ret); + + n = stream.total_out - cnt; + if (write_in_full(out, out_buf, n) != n) + die("%s aborted reading request", prog_name); + cnt += n; + + if (ret == Z_STREAM_END) + goto done; + } + } + +done: + inflateEnd(&stream); + close(out); +} + +static void run_service(const char **argv) +{ + const char *encoding = getenv("HTTP_CONTENT_ENCODING"); + const char *user = getenv("REMOTE_USER"); + const char *host = getenv("REMOTE_ADDR"); + char *env[3]; + struct strbuf buf = STRBUF_INIT; + int gzipped_request = 0; + struct child_process cld; + + if (encoding && !strcmp(encoding, "gzip")) + gzipped_request = 1; + else if (encoding && !strcmp(encoding, "x-gzip")) + gzipped_request = 1; + + if (!user || !*user) + user = "anonymous"; + if (!host || !*host) + host = "(none)"; + + memset(&env, 0, sizeof(env)); + strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user); + env[0] = strbuf_detach(&buf, NULL); + + strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host); + env[1] = strbuf_detach(&buf, NULL); + env[2] = NULL; + + memset(&cld, 0, sizeof(cld)); + cld.argv = argv; + cld.env = (const char *const *)env; + if (gzipped_request) + cld.in = -1; + cld.git_cmd = 1; + if (start_command(&cld)) + exit(1); + + close(1); + if (gzipped_request) + inflate_request(argv[0], cld.in); + else + close(0); + + if (finish_command(&cld)) + exit(1); + free(env[0]); + free(env[1]); + strbuf_release(&buf); +} + static int show_text_ref(const char *name, const unsigned char *sha1, int flag, void *cb_data) { @@ -167,11 +419,32 @@ static int show_text_ref(const char *name, const unsigned char *sha1, static void get_info_refs(char *arg) { + const char *service_name = get_parameter("service"); struct strbuf buf = STRBUF_INIT; - for_each_ref(show_text_ref, &buf); hdr_nocache(); - send_strbuf("text/plain", &buf); + + if (service_name) { + const char *argv[] = {NULL /* service name */, + "--stateless-rpc", "--advertise-refs", + ".", NULL}; + struct rpc_service *svc = select_service(service_name); + + strbuf_addf(&buf, "application/x-git-%s-advertisement", + svc->name); + hdr_str(content_type, buf.buf); + end_headers(); + + packet_write(1, "# service=git-%s\n", svc->name); + packet_flush(1); + + argv[0] = svc->name; + run_service(argv); + + } else { + for_each_ref(show_text_ref, &buf); + send_strbuf("text/plain", &buf); + } strbuf_release(&buf); } @@ -200,6 +473,48 @@ static void get_info_packs(char *arg) strbuf_release(&buf); } +static void check_content_type(const char *accepted_type) +{ + const char *actual_type = getenv("CONTENT_TYPE"); + + if (!actual_type) + actual_type = ""; + + if (strcmp(actual_type, accepted_type)) { + http_status(415, "Unsupported Media Type"); + hdr_nocache(); + end_headers(); + format_write(1, + "Expected POST with Content-Type '%s'," + " but received '%s' instead.\n", + accepted_type, actual_type); + exit(0); + } +} + +static void service_rpc(char *service_name) +{ + const char *argv[] = {NULL, "--stateless-rpc", ".", NULL}; + struct rpc_service *svc = select_service(service_name); + struct strbuf buf = STRBUF_INIT; + + strbuf_reset(&buf); + strbuf_addf(&buf, "application/x-git-%s-request", svc->name); + check_content_type(buf.buf); + + hdr_nocache(); + + strbuf_reset(&buf); + strbuf_addf(&buf, "application/x-git-%s-result", svc->name); + hdr_str(content_type, buf.buf); + + end_headers(); + + argv[0] = svc->name; + run_service(argv); + strbuf_release(&buf); +} + static NORETURN void die_webcgi(const char *err, va_list params) { char buffer[1000]; @@ -225,7 +540,10 @@ static struct service_cmd { {"GET", "/objects/info/packs$", get_info_packs}, {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object}, {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file}, - {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file} + {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file}, + + {"POST", "/git-upload-pack$", service_rpc}, + {"POST", "/git-receive-pack$", service_rpc} }; int main(int argc, char **argv) -- cgit v0.10.2-6-g49f6 From 917adc036086f52b0277ff03d10b7044c9d9d0d2 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 30 Oct 2009 17:47:35 -0700 Subject: http-backend: add GIT_PROJECT_ROOT environment var Add a new environment variable, GIT_PROJECT_ROOT, to override the method of using PATH_TRANSLATED to find the git repository on disk. This makes it much easier to configure the web server, especially when the web server's DocumentRoot does not contain the git repositories, which is the usual case. Signed-off-by: Mark Lodato Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index 022a243..99dbbfb 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -41,29 +41,24 @@ http.receivepack:: URL TRANSLATION --------------- -'git-http-backend' relies on the invoking web server to perform -URL to path translation, and store the repository path into the -PATH_TRANSLATED environment variable. Most web servers will do -this translation automatically, resolving the suffix after the -CGI name relative to the server's document root. +To determine the location of the repository on disk, 'git-http-backend' +concatenates the environment variables PATH_INFO, which is set +automatically by the web server, and GIT_PROJECT_ROOT, which must be set +manually in the web server configuration. If GIT_PROJECT_ROOT is not +set, 'git-http-backend' reads PATH_TRANSLATED, which is also set +automatically by the web server. EXAMPLES -------- Apache 2.x:: - To serve all Git repositories contained within the '/git/' - subdirectory of the DocumentRoot, ensure mod_cgi and - mod_alias are enabled, and create a ScriptAlias to the CGI: + Ensure mod_cgi, mod_alias, and mod_env are enabled, set + GIT_PROJECT_ROOT (or DocumentRoot) appropriately, and + create a ScriptAlias to the CGI: + ---------------------------------------------------------------- -ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/ - - - Options None - - - Options ExecCGI - +SetEnv GIT_PROJECT_ROOT /var/www/git +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ ---------------------------------------------------------------- + To enable anonymous read access but authenticated write access, @@ -78,16 +73,16 @@ require authorization with a LocationMatch directive: ---------------------------------------------------------------- + -To require authentication for both reads and writes, use a Directory +To require authentication for both reads and writes, use a Location directive around the repository, or one of its parent directories: + ---------------------------------------------------------------- - + AuthType Basic AuthName "Private Git Access" Require group committers ... - + ---------------------------------------------------------------- Accelerated static Apache 2.x:: @@ -97,9 +92,9 @@ Accelerated static Apache 2.x:: file contents from the file system directly to the network: + ---------------------------------------------------------------- -DocumentRoot /var/www +SetEnv GIT_PROJECT_ROOT /var/www/git -ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/ +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ Alias /git_static/ /var/www/git/ RewriteEngine on @@ -114,7 +109,7 @@ ENVIRONMENT 'git-http-backend' relies upon the CGI environment variables set by the invoking web server, including: -* PATH_TRANSLATED +* PATH_INFO (if GIT_PROJECT_ROOT is set, otherwise PATH_TRANSLATED) * REMOTE_USER * REMOTE_ADDR * CONTENT_TYPE diff --git a/http-backend.c b/http-backend.c index bfce520..7900cda 100644 --- a/http-backend.c +++ b/http-backend.c @@ -528,6 +528,26 @@ static NORETURN void die_webcgi(const char *err, va_list params) exit(0); } +static char* getdir(void) +{ + struct strbuf buf = STRBUF_INIT; + char *pathinfo = getenv("PATH_INFO"); + char *root = getenv("GIT_PROJECT_ROOT"); + char *path = getenv("PATH_TRANSLATED"); + + if (root && *root) { + if (!pathinfo || !*pathinfo) + die("GIT_PROJECT_ROOT is set but PATH_INFO is not"); + strbuf_addstr(&buf, root); + strbuf_addstr(&buf, pathinfo); + return strbuf_detach(&buf, NULL); + } else if (path && *path) { + return xstrdup(path); + } else + die("No GIT_PROJECT_ROOT or PATH_TRANSLATED from server"); + return NULL; +} + static struct service_cmd { const char *method; const char *pattern; @@ -549,7 +569,7 @@ static struct service_cmd { int main(int argc, char **argv) { char *method = getenv("REQUEST_METHOD"); - char *dir = getenv("PATH_TRANSLATED"); + char *dir; struct service_cmd *cmd = NULL; char *cmd_arg = NULL; int i; @@ -561,8 +581,7 @@ int main(int argc, char **argv) die("No REQUEST_METHOD from server"); if (!strcmp(method, "HEAD")) method = "GET"; - if (!dir) - die("No PATH_TRANSLATED from server"); + dir = getdir(); for (i = 0; i < ARRAY_SIZE(services); i++) { struct service_cmd *c = &services[i]; -- cgit v0.10.2-6-g49f6 From b9af4ab3cd33f699ea2a978acb83dbddf07900e6 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 30 Oct 2009 17:47:36 -0700 Subject: http-backend: reword some documentation Clarify some of the git-http-backend documentation, particularly: * In the Description, state that smart/dumb HTTP fetch and smart HTTP push are supported, state that authenticated clients allow push, and remove the note that this is only suited for read-only updates. * At the start of Examples, state explicitly what URL is mapping to what location on disk. Signed-off-by: Mark Lodato Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index 99dbbfb..0b5e951 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -14,13 +14,15 @@ DESCRIPTION ----------- A simple CGI program to serve the contents of a Git repository to Git clients accessing the repository over http:// and https:// protocols. +The program supports clients fetching using both the smart HTTP protcol +and the backwards-compatible dumb HTTP protocol, as well as clients +pushing using the smart HTTP protocol. By default, only the `upload-pack` service is enabled, which serves 'git-fetch-pack' and 'git-ls-remote' clients, which are invoked from -'git-fetch', 'git-pull', and 'git-clone'. - -This is ideally suited for read-only updates, i.e., pulling from -git repositories. +'git-fetch', 'git-pull', and 'git-clone'. If the client is authenticated, +the `receive-pack` service is enabled, which serves 'git-send-pack' +clients, which is invoked from 'git-push'. SERVICES -------- @@ -50,6 +52,8 @@ automatically by the web server. EXAMPLES -------- +All of the following examples map 'http://$hostname/git/foo/bar.git' +to '/var/www/git/foo/bar.git'. Apache 2.x:: Ensure mod_cgi, mod_alias, and mod_env are enabled, set -- cgit v0.10.2-6-g49f6 From 0ebb1fa78eb9d6b27ae79c9e48e9306bcfa2a4ac Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 30 Oct 2009 17:47:37 -0700 Subject: http-backend: use mod_alias instead of mod_rewrite In the git-http-backend documentation, use mod_alias exlusively, instead of using a combination of mod_alias and mod_rewrite. This makes the example slightly shorted and a bit more clear. Signed-off-by: Mark Lodato Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index 0b5e951..e67519d 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -98,13 +98,9 @@ Accelerated static Apache 2.x:: ---------------------------------------------------------------- SetEnv GIT_PROJECT_ROOT /var/www/git -ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ -Alias /git_static/ /var/www/git/ - -RewriteEngine on -RewriteRule ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /git_static/$1 [PT] -RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.pack)$ /git_static/$1 [PT] -RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.idx)$ /git_static/$1 [PT] +AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1 +AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1 +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ ---------------------------------------------------------------- -- cgit v0.10.2-6-g49f6 From 8127f778a0f6495a0b8484a21b5591e56d873de8 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 30 Oct 2009 17:47:38 -0700 Subject: http-backend: add example for gitweb on same URL In the git-http-backend documentation, add an example of how to set up gitweb and git-http-backend on the same URL by using a series of mod_alias commands. Signed-off-by: Mark Lodato Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index e67519d..2989c9f 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -88,6 +88,23 @@ directive around the repository, or one of its parent directories: ... ---------------------------------------------------------------- ++ +To serve gitweb at the same url, use a ScriptAliasMatch to only +those URLs that 'git-http-backend' can handle, and forward the +rest to gitweb: ++ +---------------------------------------------------------------- +ScriptAliasMatch \ + "(?x)^/git/(.*/(HEAD | \ + info/refs | \ + objects/(info/[^/]+ | \ + [0-9a-f]{2}/[0-9a-f]{38} | \ + pack/pack-[0-9a-f]{40}\.(pack|idx)) | \ + git-(upload|receive)-pack))$" \ + /usr/libexec/git-core/git-http-backend/$1 + +ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/ +---------------------------------------------------------------- Accelerated static Apache 2.x:: Similar to the above, but Apache can be used to return static @@ -102,6 +119,22 @@ AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1 AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1 ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ ---------------------------------------------------------------- ++ +This can be combined with the gitweb configuration: ++ +---------------------------------------------------------------- +SetEnv GIT_PROJECT_ROOT /var/www/git + +AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1 +AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1 +ScriptAliasMatch \ + "(?x)^/git/(.*/(HEAD | \ + info/refs | \ + objects/info/[^/]+ | \ + git-(upload|receive)-pack))$" \ + /usr/libexec/git-core/git-http-backend/$1 +ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/ +---------------------------------------------------------------- ENVIRONMENT -- cgit v0.10.2-6-g49f6 From f5ba2d18f96037749f370c1386935e60f034c87e Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Fri, 30 Oct 2009 17:47:39 -0700 Subject: http-backend: more explict LocationMatch In the git-http-backend examples, only match git-receive-pack within /git/. Signed-off-by: Mark Lodato Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index 2989c9f..f17251a 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -69,7 +69,7 @@ To enable anonymous read access but authenticated write access, require authorization with a LocationMatch directive: + ---------------------------------------------------------------- - + AuthType Basic AuthName "Git Access" Require group committers -- cgit v0.10.2-6-g49f6 From 97cc7bc45c2e076184b84749952b216036a6ecd6 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:40 -0700 Subject: Discover refs via smart HTTP server when available Instead of loading the cached info/refs, try to use the smart HTTP version when the server supports it. Since the smart variant is actually the pkt-line stream from the start of either upload-pack or receive-pack we need to parse these through get_remote_heads, which requires a background thread to feed its pipe. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano diff --git a/remote-curl.c b/remote-curl.c index 5c9dd97..3917d45 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -5,6 +5,7 @@ #include "http.h" #include "exec_cmd.h" #include "run-command.h" +#include "pkt-line.h" static struct remote *remote; static const char *url; @@ -75,21 +76,46 @@ static int set_option(const char *name, const char *value) } } -static struct ref *get_refs(void) +struct discovery { + const char *service; + char *buf_alloc; + char *buf; + size_t len; + unsigned proto_git : 1; +}; +static struct discovery *last_discovery; + +static void free_discovery(struct discovery *d) +{ + if (d) { + if (d == last_discovery) + last_discovery = NULL; + free(d->buf_alloc); + free(d); + } +} + +static struct discovery* discover_refs(const char *service) { struct strbuf buffer = STRBUF_INIT; - char *data, *start, *mid; - char *ref_name; + struct discovery *last = last_discovery; char *refs_url; - int i = 0; - int http_ret; + int http_ret, is_http = 0; - struct ref *refs = NULL; - struct ref *ref = NULL; - struct ref *last_ref = NULL; + if (last && !strcmp(service, last->service)) + return last; + free_discovery(last); - refs_url = xmalloc(strlen(url) + 11); - sprintf(refs_url, "%s/info/refs", url); + strbuf_addf(&buffer, "%s/info/refs", url); + if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) { + is_http = 1; + if (!strchr(url, '?')) + strbuf_addch(&buffer, '?'); + else + strbuf_addch(&buffer, '&'); + strbuf_addf(&buffer, "service=%s", service); + } + refs_url = strbuf_detach(&buffer, NULL); init_walker(); http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); @@ -104,10 +130,86 @@ static struct ref *get_refs(void) die("HTTP request failed"); } - data = buffer.buf; + last= xcalloc(1, sizeof(*last_discovery)); + last->service = service; + last->buf_alloc = strbuf_detach(&buffer, &last->len); + last->buf = last->buf_alloc; + + if (is_http && 5 <= last->len && last->buf[4] == '#') { + /* smart HTTP response; validate that the service + * pkt-line matches our request. + */ + struct strbuf exp = STRBUF_INIT; + + if (packet_get_line(&buffer, &last->buf, &last->len) <= 0) + die("%s has invalid packet header", refs_url); + if (buffer.len && buffer.buf[buffer.len - 1] == '\n') + strbuf_setlen(&buffer, buffer.len - 1); + + strbuf_addf(&exp, "# service=%s", service); + if (strbuf_cmp(&exp, &buffer)) + die("invalid server response; got '%s'", buffer.buf); + strbuf_release(&exp); + + /* The header can include additional metadata lines, up + * until a packet flush marker. Ignore these now, but + * in the future we might start to scan them. + */ + strbuf_reset(&buffer); + while (packet_get_line(&buffer, &last->buf, &last->len) > 0) + strbuf_reset(&buffer); + + last->proto_git = 1; + } + + free(refs_url); + strbuf_release(&buffer); + last_discovery = last; + return last; +} + +static int write_discovery(int fd, void *data) +{ + struct discovery *heads = data; + int err = 0; + if (write_in_full(fd, heads->buf, heads->len) != heads->len) + err = 1; + close(fd); + return err; +} + +static struct ref *parse_git_refs(struct discovery *heads) +{ + struct ref *list = NULL; + struct async async; + + memset(&async, 0, sizeof(async)); + async.proc = write_discovery; + async.data = heads; + + if (start_async(&async)) + die("cannot start thread to parse advertised refs"); + get_remote_heads(async.out, &list, 0, NULL, 0, NULL); + close(async.out); + if (finish_async(&async)) + die("ref parsing thread failed"); + return list; +} + +static struct ref *parse_info_refs(struct discovery *heads) +{ + char *data, *start, *mid; + char *ref_name; + int i = 0; + + struct ref *refs = NULL; + struct ref *ref = NULL; + struct ref *last_ref = NULL; + + data = heads->buf; start = NULL; mid = data; - while (i < buffer.len) { + while (i < heads->len) { if (!start) { start = &data[i]; } @@ -131,8 +233,7 @@ static struct ref *get_refs(void) i++; } - strbuf_release(&buffer); - + init_walker(); ref = alloc_ref("HEAD"); if (!walker->fetch_ref(walker, ref) && !resolve_remote_symref(ref, refs)) { @@ -142,11 +243,23 @@ static struct ref *get_refs(void) free(ref); } - strbuf_release(&buffer); - free(refs_url); return refs; } +static struct ref *get_refs(int for_push) +{ + struct discovery *heads; + + if (for_push) + heads = discover_refs("git-receive-pack"); + else + heads = discover_refs("git-upload-pack"); + + if (heads->proto_git) + return parse_git_refs(heads); + return parse_info_refs(heads); +} + static void output_refs(struct ref *refs) { struct ref *posn; @@ -317,7 +430,8 @@ int main(int argc, const char **argv) parse_fetch(&buf); } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) { - output_refs(get_refs()); + int for_push = !!strstr(buf.buf + 4, "for-push"); + output_refs(get_refs(for_push)); } else if (!prefixcmp(buf.buf, "push ")) { parse_push(&buf); -- cgit v0.10.2-6-g49f6 From de1a2fdd38b138c4e4fed6412783dcb74d63d2da Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:41 -0700 Subject: Smart push over HTTP: client side The git-remote-curl backend detects if the remote server supports the git-receive-pack service, and if so, runs git-send-pack in a pipe to dump the command and pack data as a single POST request. The advertisements from the server that were obtained during the discovery are passed into git-send-pack before the POST request starts. This permits git-send-pack to operate largely unmodified. For smaller packs (those under 1 MiB) a HTTP/1.0 POST with a Content-Length is used, permitting interaction with any server. The 1 MiB limit is arbitrary, but is sufficent to fit most deltas created by human authors against text sources with the occasional small binary file (e.g. few KiB icon image). The configuration option http.postBuffer can be used to increase (or shink) this buffer if the default is not sufficient. For larger packs which cannot be spooled entirely into the helper's memory space (due to http.postBuffer being too small), the POST request requires HTTP/1.1 and sets "Transfer-Encoding: chunked". This permits the client to upload an unknown amount of data in one HTTP transaction without needing to pregenerate the entire pack file locally. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index cd17814..7130d07 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1089,6 +1089,14 @@ http.maxRequests:: How many HTTP requests to launch in parallel. Can be overridden by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5. +http.postBuffer:: + Maximum size in bytes of the buffer used by smart HTTP + transports when POSTing data to the remote system. + For requests larger than this buffer size, HTTP/1.1 and + Transfer-Encoding: chunked is used to avoid creating a + massive pack file locally. Default is 1 MiB, which is + sufficient for most requests. + http.lowSpeedLimit, http.lowSpeedTime:: If the HTTP transfer speed is less than 'http.lowSpeedLimit' for longer than 'http.lowSpeedTime' seconds, the transfer is aborted. diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 37e528e..a0fbad0 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -2,9 +2,11 @@ #include "commit.h" #include "refs.h" #include "pkt-line.h" +#include "sideband.h" #include "run-command.h" #include "remote.h" #include "send-pack.h" +#include "quote.h" static const char send_pack_usage[] = "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=] [--verbose] [--thin] [:] [...]\n" @@ -59,7 +61,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext memset(&po, 0, sizeof(po)); po.argv = argv; po.in = -1; - po.out = fd; + po.out = args->stateless_rpc ? -1 : fd; po.git_cmd = 1; if (start_command(&po)) die_errno("git pack-objects failed"); @@ -83,6 +85,20 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext } close(po.in); + + if (args->stateless_rpc) { + char *buf = xmalloc(LARGE_PACKET_MAX); + while (1) { + ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX); + if (n <= 0) + break; + send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX); + } + free(buf); + close(po.out); + po.out = -1; + } + if (finish_command(&po)) return error("pack-objects died with strange error"); return 0; @@ -303,6 +319,59 @@ static int refs_pushed(struct ref *ref) return 0; } +static void print_helper_status(struct ref *ref) +{ + struct strbuf buf = STRBUF_INIT; + + for (; ref; ref = ref->next) { + const char *msg = NULL; + const char *res; + + switch(ref->status) { + case REF_STATUS_NONE: + res = "error"; + msg = "no match"; + break; + + case REF_STATUS_OK: + res = "ok"; + break; + + case REF_STATUS_UPTODATE: + res = "ok"; + msg = "up to date"; + break; + + case REF_STATUS_REJECT_NONFASTFORWARD: + res = "error"; + msg = "non-fast forward"; + break; + + case REF_STATUS_REJECT_NODELETE: + case REF_STATUS_REMOTE_REJECT: + res = "error"; + break; + + case REF_STATUS_EXPECTING_REPORT: + default: + continue; + } + + strbuf_reset(&buf); + strbuf_addf(&buf, "%s %s", res, ref->name); + if (ref->remote_status) + msg = ref->remote_status; + if (msg) { + strbuf_addch(&buf, ' '); + quote_two_c_style(&buf, "", msg, 0); + } + strbuf_addch(&buf, '\n'); + + safe_write(1, buf.buf, buf.len); + } + strbuf_release(&buf); +} + int send_pack(struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, @@ -310,6 +379,7 @@ int send_pack(struct send_pack_args *args, { int in = fd[0]; int out = fd[1]; + struct strbuf req_buf = STRBUF_INIT; struct ref *ref; int new_refs; int ask_for_status_report = 0; @@ -391,14 +461,14 @@ int send_pack(struct send_pack_args *args, char *new_hex = sha1_to_hex(ref->new_sha1); if (ask_for_status_report) { - packet_write(out, "%s %s %s%c%s", + packet_buf_write(&req_buf, "%s %s %s%c%s", old_hex, new_hex, ref->name, 0, "report-status"); ask_for_status_report = 0; expect_status_report = 1; } else - packet_write(out, "%s %s %s", + packet_buf_write(&req_buf, "%s %s %s", old_hex, new_hex, ref->name); } ref->status = expect_status_report ? @@ -406,7 +476,17 @@ int send_pack(struct send_pack_args *args, REF_STATUS_OK; } - packet_flush(out); + if (args->stateless_rpc) { + if (!args->dry_run) { + packet_buf_flush(&req_buf); + send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); + } + } else { + safe_write(out, req_buf.buf, req_buf.len); + packet_flush(out); + } + strbuf_release(&req_buf); + if (new_refs && !args->dry_run) { if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) @@ -414,11 +494,15 @@ int send_pack(struct send_pack_args *args, return -1; } } + if (args->stateless_rpc && !args->dry_run) + packet_flush(out); if (expect_status_report) ret = receive_status(in, remote_refs); else ret = 0; + if (args->stateless_rpc) + packet_flush(out); if (ret < 0) return ret; @@ -478,6 +562,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) struct extra_have_objects extra_have; struct ref *remote_refs, *local_refs; int ret; + int helper_status = 0; int send_all = 0; const char *receivepack = "git-receive-pack"; int flags; @@ -523,6 +608,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.use_thin_pack = 1; continue; } + if (!strcmp(arg, "--stateless-rpc")) { + args.stateless_rpc = 1; + continue; + } + if (!strcmp(arg, "--helper-status")) { + helper_status = 1; + continue; + } usage(send_pack_usage); } if (!dest) { @@ -551,7 +644,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) } } - conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0); + if (args.stateless_rpc) { + conn = NULL; + fd[0] = 0; + fd[1] = 1; + } else { + conn = git_connect(fd, dest, receivepack, + args.verbose ? CONNECT_VERBOSE : 0); + } memset(&extra_have, 0, sizeof(extra_have)); @@ -575,12 +675,16 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) ret = send_pack(&args, fd, conn, remote_refs, &extra_have); + if (helper_status) + print_helper_status(remote_refs); + close(fd[1]); close(fd[0]); ret |= finish_connect(conn); - print_push_status(dest, remote_refs); + if (!helper_status) + print_push_status(dest, remote_refs); if (!args.dry_run && remote) { struct ref *ref; diff --git a/http.c b/http.c index 23b2a19..ed6414a 100644 --- a/http.c +++ b/http.c @@ -1,9 +1,11 @@ #include "http.h" #include "pack.h" +#include "sideband.h" int data_received; int active_requests; int http_is_verbose; +size_t http_post_buffer = 16 * LARGE_PACKET_MAX; #ifdef USE_CURL_MULTI static int max_requests = -1; @@ -97,8 +99,6 @@ size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf) return eltsize * nmemb; } -static void finish_active_slot(struct active_request_slot *slot); - #ifdef USE_CURL_MULTI static void process_curl_messages(void) { @@ -174,6 +174,13 @@ static int http_options(const char *var, const char *value, void *cb) if (!strcmp("http.proxy", var)) return git_config_string(&curl_http_proxy, var, value); + if (!strcmp("http.postbuffer", var)) { + http_post_buffer = git_config_int(var, value); + if (http_post_buffer < LARGE_PACKET_MAX) + http_post_buffer = LARGE_PACKET_MAX; + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value, cb); } @@ -638,7 +645,7 @@ void release_active_slot(struct active_request_slot *slot) #endif } -static void finish_active_slot(struct active_request_slot *slot) +void finish_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); diff --git a/http.h b/http.h index 4c4e99c..f828e1d 100644 --- a/http.h +++ b/http.h @@ -79,6 +79,7 @@ extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp); extern struct active_request_slot *get_active_slot(void); extern int start_active_slot(struct active_request_slot *slot); extern void run_active_slot(struct active_request_slot *slot); +extern void finish_active_slot(struct active_request_slot *slot); extern void finish_all_active_slots(void); extern void release_active_slot(struct active_request_slot *slot); @@ -94,6 +95,7 @@ extern void http_cleanup(void); extern int data_received; extern int active_requests; extern int http_is_verbose; +extern size_t http_post_buffer; extern char curl_errorstr[CURL_ERROR_SIZE]; diff --git a/remote-curl.c b/remote-curl.c index 3917d45..f1206cb 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -6,6 +6,7 @@ #include "exec_cmd.h" #include "run-command.h" #include "pkt-line.h" +#include "sideband.h" static struct remote *remote; static const char *url; @@ -16,7 +17,8 @@ struct options { unsigned long depth; unsigned progress : 1, followtags : 1, - dry_run : 1; + dry_run : 1, + thin : 1; }; static struct options options; @@ -274,6 +276,188 @@ static void output_refs(struct ref *refs) free_refs(refs); } +struct rpc_state { + const char *service_name; + const char **argv; + char *service_url; + char *hdr_content_type; + char *hdr_accept; + char *buf; + size_t alloc; + size_t len; + size_t pos; + int in; + int out; + struct strbuf result; +}; + +static size_t rpc_out(void *ptr, size_t eltsize, + size_t nmemb, void *buffer_) +{ + size_t max = eltsize * nmemb; + struct rpc_state *rpc = buffer_; + size_t avail = rpc->len - rpc->pos; + + if (!avail) { + avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + if (!avail) + return 0; + rpc->pos = 0; + rpc->len = avail; + } + + if (max < avail); + avail = max; + memcpy(ptr, rpc->buf + rpc->pos, avail); + rpc->pos += avail; + return avail; +} + +static size_t rpc_in(const void *ptr, size_t eltsize, + size_t nmemb, void *buffer_) +{ + size_t size = eltsize * nmemb; + struct rpc_state *rpc = buffer_; + write_or_die(rpc->in, ptr, size); + return size; +} + +static int post_rpc(struct rpc_state *rpc) +{ + struct active_request_slot *slot; + struct slot_results results; + struct curl_slist *headers = NULL; + int err = 0, large_request = 0; + + /* Try to load the entire request, if we can fit it into the + * allocated buffer space we can use HTTP/1.0 and avoid the + * chunked encoding mess. + */ + while (1) { + size_t left = rpc->alloc - rpc->len; + char *buf = rpc->buf + rpc->len; + int n; + + if (left < LARGE_PACKET_MAX) { + large_request = 1; + break; + } + + n = packet_read_line(rpc->out, buf, left); + if (!n) + break; + rpc->len += n; + } + + slot = get_active_slot(); + slot->results = &results; + + curl_easy_setopt(slot->curl, CURLOPT_POST, 1); + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url); + + headers = curl_slist_append(headers, rpc->hdr_content_type); + headers = curl_slist_append(headers, rpc->hdr_accept); + + if (large_request) { + /* The request body is large and the size cannot be predicted. + * We must use chunked encoding to send it. + */ + headers = curl_slist_append(headers, "Expect: 100-continue"); + headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); + curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out); + curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc); + if (options.verbosity > 1) { + fprintf(stderr, "POST %s (chunked)\n", rpc->service_name); + fflush(stderr); + } + + } else { + /* We know the complete request size in advance, use the + * more normal Content-Length approach. + */ + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, rpc->buf); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, rpc->len); + if (options.verbosity > 1) { + fprintf(stderr, "POST %s (%lu bytes)\n", + rpc->service_name, (unsigned long)rpc->len); + fflush(stderr); + } + } + + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in); + curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc); + + slot->curl_result = curl_easy_perform(slot->curl); + finish_active_slot(slot); + + if (results.curl_result != CURLE_OK) { + err |= error("RPC failed; result=%d, HTTP code = %ld", + results.curl_result, results.http_code); + } + + curl_slist_free_all(headers); + return err; +} + +static int rpc_service(struct rpc_state *rpc, struct discovery *heads) +{ + const char *svc = rpc->service_name; + struct strbuf buf = STRBUF_INIT; + struct child_process client; + int err = 0; + + init_walker(); + memset(&client, 0, sizeof(client)); + client.in = -1; + client.out = -1; + client.git_cmd = 1; + client.argv = rpc->argv; + if (start_command(&client)) + exit(1); + if (heads) + write_or_die(client.in, heads->buf, heads->len); + + rpc->alloc = http_post_buffer; + rpc->buf = xmalloc(rpc->alloc); + rpc->in = client.in; + rpc->out = client.out; + strbuf_init(&rpc->result, 0); + + strbuf_addf(&buf, "%s/%s", url, svc); + rpc->service_url = strbuf_detach(&buf, NULL); + + strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc); + rpc->hdr_content_type = strbuf_detach(&buf, NULL); + + strbuf_addf(&buf, "Accept: application/x-%s-response", svc); + rpc->hdr_accept = strbuf_detach(&buf, NULL); + + while (!err) { + int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + if (!n) + break; + rpc->pos = 0; + rpc->len = n; + err |= post_rpc(rpc); + } + strbuf_read(&rpc->result, client.out, 0); + + close(client.in); + close(client.out); + client.in = -1; + client.out = -1; + + err |= finish_command(&client); + free(rpc->service_url); + free(rpc->hdr_content_type); + free(rpc->hdr_accept); + free(rpc->buf); + strbuf_release(&buf); + return err; +} + static int fetch_dumb(int nr_heads, struct ref **to_fetch) { char **targets = xmalloc(nr_heads * sizeof(char*)); @@ -371,6 +555,52 @@ static int push_dav(int nr_spec, char **specs) return 0; } +static int push_git(struct discovery *heads, int nr_spec, char **specs) +{ + struct rpc_state rpc; + const char **argv; + int argc = 0, i, err; + + argv = xmalloc((10 + nr_spec) * sizeof(char*)); + argv[argc++] = "send-pack"; + argv[argc++] = "--stateless-rpc"; + argv[argc++] = "--helper-status"; + if (options.thin) + argv[argc++] = "--thin"; + if (options.dry_run) + argv[argc++] = "--dry-run"; + if (options.verbosity > 1) + argv[argc++] = "--verbose"; + argv[argc++] = url; + for (i = 0; i < nr_spec; i++) + argv[argc++] = specs[i]; + argv[argc++] = NULL; + + memset(&rpc, 0, sizeof(rpc)); + rpc.service_name = "git-receive-pack", + rpc.argv = argv; + + err = rpc_service(&rpc, heads); + if (rpc.result.len) + safe_write(1, rpc.result.buf, rpc.result.len); + strbuf_release(&rpc.result); + free(argv); + return err; +} + +static int push(int nr_spec, char **specs) +{ + struct discovery *heads = discover_refs("git-receive-pack"); + int ret; + + if (heads->proto_git) + ret = push_git(heads, nr_spec, specs); + else + ret = push_dav(nr_spec, specs); + free_discovery(heads); + return ret; +} + static void parse_push(struct strbuf *buf) { char **specs = NULL; @@ -391,7 +621,7 @@ static void parse_push(struct strbuf *buf) break; } while (1); - if (push_dav(nr_spec, specs)) + if (push(nr_spec, specs)) exit(128); /* error already reported */ for (i = 0; i < nr_spec; i++) free(specs[i]); @@ -414,6 +644,7 @@ int main(int argc, const char **argv) options.verbosity = 1; options.progress = !!isatty(2); + options.thin = 1; remote = remote_get(argv[1]); diff --git a/send-pack.h b/send-pack.h index 8b3cf02..28141ac 100644 --- a/send-pack.h +++ b/send-pack.h @@ -8,7 +8,8 @@ struct send_pack_args { force_update:1, use_thin_pack:1, use_ofs_delta:1, - dry_run:1; + dry_run:1, + stateless_rpc:1; }; int send_pack(struct send_pack_args *args, diff --git a/sideband.c b/sideband.c index 899b1ff..d5ffa1c 100644 --- a/sideband.c +++ b/sideband.c @@ -135,9 +135,14 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet n = sz; if (packet_max - 5 < n) n = packet_max - 5; - sprintf(hdr, "%04x", n + 5); - hdr[4] = band; - safe_write(fd, hdr, 5); + if (0 <= band) { + sprintf(hdr, "%04x", n + 5); + hdr[4] = band; + safe_write(fd, hdr, 5); + } else { + sprintf(hdr, "%04x", n + 4); + safe_write(fd, hdr, 4); + } safe_write(fd, p, n); p += n; sz -= n; diff --git a/transport.c b/transport.c index 6d9652d..2ff1650 100644 --- a/transport.c +++ b/transport.c @@ -731,6 +731,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re NULL); } + memset(&args, 0, sizeof(args)); args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR); args.force_update = !!(flags & TRANSPORT_PUSH_FORCE); args.use_thin_pack = data->thin; -- cgit v0.10.2-6-g49f6 From 249b2004d8c9c58ed1ea1665dfd376af0312ed7e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:42 -0700 Subject: Smart fetch over HTTP: client side The git-remote-curl backend detects if the remote server supports the git-upload-pack service, and if so, runs git-fetch-pack locally in a pipe to generate the want/have commands. The advertisements from the server that were obtained during the discovery are passed into git-fetch-pack before the POST request starts, permitting server capability discovery and enablement. Common objects that are discovered are appended onto the request as have lines and are sent again on the next request. This allows the remote side to reinitialize its in-memory list of common objects during the next request. Because all requests are relatively short, below git-remote-curl's 1 MiB buffer limit, requests will use the standard Content-Length header and be valid HTTP/1.0 POST requests. This makes the fetch client more tolerant of proxy servers which don't support HTTP/1.1 or the chunked transfer encoding. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 615f549..8ed4a6f 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -165,6 +165,24 @@ enum ack_type { ACK_ready }; +static void consume_shallow_list(int fd) +{ + if (args.stateless_rpc && args.depth > 0) { + /* If we sent a depth we will get back "duplicate" + * shallow and unshallow commands every time there + * is a block of have lines exchanged. + */ + char line[1000]; + while (packet_read_line(fd, line, sizeof(line))) { + if (!prefixcmp(line, "shallow ")) + continue; + if (!prefixcmp(line, "unshallow ")) + continue; + die("git fetch-pack: expected shallow list"); + } + } +} + static enum ack_type get_ack(int fd, unsigned char *result_sha1) { static char line[1000]; @@ -190,6 +208,15 @@ static enum ack_type get_ack(int fd, unsigned char *result_sha1) die("git fetch_pack: expected ACK/NAK, got '%s'", line); } +static void send_request(int fd, struct strbuf *buf) +{ + if (args.stateless_rpc) { + send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); + packet_flush(fd); + } else + safe_write(fd, buf->buf, buf->len); +} + static int find_common(int fd[2], unsigned char *result_sha1, struct ref *refs) { @@ -199,7 +226,10 @@ static int find_common(int fd[2], unsigned char *result_sha1, unsigned in_vain = 0; int got_continue = 0; struct strbuf req_buf = STRBUF_INIT; + size_t state_len = 0; + if (args.stateless_rpc && multi_ack == 1) + die("--stateless-rpc requires multi_ack_detailed"); if (marked) for_each_ref(clear_marks, NULL); marked = 1; @@ -256,13 +286,13 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (args.depth > 0) packet_buf_write(&req_buf, "deepen %d", args.depth); packet_buf_flush(&req_buf); - - safe_write(fd[1], req_buf.buf, req_buf.len); + state_len = req_buf.len; if (args.depth > 0) { char line[1024]; unsigned char sha1[20]; + send_request(fd[1], &req_buf); while (packet_read_line(fd[0], line, sizeof(line))) { if (!prefixcmp(line, "shallow ")) { if (get_sha1_hex(line + 8, sha1)) @@ -284,28 +314,40 @@ static int find_common(int fd[2], unsigned char *result_sha1, } die("expected shallow/unshallow, got %s", line); } + } else if (!args.stateless_rpc) + send_request(fd[1], &req_buf); + + if (!args.stateless_rpc) { + /* If we aren't using the stateless-rpc interface + * we don't need to retain the headers. + */ + strbuf_setlen(&req_buf, 0); + state_len = 0; } flushes = 0; retval = -1; while ((sha1 = get_rev())) { - packet_write(fd[1], "have %s\n", sha1_to_hex(sha1)); + packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1)); if (args.verbose) fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); in_vain++; if (!(31 & ++count)) { int ack; - packet_flush(fd[1]); + packet_buf_flush(&req_buf); + send_request(fd[1], &req_buf); + strbuf_setlen(&req_buf, state_len); flushes++; /* * We keep one window "ahead" of the other side, and * will wait for an ACK only on the next one */ - if (count == 32) + if (!args.stateless_rpc && count == 32) continue; + consume_shallow_list(fd[0]); do { ack = get_ack(fd[0], result_sha1); if (args.verbose && ack) @@ -322,6 +364,17 @@ static int find_common(int fd[2], unsigned char *result_sha1, case ACK_continue: { struct commit *commit = lookup_commit(result_sha1); + if (args.stateless_rpc + && ack == ACK_common + && !(commit->object.flags & COMMON)) { + /* We need to replay the have for this object + * on the next RPC request so the peer knows + * it is in common with us. + */ + const char *hex = sha1_to_hex(result_sha1); + packet_buf_write(&req_buf, "have %s\n", hex); + state_len = req_buf.len; + } mark_common(commit, 0, 1); retval = 0; in_vain = 0; @@ -339,7 +392,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, } } done: - packet_write(fd[1], "done\n"); + packet_buf_write(&req_buf, "done\n"); + send_request(fd[1], &req_buf); if (args.verbose) fprintf(stderr, "done\n"); if (retval != 0) { @@ -348,6 +402,7 @@ done: } strbuf_release(&req_buf); + consume_shallow_list(fd[0]); while (flushes || multi_ack) { int ack = get_ack(fd[0], result_sha1); if (ack) { @@ -672,6 +727,8 @@ static struct ref *do_fetch_pack(int fd[2], */ warning("no common commits"); + if (args.stateless_rpc) + packet_flush(fd[1]); if (get_pack(fd, pack_lockfile)) die("git fetch-pack: fetch failed."); @@ -742,6 +799,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) struct ref *ref = NULL; char *dest = NULL, **heads; int fd[2]; + char *pack_lockfile = NULL; + char **pack_lockfile_ptr = NULL; struct child_process *conn; nr_heads = 0; @@ -791,6 +850,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.no_progress = 1; continue; } + if (!strcmp("--stateless-rpc", arg)) { + args.stateless_rpc = 1; + continue; + } + if (!strcmp("--lock-pack", arg)) { + args.lock_pack = 1; + pack_lockfile_ptr = &pack_lockfile; + continue; + } usage(fetch_pack_usage); } dest = (char *)arg; @@ -801,19 +869,27 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) if (!dest) usage(fetch_pack_usage); - conn = git_connect(fd, (char *)dest, args.uploadpack, - args.verbose ? CONNECT_VERBOSE : 0); - if (conn) { - get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL); - - ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL); - close(fd[0]); - close(fd[1]); - if (finish_connect(conn)) - ref = NULL; + if (args.stateless_rpc) { + conn = NULL; + fd[0] = 0; + fd[1] = 1; } else { - ref = NULL; + conn = git_connect(fd, (char *)dest, args.uploadpack, + args.verbose ? CONNECT_VERBOSE : 0); + } + + get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL); + + ref = fetch_pack(&args, fd, conn, ref, dest, + nr_heads, heads, pack_lockfile_ptr); + if (pack_lockfile) { + printf("lock %s\n", pack_lockfile); + fflush(stdout); } + close(fd[0]); + close(fd[1]); + if (finish_connect(conn)) + ref = NULL; ret = !ref; if (!ret && nr_heads) { diff --git a/fetch-pack.h b/fetch-pack.h index 8bd9c32..fbe85ac 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -13,7 +13,8 @@ struct fetch_pack_args fetch_all:1, verbose:1, no_progress:1, - include_tag:1; + include_tag:1, + stateless_rpc:1; }; struct ref *fetch_pack(struct fetch_pack_args *args, diff --git a/remote-curl.c b/remote-curl.c index f1206cb..0eb6fc4 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -45,7 +45,7 @@ static int set_option(const char *name, const char *value) options.progress = 0; else return -1; - return 1 /* TODO implement later */; + return 0; } else if (!strcmp(name, "depth")) { char *end; @@ -53,7 +53,7 @@ static int set_option(const char *name, const char *value) if (value == end || *end) return -1; options.depth = v; - return 1 /* TODO implement later */; + return 0; } else if (!strcmp(name, "followtags")) { if (!strcmp(value, "true")) @@ -62,7 +62,7 @@ static int set_option(const char *name, const char *value) options.followtags = 0; else return -1; - return 1 /* TODO implement later */; + return 0; } else if (!strcmp(name, "dry-run")) { if (!strcmp(value, "true")) @@ -463,6 +463,8 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) char **targets = xmalloc(nr_heads * sizeof(char*)); int ret, i; + if (options.depth) + die("dumb http transport does not support --depth"); for (i = 0; i < nr_heads; i++) targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); @@ -481,6 +483,65 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) return ret ? error("Fetch failed.") : 0; } +static int fetch_git(struct discovery *heads, + int nr_heads, struct ref **to_fetch) +{ + struct rpc_state rpc; + char *depth_arg = NULL; + const char **argv; + int argc = 0, i, err; + + argv = xmalloc((15 + nr_heads) * sizeof(char*)); + argv[argc++] = "fetch-pack"; + argv[argc++] = "--stateless-rpc"; + argv[argc++] = "--lock-pack"; + if (options.followtags) + argv[argc++] = "--include-tag"; + if (options.thin) + argv[argc++] = "--thin"; + if (options.verbosity >= 3) { + argv[argc++] = "-v"; + argv[argc++] = "-v"; + } + if (!options.progress) + argv[argc++] = "--no-progress"; + if (options.depth) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "--depth=%lu", options.depth); + depth_arg = strbuf_detach(&buf, NULL); + argv[argc++] = depth_arg; + } + argv[argc++] = url; + for (i = 0; i < nr_heads; i++) { + struct ref *ref = to_fetch[i]; + if (!ref->name || !*ref->name) + die("cannot fetch by sha1 over smart http"); + argv[argc++] = ref->name; + } + argv[argc++] = NULL; + + memset(&rpc, 0, sizeof(rpc)); + rpc.service_name = "git-upload-pack", + rpc.argv = argv; + + err = rpc_service(&rpc, heads); + if (rpc.result.len) + safe_write(1, rpc.result.buf, rpc.result.len); + strbuf_release(&rpc.result); + free(argv); + free(depth_arg); + return err; +} + +static int fetch(int nr_heads, struct ref **to_fetch) +{ + struct discovery *d = discover_refs("git-upload-pack"); + if (d->proto_git) + return fetch_git(d, nr_heads, to_fetch); + else + return fetch_dumb(nr_heads, to_fetch); +} + static void parse_fetch(struct strbuf *buf) { struct ref **to_fetch = NULL; @@ -523,7 +584,7 @@ static void parse_fetch(struct strbuf *buf) break; } while (1); - if (fetch_dumb(nr_heads, to_fetch)) + if (fetch(nr_heads, to_fetch)) exit(128); /* error already reported */ free_refs(list_head); free(to_fetch); -- cgit v0.10.2-6-g49f6 From b8538603a34b13ccb1a3a096f461b8a35d7ea878 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:43 -0700 Subject: Smart HTTP fetch: gzip requests The upload-pack requests are mostly plain text and they compress rather well. Deflating them with Content-Encoding: gzip can easily drop the size of the request by 50%, reducing the amount of data to transfer as we negotiate the common commits. Signed-off-by: Shawn O. Pearce CC: Daniel Barkalow Signed-off-by: Junio C Hamano diff --git a/remote-curl.c b/remote-curl.c index 0eb6fc4..0d7cf16 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -289,6 +289,7 @@ struct rpc_state { int in; int out; struct strbuf result; + unsigned gzip_request : 1; }; static size_t rpc_out(void *ptr, size_t eltsize, @@ -327,6 +328,8 @@ static int post_rpc(struct rpc_state *rpc) struct active_request_slot *slot; struct slot_results results; struct curl_slist *headers = NULL; + int use_gzip = rpc->gzip_request; + char *gzip_body = NULL; int err = 0, large_request = 0; /* Try to load the entire request, if we can fit it into the @@ -340,6 +343,7 @@ static int post_rpc(struct rpc_state *rpc) if (left < LARGE_PACKET_MAX) { large_request = 1; + use_gzip = 0; break; } @@ -355,6 +359,7 @@ static int post_rpc(struct rpc_state *rpc) curl_easy_setopt(slot->curl, CURLOPT_POST, 1); curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url); + curl_easy_setopt(slot->curl, CURLOPT_ENCODING, ""); headers = curl_slist_append(headers, rpc->hdr_content_type); headers = curl_slist_append(headers, rpc->hdr_accept); @@ -372,6 +377,49 @@ static int post_rpc(struct rpc_state *rpc) fflush(stderr); } + } else if (use_gzip && 1024 < rpc->len) { + /* The client backend isn't giving us compressed data so + * we can try to deflate it ourselves, this may save on. + * the transfer time. + */ + size_t size; + z_stream stream; + int ret; + + memset(&stream, 0, sizeof(stream)); + ret = deflateInit2(&stream, Z_BEST_COMPRESSION, + Z_DEFLATED, (15 + 16), + 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) + die("cannot deflate request; zlib init error %d", ret); + size = deflateBound(&stream, rpc->len); + gzip_body = xmalloc(size); + + stream.next_in = (unsigned char *)rpc->buf; + stream.avail_in = rpc->len; + stream.next_out = (unsigned char *)gzip_body; + stream.avail_out = size; + + ret = deflate(&stream, Z_FINISH); + if (ret != Z_STREAM_END) + die("cannot deflate request; zlib deflate error %d", ret); + + ret = deflateEnd(&stream); + if (ret != Z_OK) + die("cannot deflate request; zlib end error %d", ret); + + size = stream.total_out; + + headers = curl_slist_append(headers, "Content-Encoding: gzip"); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, size); + + if (options.verbosity > 1) { + fprintf(stderr, "POST %s (gzip %lu to %lu bytes)\n", + rpc->service_name, + (unsigned long)rpc->len, (unsigned long)size); + fflush(stderr); + } } else { /* We know the complete request size in advance, use the * more normal Content-Length approach. @@ -398,6 +446,7 @@ static int post_rpc(struct rpc_state *rpc) } curl_slist_free_all(headers); + free(gzip_body); return err; } @@ -523,6 +572,7 @@ static int fetch_git(struct discovery *heads, memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-upload-pack", rpc.argv = argv; + rpc.gzip_request = 1; err = rpc_service(&rpc, heads); if (rpc.result.len) -- cgit v0.10.2-6-g49f6 From eeb3aeddb23746912b6c903a7d90dffdd686708f Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 30 Oct 2009 17:47:44 -0700 Subject: t5540-http-push: remove redundant fetches Signed-off-by: Tay Ray Chuan Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index 2ece661..28a746e 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -116,9 +116,7 @@ test_expect_success 'create and delete remote branch' ' test_tick && git commit -m dev && git push origin dev && - git fetch && git push origin :dev && - git fetch && test_must_fail git show-ref --verify refs/remotes/origin/dev ' -- cgit v0.10.2-6-g49f6 From 859d1fb427ca79d3c70a3806c892e4dd148701bf Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Fri, 30 Oct 2009 17:47:45 -0700 Subject: set httpd port before sourcing lib-httpd If LIB_HTTPD_PORT is not set already, lib-httpd will set it to the default 8111. Signed-off-by: Clemens Buchacher Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index 28a746e..d1234f9 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -9,17 +9,16 @@ This test runs various sanity checks on http-push.' . ./test-lib.sh -ROOT_PATH="$PWD" -LIB_HTTPD_DAV=t -LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'} - if git http-push > /dev/null 2>&1 || [ $? -eq 128 ] then say "skipping test, USE_CURL_MULTI is not defined" test_done fi +LIB_HTTPD_DAV=t +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'} . "$TEST_DIRECTORY"/lib-httpd.sh +ROOT_PATH="$PWD" start_httpd test_expect_success 'setup remote repository' ' -- cgit v0.10.2-6-g49f6 From 024bb1256627219671a0924b195582b6e049ca87 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:46 -0700 Subject: http tests: use /dumb/ URL prefix To clarify what part of the HTTP transprot is being tested we change the URLs used by existing tests to include /dumb/ at the start, indicating they use the non-Git aware code paths. Signed-off-by: Shawn O. Pearce CC: Tay Ray Chuan Signed-off-by: Junio C Hamano diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 21aa42f..47a438f 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -8,6 +8,11 @@ ErrorLog error.log LoadModule log_config_module modules/mod_log_config.so + + LoadModule alias_module modules/mod_alias.so + + +Alias /dumb/ www/ LoadModule ssl_module modules/mod_ssl.so @@ -26,7 +31,7 @@ SSLEngine On LoadModule dav_fs_module modules/mod_dav_fs.so DAVLockDB DAVLock - + Dav on diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index d1234f9..4a9450e 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -42,7 +42,7 @@ test_expect_success 'setup remote repository' ' test_expect_success 'clone remote repository' ' cd "$ROOT_PATH" && - git clone $HTTPD_URL/test_repo.git test_repo_clone + git clone $HTTPD_URL/dumb/test_repo.git test_repo_clone ' test_expect_success 'push to remote repository with packed refs' ' @@ -75,7 +75,7 @@ test_expect_success 'http-push fetches unpacked objects' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_unpacked.git && - git clone $HTTPD_URL/test_repo_unpacked.git \ + git clone $HTTPD_URL/dumb/test_repo_unpacked.git \ "$ROOT_PATH"/fetch_unpacked && # By reset, we force git to retrieve the object @@ -84,14 +84,14 @@ test_expect_success 'http-push fetches unpacked objects' ' git remote rm origin && git reflog expire --expire=0 --all && git prune && - git push -f -v $HTTPD_URL/test_repo_unpacked.git master) + git push -f -v $HTTPD_URL/dumb/test_repo_unpacked.git master) ' test_expect_success 'http-push fetches packed objects' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git && - git clone $HTTPD_URL/test_repo_packed.git \ + git clone $HTTPD_URL/dumb/test_repo_packed.git \ "$ROOT_PATH"/test_repo_clone_packed && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git && @@ -104,7 +104,7 @@ test_expect_success 'http-push fetches packed objects' ' git remote rm origin && git reflog expire --expire=0 --all && git prune && - git push -f -v $HTTPD_URL/test_repo_packed.git master) + git push -f -v $HTTPD_URL/dumb/test_repo_packed.git master) ' test_expect_success 'create and delete remote branch' ' diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 0e69324..776057c 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -30,7 +30,7 @@ test_expect_success 'create http-accessible bare repository' ' ' test_expect_success 'clone http repository' ' - git clone $HTTPD_URL/repo.git clone && + git clone $HTTPD_URL/dumb/repo.git clone && test_cmp file clone/file ' @@ -58,7 +58,7 @@ test_expect_success 'fetch packed objects' ' cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && git --bare repack && git --bare prune-packed && - git clone $HTTPD_URL/repo_pack.git + git clone $HTTPD_URL/dumb/repo_pack.git ' stop_httpd -- cgit v0.10.2-6-g49f6 From 7da4e2280ccaf5ecb357f7cb2b81d62f78f00f9e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 30 Oct 2009 17:47:47 -0700 Subject: test smart http fetch and push The top level directory "/smart/" of the test Apache server is mapped through our git-http-backend CGI, but uses the same underlying repository space as the server's document root. This is the most simple installation possible. Server logs are checked to verify the client has accessed only the smart URLs during the test. During fetch testing the headers are also logged from libcurl to ensure we are making a reasonably sane HTTP request, and getting back reasonably sane response headers from the CGI. When validating the request headers used during smart fetch we munge away the actual Content-Length and replace it with the placeholder "xxx". This avoids unnecessary varability in the test caused by an unrelated change in the requested capabilities in the first want line of the request. However, we still want to look for and verify that Content-Length was used, because smaller payloads should be using Content-Length and not "Transfer-Encoding: chunked". When validating the server response headers we must discard both Content-Length and Transfer-Encoding, as Apache2 can use either format to return our response. During development of this test I observed Apache returning both forms, depending on when the processes got CPU time. If our CGI returned the pack data quickly, Apache just buffered the whole thing and returned a Content-Length. If our CGI took just a bit too long to complete, Apache flushed its buffer and instead used "Transfer-Encoding: chunked". Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 47a438f..0fe3fd0 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -11,9 +11,26 @@ ErrorLog error.log LoadModule alias_module modules/mod_alias.so + + LoadModule cgi_module modules/mod_cgi.so + + + LoadModule env_module modules/mod_env.so + Alias /dumb/ www/ + + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} + +ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/ + + Options None + + + Options ExecCGI + + LoadModule ssl_module modules/mod_ssl.so diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index 4a9450e..bb18f8b 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -3,7 +3,7 @@ # Copyright (c) 2008 Clemens Buchacher # -test_description='test http-push +test_description='test WebDAV http-push This test runs various sanity checks on http-push.' diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh new file mode 100755 index 0000000..2a58d0c --- /dev/null +++ b/t/t5541-http-push.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# +# Copyright (c) 2008 Clemens Buchacher +# + +test_description='test smart pushing over http via http-backend' +. ./test-lib.sh + +if test -n "$NO_CURL"; then + say 'skipping test, git built without http support' + test_done +fi + +ROOT_PATH="$PWD" +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5541'} +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'setup remote repository' ' + cd "$ROOT_PATH" && + mkdir test_repo && + cd test_repo && + git init && + : >path1 && + git add path1 && + test_tick && + git commit -m initial && + cd - && + git clone --bare test_repo test_repo.git && + cd test_repo.git && + git config http.receivepack true && + ORIG_HEAD=$(git rev-parse --verify HEAD) && + cd - && + mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH" +' + +test_expect_success 'clone remote repository' ' + cd "$ROOT_PATH" && + git clone $HTTPD_URL/smart/test_repo.git test_repo_clone +' + +test_expect_success 'push to remote repository' ' + cd "$ROOT_PATH"/test_repo_clone && + : >path2 && + git add path2 && + test_tick && + git commit -m path2 && + HEAD=$(git rev-parse --verify HEAD) && + git push && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + test $HEAD = $(git rev-parse --verify HEAD)) +' + +test_expect_success 'push already up-to-date' ' + git push +' + +test_expect_success 'create and delete remote branch' ' + cd "$ROOT_PATH"/test_repo_clone && + git checkout -b dev && + : >path3 && + git add path3 && + test_tick && + git commit -m dev && + git push origin dev && + git push origin :dev && + test_must_fail git show-ref --verify refs/remotes/origin/dev +' + +cat >exp <act <"$HTTPD_ROOT_PATH"/access.log && + test_cmp exp act +' + +stop_httpd +test_done diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 776057c..8cfce96 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='test fetching over http' +test_description='test dumb fetching over http via static file' . ./test-lib.sh if test -n "$NO_CURL"; then @@ -61,5 +61,11 @@ test_expect_success 'fetch packed objects' ' git clone $HTTPD_URL/dumb/repo_pack.git ' +test_expect_success 'did not use upload-pack service' ' + grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act + : >exp + test_cmp exp act +' + stop_httpd test_done diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh new file mode 100755 index 0000000..eb0c039 --- /dev/null +++ b/t/t5551-http-fetch.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +test_description='test smart fetching over http via http-backend' +. ./test-lib.sh + +if test -n "$NO_CURL"; then + say 'skipping test, git built without http support' + test_done +fi + +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5551'} +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'setup repository' ' + echo content >file && + git add file && + git commit -m one +' + +test_expect_success 'create http-accessible bare repository' ' + mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git --bare init + ) && + git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push public master:master +' + +cat >exp < GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 +> Accept: */* +> Pragma: no-cache + +< HTTP/1.1 200 OK +< Pragma: no-cache +< Cache-Control: no-cache, max-age=0, must-revalidate +< Content-Type: application/x-git-upload-pack-advertisement +< +> POST /smart/repo.git/git-upload-pack HTTP/1.1 +> Accept-Encoding: deflate, gzip +> Content-Type: application/x-git-upload-pack-request +> Accept: application/x-git-upload-pack-response +> Content-Length: xxx + +< HTTP/1.1 200 OK +< Pragma: no-cache +< Cache-Control: no-cache, max-age=0, must-revalidate +< Content-Type: application/x-git-upload-pack-result +< +EOF +test_expect_success 'clone http repository' ' + GIT_CURL_VERBOSE=1 git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err && + test_cmp file clone/file && + tr '\''\015'\'' Q <]/{ + s/^/> / + } + + /^> User-Agent: /d + /^> Host: /d + s/^> Content-Length: .*/> Content-Length: xxx/ + + /^< Server: /d + /^< Expires: /d + /^< Date: /d + /^< Content-Length: /d + /^< Transfer-Encoding: /d + " >act && + test_cmp exp act +' + +test_expect_success 'fetch changes via http' ' + echo content >>file && + git commit -a -m two && + git push public + (cd clone && git pull) && + test_cmp file clone/file +' + +cat >exp <act <"$HTTPD_ROOT_PATH"/access.log && + test_cmp exp act +' + +stop_httpd +test_done -- cgit v0.10.2-6-g49f6 From 5abb013b3ddfb42e5baa3c7de052af596a0ee82f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 4 Nov 2009 17:16:37 -0800 Subject: http-backend: Use http.getanyfile to disable dumb HTTP serving Some repository owners may wish to enable smart HTTP, but disallow dumb content serving. Disallowing dumb serving might be because the owners want to rely upon reachability to control which objects clients may access from the repository, or they just want to encourage clients to use the more bandwidth efficient transport. If http.getanyfile is set to false the backend CGI will return with '403 Forbidden' when an object file is accessed by a dumb client. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt index f17251a..67aec06 100644 --- a/Documentation/git-http-backend.txt +++ b/Documentation/git-http-backend.txt @@ -29,6 +29,14 @@ SERVICES These services can be enabled/disabled using the per-repository configuration file: +http.getanyfile:: + This serves older Git clients which are unable to use the + upload pack service. When enabled, clients are able to read + any file within the repository, including objects that are + no longer reachable from a branch but are still present. + It is enabled by default, but a repository can disable it + by setting this configuration item to `false`. + http.uploadpack:: This serves 'git-fetch-pack' and 'git-ls-remote' clients. It is enabled by default, but a repository can disable it diff --git a/http-backend.c b/http-backend.c index 7900cda..9021266 100644 --- a/http-backend.c +++ b/http-backend.c @@ -10,6 +10,7 @@ static const char content_type[] = "Content-Type"; static const char content_length[] = "Content-Length"; static const char last_modified[] = "Last-Modified"; +static int getanyfile = 1; static struct string_list *query_params; @@ -194,6 +195,12 @@ static NORETURN void forbidden(const char *err, ...) exit(0); } +static void select_getanyfile(void) +{ + if (!getanyfile) + forbidden("Unsupported service: getanyfile"); +} + static void send_strbuf(const char *type, struct strbuf *buf) { hdr_int(content_length, buf->len); @@ -238,38 +245,51 @@ static void send_file(const char *the_type, const char *name) static void get_text_file(char *name) { + select_getanyfile(); hdr_nocache(); send_file("text/plain", name); } static void get_loose_object(char *name) { + select_getanyfile(); hdr_cache_forever(); send_file("application/x-git-loose-object", name); } static void get_pack_file(char *name) { + select_getanyfile(); hdr_cache_forever(); send_file("application/x-git-packed-objects", name); } static void get_idx_file(char *name) { + select_getanyfile(); hdr_cache_forever(); send_file("application/x-git-packed-objects-toc", name); } static int http_config(const char *var, const char *value, void *cb) { - struct rpc_service *svc = cb; - - if (!prefixcmp(var, "http.") && - !strcmp(var + 5, svc->config_name)) { - svc->enabled = git_config_bool(var, value); + if (!strcmp(var, "http.getanyfile")) { + getanyfile = git_config_bool(var, value); return 0; } + if (!prefixcmp(var, "http.")) { + int i; + + for (i = 0; i < ARRAY_SIZE(rpc_service); i++) { + struct rpc_service *svc = &rpc_service[i]; + if (!strcmp(var + 5, svc->config_name)) { + svc->enabled = git_config_bool(var, value); + return 0; + } + } + } + /* we are not interested in parsing any other configuration here */ return 0; } @@ -293,7 +313,6 @@ static struct rpc_service *select_service(const char *name) if (!svc) forbidden("Unsupported service: '%s'", name); - git_config(http_config, svc); if (svc->enabled < 0) { const char *user = getenv("REMOTE_USER"); svc->enabled = (user && *user) ? 1 : 0; @@ -442,6 +461,7 @@ static void get_info_refs(char *arg) run_service(argv); } else { + select_getanyfile(); for_each_ref(show_text_ref, &buf); send_strbuf("text/plain", &buf); } @@ -455,6 +475,7 @@ static void get_info_packs(char *arg) struct packed_git *p; size_t cnt = 0; + select_getanyfile(); prepare_packed_git(); for (p = packed_git; p; p = p->next) { if (p->pack_local) @@ -621,6 +642,7 @@ int main(int argc, char **argv) if (!enter_repo(dir, 0)) not_found("Not a git repository: '%s'", dir); + git_config(http_config, NULL); cmd->imp(cmd_arg); return 0; } -- cgit v0.10.2-6-g49f6 From 7f640b778f8cf87159890157a815f1d728573477 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 4 Nov 2009 17:16:38 -0800 Subject: http-backend: Test configuration options Test the major configuration settings which control access to the repository: http.getanyfile http.uploadpack http.receivepack Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/t/t5560-http-backend.sh b/t/t5560-http-backend.sh new file mode 100755 index 0000000..908ba07 --- /dev/null +++ b/t/t5560-http-backend.sh @@ -0,0 +1,229 @@ +#!/bin/sh + +test_description='test git-http-backend' +. ./test-lib.sh + +if test -n "$NO_CURL"; then + say 'skipping test, git built without http support' + test_done +fi + +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5560'} +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +find_file() { + cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + find $1 -type f | + sed -e 1q +} + +config() { + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config $1 $2 +} + +GET() { + curl --include "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null && + tr '\015' Q act && + echo "HTTP/1.1 $2" >exp && + test_cmp exp act +} + +POST() { + curl --include --data "$2" \ + --header "Content-Type: application/x-$1-request" \ + "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null && + tr '\015' Q act && + echo "HTTP/1.1 $3" >exp && + test_cmp exp act +} + +log_div() { + echo >>"$HTTPD_ROOT_PATH"/access.log + echo "### $1" >>"$HTTPD_ROOT_PATH"/access.log + echo "###" >>"$HTTPD_ROOT_PATH"/access.log +} + +test_expect_success 'setup repository' ' + echo content >file && + git add file && + git commit -m one && + + mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git --bare init && + : >objects/info/alternates && + : >objects/info/http-alternates + ) && + git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push public master:master && + + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git repack -a -d + ) && + + echo other >file && + git add file && + git commit -m two && + git push public master:master && + + LOOSE_URL=$(find_file objects/??) && + PACK_URL=$(find_file objects/pack/*.pack) && + IDX_URL=$(find_file objects/pack/*.idx) +' + +get_static_files() { + GET HEAD "$1" && + GET info/refs "$1" && + GET objects/info/packs "$1" && + GET objects/info/alternates "$1" && + GET objects/info/http-alternates "$1" && + GET $LOOSE_URL "$1" && + GET $PACK_URL "$1" && + GET $IDX_URL "$1" +} + +test_expect_success 'direct refs/heads/master not found' ' + log_div "refs/heads/master" + GET refs/heads/master "404 Not Found" +' +test_expect_success 'static file is ok' ' + log_div "getanyfile default" + get_static_files "200 OK" +' +test_expect_success 'static file if http.getanyfile true is ok' ' + log_div "getanyfile true" + config http.getanyfile true && + get_static_files "200 OK" +' +test_expect_success 'static file if http.getanyfile false fails' ' + log_div "getanyfile false" + config http.getanyfile false && + get_static_files "403 Forbidden" +' + +test_expect_success 'http.uploadpack default enabled' ' + log_div "uploadpack default" + GET info/refs?service=git-upload-pack "200 OK" && + POST git-upload-pack 0000 "200 OK" +' +test_expect_success 'http.uploadpack true' ' + log_div "uploadpack true" + config http.uploadpack true && + GET info/refs?service=git-upload-pack "200 OK" && + POST git-upload-pack 0000 "200 OK" +' +test_expect_success 'http.uploadpack false' ' + log_div "uploadpack false" + config http.uploadpack false && + GET info/refs?service=git-upload-pack "403 Forbidden" && + POST git-upload-pack 0000 "403 Forbidden" +' + +test_expect_success 'http.receivepack default disabled' ' + log_div "receivepack default" + GET info/refs?service=git-receive-pack "403 Forbidden" && + POST git-receive-pack 0000 "403 Forbidden" +' +test_expect_success 'http.receivepack true' ' + log_div "receivepack true" + config http.receivepack true && + GET info/refs?service=git-receive-pack "200 OK" && + POST git-receive-pack 0000 "200 OK" +' +test_expect_success 'http.receivepack false' ' + log_div "receivepack false" + config http.receivepack false && + GET info/refs?service=git-receive-pack "403 Forbidden" && + POST git-receive-pack 0000 "403 Forbidden" +' + +cat >exp <act <"$HTTPD_ROOT_PATH"/access.log && + test_cmp exp act +' + +stop_httpd +test_done -- cgit v0.10.2-6-g49f6 From ef0555712c9821cc163508777f20b934bf146971 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 4 Nov 2009 16:32:46 -0500 Subject: pack-objects: move thread autodetection closer to relevant code Let's keep thread stuff close together if possible. And in this case, this even reduces the #ifdef noise, and allows for skipping the autodetection altogether if delta search is not needed (like with a pure clone). Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 02f9246..4c91e94 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1629,6 +1629,8 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, struct thread_params *p; int i, ret, active_threads = 0; + if (!delta_search_threads) /* --threads=0 means autodetect */ + delta_search_threads = online_cpus(); if (delta_search_threads <= 1) { find_deltas(list, &list_size, window, depth, processed); return; @@ -2324,11 +2326,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (keep_unreachable && unpack_unreachable) die("--keep-unreachable and --unpack-unreachable are incompatible."); -#ifdef THREADED_DELTA_SEARCH - if (!delta_search_threads) /* --threads=0 means autodetect */ - delta_search_threads = online_cpus(); -#endif - prepare_packed_git(); if (progress) -- cgit v0.10.2-6-g49f6 From b9759f0762c17a5b7bc36af98613c67249931330 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 6 Nov 2009 16:08:41 +0100 Subject: gitweb: Fix blob linenr links in pathinfo mode In pathinfo mode, we use that refers to the base location of gitweb in order for various external media links to work well. However, this means that for the page to refer to itself, it must regenerate full link, and this is exactly what the blob view page did not do for line numbers. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 24b2193..184b683 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5065,7 +5065,8 @@ sub git_blob { chomp $line; $nr++; $line = untabify($line); - printf "
%4i %s
\n", + printf "\n", $nr, $nr, $nr, esc_html($line, -nbsp=>1); } } -- cgit v0.10.2-6-g49f6 From 1a994dc3d258a1603752e0c472b072acc343a963 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 4 Nov 2009 22:33:52 -0800 Subject: t1200: cleanup and modernize test style Many parts of the tests in t1200 are run outside the test harness, circumventing the usefulness of -v and spewing messages to stdout when -v isn't used. Fix these problems by modernizing the test a bit. An extra test_done has existed since commit 6a74642 (git-commit --amend: two fixes., 2006-04-20) leading to the last 6 tests never being run. Remove it and teach the resolve merge test about fast-forward merges. Also fix the last test's incorrect find command and prune before checking for unpacked objects so we remove the unreachable conflict-marked blob. Finally, we remove the TODO notes, because fetch, push, and clone have their own tests since t1200 was introduced and we're not going to add them here 4 years later. Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index 67e637b..c57c9d5 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -7,14 +7,18 @@ test_description='A simple turial in the form of a test case' . ./test-lib.sh -echo "Hello World" > hello -echo "Silly example" > example +test_expect_success 'blob' ' + echo "Hello World" > hello && + echo "Silly example" > example && -git update-index --add hello example + git update-index --add hello example && -test_expect_success 'blob' "test blob = \"$(git cat-file -t 557db03)\"" + test blob = "$(git cat-file -t 557db03)" +' -test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git cat-file blob 557db03)\"" +test_expect_success 'blob 557db03' ' + test "Hello World" = "$(git cat-file blob 557db03)" +' echo "It's a new day for git" >>hello cat > diff.expect << EOF @@ -26,25 +30,33 @@ index 557db03..263414f 100644 Hello World +It's a new day for git EOF -git diff-files -p > diff.output -test_expect_success 'git diff-files -p' 'cmp diff.expect diff.output' -git diff > diff.output -test_expect_success 'git diff' 'cmp diff.expect diff.output' - -tree=$(git write-tree 2>/dev/null) -test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree" +test_expect_success 'git diff-files -p' ' + git diff-files -p > diff.output && + cmp diff.expect diff.output +' -output="$(echo "Initial commit" | git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master)" +test_expect_success 'git diff' ' + git diff > diff.output && + cmp diff.expect diff.output +' -git diff-index -p HEAD > diff.output -test_expect_success 'git diff-index -p HEAD' 'cmp diff.expect diff.output' +test_expect_success 'tree' ' + tree=$(git write-tree 2>/dev/null) + test 8988da15d077d4829fc51d8544c097def6644dbb = $tree +' -git diff HEAD > diff.output -test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output' +test_expect_success 'git diff-index -p HEAD' ' + echo "Initial commit" | \ + git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master && + git diff-index -p HEAD > diff.output && + cmp diff.expect diff.output +' -#rm hello -#test_expect_success 'git read-tree --reset HEAD' "git read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git update-index --refresh)\"" +test_expect_success 'git diff HEAD' ' + git diff HEAD > diff.output && + cmp diff.expect diff.output +' cat > whatchanged.expect << EOF commit VARIABLE @@ -69,39 +81,45 @@ index 0000000..557db03 +Hello World EOF -git whatchanged -p --root | \ - sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \ +test_expect_success 'git whatchanged -p --root' ' + git whatchanged -p --root | \ + sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \ -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \ -> whatchanged.output -test_expect_success 'git whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output' - -git tag my-first-tag -test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag' + > whatchanged.output && + cmp whatchanged.expect whatchanged.output +' -# TODO: test git clone +test_expect_success 'git tag my-first-tag' ' + git tag my-first-tag && + cmp .git/refs/heads/master .git/refs/tags/my-first-tag +' -git checkout -b mybranch -test_expect_success 'git checkout -b mybranch' 'cmp .git/refs/heads/master .git/refs/heads/mybranch' +test_expect_success 'git checkout -b mybranch' ' + git checkout -b mybranch && + cmp .git/refs/heads/master .git/refs/heads/mybranch +' cat > branch.expect < branch.output -test_expect_success 'git branch' 'cmp branch.expect branch.output' +test_expect_success 'git branch' ' + git branch > branch.output && + cmp branch.expect branch.output +' -git checkout mybranch -echo "Work, work, work" >>hello -git commit -m 'Some work.' -i hello +test_expect_success 'git resolve now fails' ' + git checkout mybranch && + echo "Work, work, work" >>hello && + git commit -m "Some work." -i hello && -git checkout master + git checkout master && -echo "Play, play, play" >>hello -echo "Lots of fun" >>example -git commit -m 'Some fun.' -i hello example + echo "Play, play, play" >>hello && + echo "Lots of fun" >>example && + git commit -m "Some fun." -i hello example && -test_expect_success 'git resolve now fails' ' test_must_fail git merge -m "Merge work in mybranch" mybranch ' @@ -112,10 +130,6 @@ Play, play, play Work, work, work EOF -git commit -m 'Merged "mybranch" changes.' -i hello - -test_done - cat > show-branch.expect << EOF * [master] Merged "mybranch" changes. ! [mybranch] Some work. @@ -124,21 +138,26 @@ cat > show-branch.expect << EOF *+ [mybranch] Some work. EOF -git show-branch --topo-order master mybranch > show-branch.output -test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output' - -git checkout mybranch +test_expect_success 'git show-branch' ' + git commit -m "Merged \"mybranch\" changes." -i hello && + git show-branch --topo-order master mybranch > show-branch.output && + cmp show-branch.expect show-branch.output +' cat > resolve.expect << EOF -Updating from VARIABLE to VARIABLE +Updating VARIABLE..VARIABLE +Fast forward (no commit created; -m option ignored) example | 1 + hello | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) EOF -git merge -s "Merge upstream changes." master | \ - sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" >resolve.output -test_expect_success 'git resolve' 'cmp resolve.expect resolve.output' +test_expect_success 'git resolve' ' + git checkout mybranch && + git merge -m "Merge upstream changes." master | \ + sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" >resolve.output && + cmp resolve.expect resolve.output +' cat > show-branch2.expect << EOF ! [master] Merged "mybranch" changes. @@ -147,17 +166,16 @@ cat > show-branch2.expect << EOF -- [master] Merged "mybranch" changes. EOF -git show-branch --topo-order master mybranch > show-branch2.output -test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output' - -# TODO: test git fetch - -# TODO: test git push +test_expect_success 'git show-branch (part 2)' ' + git show-branch --topo-order master mybranch > show-branch2.output && + cmp show-branch2.expect show-branch2.output +' test_expect_success 'git repack' 'git repack' test_expect_success 'git prune-packed' 'git prune-packed' test_expect_success '-> only packed objects' ' - ! find -type f .git/objects/[0-9a-f][0-9a-f] + git prune && # Remove conflict marked blobs + ! find .git/objects/[0-9a-f][0-9a-f] -type f ' test_done -- cgit v0.10.2-6-g49f6 From 7c5858a643724a9a4a67f1290c03d57f483ed261 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 4 Nov 2009 22:33:53 -0800 Subject: t1200: Make documentation and test agree There were some differences between t1200 and the gitcore-tutorial. Add missing tests for manually merging two branches, and use the same commands in both files. Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index b3640c4..7bdf090 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -185,7 +185,7 @@ object is. git will tell you that you have a "blob" object (i.e., just a regular file), and you can see the contents with ---------------- -$ git cat-file "blob" 557db03 +$ git cat-file blob 557db03 ---------------- which will print out "Hello World". The object `557db03` is nothing @@ -1188,7 +1188,7 @@ $ git show-branch -- + [mybranch] Some work. * [master] Some fun. -*+ [mybranch^] New day. +*+ [mybranch^] Initial commit ------------ Now we are ready to experiment with the merge by hand. @@ -1204,11 +1204,11 @@ $ mb=$(git merge-base HEAD mybranch) The command writes the commit object name of the common ancestor to the standard output, so we captured its output to a variable, because we will be using it in the next step. By the way, the common -ancestor commit is the "New day." commit in this case. You can +ancestor commit is the "Initial commit" commit in this case. You can tell it by: ------------ -$ git name-rev $mb +$ git name-rev --name-only --tags $mb my-first-tag ------------ @@ -1237,8 +1237,8 @@ inspect the index file with this command: ------------ $ git ls-files --stage 100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example -100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello -100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello ------------ @@ -1253,8 +1253,8 @@ To look at only non-zero stages, use `\--unmerged` flag: ------------ $ git ls-files --unmerged -100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello -100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello ------------ @@ -1283,8 +1283,8 @@ the working tree.. This can be seen if you run `ls-files ------------ $ git ls-files --stage 100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example -100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello -100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello ------------ diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index c57c9d5..299e724 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -47,8 +47,9 @@ test_expect_success 'tree' ' ' test_expect_success 'git diff-index -p HEAD' ' - echo "Initial commit" | \ - git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master && + tree=$(git write-tree) + commit=$(echo "Initial commit" | git commit-tree $tree) && + git update-ref HEAD $commit && git diff-index -p HEAD > diff.output && cmp diff.expect diff.output ' @@ -131,16 +132,18 @@ Work, work, work EOF cat > show-branch.expect << EOF -* [master] Merged "mybranch" changes. +* [master] Merge work in mybranch ! [mybranch] Some work. -- -- [master] Merged "mybranch" changes. +- [master] Merge work in mybranch *+ [mybranch] Some work. +* [master^] Some fun. EOF test_expect_success 'git show-branch' ' - git commit -m "Merged \"mybranch\" changes." -i hello && - git show-branch --topo-order master mybranch > show-branch.output && + git commit -m "Merge work in mybranch" -i hello && + git show-branch --topo-order --more=1 master mybranch \ + > show-branch.output && cmp show-branch.expect show-branch.output ' @@ -160,10 +163,10 @@ test_expect_success 'git resolve' ' ' cat > show-branch2.expect << EOF -! [master] Merged "mybranch" changes. - * [mybranch] Merged "mybranch" changes. +! [master] Merge work in mybranch + * [mybranch] Merge work in mybranch -- --- [master] Merged "mybranch" changes. +-- [master] Merge work in mybranch EOF test_expect_success 'git show-branch (part 2)' ' @@ -171,6 +174,82 @@ test_expect_success 'git show-branch (part 2)' ' cmp show-branch2.expect show-branch2.output ' +cat > show-branch3.expect << EOF +! [master] Merge work in mybranch + * [mybranch] Merge work in mybranch +-- +-- [master] Merge work in mybranch ++* [master^2] Some work. ++* [master^] Some fun. +EOF + +test_expect_success 'git show-branch (part 3)' ' + git show-branch --topo-order --more=2 master mybranch \ + > show-branch3.output && + cmp show-branch3.expect show-branch3.output +' + +test_expect_success 'rewind to "Some fun." and "Some work."' ' + git checkout mybranch && + git reset --hard master^2 && + git checkout master && + git reset --hard master^ +' + +cat > show-branch4.expect << EOF +* [master] Some fun. + ! [mybranch] Some work. +-- + + [mybranch] Some work. +* [master] Some fun. +*+ [mybranch^] Initial commit +EOF + +test_expect_success 'git show-branch (part 4)' ' + git show-branch --topo-order > show-branch4.output && + cmp show-branch4.expect show-branch4.output +' + +test_expect_success 'manual merge' ' + mb=$(git merge-base HEAD mybranch) && + git name-rev --name-only --tags $mb > name-rev.output && + test "my-first-tag" = $(cat name-rev.output) && + + git read-tree -m -u $mb HEAD mybranch +' + +cat > ls-files.expect << EOF +100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello +100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello +EOF + +test_expect_success 'git ls-files --stage' ' + git ls-files --stage > ls-files.output && + cmp ls-files.expect ls-files.output +' + +cat > ls-files-unmerged.expect << EOF +100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello +100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello +100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello +EOF + +test_expect_success 'git ls-files --unmerged' ' + git ls-files --unmerged > ls-files-unmerged.output && + cmp ls-files-unmerged.expect ls-files-unmerged.output +' + +test_expect_success 'git-merge-index' ' + test_must_fail git merge-index git-merge-one-file hello +' + +test_expect_success 'git ls-files --stage (part 2)' ' + git ls-files --stage > ls-files.output2 && + cmp ls-files.expect ls-files.output2 +' + test_expect_success 'git repack' 'git repack' test_expect_success 'git prune-packed' 'git prune-packed' test_expect_success '-> only packed objects' ' -- cgit v0.10.2-6-g49f6 From b9f3bde150a1be1e2adfb2834caaf415dd7b72ef Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 6 Nov 2009 22:47:37 -0800 Subject: t1200: further modernize test script style Instead of using bare "cmp", use "test_cmp". Output when the test is run with a -v option becomes easier to diagnose when something goes wrong because on saner platforms test_cmp uses "diff -u". There is no need to put an extra backslash to a line that ends with a '|' (i.e. the upstream of a pipe). Signed-off-by: Junio C Hamano diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index 299e724..7bd8e06 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -33,12 +33,12 @@ EOF test_expect_success 'git diff-files -p' ' git diff-files -p > diff.output && - cmp diff.expect diff.output + test_cmp diff.expect diff.output ' test_expect_success 'git diff' ' git diff > diff.output && - cmp diff.expect diff.output + test_cmp diff.expect diff.output ' test_expect_success 'tree' ' @@ -51,12 +51,12 @@ test_expect_success 'git diff-index -p HEAD' ' commit=$(echo "Initial commit" | git commit-tree $tree) && git update-ref HEAD $commit && git diff-index -p HEAD > diff.output && - cmp diff.expect diff.output + test_cmp diff.expect diff.output ' test_expect_success 'git diff HEAD' ' git diff HEAD > diff.output && - cmp diff.expect diff.output + test_cmp diff.expect diff.output ' cat > whatchanged.expect << EOF @@ -83,21 +83,21 @@ index 0000000..557db03 EOF test_expect_success 'git whatchanged -p --root' ' - git whatchanged -p --root | \ + git whatchanged -p --root | sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \ -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \ > whatchanged.output && - cmp whatchanged.expect whatchanged.output + test_cmp whatchanged.expect whatchanged.output ' test_expect_success 'git tag my-first-tag' ' git tag my-first-tag && - cmp .git/refs/heads/master .git/refs/tags/my-first-tag + test_cmp .git/refs/heads/master .git/refs/tags/my-first-tag ' test_expect_success 'git checkout -b mybranch' ' git checkout -b mybranch && - cmp .git/refs/heads/master .git/refs/heads/mybranch + test_cmp .git/refs/heads/master .git/refs/heads/mybranch ' cat > branch.expect < branch.output && - cmp branch.expect branch.output + test_cmp branch.expect branch.output ' test_expect_success 'git resolve now fails' ' @@ -144,7 +144,7 @@ test_expect_success 'git show-branch' ' git commit -m "Merge work in mybranch" -i hello && git show-branch --topo-order --more=1 master mybranch \ > show-branch.output && - cmp show-branch.expect show-branch.output + test_cmp show-branch.expect show-branch.output ' cat > resolve.expect << EOF @@ -157,9 +157,9 @@ EOF test_expect_success 'git resolve' ' git checkout mybranch && - git merge -m "Merge upstream changes." master | \ + git merge -m "Merge upstream changes." master | sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" >resolve.output && - cmp resolve.expect resolve.output + test_cmp resolve.expect resolve.output ' cat > show-branch2.expect << EOF @@ -171,7 +171,7 @@ EOF test_expect_success 'git show-branch (part 2)' ' git show-branch --topo-order master mybranch > show-branch2.output && - cmp show-branch2.expect show-branch2.output + test_cmp show-branch2.expect show-branch2.output ' cat > show-branch3.expect << EOF @@ -186,7 +186,7 @@ EOF test_expect_success 'git show-branch (part 3)' ' git show-branch --topo-order --more=2 master mybranch \ > show-branch3.output && - cmp show-branch3.expect show-branch3.output + test_cmp show-branch3.expect show-branch3.output ' test_expect_success 'rewind to "Some fun." and "Some work."' ' @@ -207,7 +207,7 @@ EOF test_expect_success 'git show-branch (part 4)' ' git show-branch --topo-order > show-branch4.output && - cmp show-branch4.expect show-branch4.output + test_cmp show-branch4.expect show-branch4.output ' test_expect_success 'manual merge' ' @@ -227,7 +227,7 @@ EOF test_expect_success 'git ls-files --stage' ' git ls-files --stage > ls-files.output && - cmp ls-files.expect ls-files.output + test_cmp ls-files.expect ls-files.output ' cat > ls-files-unmerged.expect << EOF @@ -238,7 +238,7 @@ EOF test_expect_success 'git ls-files --unmerged' ' git ls-files --unmerged > ls-files-unmerged.output && - cmp ls-files-unmerged.expect ls-files-unmerged.output + test_cmp ls-files-unmerged.expect ls-files-unmerged.output ' test_expect_success 'git-merge-index' ' @@ -247,7 +247,7 @@ test_expect_success 'git-merge-index' ' test_expect_success 'git ls-files --stage (part 2)' ' git ls-files --stage > ls-files.output2 && - cmp ls-files.expect ls-files.output2 + test_cmp ls-files.expect ls-files.output2 ' test_expect_success 'git repack' 'git repack' -- cgit v0.10.2-6-g49f6 From 5c5dd6e5a42d8d65362a15fb8f54f11ae6e03e22 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 6 Nov 2009 22:58:14 -0800 Subject: t1200: prepare for merging with Fast-forward bikeshedding A tree-wide bikeshedding to replace "fast forward" into "fast-forward" is in 'master'. Since we want to keep this "test modernization" series mergeable also to the maintenance track, we would need to tweak the test to accept both old spellings and new spellings. Sigh... This kind of headache is the primary reason we try not to allow such a tree-wide bike-shedding, but the damage has already been done. Signed-off-by: Junio C Hamano diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index 7bd8e06..6bf8475 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -149,7 +149,7 @@ test_expect_success 'git show-branch' ' cat > resolve.expect << EOF Updating VARIABLE..VARIABLE -Fast forward (no commit created; -m option ignored) +FASTFORWARD (no commit created; -m option ignored) example | 1 + hello | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) @@ -158,7 +158,8 @@ EOF test_expect_success 'git resolve' ' git checkout mybranch && git merge -m "Merge upstream changes." master | - sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" >resolve.output && + sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" \ + -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output && test_cmp resolve.expect resolve.output ' -- cgit v0.10.2-6-g49f6 From e5138436ddd4b5f75c1e910f6b844e4fcf91343d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 6 Nov 2009 23:06:06 -0800 Subject: builtin-commit.c: fix logic to omit empty line before existing footers "commit -s" used to add an empty line before adding S-o-b line only when the last line of the existing log message is not another S-o-b line, but c1e01b0 (commit: More generous accepting of RFC-2822 footer lines., 2009-10-28) introduced logic to omit this empty line when the message ends with a run of "footer" lines, to cover S-o-b's friends, e.g. Acked-by. However, the logic was overzealous and missed one corner case. A message that consists of a single line that begins with Token + colon, it can be mistaken as a S-o-b's friend. We do want an empty line in such a case. Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index c395cbf..cfa6b06 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -530,7 +530,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) ; /* do nothing */ if (prefixcmp(sb.buf + i, sob.buf)) { - if (!ends_rfc2822_footer(&sb)) + if (!i || !ends_rfc2822_footer(&sb)) strbuf_addch(&sb, '\n'); strbuf_addbuf(&sb, &sob); } diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 56cd866..fe94552 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -258,4 +258,13 @@ test_expect_success 'Hand committing of a redundant merge removes dups' ' ' +test_expect_success 'A single-liner subject with a token plus colon is not a footer' ' + + git reset --hard && + git commit -s -m "hello: kitty" --allow-empty && + git cat-file commit HEAD | sed -e "1,/^$/d" >actual && + test $(wc -l Date: Thu, 5 Nov 2009 11:57:57 +0100 Subject: pre-commit.sample: Diff against the empty tree when HEAD is invalid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was already the case for the old "diff --check" call, but the new one that checks whether there are any non-ascii file names was missing it, making that check fail for root commits. Signed-off-by: Björn Steinbrink Signed-off-by: Junio C Hamano diff --git a/templates/hooks--pre-commit.sample b/templates/hooks--pre-commit.sample index 043970a..439eefd 100755 --- a/templates/hooks--pre-commit.sample +++ b/templates/hooks--pre-commit.sample @@ -7,6 +7,14 @@ # # To enable this hook, rename this file to "pre-commit". +if git-rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) @@ -17,7 +25,7 @@ if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. - test "$(git diff --cached --name-only --diff-filter=A -z | + test "$(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0')" then echo "Error: Attempt to add a non-ascii file name." @@ -35,12 +43,4 @@ then exit 1 fi -if git-rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 -fi - exec git diff-index --check --cached $against -- -- cgit v0.10.2-6-g49f6 From 32ca42491246eb00d226826039fff18d58b57081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sun, 8 Nov 2009 02:04:21 +0100 Subject: log --format: don't ignore %w() at the start of format string This fixes e.g. --format='%w(72)%s'. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/pretty.c b/pretty.c index 91be0ce..5e9d1f8 100644 --- a/pretty.c +++ b/pretty.c @@ -618,7 +618,7 @@ static void rewrap_message_tail(struct strbuf *sb, if (c->width == new_width && c->indent1 == new_indent1 && c->indent2 == new_indent2) return; - if (c->wrap_start && c->wrap_start < sb->len) + if (c->wrap_start < sb->len) strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2); c->wrap_start = sb->len; c->width = new_width; -- cgit v0.10.2-6-g49f6 From 1d46f2ea143534e46e6bfee3f34cd90b734bfe80 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 4 Nov 2009 02:19:40 -0500 Subject: format-patch: make "-p" suppress diffstat Once upon a time, format-patch would use its default stat plus patch format only when no diff format was given on the command line. This meant that "format-patch -p" would suppress the stat and show just the patch. Commit 68daa64 changed this to keep the stat format when we had an "implicit" patch format, like "-U5". As a side effect, this meant that an explicit patch format was now ignored (because cmd_format_patch didn't know the reason that the format was set way down in diff_opt_parse). This patch unbreaks what 68daa64 did (while still preserving what 68daa64 was trying to do), reinstating "-p" to suppress the default behavior. We do this by parsing "-p" ourselves in format-patch, and noting whether it was used explicitly. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin-log.c b/builtin-log.c index 25e21ed..7b91c91 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -891,6 +891,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct patch_ids ids; char *add_signoff = NULL; struct strbuf buf = STRBUF_INIT; + int use_patch_format = 0; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, "use [PATCH n/m] even with a single patch", @@ -920,6 +921,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, OPT_BOOLEAN(0, "no-binary", &no_binary_diff, "don't output binary diffs"), + OPT_BOOLEAN('p', NULL, &use_patch_format, + "show patch format instead of default (patch + stat)"), OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream, "don't include a patch matching a commit upstream"), OPT_GROUP("Messaging"), @@ -1027,8 +1030,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (argc > 1) die ("unrecognized argument: %s", argv[1]); - if (!rev.diffopt.output_format - || rev.diffopt.output_format == DIFF_FORMAT_PATCH) + if (use_patch_format) + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + else if (!rev.diffopt.output_format || + rev.diffopt.output_format == DIFF_FORMAT_PATCH) rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH; if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 531f5b7..cab6ce2 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -455,6 +455,27 @@ test_expect_success 'format-patch respects -U' ' ' +cat > expect << EOF + +diff --git a/file b/file +index 40f36c6..2dc5c23 100644 +--- a/file ++++ b/file +@@ -14,3 +14,19 @@ C + D + E + F ++5 +EOF + +test_expect_success 'format-patch -p suppresses stat' ' + + git format-patch -p -2 && + sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output && + test_cmp expect output + +' + test_expect_success 'format-patch from a subdirectory (1)' ' filename=$( rm -rf sub && -- cgit v0.10.2-6-g49f6 From f9bbaa384efd4c5c56b9a13d08680f6fc09324ca Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 8 Nov 2009 16:07:16 -0600 Subject: Add intermediate build products to .gitignore Temporaries such as configure.ac+ and Documentation/*.xml+ sometimes remain after an interrupted build. Tell git not to track them. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 51a37b1..f0d2e96 100644 --- a/.gitignore +++ b/.gitignore @@ -168,6 +168,7 @@ git.spec *.exe *.[aos] *.py[co] +*+ config.mak autom4te.cache config.cache -- cgit v0.10.2-6-g49f6 From 035b76b03f63e077e3465a99bee5dffa29822344 Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Tue, 27 Oct 2009 19:11:55 +0000 Subject: Makefile: merge two Cygwin configuration sections into one Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index fea237b..8e1cfc5 100644 --- a/Makefile +++ b/Makefile @@ -782,6 +782,8 @@ ifeq ($(uname_O),Cygwin) NO_MMAP = YesPlease NO_IPV6 = YesPlease X = .exe + COMPAT_OBJS += compat/cygwin.o + UNRELIABLE_FSTAT = UnfortunatelyYes endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease @@ -891,10 +893,6 @@ ifeq ($(uname_S),HP-UX) NO_SYS_SELECT_H = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease endif -ifneq (,$(findstring CYGWIN,$(uname_S))) - COMPAT_OBJS += compat/cygwin.o - UNRELIABLE_FSTAT = UnfortunatelyYes -endif ifdef MSVC GIT_VERSION := $(GIT_VERSION).MSVC pathsep = ; -- cgit v0.10.2-6-g49f6 From d691d84eedc0f02b4caebbee89149fff18e1db91 Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Sat, 7 Nov 2009 20:08:01 +0000 Subject: Makefile: keep MSVC and Cygwin configuration separate Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 8e1cfc5..306ca86 100644 --- a/Makefile +++ b/Makefile @@ -212,6 +212,12 @@ uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not') +ifdef MSVC + # avoid the MingW and Cygwin configuration sections + uname_S := Windows + uname_O := Windows +endif + # CFLAGS and LDFLAGS are for the users to override from the command line. CFLAGS = -g -O2 -Wall @@ -893,7 +899,7 @@ ifeq ($(uname_S),HP-UX) NO_SYS_SELECT_H = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease endif -ifdef MSVC +ifeq ($(uname_S),Windows) GIT_VERSION := $(GIT_VERSION).MSVC pathsep = ; NO_PREAD = YesPlease @@ -945,7 +951,7 @@ else BASIC_CFLAGS += -Zi -MTd endif X = .exe -else +endif ifneq (,$(findstring MINGW,$(uname_S))) pathsep = ; NO_PREAD = YesPlease @@ -994,7 +1000,6 @@ else NO_PTHREADS = YesPlease endif endif -endif -include config.mak.autogen -include config.mak -- cgit v0.10.2-6-g49f6 From b1b952043f8f909649fdf053c371109c84f9cf56 Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Sat, 7 Nov 2009 20:10:31 +0000 Subject: MSVC: Add support for building with NO_MMAP When the NO_MMAP build variable is set, the msvc linker complains: error LNK2001: unresolved external symbol _getpagesize The msvc libraries do not define the getpagesize() function, so we move the mingw_getpagesize() implementation from the conditionally built win32mmap.c file to mingw.c. Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano diff --git a/compat/mingw.c b/compat/mingw.c index 6b5b5b2..15fe33e 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1000,6 +1000,18 @@ repeat: return -1; } +/* + * Note that this doesn't return the actual pagesize, but + * the allocation granularity. If future Windows specific git code + * needs the real getpagesize function, we need to find another solution. + */ +int mingw_getpagesize(void) +{ + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwAllocationGranularity; +} + struct passwd *getpwuid(int uid) { static char user_name[100]; diff --git a/compat/mingw.h b/compat/mingw.h index 5b5258b..26c4027 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -166,7 +166,7 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz); int mingw_rename(const char*, const char*); #define rename mingw_rename -#ifdef USE_WIN32_MMAP +#if defined(USE_WIN32_MMAP) || defined(_MSC_VER) int mingw_getpagesize(void); #define getpagesize mingw_getpagesize #endif diff --git a/compat/win32mmap.c b/compat/win32mmap.c index 779d796..1c5a149 100644 --- a/compat/win32mmap.c +++ b/compat/win32mmap.c @@ -1,17 +1,5 @@ #include "../git-compat-util.h" -/* - * Note that this doesn't return the actual pagesize, but - * the allocation granularity. If future Windows specific git code - * needs the real getpagesize function, we need to find another solution. - */ -int mingw_getpagesize(void) -{ - SYSTEM_INFO si; - GetSystemInfo(&si); - return si.dwAllocationGranularity; -} - void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) { HANDLE hmap; -- cgit v0.10.2-6-g49f6 From 3ce9450a810243cbd9d0250d9ae3ea6834f50b9c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 7 Nov 2009 16:13:28 +0100 Subject: gitweb: Document current snapshot rules via new tests Add t9502-gitweb-standalone-parse-output test script, which runs gitweb as a CGI script from the commandline and checks that it produces the correct output. Currently this test script contains only tests of snapshot naming (proposed name of snapshot file) and snapshot prefix (prefix of files in the archive / snapshot). It defines and uses 'tar' snapshot format, without compression, for easy checking of snapshot prefix. Testing is done using check_snapshot function. Gitweb uses the following format for snapshot filenames: - where is project name with '.git' or '/.git' suffix stripped, unless '.git' is the whole project name. For snapshot prefix it uses simply: / Disadvantages of current snapshot rules: * There exists convention that . archive unpacks to / directory (/ is prefix of archive). Gitweb does not respect it * Snapshot links generated by gitweb use full SHA-1 id as a value of 'h' / $hash parameter. With current rules it leads to long file names like e.g. repo-1005c80cc11c531d327b12195027cbbb4ff9e3cb.tgz * For handcrafted URLs, where 'h' / $hash parameter is a symbolic 'volatile' revision name such as "HEAD" or "next" snapshot name doesn't tell us what exact version it was created from * Proposed filename in Content-Disposition header should not contain any directory path information, which means that it should not contain '/' (see RFC2183)... which means that snapshot naming is broken for $hash being e.g. hirearchical branch name such as 'xx/test' This would be improved in next commit. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh new file mode 100755 index 0000000..741187b --- /dev/null +++ b/t/t9502-gitweb-standalone-parse-output.sh @@ -0,0 +1,87 @@ +#!/bin/sh +# +# Copyright (c) 2009 Mark Rada +# + +test_description='gitweb as standalone script (parsing script output). + +This test runs gitweb (git web interface) as a CGI script from the +commandline, and checks that it produces the correct output, either +in the HTTP header or the actual script output.' + + +. ./gitweb-lib.sh + +# ---------------------------------------------------------------------- +# snapshot file name and prefix + +cat >>gitweb_config.perl <<\EOF + +$known_snapshot_formats{'tar'} = { + 'display' => 'tar', + 'type' => 'application/x-tar', + 'suffix' => '.tar', + 'format' => 'tar', +}; + +$feature{'snapshot'}{'default'} = ['tar']; +EOF + +# Call check_snapshot with the arguments " []" +# +# This will check that gitweb HTTP header contains proposed filename +# as with '.tar' suffix added, and that generated tarfile +# (gitweb message body) has as prefix for al files in tarfile +# +# default to +check_snapshot () { + basename=$1 + prefix=${2:-"$1"} + echo "basename=$basename" + grep "filename=.*$basename.tar" gitweb.headers >/dev/null 2>&1 && + "$TAR" tf gitweb.body >file_list && + ! grep -v "^$prefix/" file_list +} + +test_expect_success setup ' + test_commit first foo && + git branch xx/test && + FULL_ID=$(git rev-parse --verify HEAD) && + SHORT_ID=$(git rev-parse --verify --short=7 HEAD) +' +test_debug ' + echo "FULL_ID = $FULL_ID" + echo "SHORT_ID = $SHORT_ID" +' + +test_expect_success 'snapshot: full sha1' ' + gitweb_run "p=.git;a=snapshot;h=$FULL_ID;sf=tar" && + check_snapshot ".git-$FULL_ID" ".git" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: shortened sha1' ' + gitweb_run "p=.git;a=snapshot;h=$SHORT_ID;sf=tar" && + check_snapshot ".git-$SHORT_ID" ".git" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: HEAD' ' + gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tar" && + check_snapshot ".git-HEAD" ".git" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: short branch name (master)' ' + gitweb_run "p=.git;a=snapshot;h=master;sf=tar" && + check_snapshot ".git-master" ".git" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_failure 'snapshot: hierarchical branch name (xx/test)' ' + gitweb_run "p=.git;a=snapshot;h=xx/test;sf=tar" && + ! grep "filename=.*/" gitweb.headers +' +test_debug 'cat gitweb.headers' + +test_done -- cgit v0.10.2-6-g49f6 From b629275fd02aa07c2630d1a8c8a14011ff164043 Mon Sep 17 00:00:00 2001 From: Mark Rada Date: Sat, 7 Nov 2009 16:13:29 +0100 Subject: gitweb: Smarter snapshot names Teach gitweb how to produce nicer snapshot names by only using the short hash id. If clients make requests using a tree-ish that is not a partial or full SHA-1 hash, then the short hash will also be appended to whatever they asked for. If clients request snapshot of a tag (which means that $hash ('h') parameter has 'refs/tags/' prefix), use only tag name. Update tests cases in t9502-gitweb-standalone-parse-output. Gitweb uses the following format for snapshot filenames: -. where is project name with '.git' or '/.git' suffix stripped, unless '.git' is the whole project name. For snapshot prefix it uses: -/ as compared to / before (without version info). Current rules for : * if 'h' / $hash parameter is SHA-1 or shortened SHA-1, use SHA-1 shortened to to 7 characters * otherwise if 'h' / $hash parameter is tag name (it begins with 'refs/tags/' prefix, use tag name (with 'refs/tags/' stripped * otherwise if 'h' / $hash parameter starts with 'refs/heads/' prefix, strip this prefix, convert '/' into '.', and append shortened SHA-1 after '-', i.e. use - Signed-off-by: Mark Rada Signed-off-by: Shawn O. Pearce Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8d4a2ae..d8dfd95 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1983,16 +1983,27 @@ sub quote_command { # get HEAD ref of given project as hash sub git_get_head_hash { - my $project = shift; + return git_get_full_hash(shift, 'HEAD'); +} + +sub git_get_full_hash { + return git_get_hash(@_); +} + +sub git_get_short_hash { + return git_get_hash(@_, '--short=7'); +} + +sub git_get_hash { + my ($project, $hash, @options) = @_; my $o_git_dir = $git_dir; my $retval = undef; $git_dir = "$projectroot/$project"; - if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") { - my $head = <$fd>; + if (open my $fd, '-|', git_cmd(), 'rev-parse', + '--verify', '-q', @options, $hash) { + $retval = <$fd>; + chomp $retval if defined $retval; close $fd; - if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) { - $retval = $1; - } } if (defined $o_git_dir) { $git_dir = $o_git_dir; @@ -5179,6 +5190,43 @@ sub git_tree { git_footer_html(); } +sub snapshot_name { + my ($project, $hash) = @_; + + # path/to/project.git -> project + # path/to/project/.git -> project + my $name = to_utf8($project); + $name =~ s,([^/])/*\.git$,$1,; + $name = basename($name); + # sanitize name + $name =~ s/[[:cntrl:]]/?/g; + + my $ver = $hash; + if ($hash =~ /^[0-9a-fA-F]+$/) { + # shorten SHA-1 hash + my $full_hash = git_get_full_hash($project, $hash); + if ($full_hash =~ /^$hash/ && length($hash) > 7) { + $ver = git_get_short_hash($project, $hash); + } + } elsif ($hash =~ m!^refs/tags/(.*)$!) { + # tags don't need shortened SHA-1 hash + $ver = $1; + } else { + # branches and other need shortened SHA-1 hash + if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) { + $ver = $1; + } + $ver .= '-' . git_get_short_hash($project, $hash); + } + # in case of hierarchical branch names + $ver =~ s!/!.!g; + + # name = project-version_string + $name = "$name-$ver"; + + return wantarray ? ($name, $name) : $name; +} + sub git_snapshot { my $format = $input_params{'snapshot_format'}; if (!@snapshot_fmts) { @@ -5203,24 +5251,20 @@ sub git_snapshot { die_error(400, 'Object is not a tree-ish'); } - my $name = $project; - $name =~ s,([^/])/*\.git$,$1,; - $name = basename($name); - my $filename = to_utf8($name); - $name =~ s/\047/\047\\\047\047/g; - my $cmd; - $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}"; - $cmd = quote_command( + my ($name, $prefix) = snapshot_name($project, $hash); + my $filename = "$name$known_snapshot_formats{$format}{'suffix'}"; + my $cmd = quote_command( git_cmd(), 'archive', "--format=$known_snapshot_formats{$format}{'format'}", - "--prefix=$name/", $hash); + "--prefix=$prefix/", $hash); if (exists $known_snapshot_formats{$format}{'compressor'}) { $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}}); } + $filename =~ s/(["\\])/\\$1/g; print $cgi->header( -type => $known_snapshot_formats{$format}{'type'}, - -content_disposition => 'inline; filename="' . "$filename" . '"', + -content_disposition => 'inline; filename="' . $filename . '"', -status => '200 OK'); open my $fd, "-|", $cmd diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh index 741187b..dd83890 100755 --- a/t/t9502-gitweb-standalone-parse-output.sh +++ b/t/t9502-gitweb-standalone-parse-output.sh @@ -56,29 +56,57 @@ test_debug ' test_expect_success 'snapshot: full sha1' ' gitweb_run "p=.git;a=snapshot;h=$FULL_ID;sf=tar" && - check_snapshot ".git-$FULL_ID" ".git" + check_snapshot ".git-$SHORT_ID" ' test_debug 'cat gitweb.headers && cat file_list' test_expect_success 'snapshot: shortened sha1' ' gitweb_run "p=.git;a=snapshot;h=$SHORT_ID;sf=tar" && - check_snapshot ".git-$SHORT_ID" ".git" + check_snapshot ".git-$SHORT_ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: almost full sha1' ' + ID=$(git rev-parse --short=30 HEAD) && + gitweb_run "p=.git;a=snapshot;h=$ID;sf=tar" && + check_snapshot ".git-$SHORT_ID" ' test_debug 'cat gitweb.headers && cat file_list' test_expect_success 'snapshot: HEAD' ' gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tar" && - check_snapshot ".git-HEAD" ".git" + check_snapshot ".git-HEAD-$SHORT_ID" ' test_debug 'cat gitweb.headers && cat file_list' test_expect_success 'snapshot: short branch name (master)' ' gitweb_run "p=.git;a=snapshot;h=master;sf=tar" && - check_snapshot ".git-master" ".git" + ID=$(git rev-parse --verify --short=7 master) && + check_snapshot ".git-master-$ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: short tag name (first)' ' + gitweb_run "p=.git;a=snapshot;h=first;sf=tar" && + ID=$(git rev-parse --verify --short=7 first) && + check_snapshot ".git-first-$ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: full branch name (refs/heads/master)' ' + gitweb_run "p=.git;a=snapshot;h=refs/heads/master;sf=tar" && + ID=$(git rev-parse --verify --short=7 master) && + check_snapshot ".git-master-$ID" +' +test_debug 'cat gitweb.headers && cat file_list' + +test_expect_success 'snapshot: full tag name (refs/tags/first)' ' + gitweb_run "p=.git;a=snapshot;h=refs/tags/first;sf=tar" && + check_snapshot ".git-first" ' test_debug 'cat gitweb.headers && cat file_list' -test_expect_failure 'snapshot: hierarchical branch name (xx/test)' ' +test_expect_success 'snapshot: hierarchical branch name (xx/test)' ' gitweb_run "p=.git;a=snapshot;h=xx/test;sf=tar" && ! grep "filename=.*/" gitweb.headers ' -- cgit v0.10.2-6-g49f6 From 92815b3363c6cf317337437a986bdf2e8f1aa3a0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Nov 2009 07:24:33 -0800 Subject: Git-aware CGI to provide dumb HTTP transport http-backend: Fix symbol clash on AIX 5.3 Mike says: > > +static void send_file(const char *the_type, const char *name) > > +{ > > I think a symbol clash here is responsible for a build breakage in > next on AIX 5.3: > > CC http-backend.o > http-backend.c:213: error: conflicting types for `send_file' > /usr/include/sys/socket.h:676: error: previous declaration of `send_file' > gmake: *** [http-backend.o] Error 1 So we rename the function send_local_file(). Reported-by: Mike Ralphson Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/http-backend.c b/http-backend.c index 9021266..646e910 100644 --- a/http-backend.c +++ b/http-backend.c @@ -209,7 +209,7 @@ static void send_strbuf(const char *type, struct strbuf *buf) safe_write(1, buf->buf, buf->len); } -static void send_file(const char *the_type, const char *name) +static void send_local_file(const char *the_type, const char *name) { const char *p = git_path("%s", name); size_t buf_alloc = 8192; @@ -247,28 +247,28 @@ static void get_text_file(char *name) { select_getanyfile(); hdr_nocache(); - send_file("text/plain", name); + send_local_file("text/plain", name); } static void get_loose_object(char *name) { select_getanyfile(); hdr_cache_forever(); - send_file("application/x-git-loose-object", name); + send_local_file("application/x-git-loose-object", name); } static void get_pack_file(char *name) { select_getanyfile(); hdr_cache_forever(); - send_file("application/x-git-packed-objects", name); + send_local_file("application/x-git-packed-objects", name); } static void get_idx_file(char *name) { select_getanyfile(); hdr_cache_forever(); - send_file("application/x-git-packed-objects-toc", name); + send_local_file("application/x-git-packed-objects-toc", name); } static int http_config(const char *var, const char *value, void *cb) -- cgit v0.10.2-6-g49f6 From 34b6cb8bb032bd16f3d1c93a8417beb75e51ed29 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Nov 2009 11:26:43 -0800 Subject: http-backend: Protect GIT_PROJECT_ROOT from /../ requests Eons ago HPA taught git-daemon how to protect itself from /../ attacks, which Junio brought back into service in d79374c7b58d ("daemon.c and path.enter_repo(): revamp path validation"). I did not carry this into git-http-backend as originally we relied only upon PATH_TRANSLATED, and assumed the HTTP server had done its access control checks to validate the resolved path was within a directory permitting access from the remote client. This would usually be sufficient to protect a server from requests for its /etc/passwd file by http://host/smart/../etc/passwd sorts of URLs. However in 917adc036086 Mark Lodato added GIT_PROJECT_ROOT as an additional method of configuring the CGI. When this environment variable is used the web server does not generate the final access path and therefore may blindly pass through "/../etc/passwd" in PATH_INFO under the assumption that "/../" might have special meaning to the invoked CGI. Instead of permitting these sorts of malformed path requests, we now reject them back at the client, with an error message for the server log. This matches git-daemon behavior. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index 4e283be..ecbd88a 100644 --- a/cache.h +++ b/cache.h @@ -656,6 +656,7 @@ const char *make_relative_path(const char *abs, const char *base); int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, const char *prefix_list); char *strip_path_suffix(const char *path, const char *suffix); +int daemon_avoid_alias(const char *path); /* Read and unpack a sha1 file into memory, write memory to a sha1 file */ extern int sha1_object_info(const unsigned char *, unsigned long *); diff --git a/daemon.c b/daemon.c index 1b5ada6..ce48006 100644 --- a/daemon.c +++ b/daemon.c @@ -101,53 +101,6 @@ static void NORETURN daemon_die(const char *err, va_list params) exit(1); } -static int avoid_alias(char *p) -{ - int sl, ndot; - - /* - * This resurrects the belts and suspenders paranoia check by HPA - * done in <435560F7.4080006@zytor.com> thread, now enter_repo() - * does not do getcwd() based path canonicalizations. - * - * sl becomes true immediately after seeing '/' and continues to - * be true as long as dots continue after that without intervening - * non-dot character. - */ - if (!p || (*p != '/' && *p != '~')) - return -1; - sl = 1; ndot = 0; - p++; - - while (1) { - char ch = *p++; - if (sl) { - if (ch == '.') - ndot++; - else if (ch == '/') { - if (ndot < 3) - /* reject //, /./ and /../ */ - return -1; - ndot = 0; - } - else if (ch == 0) { - if (0 < ndot && ndot < 3) - /* reject /.$ and /..$ */ - return -1; - return 0; - } - else - sl = ndot = 0; - } - else if (ch == 0) - return 0; - else if (ch == '/') { - sl = 1; - ndot = 0; - } - } -} - static char *path_ok(char *directory) { static char rpath[PATH_MAX]; @@ -157,7 +110,7 @@ static char *path_ok(char *directory) dir = directory; - if (avoid_alias(dir)) { + if (daemon_avoid_alias(dir)) { logerror("'%s': aliased", dir); return NULL; } diff --git a/http-backend.c b/http-backend.c index 646e910..f8ea9d7 100644 --- a/http-backend.c +++ b/http-backend.c @@ -559,7 +559,13 @@ static char* getdir(void) if (root && *root) { if (!pathinfo || !*pathinfo) die("GIT_PROJECT_ROOT is set but PATH_INFO is not"); + if (daemon_avoid_alias(pathinfo)) + die("'%s': aliased", pathinfo); strbuf_addstr(&buf, root); + if (buf.buf[buf.len - 1] != '/') + strbuf_addch(&buf, '/'); + if (pathinfo[0] == '/') + pathinfo++; strbuf_addstr(&buf, pathinfo); return strbuf_detach(&buf, NULL); } else if (path && *path) { diff --git a/path.c b/path.c index 047fdb0..c7679be 100644 --- a/path.c +++ b/path.c @@ -564,3 +564,50 @@ char *strip_path_suffix(const char *path, const char *suffix) return NULL; return xstrndup(path, chomp_trailing_dir_sep(path, path_len)); } + +int daemon_avoid_alias(const char *p) +{ + int sl, ndot; + + /* + * This resurrects the belts and suspenders paranoia check by HPA + * done in <435560F7.4080006@zytor.com> thread, now enter_repo() + * does not do getcwd() based path canonicalizations. + * + * sl becomes true immediately after seeing '/' and continues to + * be true as long as dots continue after that without intervening + * non-dot character. + */ + if (!p || (*p != '/' && *p != '~')) + return -1; + sl = 1; ndot = 0; + p++; + + while (1) { + char ch = *p++; + if (sl) { + if (ch == '.') + ndot++; + else if (ch == '/') { + if (ndot < 3) + /* reject //, /./ and /../ */ + return -1; + ndot = 0; + } + else if (ch == 0) { + if (0 < ndot && ndot < 3) + /* reject /.$ and /..$ */ + return -1; + return 0; + } + else + sl = ndot = 0; + } + else if (ch == 0) + return 0; + else if (ch == '/') { + sl = 1; + ndot = 0; + } + } +} diff --git a/t/t5560-http-backend.sh b/t/t5560-http-backend.sh index 908ba07..ed034bc 100755 --- a/t/t5560-http-backend.sh +++ b/t/t5560-http-backend.sh @@ -146,6 +146,37 @@ test_expect_success 'http.receivepack false' ' POST git-receive-pack 0000 "403 Forbidden" ' +run_backend() { + REQUEST_METHOD=GET \ + GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \ + PATH_INFO="$2" \ + git http-backend >act.out 2>act.err +} + +path_info() { + if test $1 = 0; then + run_backend "$2" + else + test_must_fail run_backend "$2" && + echo "fatal: '$2': aliased" >exp.err && + test_cmp exp.err act.err + fi +} + +test_expect_success 'http-backend blocks bad PATH_INFO' ' + config http.getanyfile true && + + run_backend 0 /repo.git/HEAD && + + run_backend 1 /repo.git/../HEAD && + run_backend 1 /../etc/passwd && + run_backend 1 ../etc/passwd && + run_backend 1 /etc//passwd && + run_backend 1 /etc/./passwd && + run_backend 1 /etc/.../passwd && + run_backend 1 //domain/data.txt +' + cat >exp < Date: Mon, 9 Nov 2009 10:10:36 -0800 Subject: t5551-http-fetch: Work around some libcurl versions Some versions of libcurl report their output when GIT_CURL_VERBOSE is set differently than other versions do. At least one variant (version unknown but likely pre-7.18.1) reports the POST payload to stderr, and omits the blank line after each HTTP request/response. We clip these lines out of the stderr output now before doing the compare, so we aren't surprised by this trivial difference. Reported-by: Tarmigan Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh index eb0c039..0bf165b 100755 --- a/t/t5551-http-fetch.sh +++ b/t/t5551-http-fetch.sh @@ -31,23 +31,19 @@ cat >exp < GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 > Accept: */* > Pragma: no-cache - < HTTP/1.1 200 OK < Pragma: no-cache < Cache-Control: no-cache, max-age=0, must-revalidate < Content-Type: application/x-git-upload-pack-advertisement -< > POST /smart/repo.git/git-upload-pack HTTP/1.1 > Accept-Encoding: deflate, gzip > Content-Type: application/x-git-upload-pack-request > Accept: application/x-git-upload-pack-response > Content-Length: xxx - < HTTP/1.1 200 OK < Pragma: no-cache < Cache-Control: no-cache, max-age=0, must-revalidate < Content-Type: application/x-git-upload-pack-result -< EOF test_expect_success 'clone http repository' ' GIT_CURL_VERBOSE=1 git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err && @@ -56,6 +52,8 @@ test_expect_success 'clone http repository' ' sed -e " s/Q\$// /^[*] /d + /^$/d + /^< $/d /^[^><]/{ s/^/> / @@ -64,6 +62,8 @@ test_expect_success 'clone http repository' ' /^> User-Agent: /d /^> Host: /d s/^> Content-Length: .*/> Content-Length: xxx/ + /^> 00..want /d + /^> 00.*done/d /^< Server: /d /^< Expires: /d -- cgit v0.10.2-6-g49f6 From 203666352f36702a8773ab47f67ef467528245ae Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 9 Nov 2009 10:10:37 -0800 Subject: t5551-http-fetch: Work around broken Accept header in libcurl Unfortunately at least one version of libcurl has a bug causing it to include "Accept: */*" in the same POST request where we have already asked for "Accept: application/x-git-upload-pack-response". This is a bug in libcurl, not Git, or our test vector. The application has explicitly asked the server for a single content type, but libcurl has mistakenly also told the server the client application will accept */*, which is any content type. Based on the libcurl change log, this "Accept: */*" header bug may have been fixed in version 7.18.1 released March 30, 2008: http://curl.haxx.se/changes.html#7_18_1 Rather than require users to upgrade libcurl we change the test vector to trim this line out of the 2nd request. Reported-by: Tarmigan Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh index 0bf165b..c0505ec 100755 --- a/t/t5551-http-fetch.sh +++ b/t/t5551-http-fetch.sh @@ -61,6 +61,9 @@ test_expect_success 'clone http repository' ' /^> User-Agent: /d /^> Host: /d + /^> POST /,$ { + /^> Accept: [*]\\/[*]/d + } s/^> Content-Length: .*/> Content-Length: xxx/ /^> 00..want /d /^> 00.*done/d -- cgit v0.10.2-6-g49f6 From fe9a215214acd2cf9132aec70e0758786a6e3e8b Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:41 -0600 Subject: Retire fetch--tool helper to contrib/examples When git-fetch was builtin-ized, the previous script was moved to contrib/examples. Now, it is the sole remaining user for 'git fetch--tool'. The fetch--tool code is still worth keeping around so people can try out the old git-fetch.sh, for example when investigating regressions from the builtinifaction. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 5d5976f..c0ba479 100644 --- a/Makefile +++ b/Makefile @@ -601,7 +601,6 @@ BUILTIN_OBJS += builtin-diff-index.o BUILTIN_OBJS += builtin-diff-tree.o BUILTIN_OBJS += builtin-diff.o BUILTIN_OBJS += builtin-fast-export.o -BUILTIN_OBJS += builtin-fetch--tool.o BUILTIN_OBJS += builtin-fetch-pack.o BUILTIN_OBJS += builtin-fetch.o BUILTIN_OBJS += builtin-fmt-merge-msg.o diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c deleted file mode 100644 index 3dbdf7a..0000000 --- a/builtin-fetch--tool.c +++ /dev/null @@ -1,574 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "refs.h" -#include "commit.h" -#include "sigchain.h" - -static char *get_stdin(void) -{ - struct strbuf buf = STRBUF_INIT; - if (strbuf_read(&buf, 0, 1024) < 0) { - die_errno("error reading standard input"); - } - return strbuf_detach(&buf, NULL); -} - -static void show_new(enum object_type type, unsigned char *sha1_new) -{ - fprintf(stderr, " %s: %s\n", typename(type), - find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); -} - -static int update_ref_env(const char *action, - const char *refname, - unsigned char *sha1, - unsigned char *oldval) -{ - char msg[1024]; - const char *rla = getenv("GIT_REFLOG_ACTION"); - - if (!rla) - rla = "(reflog update)"; - if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg)) - warning("reflog message too long: %.*s...", 50, msg); - return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR); -} - -static int update_local_ref(const char *name, - const char *new_head, - const char *note, - int verbose, int force) -{ - unsigned char sha1_old[20], sha1_new[20]; - char oldh[41], newh[41]; - struct commit *current, *updated; - enum object_type type; - - if (get_sha1_hex(new_head, sha1_new)) - die("malformed object name %s", new_head); - - type = sha1_object_info(sha1_new, NULL); - if (type < 0) - die("object %s not found", new_head); - - if (!*name) { - /* Not storing */ - if (verbose) { - fprintf(stderr, "* fetched %s\n", note); - show_new(type, sha1_new); - } - return 0; - } - - if (get_sha1(name, sha1_old)) { - const char *msg; - just_store: - /* new ref */ - if (!strncmp(name, "refs/tags/", 10)) - msg = "storing tag"; - else - msg = "storing head"; - fprintf(stderr, "* %s: storing %s\n", - name, note); - show_new(type, sha1_new); - return update_ref_env(msg, name, sha1_new, NULL); - } - - if (!hashcmp(sha1_old, sha1_new)) { - if (verbose) { - fprintf(stderr, "* %s: same as %s\n", name, note); - show_new(type, sha1_new); - } - return 0; - } - - if (!strncmp(name, "refs/tags/", 10)) { - fprintf(stderr, "* %s: updating with %s\n", name, note); - show_new(type, sha1_new); - return update_ref_env("updating tag", name, sha1_new, NULL); - } - - current = lookup_commit_reference(sha1_old); - updated = lookup_commit_reference(sha1_new); - if (!current || !updated) - goto just_store; - - strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); - strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); - - if (in_merge_bases(current, &updated, 1)) { - fprintf(stderr, "* %s: fast forward to %s\n", - name, note); - fprintf(stderr, " old..new: %s..%s\n", oldh, newh); - return update_ref_env("fast forward", name, sha1_new, sha1_old); - } - if (!force) { - fprintf(stderr, - "* %s: not updating to non-fast forward %s\n", - name, note); - fprintf(stderr, - " old...new: %s...%s\n", oldh, newh); - return 1; - } - fprintf(stderr, - "* %s: forcing update to non-fast forward %s\n", - name, note); - fprintf(stderr, " old...new: %s...%s\n", oldh, newh); - return update_ref_env("forced-update", name, sha1_new, sha1_old); -} - -static int append_fetch_head(FILE *fp, - const char *head, const char *remote, - const char *remote_name, const char *remote_nick, - const char *local_name, int not_for_merge, - int verbose, int force) -{ - struct commit *commit; - int remote_len, i, note_len; - unsigned char sha1[20]; - char note[1024]; - const char *what, *kind; - - if (get_sha1(head, sha1)) - return error("Not a valid object name: %s", head); - commit = lookup_commit_reference_gently(sha1, 1); - if (!commit) - not_for_merge = 1; - - if (!strcmp(remote_name, "HEAD")) { - kind = ""; - what = ""; - } - else if (!strncmp(remote_name, "refs/heads/", 11)) { - kind = "branch"; - what = remote_name + 11; - } - else if (!strncmp(remote_name, "refs/tags/", 10)) { - kind = "tag"; - what = remote_name + 10; - } - else if (!strncmp(remote_name, "refs/remotes/", 13)) { - kind = "remote branch"; - what = remote_name + 13; - } - else { - kind = ""; - what = remote_name; - } - - remote_len = strlen(remote); - for (i = remote_len - 1; remote[i] == '/' && 0 <= i; i--) - ; - remote_len = i + 1; - if (4 < i && !strncmp(".git", remote + i - 3, 4)) - remote_len = i - 3; - - note_len = 0; - if (*what) { - if (*kind) - note_len += sprintf(note + note_len, "%s ", kind); - note_len += sprintf(note + note_len, "'%s' of ", what); - } - note_len += sprintf(note + note_len, "%.*s", remote_len, remote); - fprintf(fp, "%s\t%s\t%s\n", - sha1_to_hex(commit ? commit->object.sha1 : sha1), - not_for_merge ? "not-for-merge" : "", - note); - return update_local_ref(local_name, head, note, verbose, force); -} - -static char *keep; -static void remove_keep(void) -{ - if (keep && *keep) - unlink(keep); -} - -static void remove_keep_on_signal(int signo) -{ - remove_keep(); - sigchain_pop(signo); - raise(signo); -} - -static char *find_local_name(const char *remote_name, const char *refs, - int *force_p, int *not_for_merge_p) -{ - const char *ref = refs; - int len = strlen(remote_name); - - while (ref) { - const char *next; - int single_force, not_for_merge; - - while (*ref == '\n') - ref++; - if (!*ref) - break; - next = strchr(ref, '\n'); - - single_force = not_for_merge = 0; - if (*ref == '+') { - single_force = 1; - ref++; - } - if (*ref == '.') { - not_for_merge = 1; - ref++; - if (*ref == '+') { - single_force = 1; - ref++; - } - } - if (!strncmp(remote_name, ref, len) && ref[len] == ':') { - const char *local_part = ref + len + 1; - int retlen; - - if (!next) - retlen = strlen(local_part); - else - retlen = next - local_part; - *force_p = single_force; - *not_for_merge_p = not_for_merge; - return xmemdupz(local_part, retlen); - } - ref = next; - } - return NULL; -} - -static int fetch_native_store(FILE *fp, - const char *remote, - const char *remote_nick, - const char *refs, - int verbose, int force) -{ - char buffer[1024]; - int err = 0; - - sigchain_push_common(remove_keep_on_signal); - atexit(remove_keep); - - while (fgets(buffer, sizeof(buffer), stdin)) { - int len; - char *cp; - char *local_name; - int single_force, not_for_merge; - - for (cp = buffer; *cp && !isspace(*cp); cp++) - ; - if (*cp) - *cp++ = 0; - len = strlen(cp); - if (len && cp[len-1] == '\n') - cp[--len] = 0; - if (!strcmp(buffer, "failed")) - die("Fetch failure: %s", remote); - if (!strcmp(buffer, "pack")) - continue; - if (!strcmp(buffer, "keep")) { - char *od = get_object_directory(); - int len = strlen(od) + strlen(cp) + 50; - keep = xmalloc(len); - sprintf(keep, "%s/pack/pack-%s.keep", od, cp); - continue; - } - - local_name = find_local_name(cp, refs, - &single_force, ¬_for_merge); - if (!local_name) - continue; - err |= append_fetch_head(fp, - buffer, remote, cp, remote_nick, - local_name, not_for_merge, - verbose, force || single_force); - } - return err; -} - -static int parse_reflist(const char *reflist) -{ - const char *ref; - - printf("refs='"); - for (ref = reflist; ref; ) { - const char *next; - while (*ref && isspace(*ref)) - ref++; - if (!*ref) - break; - for (next = ref; *next && !isspace(*next); next++) - ; - printf("\n%.*s", (int)(next - ref), ref); - ref = next; - } - printf("'\n"); - - printf("rref='"); - for (ref = reflist; ref; ) { - const char *next, *colon; - while (*ref && isspace(*ref)) - ref++; - if (!*ref) - break; - for (next = ref; *next && !isspace(*next); next++) - ; - if (*ref == '.') - ref++; - if (*ref == '+') - ref++; - colon = strchr(ref, ':'); - putchar('\n'); - printf("%.*s", (int)((colon ? colon : next) - ref), ref); - ref = next; - } - printf("'\n"); - return 0; -} - -static int expand_refs_wildcard(const char *ls_remote_result, int numrefs, - const char **refs) -{ - int i, matchlen, replacelen; - int found_one = 0; - const char *remote = *refs++; - numrefs--; - - if (numrefs == 0) { - fprintf(stderr, "Nothing specified for fetching with remote.%s.fetch\n", - remote); - printf("empty\n"); - } - - for (i = 0; i < numrefs; i++) { - const char *ref = refs[i]; - const char *lref = ref; - const char *colon; - const char *tail; - const char *ls; - const char *next; - - if (*lref == '+') - lref++; - colon = strchr(lref, ':'); - tail = lref + strlen(lref); - if (!(colon && - 2 < colon - lref && - colon[-1] == '*' && - colon[-2] == '/' && - 2 < tail - (colon + 1) && - tail[-1] == '*' && - tail[-2] == '/')) { - /* not a glob */ - if (!found_one++) - printf("explicit\n"); - printf("%s\n", ref); - continue; - } - - /* glob */ - if (!found_one++) - printf("glob\n"); - - /* lref to colon-2 is remote hierarchy name; - * colon+1 to tail-2 is local. - */ - matchlen = (colon-1) - lref; - replacelen = (tail-1) - (colon+1); - for (ls = ls_remote_result; ls; ls = next) { - const char *eol; - unsigned char sha1[20]; - int namelen; - - while (*ls && isspace(*ls)) - ls++; - next = strchr(ls, '\n'); - eol = !next ? (ls + strlen(ls)) : next; - if (!memcmp("^{}", eol-3, 3)) - continue; - if (eol - ls < 40) - continue; - if (get_sha1_hex(ls, sha1)) - continue; - ls += 40; - while (ls < eol && isspace(*ls)) - ls++; - /* ls to next (or eol) is the name. - * is it identical to lref to colon-2? - */ - if ((eol - ls) <= matchlen || - strncmp(ls, lref, matchlen)) - continue; - - /* Yes, it is a match */ - namelen = eol - ls; - if (lref != ref) - putchar('+'); - printf("%.*s:%.*s%.*s\n", - namelen, ls, - replacelen, colon + 1, - namelen - matchlen, ls + matchlen); - } - } - return 0; -} - -static int pick_rref(int sha1_only, const char *rref, const char *ls_remote_result) -{ - int err = 0; - int lrr_count = lrr_count, i, pass; - const char *cp; - struct lrr { - const char *line; - const char *name; - int namelen; - int shown; - } *lrr_list = lrr_list; - - for (pass = 0; pass < 2; pass++) { - /* pass 0 counts and allocates, pass 1 fills... */ - cp = ls_remote_result; - i = 0; - while (1) { - const char *np; - while (*cp && isspace(*cp)) - cp++; - if (!*cp) - break; - np = strchrnul(cp, '\n'); - if (pass) { - lrr_list[i].line = cp; - lrr_list[i].name = cp + 41; - lrr_list[i].namelen = np - (cp + 41); - } - i++; - cp = np; - } - if (!pass) { - lrr_count = i; - lrr_list = xcalloc(lrr_count, sizeof(*lrr_list)); - } - } - - while (1) { - const char *next; - int rreflen; - int i; - - while (*rref && isspace(*rref)) - rref++; - if (!*rref) - break; - next = strchrnul(rref, '\n'); - rreflen = next - rref; - - for (i = 0; i < lrr_count; i++) { - struct lrr *lrr = &(lrr_list[i]); - - if (rreflen == lrr->namelen && - !memcmp(lrr->name, rref, rreflen)) { - if (!lrr->shown) - printf("%.*s\n", - sha1_only ? 40 : lrr->namelen + 41, - lrr->line); - lrr->shown = 1; - break; - } - } - if (lrr_count <= i) { - error("pick-rref: %.*s not found", rreflen, rref); - err = 1; - } - rref = next; - } - free(lrr_list); - return err; -} - -int cmd_fetch__tool(int argc, const char **argv, const char *prefix) -{ - int verbose = 0; - int force = 0; - int sopt = 0; - - while (1 < argc) { - const char *arg = argv[1]; - if (!strcmp("-v", arg)) - verbose = 1; - else if (!strcmp("-f", arg)) - force = 1; - else if (!strcmp("-s", arg)) - sopt = 1; - else - break; - argc--; - argv++; - } - - if (argc <= 1) - return error("Missing subcommand"); - - if (!strcmp("append-fetch-head", argv[1])) { - int result; - FILE *fp; - char *filename; - - if (argc != 8) - return error("append-fetch-head takes 6 args"); - filename = git_path("FETCH_HEAD"); - fp = fopen(filename, "a"); - if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); - result = append_fetch_head(fp, argv[2], argv[3], - argv[4], argv[5], - argv[6], !!argv[7][0], - verbose, force); - fclose(fp); - return result; - } - if (!strcmp("native-store", argv[1])) { - int result; - FILE *fp; - char *filename; - - if (argc != 5) - return error("fetch-native-store takes 3 args"); - filename = git_path("FETCH_HEAD"); - fp = fopen(filename, "a"); - if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); - result = fetch_native_store(fp, argv[2], argv[3], argv[4], - verbose, force); - fclose(fp); - return result; - } - if (!strcmp("parse-reflist", argv[1])) { - const char *reflist; - if (argc != 3) - return error("parse-reflist takes 1 arg"); - reflist = argv[2]; - if (!strcmp(reflist, "-")) - reflist = get_stdin(); - return parse_reflist(reflist); - } - if (!strcmp("pick-rref", argv[1])) { - const char *ls_remote_result; - if (argc != 4) - return error("pick-rref takes 2 args"); - ls_remote_result = argv[3]; - if (!strcmp(ls_remote_result, "-")) - ls_remote_result = get_stdin(); - return pick_rref(sopt, argv[2], ls_remote_result); - } - if (!strcmp("expand-refs-wildcard", argv[1])) { - const char *reflist; - if (argc < 4) - return error("expand-refs-wildcard takes at least 2 args"); - reflist = argv[2]; - if (!strcmp(reflist, "-")) - reflist = get_stdin(); - return expand_refs_wildcard(reflist, argc - 3, argv + 3); - } - - return error("Unknown subcommand: %s", argv[1]); -} diff --git a/builtin.h b/builtin.h index a2174dc..c3f83c0 100644 --- a/builtin.h +++ b/builtin.h @@ -48,7 +48,6 @@ extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); -extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix); extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix); extern int cmd_format_patch(int argc, const char **argv, const char *prefix); diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c new file mode 100644 index 0000000..3dbdf7a --- /dev/null +++ b/contrib/examples/builtin-fetch--tool.c @@ -0,0 +1,574 @@ +#include "builtin.h" +#include "cache.h" +#include "refs.h" +#include "commit.h" +#include "sigchain.h" + +static char *get_stdin(void) +{ + struct strbuf buf = STRBUF_INIT; + if (strbuf_read(&buf, 0, 1024) < 0) { + die_errno("error reading standard input"); + } + return strbuf_detach(&buf, NULL); +} + +static void show_new(enum object_type type, unsigned char *sha1_new) +{ + fprintf(stderr, " %s: %s\n", typename(type), + find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); +} + +static int update_ref_env(const char *action, + const char *refname, + unsigned char *sha1, + unsigned char *oldval) +{ + char msg[1024]; + const char *rla = getenv("GIT_REFLOG_ACTION"); + + if (!rla) + rla = "(reflog update)"; + if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg)) + warning("reflog message too long: %.*s...", 50, msg); + return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR); +} + +static int update_local_ref(const char *name, + const char *new_head, + const char *note, + int verbose, int force) +{ + unsigned char sha1_old[20], sha1_new[20]; + char oldh[41], newh[41]; + struct commit *current, *updated; + enum object_type type; + + if (get_sha1_hex(new_head, sha1_new)) + die("malformed object name %s", new_head); + + type = sha1_object_info(sha1_new, NULL); + if (type < 0) + die("object %s not found", new_head); + + if (!*name) { + /* Not storing */ + if (verbose) { + fprintf(stderr, "* fetched %s\n", note); + show_new(type, sha1_new); + } + return 0; + } + + if (get_sha1(name, sha1_old)) { + const char *msg; + just_store: + /* new ref */ + if (!strncmp(name, "refs/tags/", 10)) + msg = "storing tag"; + else + msg = "storing head"; + fprintf(stderr, "* %s: storing %s\n", + name, note); + show_new(type, sha1_new); + return update_ref_env(msg, name, sha1_new, NULL); + } + + if (!hashcmp(sha1_old, sha1_new)) { + if (verbose) { + fprintf(stderr, "* %s: same as %s\n", name, note); + show_new(type, sha1_new); + } + return 0; + } + + if (!strncmp(name, "refs/tags/", 10)) { + fprintf(stderr, "* %s: updating with %s\n", name, note); + show_new(type, sha1_new); + return update_ref_env("updating tag", name, sha1_new, NULL); + } + + current = lookup_commit_reference(sha1_old); + updated = lookup_commit_reference(sha1_new); + if (!current || !updated) + goto just_store; + + strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); + strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); + + if (in_merge_bases(current, &updated, 1)) { + fprintf(stderr, "* %s: fast forward to %s\n", + name, note); + fprintf(stderr, " old..new: %s..%s\n", oldh, newh); + return update_ref_env("fast forward", name, sha1_new, sha1_old); + } + if (!force) { + fprintf(stderr, + "* %s: not updating to non-fast forward %s\n", + name, note); + fprintf(stderr, + " old...new: %s...%s\n", oldh, newh); + return 1; + } + fprintf(stderr, + "* %s: forcing update to non-fast forward %s\n", + name, note); + fprintf(stderr, " old...new: %s...%s\n", oldh, newh); + return update_ref_env("forced-update", name, sha1_new, sha1_old); +} + +static int append_fetch_head(FILE *fp, + const char *head, const char *remote, + const char *remote_name, const char *remote_nick, + const char *local_name, int not_for_merge, + int verbose, int force) +{ + struct commit *commit; + int remote_len, i, note_len; + unsigned char sha1[20]; + char note[1024]; + const char *what, *kind; + + if (get_sha1(head, sha1)) + return error("Not a valid object name: %s", head); + commit = lookup_commit_reference_gently(sha1, 1); + if (!commit) + not_for_merge = 1; + + if (!strcmp(remote_name, "HEAD")) { + kind = ""; + what = ""; + } + else if (!strncmp(remote_name, "refs/heads/", 11)) { + kind = "branch"; + what = remote_name + 11; + } + else if (!strncmp(remote_name, "refs/tags/", 10)) { + kind = "tag"; + what = remote_name + 10; + } + else if (!strncmp(remote_name, "refs/remotes/", 13)) { + kind = "remote branch"; + what = remote_name + 13; + } + else { + kind = ""; + what = remote_name; + } + + remote_len = strlen(remote); + for (i = remote_len - 1; remote[i] == '/' && 0 <= i; i--) + ; + remote_len = i + 1; + if (4 < i && !strncmp(".git", remote + i - 3, 4)) + remote_len = i - 3; + + note_len = 0; + if (*what) { + if (*kind) + note_len += sprintf(note + note_len, "%s ", kind); + note_len += sprintf(note + note_len, "'%s' of ", what); + } + note_len += sprintf(note + note_len, "%.*s", remote_len, remote); + fprintf(fp, "%s\t%s\t%s\n", + sha1_to_hex(commit ? commit->object.sha1 : sha1), + not_for_merge ? "not-for-merge" : "", + note); + return update_local_ref(local_name, head, note, verbose, force); +} + +static char *keep; +static void remove_keep(void) +{ + if (keep && *keep) + unlink(keep); +} + +static void remove_keep_on_signal(int signo) +{ + remove_keep(); + sigchain_pop(signo); + raise(signo); +} + +static char *find_local_name(const char *remote_name, const char *refs, + int *force_p, int *not_for_merge_p) +{ + const char *ref = refs; + int len = strlen(remote_name); + + while (ref) { + const char *next; + int single_force, not_for_merge; + + while (*ref == '\n') + ref++; + if (!*ref) + break; + next = strchr(ref, '\n'); + + single_force = not_for_merge = 0; + if (*ref == '+') { + single_force = 1; + ref++; + } + if (*ref == '.') { + not_for_merge = 1; + ref++; + if (*ref == '+') { + single_force = 1; + ref++; + } + } + if (!strncmp(remote_name, ref, len) && ref[len] == ':') { + const char *local_part = ref + len + 1; + int retlen; + + if (!next) + retlen = strlen(local_part); + else + retlen = next - local_part; + *force_p = single_force; + *not_for_merge_p = not_for_merge; + return xmemdupz(local_part, retlen); + } + ref = next; + } + return NULL; +} + +static int fetch_native_store(FILE *fp, + const char *remote, + const char *remote_nick, + const char *refs, + int verbose, int force) +{ + char buffer[1024]; + int err = 0; + + sigchain_push_common(remove_keep_on_signal); + atexit(remove_keep); + + while (fgets(buffer, sizeof(buffer), stdin)) { + int len; + char *cp; + char *local_name; + int single_force, not_for_merge; + + for (cp = buffer; *cp && !isspace(*cp); cp++) + ; + if (*cp) + *cp++ = 0; + len = strlen(cp); + if (len && cp[len-1] == '\n') + cp[--len] = 0; + if (!strcmp(buffer, "failed")) + die("Fetch failure: %s", remote); + if (!strcmp(buffer, "pack")) + continue; + if (!strcmp(buffer, "keep")) { + char *od = get_object_directory(); + int len = strlen(od) + strlen(cp) + 50; + keep = xmalloc(len); + sprintf(keep, "%s/pack/pack-%s.keep", od, cp); + continue; + } + + local_name = find_local_name(cp, refs, + &single_force, ¬_for_merge); + if (!local_name) + continue; + err |= append_fetch_head(fp, + buffer, remote, cp, remote_nick, + local_name, not_for_merge, + verbose, force || single_force); + } + return err; +} + +static int parse_reflist(const char *reflist) +{ + const char *ref; + + printf("refs='"); + for (ref = reflist; ref; ) { + const char *next; + while (*ref && isspace(*ref)) + ref++; + if (!*ref) + break; + for (next = ref; *next && !isspace(*next); next++) + ; + printf("\n%.*s", (int)(next - ref), ref); + ref = next; + } + printf("'\n"); + + printf("rref='"); + for (ref = reflist; ref; ) { + const char *next, *colon; + while (*ref && isspace(*ref)) + ref++; + if (!*ref) + break; + for (next = ref; *next && !isspace(*next); next++) + ; + if (*ref == '.') + ref++; + if (*ref == '+') + ref++; + colon = strchr(ref, ':'); + putchar('\n'); + printf("%.*s", (int)((colon ? colon : next) - ref), ref); + ref = next; + } + printf("'\n"); + return 0; +} + +static int expand_refs_wildcard(const char *ls_remote_result, int numrefs, + const char **refs) +{ + int i, matchlen, replacelen; + int found_one = 0; + const char *remote = *refs++; + numrefs--; + + if (numrefs == 0) { + fprintf(stderr, "Nothing specified for fetching with remote.%s.fetch\n", + remote); + printf("empty\n"); + } + + for (i = 0; i < numrefs; i++) { + const char *ref = refs[i]; + const char *lref = ref; + const char *colon; + const char *tail; + const char *ls; + const char *next; + + if (*lref == '+') + lref++; + colon = strchr(lref, ':'); + tail = lref + strlen(lref); + if (!(colon && + 2 < colon - lref && + colon[-1] == '*' && + colon[-2] == '/' && + 2 < tail - (colon + 1) && + tail[-1] == '*' && + tail[-2] == '/')) { + /* not a glob */ + if (!found_one++) + printf("explicit\n"); + printf("%s\n", ref); + continue; + } + + /* glob */ + if (!found_one++) + printf("glob\n"); + + /* lref to colon-2 is remote hierarchy name; + * colon+1 to tail-2 is local. + */ + matchlen = (colon-1) - lref; + replacelen = (tail-1) - (colon+1); + for (ls = ls_remote_result; ls; ls = next) { + const char *eol; + unsigned char sha1[20]; + int namelen; + + while (*ls && isspace(*ls)) + ls++; + next = strchr(ls, '\n'); + eol = !next ? (ls + strlen(ls)) : next; + if (!memcmp("^{}", eol-3, 3)) + continue; + if (eol - ls < 40) + continue; + if (get_sha1_hex(ls, sha1)) + continue; + ls += 40; + while (ls < eol && isspace(*ls)) + ls++; + /* ls to next (or eol) is the name. + * is it identical to lref to colon-2? + */ + if ((eol - ls) <= matchlen || + strncmp(ls, lref, matchlen)) + continue; + + /* Yes, it is a match */ + namelen = eol - ls; + if (lref != ref) + putchar('+'); + printf("%.*s:%.*s%.*s\n", + namelen, ls, + replacelen, colon + 1, + namelen - matchlen, ls + matchlen); + } + } + return 0; +} + +static int pick_rref(int sha1_only, const char *rref, const char *ls_remote_result) +{ + int err = 0; + int lrr_count = lrr_count, i, pass; + const char *cp; + struct lrr { + const char *line; + const char *name; + int namelen; + int shown; + } *lrr_list = lrr_list; + + for (pass = 0; pass < 2; pass++) { + /* pass 0 counts and allocates, pass 1 fills... */ + cp = ls_remote_result; + i = 0; + while (1) { + const char *np; + while (*cp && isspace(*cp)) + cp++; + if (!*cp) + break; + np = strchrnul(cp, '\n'); + if (pass) { + lrr_list[i].line = cp; + lrr_list[i].name = cp + 41; + lrr_list[i].namelen = np - (cp + 41); + } + i++; + cp = np; + } + if (!pass) { + lrr_count = i; + lrr_list = xcalloc(lrr_count, sizeof(*lrr_list)); + } + } + + while (1) { + const char *next; + int rreflen; + int i; + + while (*rref && isspace(*rref)) + rref++; + if (!*rref) + break; + next = strchrnul(rref, '\n'); + rreflen = next - rref; + + for (i = 0; i < lrr_count; i++) { + struct lrr *lrr = &(lrr_list[i]); + + if (rreflen == lrr->namelen && + !memcmp(lrr->name, rref, rreflen)) { + if (!lrr->shown) + printf("%.*s\n", + sha1_only ? 40 : lrr->namelen + 41, + lrr->line); + lrr->shown = 1; + break; + } + } + if (lrr_count <= i) { + error("pick-rref: %.*s not found", rreflen, rref); + err = 1; + } + rref = next; + } + free(lrr_list); + return err; +} + +int cmd_fetch__tool(int argc, const char **argv, const char *prefix) +{ + int verbose = 0; + int force = 0; + int sopt = 0; + + while (1 < argc) { + const char *arg = argv[1]; + if (!strcmp("-v", arg)) + verbose = 1; + else if (!strcmp("-f", arg)) + force = 1; + else if (!strcmp("-s", arg)) + sopt = 1; + else + break; + argc--; + argv++; + } + + if (argc <= 1) + return error("Missing subcommand"); + + if (!strcmp("append-fetch-head", argv[1])) { + int result; + FILE *fp; + char *filename; + + if (argc != 8) + return error("append-fetch-head takes 6 args"); + filename = git_path("FETCH_HEAD"); + fp = fopen(filename, "a"); + if (!fp) + return error("cannot open %s: %s\n", filename, strerror(errno)); + result = append_fetch_head(fp, argv[2], argv[3], + argv[4], argv[5], + argv[6], !!argv[7][0], + verbose, force); + fclose(fp); + return result; + } + if (!strcmp("native-store", argv[1])) { + int result; + FILE *fp; + char *filename; + + if (argc != 5) + return error("fetch-native-store takes 3 args"); + filename = git_path("FETCH_HEAD"); + fp = fopen(filename, "a"); + if (!fp) + return error("cannot open %s: %s\n", filename, strerror(errno)); + result = fetch_native_store(fp, argv[2], argv[3], argv[4], + verbose, force); + fclose(fp); + return result; + } + if (!strcmp("parse-reflist", argv[1])) { + const char *reflist; + if (argc != 3) + return error("parse-reflist takes 1 arg"); + reflist = argv[2]; + if (!strcmp(reflist, "-")) + reflist = get_stdin(); + return parse_reflist(reflist); + } + if (!strcmp("pick-rref", argv[1])) { + const char *ls_remote_result; + if (argc != 4) + return error("pick-rref takes 2 args"); + ls_remote_result = argv[3]; + if (!strcmp(ls_remote_result, "-")) + ls_remote_result = get_stdin(); + return pick_rref(sopt, argv[2], ls_remote_result); + } + if (!strcmp("expand-refs-wildcard", argv[1])) { + const char *reflist; + if (argc < 4) + return error("expand-refs-wildcard takes at least 2 args"); + reflist = argv[2]; + if (!strcmp(reflist, "-")) + reflist = get_stdin(); + return expand_refs_wildcard(reflist, argc - 3, argv + 3); + } + + return error("Unknown subcommand: %s", argv[1]); +} diff --git a/git.c b/git.c index bd2c5fe..f295561 100644 --- a/git.c +++ b/git.c @@ -304,7 +304,6 @@ static void handle_internal_command(int argc, const char **argv) { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, - { "fetch--tool", cmd_fetch__tool, RUN_SETUP }, { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, { "format-patch", cmd_format_patch, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From 9c855c31786b9e879ef4cd3b8b5aa97bc4bcf8ec Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:42 -0600 Subject: Show usage string for 'git grep -h' Clarification: the following description only talks about "git grep -h" without any other options and arguments. Such a change cannot be breaking backward compatibility. "grep -h" cannot be asking for suppressing filenames, as there is no match pattern specified. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-grep.c b/builtin-grep.c index 1df25b0..01be9bf 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -788,6 +788,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_END() }; + /* + * 'git grep -h', unlike 'git grep -h ', is a request + * to show usage information and exit. + */ + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(grep_usage, options); + memset(&opt, 0, sizeof(opt)); opt.prefix = prefix; opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; -- cgit v0.10.2-6-g49f6 From fef34270f209eb5d2cde01b8175b24d96d1cff21 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:43 -0600 Subject: Show usage string for 'git cherry -h' Treat an "-h" option as a request for help, rather than an "Unknown commit -h" error. "cherry -h" could be asking to compare histories that leads to our HEAD and a commit that can be named as "-h". Strictly speaking, that may be a valid refname, but the user would have to say something like "tags/-h" to name such a pathological ref already, so it is not such a big deal. The "-h" option keeps its meaning even if preceded by other options or followed by other arguments. This keeps the command-line syntax closer to what parse_options would give and supports shell aliases like 'alias cherry="git cherry -v"' a little better. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-log.c b/builtin-log.c index 207a361..5248507 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -1237,6 +1237,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) argv++; } + if (argc > 1 && !strcmp(argv[1], "-h")) + usage(cherry_usage); + switch (argc) { case 4: limit = argv[3]; -- cgit v0.10.2-6-g49f6 From 6e9daeffec0213fa1cee76ad9d899fe492409f46 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:44 -0600 Subject: Show usage string for 'git commit-tree -h' Treat an "-h" option as a request for help, rather than a "Not a valid object name" error. "commit-tree -h" could be asking to create a new commit from a treeish named "-h". Strictly speaking, such a pathological ref name is possible, but the user would have to had said something like "tags/-h" to name such a pathological already. commit-tree is usually used in scripts with raw object ids, anyway. For consistency, the "-h" option uses its new meaning even if followed by other arguments. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 6467077..ddcb7a4 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -105,7 +105,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - if (argc < 2) + if (argc < 2 || !strcmp(argv[1], "-h")) usage(commit_tree_usage); if (get_sha1(argv[1], tree_sha1)) die("Not a valid object name %s", argv[1]); -- cgit v0.10.2-6-g49f6 From 20c7e3d5cfcc0834fd5d38200e94d15a103ab271 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:45 -0600 Subject: Show usage string for 'git merge-ours -h' This change is strictly about 'git merge-ours -h' without any other options and arguments. This change cannot break compatibility since merge drivers are always passed '--', among other arguments. Any usage string for this command is a lie, since it ignored its arguments until now. Still, it makes sense to let the user know the expected usage when asked. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-merge-ours.c b/builtin-merge-ours.c index 8f5bbaf..6844116 100644 --- a/builtin-merge-ours.c +++ b/builtin-merge-ours.c @@ -10,6 +10,9 @@ #include "git-compat-util.h" #include "builtin.h" +static const char builtin_merge_ours_usage[] = + "git merge-ours ... -- HEAD ..."; + static const char *diff_index_args[] = { "diff-index", "--quiet", "--cached", "HEAD", "--", NULL }; @@ -17,6 +20,9 @@ static const char *diff_index_args[] = { int cmd_merge_ours(int argc, const char **argv, const char *prefix) { + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(builtin_merge_ours_usage); + /* * We need to exit with 2 if the index does not match our HEAD tree, * because the current index is what we will be committing as the -- cgit v0.10.2-6-g49f6 From e62b393505616c3ce313f6dac5060d9e1cde8e42 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:46 -0600 Subject: Show usage string for 'git show-ref -h' This only changes the behavior of "git show-ref -h" without any other options and arguments. "show-ref -h" currently is short for "show-ref --head", which shows all the refs/* and HEAD, as opposed to "show-ref" that shows all the refs/* and not HEAD. Does anybody use "show-ref -h"? It was in Linus's original, most likely only because "it might be handy", not because "the command should not show the HEAD by default for such and such reasons". So I think it is okay if "show-ref -h" (but not "show-ref --head") gives help and exits. If a current script uses "git show-ref -h" without any other arguments, it would have to be adapted by changing "-h" to "--head". Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index f4429bd..70f400b 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -8,7 +8,7 @@ git-show-ref - List references in a local repository SYNOPSIS -------- [verse] -'git show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] +'git show-ref' [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=]] [--abbrev[=]] [--tags] [--heads] [--] ... 'git show-ref' --exclude-existing[=] < ref-list @@ -30,7 +30,6 @@ the `.git` directory. OPTIONS ------- --h:: --head:: Show the HEAD reference. diff --git a/builtin-show-ref.c b/builtin-show-ref.c index c46550c..17ada88 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -7,7 +7,7 @@ #include "parse-options.h" static const char * const show_ref_usage[] = { - "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=]] [--abbrev[=]] [--tags] [--heads] [--] [pattern*] ", + "git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=]] [--abbrev[=]] [--tags] [--heads] [--] [pattern*] ", "git show-ref --exclude-existing[=pattern] < ref-list", NULL }; @@ -183,7 +183,10 @@ static const struct option show_ref_options[] = { OPT_BOOLEAN(0, "heads", &heads_only, "only show heads (can be combined with tags)"), OPT_BOOLEAN(0, "verify", &verify, "stricter reference checking, " "requires exact ref path"), - OPT_BOOLEAN('h', "head", &show_head, "show the HEAD reference"), + { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL, + "show the HEAD reference", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + OPT_BOOLEAN(0, "head", &show_head, "show the HEAD reference"), OPT_BOOLEAN('d', "dereference", &deref_tags, "dereference tags into object IDs"), { OPTION_CALLBACK, 's', "hash", &abbrev, "n", @@ -201,6 +204,9 @@ static const struct option show_ref_options[] = { int cmd_show_ref(int argc, const char **argv, const char *prefix) { + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(show_ref_usage, show_ref_options); + argc = parse_options(argc, argv, prefix, show_ref_options, show_ref_usage, PARSE_OPT_NO_INTERNAL_HELP); -- cgit v0.10.2-6-g49f6 From 9c4a036b34acef63ab754f0e27e5e54bd9d9a210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Mon, 9 Nov 2009 21:09:56 +0100 Subject: Teach the --all option to 'git fetch' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'git remote' is meant for managing remotes and 'git fetch' is meant for actually fetching data from remote repositories. Therefore, it is not logical that you must use 'git remote update' to fetch from more than one repository at once. Add the --all option to 'git fetch', to tell it to attempt to fetch from all remotes. Also, if --all is not given, the argument is allowed to be the name of a group, to allow fetching from all repositories in the group. Other options except -v and -q are silently ignored. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 2886874..93d73c3 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -1,3 +1,6 @@ +--all:: + Fetch all remotes. + -a:: --append:: Append ref names and object names of fetched refs to the diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index d3164c5..334744c 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -10,11 +10,15 @@ SYNOPSIS -------- 'git fetch' ... +'git fetch' + +'git fetch' --all + DESCRIPTION ----------- -Fetches named heads or tags from another repository, along with -the objects necessary to complete them. +Fetches named heads or tags from one or more other repositories, +along with the objects necessary to complete them. The ref names and their object names of fetched refs are stored in `.git/FETCH_HEAD`. This information is left for a later merge @@ -28,6 +32,10 @@ pointed by remote tags that it does not yet have, then fetch those missing tags. If the other end has tags that point at branches you are not interested in, you will not get them. +'git fetch' can fetch from either a single named repository, or +or from several repositories at once if is given and +there is a remotes. entry in the configuration file. +(See linkgit:git-config[1]). OPTIONS ------- diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt index f9811f2..712b91a 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -4,6 +4,13 @@ (see the section <> below) or the name of a remote (see the section <> below). +ifndef::git-pull[] +:: + A name referring to a list of repositories as the value + of remotes. in the configuration file. + (See linkgit:git-config[1]). +endif::git-pull[] + :: The format of a parameter is an optional plus `{plus}`, followed by the source ref , followed diff --git a/builtin-fetch.c b/builtin-fetch.c index cb48c57..e6bbdc7 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -14,6 +14,8 @@ static const char * const builtin_fetch_usage[] = { "git fetch [options] [ ...]", + "git fetch [options] ", + "git fetch --all [options]", NULL }; @@ -23,7 +25,7 @@ enum { TAGS_SET = 2 }; -static int append, force, keep, update_head_ok, verbosity; +static int all, append, force, keep, update_head_ok, verbosity; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -32,6 +34,8 @@ static struct transport *transport; static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), + OPT_BOOLEAN(0, "all", &all, + "fetch from all remotes"), OPT_BOOLEAN('a', "append", &append, "append to .git/FETCH_HEAD instead of overwriting"), OPT_STRING(0, "upload-pack", &upload_pack, "PATH", @@ -639,27 +643,89 @@ static void set_option(const char *name, const char *value) name, transport->url); } -int cmd_fetch(int argc, const char **argv, const char *prefix) +static int get_one_remote_for_fetch(struct remote *remote, void *priv) +{ + struct string_list *list = priv; + string_list_append(remote->name, list); + return 0; +} + +struct remote_group_data { + const char *name; + struct string_list *list; +}; + +static int get_remote_group(const char *key, const char *value, void *priv) +{ + struct remote_group_data *g = priv; + + if (!prefixcmp(key, "remotes.") && + !strcmp(key + 8, g->name)) { + /* split list by white space */ + int space = strcspn(value, " \t\n"); + while (*value) { + if (space > 1) { + string_list_append(xstrndup(value, space), + g->list); + } + value += space + (value[space] != '\0'); + space = strcspn(value, " \t\n"); + } + } + + return 0; +} + +static int add_remote_or_group(const char *name, struct string_list *list) +{ + int prev_nr = list->nr; + struct remote_group_data g = { name, list }; + + git_config(get_remote_group, &g); + if (list->nr == prev_nr) { + struct remote *remote; + if (!remote_is_configured(name)) + return 0; + remote = remote_get(name); + string_list_append(remote->name, list); + } + return 1; +} + +static int fetch_multiple(struct string_list *list) +{ + int i, result = 0; + const char *argv[] = { "fetch", NULL, NULL, NULL, NULL }; + int argc = 1; + + if (verbosity >= 2) + argv[argc++] = "-v"; + if (verbosity >= 1) + argv[argc++] = "-v"; + else if (verbosity < 0) + argv[argc++] = "-q"; + + for (i = 0; i < list->nr; i++) { + const char *name = list->items[i].string; + argv[argc] = name; + if (verbosity >= 0) + printf("Fetching %s\n", name); + if (run_command_v_opt(argv, RUN_GIT_CMD)) { + error("Could not fetch %s", name); + result = 1; + } + } + + return result; +} + +static int fetch_one(struct remote *remote, int argc, const char **argv) { - struct remote *remote; int i; static const char **refs = NULL; int ref_nr = 0; int exit_code; - /* Record the command line for the reflog */ - strbuf_addstr(&default_rla, "fetch"); - for (i = 1; i < argc; i++) - strbuf_addf(&default_rla, " %s", argv[i]); - - argc = parse_options(argc, argv, prefix, - builtin_fetch_options, builtin_fetch_usage, 0); - - if (argc == 0) - remote = remote_get(NULL); - else - remote = remote_get(argv[0]); - if (!remote) die("Where do you want to fetch from today?"); @@ -675,10 +741,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth) set_option(TRANS_OPT_DEPTH, depth); - if (argc > 1) { + if (argc > 0) { int j = 0; refs = xcalloc(argc + 1, sizeof(const char *)); - for (i = 1; i < argc; i++) { + for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "tag")) { char *ref; i++; @@ -705,3 +771,51 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) transport = NULL; return exit_code; } + +int cmd_fetch(int argc, const char **argv, const char *prefix) +{ + int i; + struct string_list list = { NULL, 0, 0, 0 }; + struct remote *remote; + int result = 0; + + /* Record the command line for the reflog */ + strbuf_addstr(&default_rla, "fetch"); + for (i = 1; i < argc; i++) + strbuf_addf(&default_rla, " %s", argv[i]); + + argc = parse_options(argc, argv, prefix, + builtin_fetch_options, builtin_fetch_usage, 0); + + if (all) { + if (argc == 1) + die("fetch --all does not take a repository argument"); + else if (argc > 1) + die("fetch --all does not make sense with refspecs"); + (void) for_each_remote(get_one_remote_for_fetch, &list); + result = fetch_multiple(&list); + } else if (argc == 0) { + /* No arguments -- use default remote */ + remote = remote_get(NULL); + result = fetch_one(remote, argc, argv); + } else { + /* Single remote or group */ + (void) add_remote_or_group(argv[0], &list); + if (list.nr > 1) { + /* More than one remote */ + if (argc > 1) + die("Fetching a group and specifying refspecs does not make sense"); + result = fetch_multiple(&list); + } else { + /* Zero or one remotes */ + remote = remote_get(argv[0]); + result = fetch_one(remote, argc-1, argv+1); + } + } + + /* All names were strdup()ed or strndup()ed */ + list.strdup_strings = 1; + string_list_clear(&list, 0); + + return result; +} diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh index 2a1806b..b7b7dda 100755 --- a/t/t5506-remote-groups.sh +++ b/t/t5506-remote-groups.sh @@ -51,7 +51,7 @@ test_expect_success 'nonexistant group produces error' ' ! repo_fetched two ' -test_expect_success 'updating group updates all members' ' +test_expect_success 'updating group updates all members (remote update)' ' mark group-all && update_repos && git config --add remotes.all one && @@ -61,7 +61,15 @@ test_expect_success 'updating group updates all members' ' repo_fetched two ' -test_expect_success 'updating group does not update non-members' ' +test_expect_success 'updating group updates all members (fetch)' ' + mark fetch-group-all && + update_repos && + git fetch all && + repo_fetched one && + repo_fetched two +' + +test_expect_success 'updating group does not update non-members (remote update)' ' mark group-some && update_repos && git config --add remotes.some one && @@ -70,6 +78,15 @@ test_expect_success 'updating group does not update non-members' ' ! repo_fetched two ' +test_expect_success 'updating group does not update non-members (fetch)' ' + mark fetch-group-some && + update_repos && + git config --add remotes.some one && + git remote update some && + repo_fetched one && + ! repo_fetched two +' + test_expect_success 'updating remote name updates that remote' ' mark remote-name && update_repos && diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh new file mode 100755 index 0000000..25244bf --- /dev/null +++ b/t/t5514-fetch-multiple.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +test_description='fetch --all works correctly' + +. ./test-lib.sh + +setup_repository () { + mkdir "$1" && ( + cd "$1" && + git init && + >file && + git add file && + test_tick && + git commit -m "Initial" && + git checkout -b side && + >elif && + git add elif && + test_tick && + git commit -m "Second" && + git checkout master + ) +} + +test_expect_success setup ' + setup_repository one && + setup_repository two && + ( + cd two && git branch another + ) && + git clone --mirror two three + git clone one test +' + +cat > test/expect << EOF + one/master + one/side + origin/HEAD -> origin/master + origin/master + origin/side + three/another + three/master + three/side + two/another + two/master + two/side +EOF + +test_expect_success 'git fetch --all' ' + (cd test && + git remote add one ../one && + git remote add two ../two && + git remote add three ../three && + git fetch --all && + git branch -r > output && + test_cmp expect output) +' + +test_expect_success 'git fetch --all should continue if a remote has errors' ' + (git clone one test2 && + cd test2 && + git remote add bad ../non-existing && + git remote add one ../one && + git remote add two ../two && + git remote add three ../three && + test_must_fail git fetch --all && + git branch -r > output && + test_cmp ../test/expect output) +' + +test_expect_success 'git fetch --all does not allow non-option arguments' ' + (cd test && + test_must_fail git fetch --all origin && + test_must_fail git fetch --all origin master) +' + +test_done -- cgit v0.10.2-6-g49f6 From 16679e373fa85a75c85e6e3b4ae5cd58a89a4114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Mon, 9 Nov 2009 21:10:32 +0100 Subject: Teach the --multiple option to 'git fetch' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the --multiple option to specify that all arguments are either groups or remotes. The primary reason for adding this option is to allow us to re-implement 'git remote update' using fetch. It would have been nice if this option was not needed, but since the colon in a refspec is optional, it is in general not possible to know whether a single, colon-less argument is a remote or a refspec. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 93d73c3..8b0cf58 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -24,6 +24,12 @@ --keep:: Keep downloaded pack. +ifndef::git-pull[] +--multiple:: + Allow several and arguments to be + specified. No s may be specified. +endif::git-pull[] + ifdef::git-pull[] --no-tags:: endif::git-pull[] diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index 334744c..edb77dc 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -12,6 +12,8 @@ SYNOPSIS 'git fetch' +'git fetch' --multiple [ | ]... + 'git fetch' --all diff --git a/builtin-fetch.c b/builtin-fetch.c index e6bbdc7..c903c66 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -15,6 +15,7 @@ static const char * const builtin_fetch_usage[] = { "git fetch [options] [ ...]", "git fetch [options] ", + "git fetch --multiple [options] [ | ]...", "git fetch --all [options]", NULL }; @@ -25,7 +26,7 @@ enum { TAGS_SET = 2 }; -static int all, append, force, keep, update_head_ok, verbosity; +static int all, append, force, keep, multiple, update_head_ok, verbosity; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -42,6 +43,8 @@ static struct option builtin_fetch_options[] = { "path to upload pack on remote end"), OPT_BOOLEAN('f', "force", &force, "force overwrite of local branch"), + OPT_BOOLEAN('m', "multiple", &multiple, + "fetch from multiple remotes"), OPT_SET_INT('t', "tags", &tags, "fetch all tags and associated objects", TAGS_SET), OPT_SET_INT('n', NULL, &tags, @@ -798,6 +801,12 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) /* No arguments -- use default remote */ remote = remote_get(NULL); result = fetch_one(remote, argc, argv); + } else if (multiple) { + /* All arguments are assumed to be remotes or groups */ + for (i = 0; i < argc; i++) + if (!add_remote_or_group(argv[i], &list)) + die("No such remote or remote group: %s", argv[i]); + result = fetch_multiple(&list); } else { /* Single remote or group */ (void) add_remote_or_group(argv[0], &list); diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index 25244bf..69c64ab 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -73,4 +73,48 @@ test_expect_success 'git fetch --all does not allow non-option arguments' ' test_must_fail git fetch --all origin master) ' +cat > expect << EOF + origin/HEAD -> origin/master + origin/master + origin/side + three/another + three/master + three/side +EOF + +test_expect_success 'git fetch --multiple (but only one remote)' ' + (git clone one test3 && + cd test3 && + git remote add three ../three && + git fetch --multiple three && + git branch -r > output && + test_cmp ../expect output) +' + +cat > expect << EOF + one/master + one/side + origin/HEAD -> origin/master + origin/master + origin/side + two/another + two/master + two/side +EOF + +test_expect_success 'git fetch --multiple (two remotes)' ' + (git clone one test4 && + cd test4 && + git remote add one ../one && + git remote add two ../two && + git fetch --multiple one two && + git branch -r > output && + test_cmp ../expect output) +' + +test_expect_success 'git fetch --multiple (bad remote names)' ' + (cd test4 && + test_must_fail git fetch --multiple four) +' + test_done -- cgit v0.10.2-6-g49f6 From 7cc91a2f71c95b4e695549b91c4b629f56887a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Mon, 9 Nov 2009 21:11:06 +0100 Subject: Add the configuration option skipFetchAll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the configuration skipFetchAll option to allow certain remotes to be skipped when doing 'git fetch --all' and 'git remote update'. The existing skipDefaultUpdate variable is still honored (by 'git fetch --all' and 'git remote update'). (If both are set in the configuration file with different values, the value of the last occurrence will be used.) Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index cd17814..50a65ab 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1381,7 +1381,13 @@ remote..mirror:: remote..skipDefaultUpdate:: If true, this remote will be skipped by default when updating - using the update subcommand of linkgit:git-remote[1]. + using linkgit:git-fetch[1] or the `update` subcommand of + linkgit:git-remote[1]. + +remote..skipFetchAll:: + If true, this remote will be skipped by default when updating + using linkgit:git-fetch[1] or the `update` subcommand of + linkgit:git-remote[1]. remote..receivepack:: The default program to execute on the remote side when pushing. See diff --git a/builtin-fetch.c b/builtin-fetch.c index c903c66..4cbd690 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -649,7 +649,8 @@ static void set_option(const char *name, const char *value) static int get_one_remote_for_fetch(struct remote *remote, void *priv) { struct string_list *list = priv; - string_list_append(remote->name, list); + if (!remote->skip_default_update) + string_list_append(remote->name, list); return 0; } diff --git a/remote.c b/remote.c index 73d33f2..beaf9fb 100644 --- a/remote.c +++ b/remote.c @@ -396,7 +396,8 @@ static int handle_config(const char *key, const char *value, void *cb) remote->mirror = git_config_bool(key, value); else if (!strcmp(subkey, ".skipdefaultupdate")) remote->skip_default_update = git_config_bool(key, value); - + else if (!strcmp(subkey, ".skipfetchall")) + remote->skip_default_update = git_config_bool(key, value); else if (!strcmp(subkey, ".url")) { const char *v; if (git_config_string(&v, key, value)) diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index 69c64ab..b737332 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -94,9 +94,6 @@ test_expect_success 'git fetch --multiple (but only one remote)' ' cat > expect << EOF one/master one/side - origin/HEAD -> origin/master - origin/master - origin/side two/another two/master two/side @@ -105,6 +102,7 @@ EOF test_expect_success 'git fetch --multiple (two remotes)' ' (git clone one test4 && cd test4 && + git remote rm origin && git remote add one ../one && git remote add two ../two && git fetch --multiple one two && @@ -117,4 +115,40 @@ test_expect_success 'git fetch --multiple (bad remote names)' ' test_must_fail git fetch --multiple four) ' + +test_expect_success 'git fetch --all (skipFetchAll)' ' + (cd test4 && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git remote add three ../three && + git config remote.three.skipFetchAll true && + git fetch --all && + git branch -r > output && + test_cmp ../expect output) +' + +cat > expect << EOF + one/master + one/side + three/another + three/master + three/side + two/another + two/master + two/side +EOF + +test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' ' + (cd test4 && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git fetch --multiple one two three && + git branch -r > output && + test_cmp ../expect output) +' + test_done -- cgit v0.10.2-6-g49f6 From e2d41c64bfbea592daeb3a065f1aa3900a03e07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Mon, 9 Nov 2009 21:11:59 +0100 Subject: Add missing test for 'git remote update --prune' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 852ccb5..e931ce6 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -365,6 +365,17 @@ test_expect_success 'update with arguments' ' ' +test_expect_success 'update --prune' ' + + (cd one && + git branch -m side2 side3) && + (cd test && + git remote update --prune && + (cd ../one && git branch -m side3 side2) + git rev-parse refs/remotes/origin/side3 && + test_must_fail git rev-parse refs/remotes/origin/side2) +' + cat > one/expect << EOF apis/master apis/side -- cgit v0.10.2-6-g49f6 From f2ef6075c9d248523bf658d82065b46d892b5601 Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Tue, 10 Nov 2009 00:03:31 -0500 Subject: remote: refactor some logic into get_stale_heads() Move the logic in builtin-remote.c which determines which local heads are stale to remote.c so it can be used by other builtins. Signed-off-by: Jay Soffian Signed-off-by: Junio C Hamano diff --git a/builtin-remote.c b/builtin-remote.c index 0777dd7..b48267b 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -227,32 +227,10 @@ struct ref_states { int queried; }; -static int handle_one_branch(const char *refname, - const unsigned char *sha1, int flags, void *cb_data) -{ - struct ref_states *states = cb_data; - struct refspec refspec; - - memset(&refspec, 0, sizeof(refspec)); - refspec.dst = (char *)refname; - if (!remote_find_tracking(states->remote, &refspec)) { - struct string_list_item *item; - const char *name = abbrev_branch(refspec.src); - /* symbolic refs pointing nowhere were handled already */ - if ((flags & REF_ISSYMREF) || - string_list_has_string(&states->tracked, name) || - string_list_has_string(&states->new, name)) - return 0; - item = string_list_append(name, &states->stale); - item->util = xstrdup(refname); - } - return 0; -} - static int get_ref_states(const struct ref *remote_refs, struct ref_states *states) { struct ref *fetch_map = NULL, **tail = &fetch_map; - struct ref *ref; + struct ref *ref, *stale_refs; int i; for (i = 0; i < states->remote->fetch_refspec_nr; i++) @@ -268,11 +246,17 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat else string_list_append(abbrev_branch(ref->name), &states->tracked); } + stale_refs = get_stale_heads(states->remote, fetch_map); + for (ref = stale_refs; ref; ref = ref->next) { + struct string_list_item *item = + string_list_append(abbrev_branch(ref->name), &states->stale); + item->util = xstrdup(ref->name); + } + free_refs(stale_refs); free_refs(fetch_map); sort_string_list(&states->new); sort_string_list(&states->tracked); - for_each_ref(handle_one_branch, states); sort_string_list(&states->stale); return 0; diff --git a/remote.c b/remote.c index beaf9fb..eae5866 100644 --- a/remote.c +++ b/remote.c @@ -6,6 +6,7 @@ #include "revision.h" #include "dir.h" #include "tag.h" +#include "string-list.h" static struct refspec s_tag_refspec = { 0, @@ -1587,3 +1588,42 @@ struct ref *guess_remote_head(const struct ref *head, return list; } + +struct stale_heads_info { + struct remote *remote; + struct string_list *ref_names; + struct ref **stale_refs_tail; +}; + +static int get_stale_heads_cb(const char *refname, + const unsigned char *sha1, int flags, void *cb_data) +{ + struct stale_heads_info *info = cb_data; + struct refspec refspec; + memset(&refspec, 0, sizeof(refspec)); + refspec.dst = (char *)refname; + if (!remote_find_tracking(info->remote, &refspec)) { + if (!((flags & REF_ISSYMREF) || + string_list_has_string(info->ref_names, refspec.src))) { + struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail); + hashcpy(ref->new_sha1, sha1); + } + } + return 0; +} + +struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map) +{ + struct ref *ref, *stale_refs = NULL; + struct string_list ref_names = { NULL, 0, 0, 0 }; + struct stale_heads_info info; + info.remote = remote; + info.ref_names = &ref_names; + info.stale_refs_tail = &stale_refs; + for (ref = fetch_map; ref; ref = ref->next) + string_list_append(ref->name, &ref_names); + sort_string_list(&ref_names); + for_each_ref(get_stale_heads_cb, &info); + string_list_clear(&ref_names, 0); + return stale_refs; +} diff --git a/remote.h b/remote.h index 5db8420..d0aba81 100644 --- a/remote.h +++ b/remote.h @@ -154,4 +154,7 @@ struct ref *guess_remote_head(const struct ref *head, const struct ref *refs, int all); +/* Return refs which no longer exist on remote */ +struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map); + #endif -- cgit v0.10.2-6-g49f6 From 3cf6134ad016712ecb78186d9079b9cff7b25416 Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Tue, 10 Nov 2009 00:03:32 -0500 Subject: teach warn_dangling_symref to take a FILE argument Different callers of warn_dangling_symref() may want to control whether its output goes to stdout or stderr so let it take a FILE argument. Signed-off-by: Jay Soffian Signed-off-by: Junio C Hamano diff --git a/builtin-remote.c b/builtin-remote.c index b48267b..56cd5af 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -1166,7 +1166,7 @@ static int prune_remote(const char *remote, int dry_run) printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", abbrev_ref(refname, "refs/remotes/")); - warn_dangling_symref(dangling_msg, refname); + warn_dangling_symref(stdout, dangling_msg, refname); } free_remote_ref_states(&states); diff --git a/refs.c b/refs.c index 808f56b..3e73a0a 100644 --- a/refs.c +++ b/refs.c @@ -286,6 +286,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) } struct warn_if_dangling_data { + FILE *fp; const char *refname; const char *msg_fmt; }; @@ -304,13 +305,13 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha if (!resolves_to || strcmp(resolves_to, d->refname)) return 0; - printf(d->msg_fmt, refname); + fprintf(d->fp, d->msg_fmt, refname); return 0; } -void warn_dangling_symref(const char *msg_fmt, const char *refname) +void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname) { - struct warn_if_dangling_data data = { refname, msg_fmt }; + struct warn_if_dangling_data data = { fp, refname, msg_fmt }; for_each_rawref(warn_if_dangling_symref, &data); } diff --git a/refs.h b/refs.h index 777b5b7..e141991 100644 --- a/refs.h +++ b/refs.h @@ -29,7 +29,7 @@ extern int for_each_replace_ref(each_ref_fn, void *); /* can be used to learn about broken ref and symref */ extern int for_each_rawref(each_ref_fn, void *); -extern void warn_dangling_symref(const char *msg_fmt, const char *refname); +extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname); /* * Extra refs will be listed by for_each_ref() before any actual refs -- cgit v0.10.2-6-g49f6 From f360d844de4b752b4cba2343b95592ae4d55d09a Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Tue, 10 Nov 2009 09:15:47 +0100 Subject: builtin-fetch: add --prune option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Teach fetch to cull stale remote tracking branches after fetching via --prune. Signed-off-by: Jay Soffian Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 8b0cf58..500637a 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -28,6 +28,10 @@ ifndef::git-pull[] --multiple:: Allow several and arguments to be specified. No s may be specified. + +--prune:: + After fetching, remove any remote tracking branches which + no longer exist on the remote. endif::git-pull[] ifdef::git-pull[] diff --git a/builtin-fetch.c b/builtin-fetch.c index 4cbd690..81974ea 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -26,7 +26,7 @@ enum { TAGS_SET = 2 }; -static int all, append, force, keep, multiple, update_head_ok, verbosity; +static int all, append, force, keep, multiple, prune, update_head_ok, verbosity; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -49,6 +49,8 @@ static struct option builtin_fetch_options[] = { "fetch all tags and associated objects", TAGS_SET), OPT_SET_INT('n', NULL, &tags, "do not fetch all tags (--no-tags)", TAGS_UNSET), + OPT_BOOLEAN('p', "prune", &prune, + "prune tracking branches no longer on remote"), OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, "allow updating of HEAD ref"), @@ -492,6 +494,28 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map) return ret; } +static int prune_refs(struct transport *transport, struct ref *ref_map) +{ + int result = 0; + struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map); + const char *dangling_msg = dry_run + ? " (%s will become dangling)\n" + : " (%s has become dangling)\n"; + + for (ref = stale_refs; ref; ref = ref->next) { + if (!dry_run) + result |= delete_ref(ref->name, NULL, 0); + if (verbosity >= 0) { + fprintf(stderr, " x %-*s %-*s -> %s\n", + SUMMARY_WIDTH, "[deleted]", + REFCOL_WIDTH, "(none)", prettify_refname(ref->name)); + warn_dangling_symref(stderr, dangling_msg, ref->name); + } + } + free_refs(stale_refs); + return result; +} + static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { @@ -616,6 +640,8 @@ static int do_fetch(struct transport *transport, free_refs(ref_map); return 1; } + if (prune) + prune_refs(transport, ref_map); free_refs(ref_map); /* if neither --no-tags nor --tags was specified, do automated tag @@ -699,9 +725,11 @@ static int add_remote_or_group(const char *name, struct string_list *list) static int fetch_multiple(struct string_list *list) { int i, result = 0; - const char *argv[] = { "fetch", NULL, NULL, NULL, NULL }; + const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL }; int argc = 1; + if (prune) + argv[argc++] = "--prune"; if (verbosity >= 2) argv[argc++] = "-v"; if (verbosity >= 1) -- cgit v0.10.2-6-g49f6 From 28a1540132ab98a82cb6ed2cb98458e4e7926acd Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Tue, 10 Nov 2009 09:19:43 +0100 Subject: builtin-fetch: add --dry-run option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Teach fetch --dry-run as users of "git remote prune" switching to "git fetch --prune" may expect it. Unfortunately OPT__DRY_RUN() cannot be used as fetch already uses "-n" for something else. Signed-off-by: Jay Soffian Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 500637a..ab6419f 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -12,6 +12,11 @@ `git clone` with `--depth=` option (see linkgit:git-clone[1]) by the specified number of commits. +ifndef::git-pull[] +--dry-run:: + Show what would be done, without making any changes. +endif::git-pull[] + -f:: --force:: When 'git-fetch' is used with `:` diff --git a/builtin-fetch.c b/builtin-fetch.c index 81974ea..76ec5ea 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -26,7 +26,7 @@ enum { TAGS_SET = 2 }; -static int all, append, force, keep, multiple, prune, update_head_ok, verbosity; +static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -51,6 +51,8 @@ static struct option builtin_fetch_options[] = { "do not fetch all tags (--no-tags)", TAGS_UNSET), OPT_BOOLEAN('p', "prune", &prune, "prune tracking branches no longer on remote"), + OPT_BOOLEAN(0, "dry-run", &dry_run, + "dry run"), OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, "allow updating of HEAD ref"), @@ -187,6 +189,8 @@ static int s_update_ref(const char *action, char *rla = getenv("GIT_REFLOG_ACTION"); static struct ref_lock *lock; + if (dry_run) + return 0; if (!rla) rla = default_rla.buf; snprintf(msg, sizeof(msg), "%s: %s", rla, action); @@ -312,7 +316,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, char note[1024]; const char *what, *kind; struct ref *rm; - char *url, *filename = git_path("FETCH_HEAD"); + char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); fp = fopen(filename, "a"); if (!fp) @@ -617,7 +621,7 @@ static int do_fetch(struct transport *transport, die("Don't know how to fetch from %s", transport->url); /* if not appending, truncate FETCH_HEAD */ - if (!append) { + if (!append && !dry_run) { char *filename = git_path("FETCH_HEAD"); FILE *fp = fopen(filename, "w"); if (!fp) @@ -725,9 +729,11 @@ static int add_remote_or_group(const char *name, struct string_list *list) static int fetch_multiple(struct string_list *list) { int i, result = 0; - const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL }; + const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL }; int argc = 1; + if (dry_run) + argv[argc++] = "--dry-run"; if (prune) argv[argc++] = "--prune"; if (verbosity >= 2) -- cgit v0.10.2-6-g49f6 From 8db355964d89c19eb262ffe38e57e5a610e1cc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Tue, 10 Nov 2009 09:21:32 +0100 Subject: Re-implement 'git remote update' using 'git fetch' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order not to duplicate functionality, re-implement 'git remote update' in terms of 'git fetch'. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/builtin-remote.c b/builtin-remote.c index 56cd5af..fb0d66d 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -1173,88 +1173,56 @@ static int prune_remote(const char *remote, int dry_run) return result; } -static int get_one_remote_for_update(struct remote *remote, void *priv) +static int get_remote_default(const char *key, const char *value, void *priv) { - struct string_list *list = priv; - if (!remote->skip_default_update) - string_list_append(remote->name, list); - return 0; -} - -static struct remote_group { - const char *name; - struct string_list *list; -} remote_group; - -static int get_remote_group(const char *key, const char *value, void *num_hits) -{ - if (!prefixcmp(key, "remotes.") && - !strcmp(key + 8, remote_group.name)) { - /* split list by white space */ - int space = strcspn(value, " \t\n"); - while (*value) { - if (space > 1) { - string_list_append(xstrndup(value, space), - remote_group.list); - ++*((int *)num_hits); - } - value += space + (value[space] != '\0'); - space = strcspn(value, " \t\n"); - } + if (strcmp(key, "remotes.default") == 0) { + int *found = priv; + *found = 1; } - return 0; } static int update(int argc, const char **argv) { - int i, result = 0, prune = 0; - struct string_list list = { NULL, 0, 0, 0 }; - static const char *default_argv[] = { NULL, "default", NULL }; + int i, prune = 0; struct option options[] = { OPT_GROUP("update specific options"), OPT_BOOLEAN('p', "prune", &prune, "prune remotes after fetching"), OPT_END() }; + const char **fetch_argv; + int fetch_argc = 0; + int default_defined = 0; + + fetch_argv = xmalloc(sizeof(char *) * (argc+5)); argc = parse_options(argc, argv, NULL, options, builtin_remote_usage, PARSE_OPT_KEEP_ARGV0); - if (argc < 2) { - argc = 2; - argv = default_argv; - } - remote_group.list = &list; - for (i = 1; i < argc; i++) { - int groups_found = 0; - remote_group.name = argv[i]; - result = git_config(get_remote_group, &groups_found); - if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) { - struct remote *remote; - if (!remote_is_configured(argv[i])) - die("No such remote or remote group: %s", - argv[i]); - remote = remote_get(argv[i]); - string_list_append(remote->name, remote_group.list); - } - } + fetch_argv[fetch_argc++] = "fetch"; - if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default")) - result = for_each_remote(get_one_remote_for_update, &list); + if (prune) + fetch_argv[fetch_argc++] = "--prune"; + if (verbose) + fetch_argv[fetch_argc++] = "-v"; + if (argc < 2) { + fetch_argv[fetch_argc++] = "default"; + } else { + fetch_argv[fetch_argc++] = "--multiple"; + for (i = 1; i < argc; i++) + fetch_argv[fetch_argc++] = argv[i]; + } - for (i = 0; i < list.nr; i++) { - int err = fetch_remote(list.items[i].string); - result |= err; - if (!err && prune) - result |= prune_remote(list.items[i].string, 0); + if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) { + git_config(get_remote_default, &default_defined); + if (!default_defined) + fetch_argv[fetch_argc-1] = "--all"; } - /* all names were strdup()ed or strndup()ed */ - list.strdup_strings = 1; - string_list_clear(&list, 0); + fetch_argv[fetch_argc] = NULL; - return result; + return run_command_v_opt(fetch_argv, RUN_GIT_CMD); } static int get_one_entry(struct remote *remote, void *priv) -- cgit v0.10.2-6-g49f6 From 466d1f151a8a0bfde63f0fa5aba91bb8e0efe946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20N=C4=9Bmec?= Date: Tue, 10 Nov 2009 19:11:51 +0100 Subject: git-update-index.txt: Document the --really-refresh option. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the description next to --assume-unchanged because this option is only useful in a special case of using that option. Signed-off-by: Štěpán Němec Signed-off-by: Junio C Hamano diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 25e0bbe..6052484 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -99,6 +99,10 @@ in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, you will need to handle the situation manually. +--really-refresh:: + Like '--refresh', but checks stat information unconditionally, + without regard to the "assume unchanged" setting. + -g:: --again:: Runs 'git-update-index' itself on the paths whose index @@ -308,7 +312,7 @@ Configuration ------------- The command honors `core.filemode` configuration variable. If -your repository is on an filesystem whose executable bits are +your repository is on a filesystem whose executable bits are unreliable, this should be set to 'false' (see linkgit:git-config[1]). This causes the command to ignore differences in file modes recorded in the index and the file mode on the filesystem if they differ only on -- cgit v0.10.2-6-g49f6 From 6586b1f3465a93e908664ea24fa2f5ff47a587e3 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:47 -0600 Subject: check-ref-format: update usage string 'git check-ref-format' has learned --branch and --print options since the usage string was last updated. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index f9381e0..96382e3 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -7,6 +7,10 @@ #include "builtin.h" #include "strbuf.h" +static const char builtin_check_ref_format_usage[] = +"git check-ref-format [--print] \n" +" or: git check-ref-format --branch "; + int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { if (argc == 3 && !strcmp(argv[1], "--branch")) { @@ -18,6 +22,6 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) exit(0); } if (argc != 2) - usage("git check-ref-format refname"); + usage(builtin_check_ref_format_usage); return !!check_ref_format(argv[1]); } -- cgit v0.10.2-6-g49f6 From d629c40b0b8b63b0c8b33d52dcba0ed202d898d7 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:58 -0600 Subject: merge: do not setup worktree twice Builtins do not need to run setup_worktree() for themselves, since the builtin machinery runs it for them. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-merge.c b/builtin-merge.c index b6b8428..e95c5dc 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -840,7 +840,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; - setup_work_tree(); if (file_exists(git_path("MERGE_HEAD"))) die("You have not concluded your merge. (MERGE_HEAD exists)"); if (read_cache_unmerged()) -- cgit v0.10.2-6-g49f6 From f01d74960363736caaf4d98d39555e99bdc72c25 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:05:00 -0600 Subject: http-fetch: add missing initialization of argv0_path According to c6dfb39 (remote-curl: add missing initialization of argv0_path, 2009-10-13), programs with "main" must call this to work correctly on MinGW. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/http-fetch.c b/http-fetch.c index e8f44ba..88f7dc8 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "exec_cmd.h" #include "walker.h" int main(int argc, const char **argv) @@ -19,8 +20,8 @@ int main(int argc, const char **argv) int get_verbosely = 0; int get_recover = 0; + git_extract_argv0_path(argv[0]); prefix = setup_git_directory(); - git_config(git_default_config, NULL); while (arg < argc && argv[arg][0] == '-') { -- cgit v0.10.2-6-g49f6 From aeda85a81547b05c36939defb420e5f02bf05e51 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:48 -0600 Subject: Show usage string for 'git check-ref-format -h' This only changes the behavior of "git check-ref-format -h" without any other options and arguments. This change cannot be breaking backward compatibility, since any valid refname must contain a /. Most existing scripts use arguments such as "heads/$foo". If some script checks the refname "-h" alone, git check-ref-format will still exit with nonzero status, and the only detrimental side-effect will be a usage string sent to stderr. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index e3e7bdf..0a576af 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -31,6 +31,9 @@ static void collapse_slashes(char *dst, const char *src) int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(builtin_check_ref_format_usage); + if (argc == 3 && !strcmp(argv[1], "--branch")) { struct strbuf sb = STRBUF_INIT; -- cgit v0.10.2-6-g49f6 From 71a04a8b52b8a89addd31da5b2272d572fdc7e40 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:49 -0600 Subject: Show usage string for 'git fast-import -h' Let "git fast-import -h" (with no other arguments) print usage before exiting, even when run outside any repository. Cc: Shawn O. Pearce Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/fast-import.c b/fast-import.c index 6faaaac..f4f1de6 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2405,6 +2405,9 @@ int main(int argc, const char **argv) git_extract_argv0_path(argv[0]); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(fast_import_usage); + setup_git_directory(); git_config(git_pack_config, NULL); if (!pack_compression_seen && core_compression_seen) -- cgit v0.10.2-6-g49f6 From e9dd085d93fb6695f1d28572b48374ef818cf9c3 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:50 -0600 Subject: Show usage string for 'git get-tar-commit-id -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 8b3a35e..3f1e701 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -11,6 +11,9 @@ static const char tar_tree_usage[] = "git tar-tree [--remote=] [basedir]\n" "*** Note that this command is now deprecated; use \"git archive\" instead."; +static const char builtin_get_tar_commit_id_usage[] = +"git get-tar-commit-id < "; + int cmd_tar_tree(int argc, const char **argv, const char *prefix) { /* @@ -81,6 +84,9 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) char *content = buffer + RECORDSIZE; ssize_t n; + if (argc != 1) + usage(builtin_get_tar_commit_id_usage); + n = read_in_full(0, buffer, HEADERSIZE); if (n < HEADERSIZE) die("git get-tar-commit-id: read error"); -- cgit v0.10.2-6-g49f6 From 9a2861e32a147d89f22961bd7d3257b8776de4d2 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:51 -0600 Subject: Show usage string for 'git imap-send -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/imap-send.c b/imap-send.c index 3847fd1..04e5374 100644 --- a/imap-send.c +++ b/imap-send.c @@ -93,6 +93,8 @@ struct msg_data { unsigned int crlf:1; }; +static const char imap_send_usage[] = "git imap-send < "; + #define DRV_OK 0 #define DRV_MSG_BAD -1 #define DRV_BOX_BAD -2 @@ -1491,6 +1493,9 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); + if (argc != 1) + usage(imap_send_usage); + /* init the random number generator */ arc4_init(); -- cgit v0.10.2-6-g49f6 From aa481d38b059a093829bbe0745e29244a896c499 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:52 -0600 Subject: Show usage string for 'git mailsplit -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c index dfe5b15..207e358 100644 --- a/builtin-mailsplit.c +++ b/builtin-mailsplit.c @@ -231,6 +231,8 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix) continue; } else if ( arg[1] == 'f' ) { nr = strtol(arg+2, NULL, 10); + } else if ( arg[1] == 'h' ) { + usage(git_mailsplit_usage); } else if ( arg[1] == 'b' && !arg[2] ) { allow_bare = 1; } else if (!strcmp(arg, "--keep-cr")) { -- cgit v0.10.2-6-g49f6 From ae5bdda36cddeb837dc43804966bc99baa385772 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:53 -0600 Subject: Show usage string for 'git merge-one-file -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index 9c2c1b7..d067894 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -16,6 +16,18 @@ # been handled already by git read-tree, but that one doesn't # do any merges that might change the tree layout. +USAGE=' ' +USAGE="$USAGE " +LONG_USAGE="Usage: git merge-one-file $USAGE + +Blob ids and modes should be empty for missing files." + +if ! test "$#" -eq 7 +then + echo "$LONG_USAGE" + exit 1 +fi + case "${1:-.}${2:-.}${3:-.}" in # # Deleted in both or deleted in one and unchanged in the other -- cgit v0.10.2-6-g49f6 From 7006b5beceb296e7e853806622204b2b55649fc5 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:54 -0600 Subject: Show usage string for 'git rev-parse -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 45bead6..24ee8b3 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -426,6 +426,13 @@ static void die_no_single_rev(int quiet) die("Needed a single revision"); } +static const char builtin_rev_parse_usage[] = +"git rev-parse --parseopt [options] -- [...]\n" +" or: git rev-parse --sq-quote [...]\n" +" or: git rev-parse [options] [...]\n" +"\n" +"Run \"git rev-parse --parseopt -h\" for more information on the first usage."; + int cmd_rev_parse(int argc, const char **argv, const char *prefix) { int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0; @@ -438,6 +445,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (argc > 1 && !strcmp("--sq-quote", argv[1])) return cmd_sq_quote(argc - 2, argv + 2); + if (argc > 1 && !strcmp("-h", argv[1])) + usage(builtin_rev_parse_usage); + prefix = setup_git_directory(); git_config(git_default_config, NULL); for (i = 1; i < argc; i++) { -- cgit v0.10.2-6-g49f6 From 03c5c10263fb0d80151d108a4c61253e1d0a8de6 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:55 -0600 Subject: Show usage string for 'git show-index -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/show-index.c b/show-index.c index 45bb535..63f9da5 100644 --- a/show-index.c +++ b/show-index.c @@ -1,6 +1,9 @@ #include "cache.h" #include "pack.h" +static const char show_index_usage[] = +"git show-index < "; + int main(int argc, char **argv) { int i; @@ -8,6 +11,8 @@ int main(int argc, char **argv) unsigned int version; static unsigned int top_index[256]; + if (argc != 1) + usage(show_index_usage); if (fread(top_index, 2 * 4, 1, stdin) != 1) die("unable to read header"); if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) { -- cgit v0.10.2-6-g49f6 From 1507301204180a17f915b929fc8377e69aef979d Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:56 -0600 Subject: Show usage string for 'git unpack-file -h' "unpack-file -h" could be asking to save the contents of a blob named "-h". Strictly speaking, such a pathological ref name is possible, but the user would have to had said something like "tags/-h" to name such a pathological ref already. When used in scripts, unpack-file is typically not passed a user-supplied tag name directly. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/unpack-file.c b/unpack-file.c index ac9cbf7..e9d8934 100644 --- a/unpack-file.c +++ b/unpack-file.c @@ -28,7 +28,7 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); - if (argc != 2) + if (argc != 2 || !strcmp(argv[1], "-h")) usage("git unpack-file "); if (get_sha1(argv[1], sha1)) die("Not a valid object name %s", argv[1]); -- cgit v0.10.2-6-g49f6 From 4751f11224600ab25adb0a200fec55a734bc2936 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:57 -0600 Subject: Show usage string for 'git stripspace -h' Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/builtin-stripspace.c b/builtin-stripspace.c index 1fd2205..4d3b93f 100644 --- a/builtin-stripspace.c +++ b/builtin-stripspace.c @@ -73,9 +73,11 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; int strip_comments = 0; - if (argc > 1 && (!strcmp(argv[1], "-s") || + if (argc == 2 && (!strcmp(argv[1], "-s") || !strcmp(argv[1], "--strip-comments"))) strip_comments = 1; + else if (argc > 1) + usage("git stripspace [-s | --strip-comments] < "); if (strbuf_read(&buf, 0, 1024) < 0) die_errno("could not read the input"); -- cgit v0.10.2-6-g49f6 From 616f86d71325da1ce692f8e878fe066758f88554 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 9 Nov 2009 09:04:59 -0600 Subject: Let 'git http-fetch -h' show usage outside any git repository Delay search for a git directory until option parsing has finished. None of the functions used in option parsing look for or read any files other than stdin, so this is safe. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano diff --git a/http-fetch.c b/http-fetch.c index 88f7dc8..ffd0ad7 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -2,6 +2,9 @@ #include "exec_cmd.h" #include "walker.h" +static const char http_fetch_usage[] = "git http-fetch " +"[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"; + int main(int argc, const char **argv) { const char *prefix; @@ -21,8 +24,6 @@ int main(int argc, const char **argv) int get_recover = 0; git_extract_argv0_path(argv[0]); - prefix = setup_git_directory(); - git_config(git_default_config, NULL); while (arg < argc && argv[arg][0] == '-') { if (argv[arg][1] == 't') { @@ -38,6 +39,8 @@ int main(int argc, const char **argv) } else if (argv[arg][1] == 'w') { write_ref = &argv[arg + 1]; arg++; + } else if (argv[arg][1] == 'h') { + usage(http_fetch_usage); } else if (!strcmp(argv[arg], "--recover")) { get_recover = 1; } else if (!strcmp(argv[arg], "--stdin")) { @@ -45,10 +48,8 @@ int main(int argc, const char **argv) } arg++; } - if (argc < arg + 2 - commits_on_stdin) { - usage("git http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"); - return 1; - } + if (argc != arg + 2 - commits_on_stdin) + usage(http_fetch_usage); if (commits_on_stdin) { commits = walker_targets_stdin(&commit_id, &write_ref); } else { @@ -56,6 +57,11 @@ int main(int argc, const char **argv) commits = 1; } url = argv[arg]; + + prefix = setup_git_directory(); + + git_config(git_default_config, NULL); + if (url && url[strlen(url)-1] != '/') { rewritten_url = xmalloc(strlen(url)+2); strcpy(rewritten_url, url); -- cgit v0.10.2-6-g49f6 From 02bc5b03f54b51f8b45c81cce74284ced602e6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Sat, 7 Nov 2009 10:51:56 +0100 Subject: format-patch: Always generate a patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jeff King recently reinstated -p to suppress the default diffstat (as -p used to work before 68daa64, about 14 months ago). However, -p is also needed in combination with certain options (e.g. --stat or --numstat) in order to produce any patch at all. The documentation does not mention this. Since the purpose of format-patch is to produce a patch that can be emailed, it does not make sense that certain combination of options will suppress the generation of the patch itself. Therefore: * Update 'git format-patch' to always generate a patch. * Since the --name-only, --name-status, and --check suppresses the generation of the patch, disallow those options, and remove the description of them in the documentation. * Remove the reference to -p in the description of -U. * Remove the descriptions of the options that are synonyms for -p plus another option (--patch-with-raw and --patch-with-stat). * While at it, slightly tweak the description of -p itself to say that it generates "plain patches", so that you can think of -p as "plain patch" as an mnemonic aid. Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 9276fae..c58d085 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -14,7 +14,7 @@ endif::git-format-patch[] ifdef::git-format-patch[] -p:: - Generate patches without diffstat. + Generate plain patches without any diffstats. endif::git-format-patch[] ifndef::git-format-patch[] @@ -27,14 +27,19 @@ endif::git-format-patch[] -U:: --unified=:: Generate diffs with lines of context instead of - the usual three. Implies "-p". + the usual three. +ifndef::git-format-patch[] + Implies "-p". +endif::git-format-patch[] --raw:: Generate the raw format. {git-diff-core? This is the default.} +ifndef::git-format-patch[] --patch-with-raw:: Synonym for "-p --raw". +endif::git-format-patch[] --patience:: Generate a diff using the "patience diff" algorithm. @@ -71,21 +76,24 @@ endif::git-format-patch[] Output a condensed summary of extended header information such as creations, renames and mode changes. +ifndef::git-format-patch[] --patch-with-stat:: Synonym for "-p --stat". - {git-format-patch? This is the default.} +endif::git-format-patch[] -z:: NUL-line termination on output. This affects the --raw output field terminator. Also output from commands such as "git-log" will be delimited with NUL between commits. +ifndef::git-format-patch[] --name-only:: Show only names of changed files. --name-status:: Show only names and status of changed files. See the description of the `--diff-filter` option on what the status letters mean. +endif::git-format-patch[] --color:: Show colored diff. @@ -115,11 +123,13 @@ override configuration settings. Turn off rename detection, even when the configuration file gives the default to do so. +ifndef::git-format-patch[] --check:: Warn if changes introduce trailing whitespace or an indent that uses a space before a tab. Exits with non-zero status if problems are found. Not compatible with --exit-code. +endif::git-format-patch[] --full-index:: Instead of the first handful of characters, show the full diff --git a/builtin-log.c b/builtin-log.c index 7b91c91..ce7ab81 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -921,10 +921,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, OPT_BOOLEAN(0, "no-binary", &no_binary_diff, "don't output binary diffs"), - OPT_BOOLEAN('p', NULL, &use_patch_format, - "show patch format instead of default (patch + stat)"), OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream, "don't include a patch matching a commit upstream"), + OPT_BOOLEAN('p', NULL, &use_patch_format, + "show patch format instead of default (patch + stat)"), OPT_GROUP("Messaging"), { OPTION_CALLBACK, 0, "add-header", NULL, "header", "add email header", PARSE_OPT_NONEG, @@ -1030,11 +1030,20 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (argc > 1) die ("unrecognized argument: %s", argv[1]); - if (use_patch_format) - rev.diffopt.output_format |= DIFF_FORMAT_PATCH; - else if (!rev.diffopt.output_format || - rev.diffopt.output_format == DIFF_FORMAT_PATCH) - rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH; + if (rev.diffopt.output_format & DIFF_FORMAT_NAME) + die("--name-only does not make sense"); + if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS) + die("--name-status does not make sense"); + if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF) + die("--check does not make sense"); + + if (!use_patch_format && + (!rev.diffopt.output_format || + rev.diffopt.output_format == DIFF_FORMAT_PATCH)) + rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY; + + /* Always generate a patch */ + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) DIFF_OPT_SET(&rev.diffopt, BINARY); diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index cab6ce2..5689d59 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -536,4 +536,22 @@ test_expect_success 'format-patch --signoff' ' grep "^Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" ' +echo "fatal: --name-only does not make sense" > expect.name-only +echo "fatal: --name-status does not make sense" > expect.name-status +echo "fatal: --check does not make sense" > expect.check + +test_expect_success 'options no longer allowed for format-patch' ' + test_must_fail git format-patch --name-only 2> output && + test_cmp expect.name-only output && + test_must_fail git format-patch --name-status 2> output && + test_cmp expect.name-status output && + test_must_fail git format-patch --check 2> output && + test_cmp expect.check output' + +test_expect_success 'format-patch --numstat should produce a patch' ' + git format-patch --numstat --stdout master..side | + grep "^diff --git a/" | + wc -l | + xargs test 6 = ' + test_done -- cgit v0.10.2-6-g49f6 From d4cb003fffe2af3b868f57d75a352714e7ab03ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Sat, 7 Nov 2009 10:52:29 +0100 Subject: format-patch documentation: Remove diff options that are not useful MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To simplify reading the documentation for format-patch, remove the description of common diff options that are not useful for the purpose of the command (i.e. "Prepare patches for e-mail submission"). Specifically, this removes the description of the following options: --raw -z --color --no-color --color-words --diff-filter -S --pickaxe-all --pickaxe-regex -R --relative --exit-code --quiet Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index c58d085..a03f1a7 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -32,9 +32,11 @@ ifndef::git-format-patch[] Implies "-p". endif::git-format-patch[] +ifndef::git-format-patch[] --raw:: Generate the raw format. {git-diff-core? This is the default.} +endif::git-format-patch[] ifndef::git-format-patch[] --patch-with-raw:: @@ -81,19 +83,18 @@ ifndef::git-format-patch[] Synonym for "-p --stat". endif::git-format-patch[] +ifndef::git-format-patch[] -z:: NUL-line termination on output. This affects the --raw output field terminator. Also output from commands such as "git-log" will be delimited with NUL between commits. -ifndef::git-format-patch[] --name-only:: Show only names of changed files. --name-status:: Show only names and status of changed files. See the description of the `--diff-filter` option on what the status letters mean. -endif::git-format-patch[] --color:: Show colored diff. @@ -118,6 +119,7 @@ The regex can also be set via a diff driver or configuration option, see linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly overrides any diff driver or configuration setting. Diff drivers override configuration settings. +endif::git-format-patch[] --no-renames:: Turn off rename detection, even when the configuration @@ -157,6 +159,7 @@ endif::git-format-patch[] -C:: Detect copies as well as renames. See also `--find-copies-harder`. +ifndef::git-format-patch[] --diff-filter=[ACDMRTUXB*]:: Select only files that are Added (`A`), Copied (`C`), Deleted (`D`), Modified (`M`), Renamed (`R`), have their @@ -168,6 +171,7 @@ endif::git-format-patch[] paths are selected if there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected. +endif::git-format-patch[] --find-copies-harder:: For performance reasons, by default, `-C` option finds copies only @@ -185,6 +189,7 @@ endif::git-format-patch[] the number of rename/copy targets exceeds the specified number. +ifndef::git-format-patch[] -S:: Look for differences that introduce or remove an instance of . Note that this is different than the string simply @@ -199,11 +204,13 @@ endif::git-format-patch[] --pickaxe-regex:: Make the not a plain string but an extended POSIX regex to match. +endif::git-format-patch[] -O:: Output the patch in the order specified in the , which has one shell glob pattern per line. +ifndef::git-format-patch[] -R:: Swap two inputs; that is, show differences from index or on-disk file to tree contents. @@ -215,6 +222,7 @@ endif::git-format-patch[] not in a subdirectory (e.g. in a bare repository), you can name which subdirectory to make the output relative to by giving a as an argument. +endif::git-format-patch[] -a:: --text:: @@ -239,6 +247,7 @@ endif::git-format-patch[] Show the context between diff hunks, up to the specified number of lines, thereby fusing hunks that are close to each other. +ifndef::git-format-patch[] --exit-code:: Make the program exit with codes similar to diff(1). That is, it exits with 1 if there were differences and @@ -246,6 +255,7 @@ endif::git-format-patch[] --quiet:: Disable all output of the program. Implies --exit-code. +endif::git-format-patch[] --ext-diff:: Allow an external diff helper to be executed. If you set an -- cgit v0.10.2-6-g49f6 From dce5ef142046e107e20aa03ca5a353032bbf9653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Sat, 7 Nov 2009 10:53:07 +0100 Subject: format-patch documentation: Fix formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Format git commands and options consistently using back quotes (i.e. a fixed font in the resulting HTML document). Signed-off-by: Björn Gustavsson Signed-off-by: Junio C Hamano diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index a03f1a7..9398329 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -29,7 +29,7 @@ endif::git-format-patch[] Generate diffs with lines of context instead of the usual three. ifndef::git-format-patch[] - Implies "-p". + Implies `-p`. endif::git-format-patch[] ifndef::git-format-patch[] @@ -40,7 +40,7 @@ endif::git-format-patch[] ifndef::git-format-patch[] --patch-with-raw:: - Synonym for "-p --raw". + Synonym for `-p --raw`. endif::git-format-patch[] --patience:: @@ -48,19 +48,19 @@ endif::git-format-patch[] --stat[=width[,name-width]]:: Generate a diffstat. You can override the default - output width for 80-column terminal by "--stat=width". + output width for 80-column terminal by `--stat=width`. The width of the filename part can be controlled by giving another width to it separated by a comma. --numstat:: - Similar to \--stat, but shows number of added and + Similar to `\--stat`, but shows number of added and deleted lines in decimal notation and pathname without abbreviation, to make it more machine friendly. For binary files, outputs two `-` instead of saying `0 0`. --shortstat:: - Output only the last line of the --stat format containing total + Output only the last line of the `--stat` format containing total number of modified files, as well as number of added and deleted lines. @@ -68,11 +68,11 @@ endif::git-format-patch[] Output the distribution of relative amount of changes (number of lines added or removed) for each sub-directory. Directories with changes below a cut-off percent (3% by default) are not shown. The cut-off percent - can be set with "--dirstat=limit". Changes in a child directory is not - counted for the parent directory, unless "--cumulative" is used. + can be set with `--dirstat=limit`. Changes in a child directory is not + counted for the parent directory, unless `--cumulative` is used. --dirstat-by-file[=limit]:: - Same as --dirstat, but counts changed files instead of lines. + Same as `--dirstat`, but counts changed files instead of lines. --summary:: Output a condensed summary of extended header information @@ -80,14 +80,14 @@ endif::git-format-patch[] ifndef::git-format-patch[] --patch-with-stat:: - Synonym for "-p --stat". + Synonym for `-p --stat`. endif::git-format-patch[] ifndef::git-format-patch[] -z:: - NUL-line termination on output. This affects the --raw + NUL-line termination on output. This affects the `--raw` output field terminator. Also output from commands such - as "git-log" will be delimited with NUL between commits. + as `git-log` will be delimited with NUL between commits. --name-only:: Show only names of changed files. @@ -139,16 +139,16 @@ endif::git-format-patch[] line when generating patch format output. --binary:: - In addition to --full-index, output "binary diff" that - can be applied with "git apply". + In addition to `--full-index`, output a binary diff that + can be applied with `git-apply`. --abbrev[=]:: Instead of showing the full 40-byte hexadecimal object name in diff-raw format output and diff-tree header lines, show only a partial prefix. This is - independent of --full-index option above, which controls + independent of the `--full-index` option above, which controls the diff-patch output format. Non default number of - digits can be specified with --abbrev=. + digits can be specified with `--abbrev=`. -B:: Break complete rewrite changes into pairs of delete and create. @@ -183,7 +183,7 @@ endif::git-format-patch[] `-C` option has the same effect. -l:: - -M and -C options require O(n^2) processing time where n + The `-M` and `-C` options require O(n^2) processing time where n is the number of potential rename/copy targets. This option prevents rename/copy detection from running if the number of rename/copy targets exceeds the specified @@ -197,7 +197,7 @@ ifndef::git-format-patch[] linkgit:gitdiffcore[7] for more details. --pickaxe-all:: - When -S finds a change, show all the changes in that + When `-S` finds a change, show all the changes in that changeset, not just the files that contain the change in . @@ -254,7 +254,7 @@ ifndef::git-format-patch[] 0 means no differences. --quiet:: - Disable all output of the program. Implies --exit-code. + Disable all output of the program. Implies `--exit-code`. endif::git-format-patch[] --ext-diff:: diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 687e667..f1fd0df 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -43,28 +43,28 @@ There are two ways to specify which commits to operate on. The first rule takes precedence in the case of a single . To apply the second rule, i.e., format everything since the beginning of -history up until , use the '\--root' option: "git format-patch -\--root ". If you want to format only itself, you -can do this with "git format-patch -1 ". +history up until , use the '\--root' option: `git format-patch +\--root `. If you want to format only itself, you +can do this with `git format-patch -1 `. By default, each output file is numbered sequentially from 1, and uses the first line of the commit message (massaged for pathname safety) as -the filename. With the --numbered-files option, the output file names +the filename. With the `--numbered-files` option, the output file names will only be numbers, without the first line of the commit appended. The names of the output files are printed to standard -output, unless the --stdout option is specified. +output, unless the `--stdout` option is specified. -If -o is specified, output files are created in . Otherwise +If `-o` is specified, output files are created in . Otherwise they are created in the current working directory. By default, the subject of a single patch is "[PATCH] First Line" and the subject when multiple patches are output is "[PATCH n/m] First -Line". To force 1/1 to be added for a single patch, use -n. To omit -patch numbers from the subject, use -N +Line". To force 1/1 to be added for a single patch, use `-n`. To omit +patch numbers from the subject, use `-N`. -If given --thread, 'git-format-patch' will generate In-Reply-To and -References headers to make the second and subsequent patch mails appear -as replies to the first mail; this also generates a Message-Id header to +If given `--thread`, `git-format-patch` will generate `In-Reply-To` and +`References` headers to make the second and subsequent patch mails appear +as replies to the first mail; this also generates a `Message-Id` header to reference. OPTIONS @@ -112,7 +112,7 @@ include::diff-options.txt[] --attach[=]:: Create multipart/mixed attachment, the first part of which is the commit message and the patch itself in the - second part, with "Content-Disposition: attachment". + second part, with `Content-Disposition: attachment`. --no-attach:: Disable the creation of an attachment, overriding the @@ -121,13 +121,13 @@ include::diff-options.txt[] --inline[=]:: Create multipart/mixed attachment, the first part of which is the commit message and the patch itself in the - second part, with "Content-Disposition: inline". + second part, with `Content-Disposition: inline`. --thread[=