diff options
306 files changed, 11773 insertions, 4013 deletions
diff --git a/.gitignore b/.gitignore index 1640c3ad00..6b1fd1bfb0 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ /git-cat-file /git-check-attr /git-check-ignore +/git-check-mailmap /git-check-ref-format /git-checkout /git-checkout-index @@ -191,6 +192,8 @@ /test-mktemp /test-parse-options /test-path-utils +/test-prio-queue +/test-read-cache /test-regex /test-revision-walking /test-run-command @@ -5,98 +5,216 @@ # same person appearing not to be so. # +<nico@fluxnic.net> <nico@cam.org> +Alejandro R. Sedeño <asedeno@MIT.EDU> <asedeno@mit.edu> Alex Bennée <kernel-hacker@bennee.com> +Alex Riesen <raa.lkml@gmail.com> <fork0@t-online.de> +Alex Riesen <raa.lkml@gmail.com> <raa@limbo.localdomain> +Alex Riesen <raa.lkml@gmail.com> <raa@steel.home> +Alex Vandiver <alex@chmrr.net> <alexmv@MIT.EDU> Alexander Gavrilov <angavrilov@gmail.com> +Alexey Shumkin <alex.crezoff@gmail.com> <zapped@mail.ru> +Anders Kaseorg <andersk@MIT.EDU> <andersk@ksplice.com> +Anders Kaseorg <andersk@MIT.EDU> <andersk@mit.edu> Aneesh Kumar K.V <aneesh.kumar@gmail.com> +Ben Walton <bdwalton@gmail.com> <bwalton@artsci.utoronto.ca> +Bernt Hansen <bernt@norang.ca> <bernt@alumni.uwaterloo.ca> +Brandon Casey <drafnel@gmail.com> <casey@nrlssc.navy.mil> Brian M. Carlson <sandals@crustytoothpaste.ath.cx> +Bryan Larsen <bryan@larsen.st> <bryan.larsen@gmail.com> +Bryan Larsen <bryan@larsen.st> <bryanlarsen@yahoo.com> Cheng Renquan <crquan@gmail.com> Chris Shoemaker <c.shoemaker@cox.net> +Chris Wright <chrisw@sous-sol.org> <chrisw@osdl.org> +Csaba Henk <csaba@gluster.com> <csaba@lowlife.hu> Dan Johnson <computerdruid@gmail.com> -Dana L. How <danahow@gmail.com> -Dana L. How <how@deathvalley.cswitch.com> +Dana L. How <danahow@gmail.com> <how@deathvalley.cswitch.com> Daniel Barkalow <barkalow@iabervon.org> +David Brown <git@davidb.org> <davidb@quicinc.com> David D. Kilzer <ddkilzer@kilzer.net> David KÃ¥gedal <davidk@lysator.liu.se> +David Reiss <dreiss@facebook.com> <dreiss@dreiss-vmware.(none)> David S. Miller <davem@davemloft.net> Deskin Miller <deskinm@umich.edu> Dirk Süsserott <newsletter@dirk.my1.cc> +Eric Blake <eblake@redhat.com> <ebb9@byu.net> +Eric Hanchrow <eric.hanchrow@gmail.com> <offby1@blarg.net> Eric S. Raymond <esr@thyrsus.com> Erik Faye-Lund <kusmabite@gmail.com> <kusmabite@googlemail.com> -Fredrik Kuivinen <freku045@student.liu.se> +Eyvind Bernhardsen <eyvind.bernhardsen@gmail.com> <eyvind-git@orakel.ntnu.no> +Florian Achleitner <florian.achleitner.2.6.31@gmail.com> <florian.achleitner2.6.31@gmail.com> +Franck Bui-Huu <vagabon.xyz@gmail.com> <fbuihuu@gmail.com> +Frank Lichtenheld <frank@lichtenheld.de> <djpig@debian.org> +Frank Lichtenheld <frank@lichtenheld.de> <flichtenheld@astaro.com> +Fredrik Kuivinen <frekui@gmail.com> <freku045@student.liu.se> Frédéric Heitzmann <frederic.heitzmann@gmail.com> -H. Peter Anvin <hpa@bonde.sc.orionmulti.com> -H. Peter Anvin <hpa@tazenda.sc.orionmulti.com> -H. Peter Anvin <hpa@trantor.hos.anvin.org> +Garry Dolley <gdolley@ucla.edu> <gdolley@arpnetworks.com> +Greg Price <price@mit.edu> <price@MIT.EDU> +Greg Price <price@mit.edu> <price@ksplice.com> +Heiko Voigt <hvoigt@hvoigt.net> <git-list@hvoigt.net> +H. Merijn Brand <h.m.brand@xs4all.nl> H.Merijn Brand <h.m.brand@xs4all.nl> +H. Peter Anvin <hpa@zytor.com> <hpa@bonde.sc.orionmulti.com> +H. Peter Anvin <hpa@zytor.com> <hpa@smyrno.hos.anvin.org> +H. Peter Anvin <hpa@zytor.com> <hpa@tazenda.sc.orionmulti.com> +H. Peter Anvin <hpa@zytor.com> <hpa@trantor.hos.anvin.org> +Han-Wen Nienhuys <hanwen@google.com> Han-Wen Nienhuys <hanwen@xs4all.nl> Horst H. von Brand <vonbrand@inf.utfsm.cl> -Ä°smail Dönmez <ismail@pardus.org.tr> +J. Bruce Fields <bfields@citi.umich.edu> <bfields@fieldses.org> +J. Bruce Fields <bfields@citi.umich.edu> <bfields@pig.linuxdev.us.dell.com> +J. Bruce Fields <bfields@citi.umich.edu> <bfields@puzzle.fieldses.org> Jakub NarÄ™bski <jnareb@gmail.com> -Jay Soffian <jaysoffian+git@gmail.com> +Jason Riedy <ejr@eecs.berkeley.edu> <ejr@EECS.Berkeley.EDU> +Jason Riedy <ejr@eecs.berkeley.edu> <ejr@cs.berkeley.edu> +Jay Soffian <jaysoffian@gmail.com> <jaysoffian+git@gmail.com> Jeff King <peff@peff.net> <peff@github.com> +Jeff Muizelaar <jmuizelaar@mozilla.com> <jeff@infidigm.net> +Jim Meyering <jim@meyering.net> <meyering@redhat.com> Joachim Berdal Haga <cjhaga@fys.uio.no> -Johannes Sixt <j6t@kdbg.org> <johannes.sixt@telecom.at> -Johannes Sixt <j6t@kdbg.org> <j.sixt@viscovery.net> +Johannes Schindelin <Johannes.Schindelin@gmx.de> <johannes.schindelin@gmx.de> Johannes Sixt <j6t@kdbg.org> <J.Sixt@eudaptics.com> -Jon Loeliger <jdl@freescale.com> -Jon Seymour <jon@blackcubes.dyndns.org> -Jonathan Nieder <jrnieder@uchicago.edu> +Johannes Sixt <j6t@kdbg.org> <j.sixt@viscovery.net> +Johannes Sixt <j6t@kdbg.org> <johannes.sixt@telecom.at> +Jon Loeliger <jdl@jdl.com> <jdl@freescale.com> +Jon Loeliger <jdl@jdl.com> <jdl@freescale.org> +Jon Seymour <jon.seymour@gmail.com> <jon@blackcubes.dyndns.org> +Jonathan Nieder <jrnieder@gmail.com> <jrnieder@uchicago.edu> +Jonathan del Strother <jon.delStrother@bestbefore.tv> <maillist@steelskies.com> +Josh Triplett <josh@joshtriplett.org> <josh@freedesktop.org> +Josh Triplett <josh@joshtriplett.org> <josht@us.ibm.com> +Julian Phillips <julian@quantumfyre.co.uk> <jp3@quantumfyre.co.uk> Junio C Hamano <gitster@pobox.com> <gitster@pobox.com> -Junio C Hamano <gitster@pobox.com> <junio@pobox.com> -Junio C Hamano <gitster@pobox.com> <junio@twinsun.com> -Junio C Hamano <gitster@pobox.com> <junkio@twinsun.com> Junio C Hamano <gitster@pobox.com> <junio@hera.kernel.org> Junio C Hamano <gitster@pobox.com> <junio@kernel.org> +Junio C Hamano <gitster@pobox.com> <junio@pobox.com> +Junio C Hamano <gitster@pobox.com> <junio@twinsun.com> Junio C Hamano <gitster@pobox.com> <junkio@cox.net> -Karl Hasselström <kha@treskal.com> -Kevin Leung <kevinlsk@gmail.com> +Junio C Hamano <gitster@pobox.com> <junkio@twinsun.com> +Karl Wiberg <kha@treskal.com> Karl Hasselström <kha@treskal.com> +Karl Wiberg <kha@treskal.com> Karl Hasselström <kha@yoghurt.hemma.treskal.com> +Karsten Blees <blees@dcon.de> <karsten.blees@dcon.de> +Karsten Blees <blees@dcon.de> <karsten.blees@gmail.com> +Kay Sievers <kay.sievers@vrfy.org> <kay.sievers@suse.de> +Kay Sievers <kay.sievers@vrfy.org> <kay@mam.(none)> +Keith Cascio <keith@CS.UCLA.EDU> <keith@cs.ucla.edu> Kent Engstrom <kent@lysator.liu.se> +Kevin Leung <kevinlsk@gmail.com> +Kirill Smelkov <kirr@navytux.spb.ru> <kirr@landau.phys.spbu.ru> +Kirill Smelkov <kirr@navytux.spb.ru> <kirr@mns.spb.ru> +Knut Franke <Knut.Franke@gmx.de> <k.franke@science-computing.de> Lars Doelle <lars.doelle@on-line ! de> Lars Doelle <lars.doelle@on-line.de> +Lars Noschinski <lars@public.noschinski.de> <lars.noschinski@rwth-aachen.de> Li Hong <leehong@pku.edu.cn> -Linus Torvalds <torvalds@linux-foundation.org> <torvalds@woody.linux-foundation.org> -Linus Torvalds <torvalds@linux-foundation.org> <torvalds@osdl.org> -Linus Torvalds <torvalds@linux-foundation.org> <torvalds@g5.osdl.org> Linus Torvalds <torvalds@linux-foundation.org> <torvalds@evo.osdl.org> -Linus Torvalds <torvalds@linux-foundation.org> <torvalds@ppc970.osdl.org> +Linus Torvalds <torvalds@linux-foundation.org> <torvalds@g5.osdl.org> +Linus Torvalds <torvalds@linux-foundation.org> <torvalds@osdl.org> Linus Torvalds <torvalds@linux-foundation.org> <torvalds@ppc970.osdl.org.(none)> -Lukas Sandström <lukass@etek.chalmers.se> +Linus Torvalds <torvalds@linux-foundation.org> <torvalds@ppc970.osdl.org> +Linus Torvalds <torvalds@linux-foundation.org> <torvalds@woody.linux-foundation.org> +Lukas Sandström <luksan@gmail.com> <lukass@etek.chalmers.se> +Marc Khouzam <marc.khouzam@ericsson.com> <marc.khouzam@gmail.com> Marc-André Lureau <marcandre.lureau@gmail.com> +Marco Costalba <mcostalba@gmail.com> <mcostalba@yahoo.it> +Mark Levedahl <mdl123@verizon.net> <mlevedahl@gmail.com> Mark Rada <marada@uwaterloo.ca> Martin Langhoff <martin@laptop.org> <martin@catalyst.net.nz> Martin von Zweigbergk <martinvonz@gmail.com> <martin.von.zweigbergk@gmail.com> +Matt Draisey <matt@draisey.ca> <mattdraisey@sympatico.ca> +Matt Kraai <kraai@ftbfs.org> <matt.kraai@amo.abbott.com> +Matt McCutchen <matt@mattmccutchen.net> <hashproduct@gmail.com> +Matthias Kestenholz <matthias@spinlock.ch> <mk@spinlock.ch> +Matthias Urlichs <matthias@urlichs.de> <smurf@kiste.(none)> +Matthias Urlichs <matthias@urlichs.de> <smurf@smurf.noris.de> Michael Coleman <tutufan@gmail.com> Michael J Gruber <git@drmicha.warpmail.net> <michaeljgruber+gmane@fastmail.fm> Michael W. Olson <mwolson@gnu.org> +Michael Witten <mfwitten@gmail.com> <mfwitten@MIT.EDU> +Michael Witten <mfwitten@gmail.com> <mfwitten@mit.edu> +Michal Rokos <michal.rokos@nextsoft.cz> <rokos@nextsoft.cz> Michele Ballabio <barra_cuda@katamail.com> +Miklos Vajna <vmiklos@frugalware.org> <vmiklos@suse.cz> +Namhyung Kim <namhyung@gmail.com> <namhyung.kim@lge.com> +Namhyung Kim <namhyung@gmail.com> <namhyung@kernel.org> Nanako Shiraishi <nanako3@bluebottle.com> Nanako Shiraishi <nanako3@lavabit.com> +Nelson Elhage <nelhage@mit.edu> <nelhage@MIT.EDU> +Nelson Elhage <nelhage@mit.edu> <nelhage@ksplice.com> Nguyá»…n Thái Ngá»c Duy <pclouds@gmail.com> -<nico@fluxnic.net> <nico@cam.org> -Peter Krefting <peter@softwolves.pp.se> <peter@svarten.intern.softwolves.pp.se> +Nick Stokoe <nick@noodlefactory.co.uk> Nick Woolley <nick@noodlefactory.co.uk> +Nick Stokoe <nick@noodlefactory.co.uk> Nick Woolley <nickwoolley@yahoo.co.uk> +Nicolas Morey-Chaisemartin <devel-git@morey-chaisemartin.com> <nicolas.morey@free.fr> +Nicolas Morey-Chaisemartin <devel-git@morey-chaisemartin.com> <nmorey@kalray.eu> +Nicolas Sebrecht <nicolas.s.dev@gmx.fr> <ni.s@laposte.net> +Paolo Bonzini <bonzini@gnu.org> <paolo.bonzini@lu.unisi.ch> +Pascal Obry <pascal@obry.net> <pascal.obry@gmail.com> +Pascal Obry <pascal@obry.net> <pascal.obry@wanadoo.fr> +Pat Notz <patnotz@gmail.com> <pknotz@sandia.gov> +Paul Mackerras <paulus@samba.org> <paulus@dorrigo.(none)> +Paul Mackerras <paulus@samba.org> <paulus@pogo.(none)> +Peter Baumann <waste.manager@gmx.de> <Peter.B.Baumann@stud.informatik.uni-erlangen.de> +Peter Baumann <waste.manager@gmx.de> <siprbaum@stud.informatik.uni-erlangen.de> Peter Krefting <peter@softwolves.pp.se> <peter@softwolves.pp.se> +Peter Krefting <peter@softwolves.pp.se> <peter@svarten.intern.softwolves.pp.se> Petr Baudis <pasky@ucw.cz> <pasky@suse.cz> +Petr Baudis <pasky@ucw.cz> <xpasky@machine> +Phil Hord <hordp@cisco.com> <phil.hord@gmail.com> +Philip Jägenstedt <philip@foolip.org> <philip.jagenstedt@gmail.com> +Philipp A. Hartmann <pah@qo.cx> <ph@sorgh.de> Philippe Bruhat <book@cpan.org> Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com> Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk> -René Scharfe <rene.scharfe@lsrfire.ath.cx> +René Scharfe <l.s.r@web.de> <rene.scharfe@lsrfire.ath.cx> Robert Fitzsimons <robfitz@273k.net> +Robert Shearman <robertshearman@gmail.com> <rob@codeweavers.com> Robert Zeh <robert.a.zeh@gmail.com> -Sam Vilain <sam@vilain.net> -Santi Béjar <sbejar@gmail.com> +Robin Rosenberg <robin.rosenberg@dewire.com> <robin.rosenberg.lists@dewire.com> +Ryan Anderson <ryan@michonline.com> <rda@google.com> +Salikh Zakirov <salikh.zakirov@gmail.com> <Salikh.Zakirov@Intel.com> +Sam Vilain <sam@vilain.net> <sam.vilain@catalyst.net.nz> +Santi Béjar <santi@agolina.net> <sbejar@gmail.com> Sean Estabrooks <seanlkml@sympatico.ca> +Sebastian Schuberth <sschuberth@gmail.com> <sschuberth@visageimaging.com> +Seth Falcon <seth@userprimary.net> <sfalcon@fhcrc.org> Shawn O. Pearce <spearce@spearce.org> -Steven Grimm <koreth@midwinter.com> +Simon Hausmann <hausmann@kde.org> <simon@lst.de> +Simon Hausmann <hausmann@kde.org> <shausman@trolltech.com> +Stefan Naewe <stefan.naewe@gmail.com> <stefan.naewe@atlas-elektronik.com> +Stefan Naewe <stefan.naewe@gmail.com> <stefan.naewe@googlemail.com> +Stefan Sperling <stsp@elego.de> <stsp@stsp.name> +Stephen Boyd <bebarino@gmail.com> <sboyd@codeaurora.org> +Steven Drake <sdrake@xnet.co.nz> <sdrake@ihug.co.nz> +Steven Grimm <koreth@midwinter.com> <sgrimm@sgrimm-mbp.local> +Steven Walter <stevenrwalter@gmail.com> <swalter@lexmark.com> +Steven Walter <stevenrwalter@gmail.com> <swalter@lpdev.prtdev.lexmark.com> +Sven Verdoolaege <skimo@kotnet.org> <Sven.Verdoolaege@cs.kuleuven.ac.be> +Sven Verdoolaege <skimo@kotnet.org> <skimo@liacs.nl> Tay Ray Chuan <rctay89@gmail.com> +Ted Percival <ted@midg3t.net> <ted.percival@quest.com> Theodore Ts'o <tytso@mit.edu> +Thomas Ackermann <th.acker@arcor.de> <th.acker66@arcor.de> Thomas Rast <trast@inf.ethz.ch> <trast@student.ethz.ch> +Timo Hirvonen <tihirvon@gmail.com> <tihirvon@ee.oulu.fi> +Toby Allsopp <Toby.Allsopp@navman.co.nz> <toby.allsopp@navman.co.nz> +Tom Grennan <tmgrennan@gmail.com> <tgrennan@redback.com> +Tommi Virtanen <tv@debian.org> <tv@eagain.net> +Tommi Virtanen <tv@debian.org> <tv@inoi.fi> +Tommy Thorn <tommy-git@thorn.ws> <tt1729@yahoo.com> Tony Luck <tony.luck@intel.com> -Uwe Kleine-König <Uwe_Zeisberger@digi.com> -Uwe Kleine-König <Uwe.Kleine-Koenig@digi.com> -Uwe Kleine-König <ukleinek@informatik.uni-freiburg.de> -Uwe Kleine-König <uzeisberger@io.fsforth.de> -Uwe Kleine-König <zeisberg@informatik.uni-freiburg.de> -Ville Skyttä <scop@xemacs.org> +Tor Arne Vestbø <torarnv@gmail.com> <tavestbo@trolltech.com> +Trent Piepho <tpiepho@gmail.com> <tpiepho@freescale.com> +Trent Piepho <tpiepho@gmail.com> <xyzzy@speakeasy.org> +Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <Uwe.Kleine-Koenig@digi.com> +Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <ukleinek@informatik.uni-freiburg.de> +Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <uzeisberger@io.fsforth.de> +Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <zeisberg@informatik.uni-freiburg.de> +Ville Skyttä <ville.skytta@iki.fi> <scop@xemacs.org> Vitaly "_Vi" Shukela <public_vi@tut.by> +W. Trevor King <wking@tremily.us> <wking@drexel.edu> William Pursell <bill.pursell@gmail.com> +YONETANI Tomokazu <y0n3t4n1@gmail.com> <qhwt+git@les.ath.cx> +YONETANI Tomokazu <y0n3t4n1@gmail.com> <y0netan1@dragonflybsd.org> YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> +# the two anonymous contributors are different persons: anonymous <linux@horizon.com> anonymous <linux@horizon.net> +Ä°smail Dönmez <ismail@pardus.org.tr> diff --git a/Documentation/Makefile b/Documentation/Makefile index 62dbd9ac7c..0cfdc36b44 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -31,11 +31,11 @@ MAN7_TXT += gittutorial.txt MAN7_TXT += gitworkflows.txt MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT) -MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT)) -MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT)) +MAN_XML = $(patsubst %.txt,%.xml,$(MAN_TXT)) +MAN_HTML = $(patsubst %.txt,%.html,$(MAN_TXT)) OBSOLETE_HTML = git-remote-helpers.html -DOC_HTML=$(MAN_HTML) $(OBSOLETE_HTML) +DOC_HTML = $(MAN_HTML) $(OBSOLETE_HTML) ARTICLES = howto-index ARTICLES += everyday @@ -74,35 +74,35 @@ SP_ARTICLES += technical/api-index DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES)) -DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT)) -DOC_MAN5=$(patsubst %.txt,%.5,$(MAN5_TXT)) -DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT)) - -prefix?=$(HOME) -bindir?=$(prefix)/bin -htmldir?=$(prefix)/share/doc/git-doc -pdfdir?=$(prefix)/share/doc/git-doc -mandir?=$(prefix)/share/man -man1dir=$(mandir)/man1 -man5dir=$(mandir)/man5 -man7dir=$(mandir)/man7 -# DESTDIR= +DOC_MAN1 = $(patsubst %.txt,%.1,$(MAN1_TXT)) +DOC_MAN5 = $(patsubst %.txt,%.5,$(MAN5_TXT)) +DOC_MAN7 = $(patsubst %.txt,%.7,$(MAN7_TXT)) + +prefix ?= $(HOME) +bindir ?= $(prefix)/bin +htmldir ?= $(prefix)/share/doc/git-doc +infodir ?= $(prefix)/share/info +pdfdir ?= $(prefix)/share/doc/git-doc +mandir ?= $(prefix)/share/man +man1dir = $(mandir)/man1 +man5dir = $(mandir)/man5 +man7dir = $(mandir)/man7 +# DESTDIR = ASCIIDOC = asciidoc ASCIIDOC_EXTRA = MANPAGE_XSL = manpage-normal.xsl XMLTO = xmlto XMLTO_EXTRA = -INSTALL?=install +INSTALL ?= install RM ?= rm -f MAN_REPO = ../../git-manpages HTML_REPO = ../../git-htmldocs -infodir?=$(prefix)/share/info -MAKEINFO=makeinfo -INSTALL_INFO=install-info -DOCBOOK2X_TEXI=docbook2x-texi -DBLATEX=dblatex +MAKEINFO = makeinfo +INSTALL_INFO = install-info +DOCBOOK2X_TEXI = docbook2x-texi +DBLATEX = dblatex ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif diff --git a/Documentation/RelNotes/1.8.3.2.txt b/Documentation/RelNotes/1.8.3.2.txt new file mode 100644 index 0000000000..26ae142c3d --- /dev/null +++ b/Documentation/RelNotes/1.8.3.2.txt @@ -0,0 +1,59 @@ +Git v1.8.3.2 Release Notes +========================== + +Fixes since v1.8.3.1 +-------------------- + + * Cloning with "git clone --depth N" while fetch.fsckobjects (or + transfer.fsckobjects) is set to true did not tell the cut-off + points of the shallow history to the process that validates the + objects and the history received, causing the validation to fail. + + * "git checkout foo" DWIMs the intended "upstream" and turns it into + "git checkout -t -b foo remotes/origin/foo". This codepath has been + updated to correctly take existing remote definitions into account. + + * "git fetch" into a shallow repository from a repository that does + not know about the shallow boundary commits (e.g. a different fork + from the repository the current shallow repository was cloned from) + did not work correctly. + + * "git subtree" (in contrib/) had one codepath with loose error + checks to lose data at the remote side. + + * "git log --ancestry-path A...B" did not work as expected, as it did + not pay attention to the fact that the merge base between A and B + was the bottom of the range being specified. + + * "git diff -c -p" was not showing a deleted line from a hunk when + another hunk immediately begins where the earlier one ends. + + * "git merge @{-1}~22" was rewritten to "git merge frotz@{1}~22" + incorrectly when your previous branch was "frotz" (it should be + rewritten to "git merge frotz~22" instead). + + * "git commit --allow-empty-message -m ''" should not start an + editor. + + * "git push --[no-]verify" was not documented. + + * An entry for "file://" scheme in the enumeration of URL types Git + can take in the HTML documentation was made into a clickable link + by mistake. + + * zsh prompt script that borrowed from bash prompt script did not + work due to slight differences in array variable notation between + these two shells. + + * The bash prompt code (in contrib/) displayed the name of the branch + being rebased when "rebase -i/-m/-p" modes are in use, but not the + plain vanilla "rebase". + + * "git push $there HEAD:branch" did not resolve HEAD early enough, so + it was easy to flip it around while push is still going on and push + out a branch that the user did not originally intended when the + command was started. + + * "difftool --dir-diff" did not copy back changes made by the + end-user in the diff tool backend to the working tree in some + cases. diff --git a/Documentation/RelNotes/1.8.3.3.txt b/Documentation/RelNotes/1.8.3.3.txt new file mode 100644 index 0000000000..9ba4f4da0f --- /dev/null +++ b/Documentation/RelNotes/1.8.3.3.txt @@ -0,0 +1,47 @@ +Git v1.8.3.3 Release Notes +========================== + +Fixes since v1.8.3.2 +-------------------- + + * "git apply" parsed patches that add new files, generated by programs + other than Git, incorrectly. This is an old breakage in v1.7.11. + + * Older cURL wanted piece of memory we call it with to be stable, but + we updated the auth material after handing it to a call. + + * "git pull" into nothing trashed "local changes" that were in the + index. + + * Many "git submodule" operations did not work on a submodule at a + path whose name is not in ASCII. + + * "cherry-pick" had a small leak in its error codepath. + + * Logic used by git-send-email to suppress cc mishandled names like + "A U. Thor" <author@example.xz>, where the human readable part + needs to be quoted (the user input may not have the double quotes + around the name, and comparison was done between quoted and + unquoted strings). It also mishandled names that need RFC2047 + quoting. + + * "gitweb" forgot to clear a global variable $search_regexp upon each + request, mistakenly carrying over the previous search to a new one + when used as a persistent CGI. + + * The wildmatch engine did not honor WM_CASEFOLD option correctly. + + * "git log -c --follow $path" segfaulted upon hitting the commit that + renamed the $path being followed. + + * When a reflog notation is used for implicit "current branch", + e.g. "git log @{u}", we did not say which branch and worse said + "branch ''" in the error messages. + + * Mac OS X does not like to write(2) more than INT_MAX number of + bytes; work it around by chopping write(2) into smaller pieces. + + * Newer MacOS X encourages the programs to compile and link with + their CommonCrypto, not with OpenSSL. + +Also contains various minor documentation updates. diff --git a/Documentation/RelNotes/1.8.3.4.txt b/Documentation/RelNotes/1.8.3.4.txt new file mode 100644 index 0000000000..56f106e262 --- /dev/null +++ b/Documentation/RelNotes/1.8.3.4.txt @@ -0,0 +1,20 @@ +Git v1.8.3.4 Release Notes +========================== + +This update is mostly to propagate documentation fixes and test +updates from the master front back to the maintenance track. + +Fixes since v1.8.3.3 +-------------------- + + * The bisect log listed incorrect commits when bisection ends with + only skipped ones. + + * The test coverage framework was left broken for some time. + + * The test suite for HTTP transport did not run with Apache 2.4. + + * "git diff" used to fail when core.safecrlf is set and the working + tree contents had mixed CRLF/LF line endings. Committing such a + content must be prohibited, but "git diff" should help the user to + locate and fix such problems without failing. diff --git a/Documentation/RelNotes/1.8.4.txt b/Documentation/RelNotes/1.8.4.txt index b974d492dc..ad58ac6dbd 100644 --- a/Documentation/RelNotes/1.8.4.txt +++ b/Documentation/RelNotes/1.8.4.txt @@ -1,10 +1,59 @@ Git v1.8.4 Release Notes ======================== +Backward compatibility notes (for Git 2.0) +------------------------------------------ + +When "git push [$there]" does not say what to push, we have used the +traditional "matching" semantics so far (all your branches were sent +to the remote as long as there already are branches of the same name +over there). In Git 2.0, the default will change to the "simple" +semantics that pushes: + + - only the current branch to the branch with the same name, and only + when the current branch is set to integrate with that remote + branch, if you are pushing to the same remote as you fetch from; or + + - only the current branch to the branch with the same name, if you + are pushing to a remote that is not where you usually fetch from. + +Use the user preference configuration variable "push.default" to +change this. If you are an old-timer who is used to the "matching" +semantics, you can set the variable to "matching" to keep the +traditional behaviour. If you want to live in the future early, you +can set it to "simple" today without waiting for Git 2.0. + +When "git add -u" (and "git add -A") is run inside a subdirectory and +does not specify which paths to add on the command line, it +will operate on the entire tree in Git 2.0 for consistency +with "git commit -a" and other commands. There will be no +mechanism to make plain "git add -u" behave like "git add -u .". +Current users of "git add -u" (without a pathspec) should start +training their fingers to explicitly say "git add -u ." +before Git 2.0 comes. A warning is issued when these commands are +run without a pathspec and when you have local changes outside the +current directory, because the behaviour in Git 2.0 will be different +from today's version in such a situation. + +In Git 2.0, "git add <path>" will behave as "git add -A <path>", so +that "git add dir/" will notice paths you removed from the directory +and record the removal. Versions before Git 2.0, including this +release, will keep ignoring removals, but the users who rely on this +behaviour are encouraged to start using "git add --ignore-removal <path>" +now before 2.0 is released. + + Updates since v1.8.3 -------------------- -Foreign interfaces and ports. +Foreign interfaces, subsystems and ports. + + * "git rebase -i" now honors --strategy and -X options. + + * Git-gui has been updated to its 0.18.0 version. + + * MediaWiki remote helper (in contrib/) has been updated to use the + credential helper interface from Git.pm. * Update build for Cygwin 1.[57]. Torsten Bögershausen reports that this is fine with Cygwin 1.7 ($gmane/225824) so let's try moving it @@ -27,9 +76,108 @@ Foreign interfaces and ports. * git-remote-mw (in contrib/) hints users to check the certificate, when https:// connection failed. + * git-remote-mw (in contrib/) adds a command to allow previewing the + contents locally before pushing it out, when working with a + MediaWiki remote. + UI, Workflows & Features + * "git clean" command learned an interactive mode. + + * The "--head" option to "git show-ref" was only to add "HEAD" to the + list of candidate refs to be filtered by the usual rules + (e.g. "--heads" that only show refs under refs/heads). The meaning + of the option has been changed to always show "HEAD" regardless of + what filtering will be applied to any other ref. + + This is a backward incompatible change and might cause breakages to + people's existing scripts. + + * "git show -s" was less discoverable than it should have been. It + now has a natural synonym "git show --no-patch". + + * "git check-mailmap" is a new command that lets you map usernames + and e-mail addresses through the mailmap mechanism, just like many + built-in commands do. + + * "git name-rev" learned to name an annotated tag object back to its + tagname; "git name-rev $(git rev-parse v1.0.0)" gives "tags/v1.0.0", + for example. + + * "git cat-file --batch-check=<format>" is added, primarily to allow + on-disk footprint of objects in packfiles (often they are a lot + smaller than their true size, when expressed as deltas) to be + reported. + + * "git rebase [-i]" used to leave just "rebase" as its reflog messages + for some operations. They have been reworded to be more informative. + + * In addition to the choice from "rebase, merge, or checkout-detach", + "submodule update" can allow a custom command to be used in to + update the working tree of submodules via the "submodule.*.update" + configuration variable. + + * "git submodule update" can optionally clone the submodule + repositories shallowly. + + * "git format-patch" learned "--from[=whom]" option, which sets the + "From: " header to the specified person (or the person who runs the + command, if "=whom" part is missing) and move the original author + information to an in-body From: header as necessary. + + * The configuration variable "merge.ff" was cleary a tri-state to + choose one from "favor fast-forward when possible", "always create + a merge even when the history could fast-forward" and "do not + create any merge, only update when the history fast-forwards", but + the command line parser did not implement the usual convention of + "last one wins, and command line overrides the configuration" + correctly. + + * "gitweb" learned to optionally place extra links that point at the + levels higher than the Gitweb pages themselves in the breadcrumbs, + so that it can be used as part of a larger installation. + + * "git log --format=" now honors i18n.logoutputencoding configuration + variable. + + * The "push.default=simple" mode of "git push" has been updated to + behave like "current" without requiring a remote tracking + information, when you push to a remote that is different from where + you fetch from (i.e. a triangular workflow). + + * Having multiple "fixup!" on a line in the rebase instruction sheet + did not work very well with "git rebase -i --autosquash". + + * "git log" learned the "--author-date-order" option, with which the + output is topologically sorted and commits in parallel histories + are shown intermixed together based on the author timestamp. + + * Various subcommands of "git submodule" refused to run from anywhere + other than the top of the working tree of the superproject, but + they have been taught to let you run from a subdirectory. + + * "git diff" learned a mode that ignores hunks whose change consists + only of additions and removals of blank lines, which is the same as + "diff -B" (ignore blank lines) of GNU diff. + + * "git rm" gives a single message followed by list of paths to report + multiple paths that cannot be removed. + + * "git rebase" can be told with ":/look for this string" syntax commits + to replay the changes onto and where the work to be replayed begins. + + * Many tutorials teach users to set "color.ui" to "auto" as the first + thing after you set "user.name/email" to introduce yourselves to + Git. Now the variable defaults to "auto". + + * On Cygwin, "cygstart" is now recognised as a possible way to start + a web browser (used in "help -w" and "instaweb" among others). + + * "git status" learned status.branch and status.short configuration + variables to use --branch and --short options by default (override + with --no-branch and --no-short options from the command line). + * "git cmd <name>", when <name> happens to be a 40-hex string, directly uses the 40-hex string as an object name, even if a ref "refs/<some hierarchy>/<name>" exists. This disambiguation order @@ -39,7 +187,8 @@ UI, Workflows & Features * "git rebase" learned "--[no-]autostash" option to save local changes instead of refusing to run (to which people's normal - response was to stash them and re-run). + response was to stash them and re-run). This introduced a corner + case breakage to "git am --abort" but it has been fixed. * Instead of typing four capital letters "HEAD", you can say "@" now, e.g. "git log @". @@ -85,6 +234,45 @@ UI, Workflows & Features Performance, Internal Implementation, etc. + * The function attributes extensions are used to catch mistakes in + use of our own variadic functions that use NULL sentinel at the end + (i.e. like execl(3)) and format strings (i.e. like printf(3)). + + * The code to allow configuration data to be read from in-tree blob + objects is in. This may help working in a bare repository and + submodule updates. + + * Fetching between repositories with many refs employed O(n^2) + algorithm to match up the common objects, which has been corrected. + + * The original way to specify remote repository using .git/branches/ + used to have a nifty feature. The code to support the feature was + still in a function but the caller was changed not to call it 5 + years ago, breaking that feature and leaving the supporting code + unreachable. The dead code has been removed. + + * "git pack-refs" that races with new ref creation or deletion have + been susceptible to lossage of refs under right conditions, which + has been tightened up. + + * We read loose and packed rerferences in two steps, but after + deciding to read a loose ref but before actually opening it to read + it, another process racing with us can unlink it, which would cause + us to barf. The codepath has been updated to retry when such a + race is detected, instead of outright failing. + + * Uses of the platform fnmatch(3) function (many places in the code, + matching pathspec, .gitignore and .gitattributes to name a few) + have been replaced with wildmatch, allowing "foo/**/bar" that would + match foo/bar, foo/a/bar, foo/a/b/bar, etc. + + * Memory ownership and lifetime rules for what for-each-ref feeds to + its callbacks have been clarified (in short, "you do not own it, so + make a copy if you want to keep it"). + + * The revision traversal logic to improve culling of irrelevant + parents while traversing a mergy history has been updated. + * Some leaks in unpack-trees (used in merge, cherry-pick and other codepaths) have been plugged. @@ -122,105 +310,171 @@ Unless otherwise noted, all the fixes since v1.8.3 in the maintenance track are contained in this release (see release notes to them for details). + * Newer Net::SMTP::SSL module does not want the user programs to use + the default behaviour to let server certificate go without + verification, so by default enable the verification with a + mechanism to turn it off if needed. + (merge 35035bb rr/send-email-ssl-verify later to maint). + + * When "git" is spawned in such a way that any of the low 3 file + descriptors is closed, our first open() may yield file descriptor 2, + and writing error message to it would screw things up in a big way. + (merge a11c396 tr/protect-low-3-fds later to maint). + + * The mailmap mechanism unnecessarily downcased the e-mail addresses + in the output, and also ignored the human name when it is a single + character name. + (merge bd23794 jc/mailmap-case-insensitivity later to maint). + + * In two places we did not check return value (expected to be a file + descriptor) correctly. + (merge a77f106 tr/fd-gotcha-fixes later to maint). + + * Logic to auto-detect character encodings in the commit log message + did not reject overlong and invalid UTF-8 characters. + (merge 81050ac bc/commit-invalid-utf8 later to maint). + + * Pass port number as a separate argument when "send-email" initializes + Net::SMTP, instead of as a part of the hostname, i.e. host:port. + This allows GSSAPI codepath to match with the hostname given. + (merge 1a741bf bc/send-email-use-port-as-separate-param later to maint). + + * "git diff" refused to even show difference when core.safecrlf is + set to true (i.e. error out) and there are offending lines in the + working tree files. + (merge 5430bb2 jc/maint-diff-core-safecrlf later to maint). + + * A test that should have failed but didn't revealed a bug that needs + to be corrected. + (merge 94d75d1 jc/t1512-fix later to maint). + + * An overlong path to a .git directory may have overflown the + temporary path buffer used to create a name for lockfiles. + (merge 2fbd4f9 mh/maint-lockfile-overflow later to maint). + + * Invocations of "git checkout" used internally by "git rebase" were + counted as "checkout", and affected later "git checkout -" to the + the user to an unexpected place. + (merge 3bed291 rr/rebase-checkout-reflog later to maint). + + * "git stash save", when your local change turns a tracked file into + a directory, has to remove files in that directory in order to + revert your working tree to a pristine state. This will lose + untracked files in such a directory, and the command now requires + you to "--force" it. + + * The configuration variable column.ui was poorly documented. + (merge 5e62cc1 rr/column-doc later to maint). + + * "git name-rev --refs=tags/v*" were forbidden, which was a bit + inconvenient (you had to give a pattern to match refs fully, like + --refs=refs/tags/v*). + (merge 98c5c4a nk/name-rev-abbreviated-refs later to maint). + + * "git apply" parsed patches that add new files, generated by + programs other than Git, incorrectly. This is an old breakage in + v1.7.11 and will need to be merged down to the maintanance tracks. + + * Older cURL wanted piece of memory we call it with to be stable, but + we updated the auth material after handing it to a call. + + * "git pull" into nothing trashed "local changes" that were in the + index, and this avoids it. + + * Many "git submodule" operations do not work on a submodule at a + path whose name is not in ASCII. + + * "cherry-pick" had a small leak in an error codepath. + + * Logic used by git-send-email to suppress cc mishandled names like + "A U. Thor" <author@example.xz>, where the human readable part + needs to be quoted (the user input may not have the double quotes + around the name, and comparison was done between quoted and + unquoted strings). It also mishandled names that need RFC2047 + quoting. + + * Call to discard_cache/discard_index (used when we use different + contents of the index in-core, in many operations like commit, + apply, and merge) used to leak memory that held the array of index + entries, which has been plugged. + (merge a0fc4db rs/discard-index-discard-array later to maint). + * "gitweb" forgot to clear a global variable $search_regexp upon each request, mistakenly carrying over the previous search to a new one when used as a persistent CGI. - (merge ca7a5dc cm/gitweb-project-list-persistent-cgi-fix later to maint). * The wildmatch engine did not honor WM_CASEFOLD option correctly. - (merge b79c0c3 ar/wildmatch-foldcase later to maint). * "git log -c --follow $path" segfaulted upon hitting the commit that renamed the $path being followed. - (merge 46ec510 cb/log-follow-with-combined later to maint). * When a reflog notation is used for implicit "current branch", we did not say which branch and worse said "branch ''". - (merge 305ebea rr/die-on-missing-upstream later to maint). * "difftool --dir-diff" did not copy back changes made by the end-user in the diff tool backend to the working tree in some cases. - (merge 32eaf1d ks/difftool-dir-diff-copy-fix later to maint). * "git push $there HEAD:branch" did not resolve HEAD early enough, so it was easy to flip it around while push is still going on and push out a branch that the user did not originally intended when the command was started. - (merge 0f075b2 rr/push-head later to maint). * The bash prompt code (in contrib/) displayed the name of the branch being rebased when "rebase -i/-m/-p" modes are in use, but not the plain vanilla "rebase". - (merge 1306321 fc/show-branch-in-rebase-am later to maint). * Handling of negative exclude pattern for directories "!dir" was broken in the update to v1.8.3. - (merge c3c327d kb/status-ignored-optim-2 later to maint). * zsh prompt script that borrowed from bash prompt script did not work due to slight differences in array variable notation between these two shells. - (merge d0583da tg/maint-zsh-svn-remote-prompt later to maint). * An entry for "file://" scheme in the enumeration of URL types Git can take in the HTML documentation was made into a clickable link by mistake. - (merge 4c32e36 nd/urls-doc-no-file-hyperlink-fix later to maint). * "git push --[no-]verify" was not documented. - (merge 90d32d1 tr/push-no-verify-doc later to maint). * Stop installing the git-remote-testpy script that is only used for testing. - (merge 416fda6 fc/makefile later to maint). * "git commit --allow-empty-message -m ''" should not start an editor. - (merge 2520677 rs/commit-m-no-edit later to maint). * "git merge @{-1}~22" was rewritten to "git merge frotz@{1}~22" incorrectly when your previous branch was "frotz" (it should be rewritten to "git merge frotz~22" instead). - (merge 84cf246 jc/strbuf-branchname-fix later to maint). * "git diff -c -p" was not showing a deleted line from a hunk when another hunk immediately begins where the earlier one ends. - (merge aac3857 mk/combine-diff-context-horizon-fix later to maint). * "git log --ancestry-path A...B" did not work as expected, as it did not pay attention to the fact that the merge base between A and B was the bottom of the range being specified. - (merge a765499 kb/ancestry-path-threedots later to maint). * Mac OS X does not like to write(2) more than INT_MAX number of bytes; work it around by chopping write(2) into smaller pieces. - (merge 6c642a8 fc/macos-x-clipped-write later to maint). * Newer MacOS X encourages the programs to compile and link with their CommonCrypto, not with OpenSSL. - (merge be4c828 da/darwin later to maint). * "git clone foo/bar:baz" cannot be a request to clone from a remote over git-over-ssh specified in the scp style. This case is now detected and clones from a local repository at "foo/bar:baz". - (merge 6000334 nd/clone-local-with-colon later to maint). * When $HOME is misconfigured to point at an unreadable directory, we used to complain and die. Loosen the check. - (merge 4698c8f jn/config-ignore-inaccessible later to maint). * "git subtree" (in contrib/) had one codepath with loose error checks to lose data at the remote side. - (merge 3212d56 jk/subtree-do-not-push-if-split-fails later to maint). * "git fetch" into a shallow repository from a repository that does not know about the shallow boundary commits (e.g. a different fork from the repository the current shallow repository was cloned from) did not work correctly. - (merge 71d5f93 mh/fetch-into-shallow later to maint). * "git checkout foo" DWIMs the intended "upstream" and turns it into "git checkout -t -b foo remotes/origin/foo". This codepath has been updated to correctly take existing remote definitions into account. - (merge 229177a jh/checkout-auto-tracking later to maint). diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index e9f984ba01..4e55b1564e 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -9,9 +9,13 @@ --show-stats:: Include additional statistics at the end of blame output. --L <start>,<end>, -L :<regex>:: - Annotate only the given line range. <start> and <end> can take - one of these forms: +-L <start>,<end>:: +-L :<regex>:: + Annotate only the given line range. <start> and <end> are optional. + ``-L <start>'' or ``-L <start>,'' spans from <start> to end of file. + ``-L ,<end>'' spans from start of file to <end>. ++ +<start> and <end> can take one of these forms: include::line-range-format.txt[] diff --git a/Documentation/config.txt b/Documentation/config.txt index 7fd4035cb5..e0b923f428 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -199,6 +199,9 @@ advice.*:: amWorkDir:: Advice that shows the location of the patch file when linkgit:git-am[1] fails to apply it. + rmHints:: + In case of failure in the output of linkgit:git-rm[1], + show directions on how to proceed from the current state. -- core.fileMode:: @@ -876,16 +879,17 @@ The values of these variables may be specified as in color.branch.<slot>. color.interactive:: When set to `always`, always use colors for interactive prompts - and displays (such as those used by "git-add --interactive"). - When false (or `never`), never. When set to `true` or `auto`, use - colors only when the output is to the terminal. Defaults to false. + and displays (such as those used by "git-add --interactive" and + "git-clean --interactive"). When false (or `never`), never. + When set to `true` or `auto`, use colors only when the output is + to the terminal. Defaults to false. color.interactive.<slot>:: - Use customized color for 'git add --interactive' - output. `<slot>` may be `prompt`, `header`, `help` or `error`, for - four distinct types of normal output from interactive - commands. The values of these variables may be specified as - in color.branch.<slot>. + Use customized color for 'git add --interactive' and 'git clean + --interactive' output. `<slot>` may be `prompt`, `header`, `help` + or `error`, for four distinct types of normal output from + interactive commands. The values of these variables may be + specified as in color.branch.<slot>. color.pager:: A boolean to enable/disable colored output when the pager is in @@ -919,17 +923,21 @@ color.ui:: as `color.diff` and `color.grep` that control the use of color per command family. Its scope will expand as more commands learn configuration to set a default for the `--color` option. Set it - to `always` if you want all output not intended for machine - consumption to use color, to `true` or `auto` if you want such - output to use color when written to the terminal, or to `false` or - `never` if you prefer Git commands not to use color unless enabled - explicitly with some other configuration or the `--color` option. + to `false` or `never` if you prefer Git commands not to use + color unless enabled explicitly with some other configuration + or the `--color` option. Set it to `always` if you want all + output not intended for machine consumption to use color, to + `true` or `auto` (this is the default since Git 1.8.4) if you + want such output to use color when written to the terminal. column.ui:: Specify whether supported commands should output in columns. This variable consists of a list of tokens separated by spaces or commas: + +These options control when the feature should be enabled +(defaults to 'never'): ++ -- `always`;; always show in columns @@ -937,24 +945,39 @@ column.ui:: never show in columns `auto`;; show in columns if the output is to the terminal +-- ++ +These options control layout (defaults to 'column'). Setting any +of these implies 'always' if none of 'always', 'never', or 'auto' are +specified. ++ +-- `column`;; - fill columns before rows (default) + fill columns before rows `row`;; fill rows before columns `plain`;; show in one column +-- ++ +Finally, these options can be combined with a layout option (defaults +to 'nodense'): ++ +-- `dense`;; make unequal size columns to utilize more space `nodense`;; make equal size columns -- -+ -This option defaults to 'never'. column.branch:: Specify whether to output branch listing in `git branch` in columns. See `column.ui` for details. +column.clean:: + Specify the layout when list items in `git clean -i`, which always + shows files and directories in columns. See `column.ui` for details. + column.status:: Specify whether to output untracked files in `git status` in columns. See `column.ui` for details. @@ -1826,39 +1849,59 @@ pull.twohead:: The default merge strategy to use when pulling a single branch. push.default:: - Defines the action `git push` should take if no refspec is given - on the command line, no refspec is configured in the remote, and - no refspec is implied by any of the options given on the command - line. Possible values are: + Defines the action `git push` should take if no refspec is + explicitly given. Different values are well-suited for + specific workflows; for instance, in a purely central workflow + (i.e. the fetch source is equal to the push destination), + `upstream` is probably what you want. Possible values are: + -- -* `nothing` - do not push anything. -* `matching` - push all branches having the same name in both ends. - This is for those who prepare all the branches into a publishable - shape and then push them out with a single command. It is not - appropriate for pushing into a repository shared by multiple users, - since locally stalled branches will attempt a non-fast forward push - if other users updated the branch. - + - This is currently the default, but Git 2.0 will change the default - to `simple`. -* `upstream` - push the current branch to its upstream branch - (`tracking` is a deprecated synonym for this). - With this, `git push` will update the same remote ref as the one which - is merged by `git pull`, making `push` and `pull` symmetrical. - See "branch.<name>.merge" for how to configure the upstream branch. -* `simple` - like `upstream`, but refuses to push if the upstream - branch's name is different from the local one. This is the safest - option and is well-suited for beginners. It will become the default - in Git 2.0. -* `current` - push the current branch to a branch of the same name. --- + +* `nothing` - do not push anything (error out) unless a refspec is + explicitly given. This is primarily meant for people who want to + avoid mistakes by always being explicit. + +* `current` - push the current branch to update a branch with the same + name on the receiving end. Works in both central and non-central + workflows. + +* `upstream` - push the current branch back to the branch whose + changes are usually integrated into the current branch (which is + called `@{upstream}`). This mode only makes sense if you are + pushing to the same repository you would normally pull from + (i.e. central workflow). + +* `simple` - in centralized workflow, work like `upstream` with an + added safety to refuse to push if the upstream branch's name is + different from the local one. ++ +When pushing to a remote that is different from the remote you normally +pull from, work as `current`. This is the safest option and is suited +for beginners. ++ +This mode will become the default in Git 2.0. + +* `matching` - push all branches having the same name on both ends. + This makes the repository you are pushing to remember the set of + branches that will be pushed out (e.g. if you always push 'maint' + and 'master' there and no other branches, the repository you push + to will have these two branches, and your local 'maint' and + 'master' will be pushed there). + -The `simple`, `current` and `upstream` modes are for those who want to -push out a single branch after finishing work, even when the other -branches are not yet ready to be pushed out. If you are working with -other people to push into the same shared repository, you would want -to use one of these. +To use this mode effectively, you have to make sure _all_ the +branches you would push out are ready to be pushed out before +running 'git push', as the whole point of this mode is to allow you +to push all of the branches in one go. If you usually finish work +on only one branch and push out the result, while other branches are +unfinished, this mode is not for you. Also this mode is not +suitable for pushing into a shared central repository, as other +people may add new branches there, or update the tip of existing +branches outside your control. ++ +This is currently the default, but Git 2.0 will change the default +to `simple`. + +-- rebase.stat:: Whether to show a diffstat of what changed upstream since the last @@ -2030,6 +2073,10 @@ sendemail.smtpencryption:: sendemail.smtpssl:: Deprecated alias for 'sendemail.smtpencryption = ssl'. +sendemail.smtpsslcertpath:: + Path to ca-certificates (either a directory or a single file). + Set it to an empty string to disable certificate verification. + sendemail.<identity>.*:: Identity-specific versions of the 'sendemail.*' parameters found below, taking precedence over those when the this @@ -2074,6 +2121,14 @@ status.relativePaths:: relative to the repository root (this was the default for Git prior to v1.5.4). +status.short:: + Set to true to enable --short by default in linkgit:git-status[1]. + The option --no-short takes precedence over this variable. + +status.branch:: + Set to true to enable --branch by default in linkgit:git-status[1]. + The option --no-branch takes precedence over this variable. + status.showUntrackedFiles:: By default, linkgit:git-status[1] and linkgit:git-commit[1] show files which are not currently tracked by Git. Directories which diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index a85288f23e..bbed2cd79c 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -26,6 +26,11 @@ ifndef::git-format-patch[] {git-diff? This is the default.} endif::git-format-patch[] +-s:: +--no-patch:: + Suppress diff output. Useful for commands like `git show` that + show the patch by default, or to cancel the effect of `--patch`. + -U<n>:: --unified=<n>:: Generate diffs with <n> lines of context instead of @@ -333,7 +338,7 @@ endif::git-log[] a fraction, with a decimal point before it. I.e., `-M5` becomes 0.5, and is thus the same as `-M50%`. Similarly, `-M05` is the same as `-M5%`. To limit detection to exact renames, use - `-M100%`. + `-M100%`. The default similarity index is 50%. -C[<n>]:: --find-copies[=<n>]:: @@ -461,6 +466,9 @@ endif::git-format-patch[] differences even if one line has whitespace where the other line has none. +--ignore-blank-lines:: + Ignore changes whose lines are all blank. + --inter-hunk-context=<lines>:: Show the context between diff hunks, up to the specified number of lines, thereby fusing hunks that are close to each other. diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 5bbe7b6d10..54d8461d61 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -132,7 +132,7 @@ default. You can use `--no-utf8` to override this. --resolvemsg=<msg>:: When a patch failure occurs, <msg> will be printed to the screen before exiting. This overrides the - standard message informing you to use `--resolved` + standard message informing you to use `--continue` or `--skip` to handle the failure. This is solely for internal use between 'git rebase' and 'git am'. @@ -176,7 +176,7 @@ aborts in the middle. You can recover from this in one of two ways: . hand resolve the conflict in the working directory, and update the index file to bring it into a state that the patch should - have produced. Then run the command with the '--resolved' option. + have produced. Then run the command with the '--continue' option. The command refuses to process new mailboxes until the current operation is finished, so if you decide to start over from scratch, diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 30d585af5d..3ddec0b65b 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -58,12 +58,16 @@ OPTIONS to apply the filter to the content recorded in the index at <path>. --batch:: - Print the SHA-1, type, size, and contents of each object provided on - stdin. May not be combined with any other options or arguments. +--batch=<format>:: + Print object information and contents for each object provided + on stdin. May not be combined with any other options or arguments. + See the section `BATCH OUTPUT` below for details. --batch-check:: - Print the SHA-1, type, and size of each object provided on stdin. May not - be combined with any other options or arguments. +--batch-check=<format>:: + Print object information for each object provided on stdin. May + not be combined with any other options or arguments. See the + section `BATCH OUTPUT` below for details. OUTPUT ------ @@ -78,28 +82,81 @@ If '-p' is specified, the contents of <object> are pretty-printed. If <type> is specified, the raw (though uncompressed) contents of the <object> will be returned. -If '--batch' is specified, output of the following form is printed for each -object specified on stdin: +BATCH OUTPUT +------------ + +If `--batch` or `--batch-check` is given, `cat-file` will read objects +from stdin, one per line, and print information about them. + +Each line is split at the first whitespace boundary. All characters +before that whitespace are considered as a whole object name, and are +parsed as if given to linkgit:git-rev-parse[1]. Characters after that +whitespace can be accessed using the `%(rest)` atom (see below). + +You can specify the information shown for each object by using a custom +`<format>`. The `<format>` is copied literally to stdout for each +object, with placeholders of the form `%(atom)` expanded, followed by a +newline. The available atoms are: + +`objectname`:: + The 40-hex object name of the object. + +`objecttype`:: + The type of of the object (the same as `cat-file -t` reports). + +`objectsize`:: + The size, in bytes, of the object (the same as `cat-file -s` + reports). + +`objectsize:disk`:: + The size, in bytes, that the object takes up on disk. See the + note about on-disk sizes in the `CAVEATS` section below. + +`rest`:: + The text (if any) found after the first run of whitespace on the + input line (i.e., the "rest" of the line). + +If no format is specified, the default format is `%(objectname) +%(objecttype) %(objectsize)`. + +If `--batch` is specified, the object information is followed by the +object contents (consisting of `%(objectsize)` bytes), followed by a +newline. + +For example, `--batch` without a custom format would produce: ------------ <sha1> SP <type> SP <size> LF <contents> LF ------------ -If '--batch-check' is specified, output of the following form is printed for -each object specified on stdin: +Whereas `--batch-check='%(objectname) %(objecttype)'` would produce: ------------ -<sha1> SP <type> SP <size> LF +<sha1> SP <type> LF ------------ -For both '--batch' and '--batch-check', output of the following form is printed -for each object specified on stdin that does not exist in the repository: +If a name is specified on stdin that cannot be resolved to an object in +the repository, then `cat-file` will ignore any custom format and print: ------------ <object> SP missing LF ------------ + +CAVEATS +------- + +Note that the sizes of objects on disk are reported accurately, but care +should be taken in drawing conclusions about which refs or objects are +responsible for disk usage. The size of a packed non-delta object may be +much larger than the size of objects which delta against it, but the +choice of which object is the base and which is the delta is arbitrary +and is subject to change during a repack. Note also that multiple copies +of an object may be present in the object database; in this case, it is +undefined which copy's size will be reported. + + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt index 8e1f7ab7ea..d2df487aa2 100644 --- a/Documentation/git-check-ignore.txt +++ b/Documentation/git-check-ignore.txt @@ -102,7 +102,7 @@ SEE ALSO -------- linkgit:gitignore[5] linkgit:gitconfig[5] -linkgit:git-ls-files[5] +linkgit:git-ls-files[1] GIT --- diff --git a/Documentation/git-check-mailmap.txt b/Documentation/git-check-mailmap.txt new file mode 100644 index 0000000000..39028ee1a3 --- /dev/null +++ b/Documentation/git-check-mailmap.txt @@ -0,0 +1,47 @@ +git-check-mailmap(1) +==================== + +NAME +---- +git-check-mailmap - Show canonical names and email addresses of contacts + + +SYNOPSIS +-------- +[verse] +'git check-mailmap' [options] <contact>... + + +DESCRIPTION +----------- + +For each ``Name $$<user@host>$$'' or ``$$<user@host>$$'' from the command-line +or standard input (when using `--stdin`), look up the person's canonical name +and email address (see "Mapping Authors" below). If found, print them; +otherwise print the input as-is. + + +OPTIONS +------- +--stdin:: + Read contacts, one per line, from the standard input after exhausting + contacts provided on the command-line. + + +OUTPUT +------ + +For each contact, a single line is output, terminated by a newline. If the +name is provided or known to the 'mailmap', ``Name $$<user@host>$$'' is +printed; otherwise only ``$$<user@host>$$'' is printed. + + +MAPPING AUTHORS +--------------- + +include::mailmap.txt[] + + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index bdc3ab80c7..75fb543393 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree SYNOPSIS -------- [verse] -'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>... +'git clean' [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>... DESCRIPTION ----------- @@ -34,7 +34,13 @@ OPTIONS -f:: --force:: If the Git configuration variable clean.requireForce is not set - to false, 'git clean' will refuse to run unless given -f or -n. + to false, 'git clean' will refuse to run unless given -f, -n or + -i. + +-i:: +--interactive:: + Show what would be done and clean files interactively. See + ``Interactive mode'' for details. -n:: --dry-run:: @@ -63,6 +69,67 @@ OPTIONS Remove only files ignored by Git. This may be useful to rebuild everything from scratch, but keep manually created files. +Interactive mode +---------------- +When the command enters the interactive mode, it shows the +files and directories to be cleaned, and goes into its +interactive command loop. + +The command loop shows the list of subcommands available, and +gives a prompt "What now> ". In general, when the prompt ends +with a single '>', you can pick only one of the choices given +and type return, like this: + +------------ + *** Commands *** + 1: clean 2: filter by pattern 3: select by numbers + 4: ask each 5: quit 6: help + What now> 1 +------------ + +You also could say `c` or `clean` above as long as the choice is unique. + +The main command loop has 6 subcommands. + +clean:: + + Start cleaning files and directories, and then quit. + +filter by pattern:: + + This shows the files and directories to be deleted and issues an + "Input ignore patterns>>" prompt. You can input space-seperated + patterns to exclude files and directories from deletion. + E.g. "*.c *.h" will excludes files end with ".c" and ".h" from + deletion. When you are satisfied with the filtered result, press + ENTER (empty) back to the main menu. + +select by numbers:: + + This shows the files and directories to be deleted and issues an + "Select items to delete>>" prompt. When the prompt ends with double + '>>' like this, you can make more than one selection, concatenated + with whitespace or comma. Also you can say ranges. E.g. "2-5 7,9" + to choose 2,3,4,5,7,9 from the list. If the second number in a + range is omitted, all remaining patches are taken. E.g. "7-" to + choose 7,8,9 from the list. You can say '*' to choose everything. + Also when you are satisfied with the filtered result, press ENTER + (empty) back to the main menu. + +ask each:: + + This will start to clean, and you must confirm one by one in order + to delete items. Please note that this action is not as efficient + as the above two actions. + +quit:: + + This lets you quit without do cleaning. + +help:: + + Show brief usage of interactive git-clean. + SEE ALSO -------- linkgit:gitignore[5] diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index a0727d7759..450f158779 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -239,8 +239,8 @@ Examples * Clone from upstream: + ------------ -$ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6 -$ cd my2.6 +$ git clone git://git.kernel.org/pub/scm/.../linux.git my-linux +$ cd my-linux $ make ------------ @@ -257,10 +257,10 @@ $ git show-branch * Clone from upstream while borrowing from an existing local directory: + ------------ -$ git clone --reference my2.6 \ - git://git.kernel.org/pub/scm/.../linux-2.7 \ - my2.7 -$ cd my2.7 +$ git clone --reference /git/linux.git \ + git://git.kernel.org/pub/scm/.../linux.git \ + my-linux +$ cd my-linux ------------ @@ -271,13 +271,6 @@ $ git clone --bare -l /home/proj/.git /pub/scm/proj.git ------------ -* Create a repository on the kernel.org machine that borrows from Linus: -+ ------------- -$ git clone --bare -l -s /pub/scm/.../torvalds/linux-2.6.git \ - /pub/scm/.../me/subsys-2.6.git ------------- - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index d88a6fcb29..34b0894646 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -82,7 +82,7 @@ OPTIONS --get:: Get the value for a given key (optionally filtered by a regex matching the value). Returns error code 1 if the key was not - found and error code 2 if multiple key values were found. + found and the last value if multiple key values were found. --get-all:: Like get, but does not fail if the number of values for the key @@ -114,10 +114,26 @@ rather than from all available files. + See also <<FILES>>. +--local:: + For writing options: write to the repository .git/config file. + This is the default behavior. ++ +For reading options: read only from the repository .git/config rather than +from all available files. ++ +See also <<FILES>>. + -f config-file:: --file config-file:: Use the given config file instead of the one specified by GIT_CONFIG. +--blob blob:: + Similar to '--file' but use the given blob instead of a file. E.g. + you can use 'master:.gitmodules' to read values from the file + '.gitmodules' in the master branch. See "SPECIFYING REVISIONS" + section in linkgit:gitrevisions[7] for a more complete list of + ways to spell blob names. + --remove-section:: Remove the given section from the configuration file. @@ -197,12 +213,8 @@ FILES If not set explicitly with '--file', there are four files where 'git config' will search for configuration options: -$GIT_DIR/config:: - Repository specific configuration file. - -~/.gitconfig:: - User-specific configuration file. Also called "global" - configuration file. +$(prefix)/etc/gitconfig:: + System-wide configuration file. $XDG_CONFIG_HOME/git/config:: Second user-specific configuration file. If $XDG_CONFIG_HOME is not set @@ -212,8 +224,12 @@ $XDG_CONFIG_HOME/git/config:: you sometimes use older versions of Git, as support for this file was added fairly recently. -$(prefix)/etc/gitconfig:: - System-wide configuration file. +~/.gitconfig:: + User-specific configuration file. Also called "global" + configuration file. + +$GIT_DIR/config:: + Repository specific configuration file. If no further options are given, all reading options will read all of these files that are available. If the global or the system-wide configuration @@ -221,6 +237,10 @@ file are not available they will be ignored. If the repository configuration file is not available or readable, 'git config' will exit with a non-zero error code. However, in neither case will an error message be issued. +The files are read in the order given above, with last value found taking +precedence over values read earlier. When multiple values are taken then all +values of a key from all files will be used. + All writing options will per default write to the repository specific configuration file. Note that this also affects options like '--replace-all' and '--unset'. *'git config' will only ever change one file at a time*. diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index a7b46208f6..78d6d50489 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -18,8 +18,8 @@ SYNOPSIS DESCRIPTION ----------- Show changes between the working tree and the index or a tree, changes -between the index and a tree, changes between two trees, or changes -between two files on disk. +between the index and a tree, changes between two trees, changes between +two blob objects, or changes between two files on disk. 'git diff' [--options] [--] [<path>...]:: @@ -56,11 +56,6 @@ directories. This behavior can be forced by --no-index. This is to view the changes between two arbitrary <commit>. -'git diff' [options] <blob> <blob>:: - - This form is to view the differences between the raw - contents of two blob objects. - 'git diff' [--options] <commit>..<commit> [--] [<path>...]:: This is synonymous to the previous form. If <commit> on @@ -87,6 +82,11 @@ and the range notations ("<commit>..<commit>" and "<commit>\...<commit>") do not mean a range as defined in the "SPECIFYING RANGES" section in linkgit:gitrevisions[7]. +'git diff' [options] <blob> <blob>:: + + This form is to view the differences between the raw + contents of two blob objects. + OPTIONS ------- :git-diff: 1 diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index efb03806f5..85f1f30fdf 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -141,7 +141,7 @@ Limitations ----------- Since 'git fast-import' cannot tag trees, you will not be -able to export the linux-2.6.git repository completely, as it contains +able to export the linux.git repository completely, as it contains a tag referencing a tree instead of a commit. GIT diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 39118774af..e394276b1a 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -187,6 +187,21 @@ will want to ensure that threading is disabled for `git send-email`. The negated form `--no-cc` discards all `Cc:` headers added so far (from config or command line). +--from:: +--from=<ident>:: + Use `ident` in the `From:` header of each commit email. If the + author ident of the commit is not textually identical to the + provided `ident`, place a `From:` header in the body of the + message with the original author. If no `ident` is given, use + the committer ident. ++ +Note that this option is only useful if you are actually sending the +emails and want to identify yourself as the sender, but retain the +original author (and `git am` will correctly pick up the in-body +header). Note also that `git send-email` already handles this +transformation for you, and this option should not be used if you are +feeding the result to `git send-email`. + --add-header=<header>:: Add an arbitrary header to the email headers. This is in addition to any configured headers, and may be used multiple times. diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 4687fe8192..ac2694d04c 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -97,7 +97,7 @@ include::rev-list-options.txt[] include::pretty-formats.txt[] -Common diff options +COMMON DIFF OPTIONS ------------------- :git-log: 1 @@ -105,7 +105,7 @@ include::diff-options.txt[] include::diff-generate-patch.txt[] -Examples +EXAMPLES -------- `git log --no-merges`:: @@ -128,9 +128,9 @@ Examples in the "release" branch, along with the list of paths each commit modifies. -`git log --follow builtin-rev-list.c`:: +`git log --follow builtin/rev-list.c`:: - Shows the commits that changed builtin-rev-list.c, including + Shows the commits that changed builtin/rev-list.c, including those commits that occurred before the file was given its present name. @@ -153,7 +153,7 @@ Examples This makes sense only when following a strict policy of merging all topic branches when staying on a single integration branch. -git log -L '/int main/',/^}/:main.c:: +`git log -L '/int main/',/^}/:main.c`:: Shows how the function `main()` in the file 'main.c' evolved over time. @@ -161,12 +161,12 @@ git log -L '/int main/',/^}/:main.c:: `git log -3`:: Limits the number of commits to show to 3. -Discussion +DISCUSSION ---------- include::i18n.txt[] -Configuration +CONFIGURATION ------------- See linkgit:git-config[1] for core variables and linkgit:git-diff[1] diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt index 774de5e9d9..2e22915eb8 100644 --- a/Documentation/git-ls-remote.txt +++ b/Documentation/git-ls-remote.txt @@ -48,9 +48,9 @@ OPTIONS exit without talking to the remote. <repository>:: - Location of the repository. The shorthand defined in - $GIT_DIR/branches/ can be used. Use "." (dot) to list references in - the local repository. + The "remote" repository to query. This parameter can be + either a URL or the name of a remote (see the GIT URLS and + REMOTES sections of linkgit:git-fetch[1]). <refs>...:: When unspecified, all references, after filtering done @@ -70,9 +70,8 @@ EXAMPLES $ git ls-remote http://www.kernel.org/pub/scm/git/git.git master pu rc 5fe978a5381f1fbad26a80e682ddd2a401966740 refs/heads/master c781a84b5204fb294c9ccc79f8b3baceeb32c061 refs/heads/pu - b1d096f2926c4e37c9c0b6a7bf2119bedaa277cb refs/heads/rc - $ echo http://www.kernel.org/pub/scm/git/git.git >.git/branches/public - $ git ls-remote --tags public v\* + $ git remote add korg http://www.kernel.org/pub/scm/git/git.git + $ git ls-remote --tags korg v\* d6602ec5194c87b0fc87103ca4d67251c76f233a refs/tags/v0.99 f25a265a342aed6041ab0cc484224d9ca54b6f41 refs/tags/v0.99.1 c5db5456ae3b0873fc659c19fafdde22313cc441 refs/tags/v0.99.2 diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 67ca99cd92..8c7f2f66d8 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -56,8 +56,8 @@ especially if those changes were further modified after the merge was started), 'git merge --abort' will in some cases be unable to reconstruct the original (pre-merge) changes. Therefore: -*Warning*: Running 'git merge' with uncommitted changes is -discouraged: while possible, it leaves you in a state that is hard to +*Warning*: Running 'git merge' with non-trivial uncommitted changes is +discouraged: while possible, it may leave you in a state that is hard to back out of in the case of a conflict. diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt index ad1d1468c9..15b00e0991 100644 --- a/Documentation/git-name-rev.txt +++ b/Documentation/git-name-rev.txt @@ -25,14 +25,17 @@ OPTIONS Do not use branch names, but only tags to name the commits --refs=<pattern>:: - Only use refs whose names match a given shell pattern. + Only use refs whose names match a given shell pattern. The pattern + can be one of branch name, tag name or fully qualified ref name. --all:: List all commits reachable from all refs --stdin:: - Read from stdin, append "(<rev_name>)" to all sha1's of nameable - commits, and pass to stdout + Transform stdin by substituting all the 40-character SHA-1 + hexes (say $hex) with "$hex ($rev_name)". When used with + --name-only, substitute with "$rev_name", omitting $hex + altogether. Intended for the scripter's use. --name-only:: Instead of printing both the SHA-1 and the name, print only diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index c579fbc2b8..8cba16d67f 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -176,13 +176,16 @@ Sync options These options can be used in the initial 'clone' as well as in subsequent 'sync' operations. ---branch <branch>:: - Import changes into given branch. If the branch starts with - 'refs/', it will be used as is. Otherwise if it does not start - with 'p4/', that prefix is added. The branch is assumed to - name a remote tracking, but this can be modified using - '--import-local', or by giving a full ref name. The default - branch is 'master'. +--branch <ref>:: + Import changes into <ref> instead of refs/remotes/p4/master. + If <ref> starts with refs/, it is used as is. Otherwise, if + it does not start with p4/, that prefix is added. ++ +By default a <ref> not starting with refs/ is treated as the +name of a remote-tracking branch (under refs/remotes/). This +behavior can be modified using the --import-local option. ++ +The default <ref> is "master". + This example imports a new remote "p4/proj2" into an existing Git repository: diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 24ab07a3f8..6ef8d599d3 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -3,7 +3,7 @@ git-pull(1) NAME ---- -git-pull - Fetch from and merge with another repository or a local branch +git-pull - Fetch from and integrate with another repository or a local branch SYNOPSIS diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index df5be268ba..f7dfe48d28 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -136,6 +136,15 @@ already exists on the remote side. not an ancestor of the local ref used to overwrite it. This flag disables the check. This can cause the remote repository to lose commits; use it with care. + Note that `--force` applies to all the refs that are pushed, + hence using it with `push.default` set to `matching` or with + multiple push destinations configured with `remote.*.push` + may overwrite refs other than the current branch (including + local refs that are strictly behind their remote counterpart). + To force a push to only one branch, use a `+` in front of the + refspec to push (e.g `git push origin +master` to force a push + to the `master` branch). See the `<refspec>...` section above + for details. --repo=<repository>:: This option is only relevant if no <repository> argument is diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index c84854ae87..6b2e1c86ab 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -389,7 +389,9 @@ squash/fixup series. the same ..., automatically modify the todo list of rebase -i so that the commit marked for squashing comes right after the commit to be modified, and change the action of the moved - commit from `pick` to `squash` (or `fixup`). + commit from `pick` to `squash` (or `fixup`). Ignores subsequent + "fixup! " or "squash! " after the first, in case you referred to an + earlier fixup/squash with `git commit --fixup/--squash`. + This option is only valid when the '--interactive' option is used. + diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 581bb4c413..9c3e3bf83a 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -187,18 +187,25 @@ Examples $ git remote origin $ git branch -r -origin/master -$ git remote add linux-nfs git://linux-nfs.org/pub/linux/nfs-2.6.git + origin/HEAD -> origin/master + origin/master +$ git remote add staging git://git.kernel.org/.../gregkh/staging.git $ git remote -linux-nfs origin -$ git fetch -* refs/remotes/linux-nfs/master: storing branch 'master' ... - commit: bf81b46 +staging +$ git fetch staging +... +From git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging + * [new branch] master -> staging/master + * [new branch] staging-linus -> staging/staging-linus + * [new branch] staging-next -> staging/staging-next $ git branch -r -origin/master -linux-nfs/master -$ git checkout -b nfs linux-nfs/master + origin/HEAD -> origin/master + origin/master + staging/master + staging/staging-linus + staging/staging-next +$ git checkout -b staging staging/master ... ------------ diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index a404b47b7b..f445cb38fa 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git reset' [-q] [<tree-ish>] [--] <paths>... -'git reset' (--patch | -p) [<tree-sh>] [--] [<paths>...] +'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...] 'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>] DESCRIPTION diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 947d62fd25..993903c9f1 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -59,6 +59,22 @@ OPTIONS If there is no parameter given by the user, use `<arg>` instead. +--prefix <arg>:: + Behave as if 'git rev-parse' was invoked from the `<arg>` + subdirectory of the working tree. Any relative filenames are + resolved as if they are prefixed by `<arg>` and will be printed + in that form. ++ +This can be used to convert arguments to a command run in a subdirectory +so that they can still be used after moving to the top-level of the +repository. For example: ++ +---- +prefix=$(git rev-parse --show-prefix) +cd "$(git rev-parse --show-toplevel)" +eval "set -- $(git rev-parse --sq --prefix "$prefix" "$@")" +---- + --verify:: Verify that exactly one parameter is provided, and that it can be turned into a raw 20-byte SHA-1 that can be used to diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 40a9a9abc1..f0e57a597b 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -198,6 +198,12 @@ must be used for each option. --smtp-ssl:: Legacy alias for '--smtp-encryption ssl'. +--smtp-ssl-cert-path:: + Path to ca-certificates (either a directory or a single file). + Set it to an empty string to disable certificate verification. + Defaults to the value set to the 'sendemail.smtpsslcertpath' + configuration variable, if set, or `/etc/ssl/certs` otherwise. + --smtp-user=<user>:: Username for SMTP-AUTH. Default is the value of 'sendemail.smtpuser'; if a username is not specified (with '--smtp-user' or 'sendemail.smtpuser'), diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index de4d352da2..b0a309b117 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -21,6 +21,8 @@ commit IDs. Results can be filtered using a pattern and tags can be dereferenced into object IDs. Additionally, it can be used to test whether a particular ref exists. +By default, shows the tags, heads, and remote refs. + The --exclude-existing form is a filter that does the inverse, it shows the refs from stdin that don't exist in the local repository. @@ -32,14 +34,14 @@ OPTIONS --head:: - Show the HEAD reference. + Show the HEAD reference, even if it would normally be filtered out. --tags:: --heads:: - Limit to only "refs/heads" and "refs/tags", respectively. These - options are not mutually exclusive; when given both, references stored - in "refs/heads" and "refs/tags" are displayed. + Limit to "refs/heads" and "refs/tags", respectively. These options + are not mutually exclusive; when given both, references stored in + "refs/heads" and "refs/tags" are displayed. -d:: --dereference:: diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt index ae4edcccfb..4e617e6979 100644 --- a/Documentation/git-show.txt +++ b/Documentation/git-show.txt @@ -45,6 +45,15 @@ include::pretty-options.txt[] include::pretty-formats.txt[] +COMMON DIFF OPTIONS +------------------- + +:git-log: 1 +include::diff-options.txt[] + +include::diff-generate-patch.txt[] + + EXAMPLES -------- diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 711ffe17a7..7c8b648fbe 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -13,10 +13,12 @@ SYNOPSIS 'git stash' drop [-q|--quiet] [<stash>] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] 'git stash' branch <branchname> [<stash>] -'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] - [-u|--include-untracked] [-a|--all] [<message>]] +'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] + [-u|--include-untracked] [-a|--all] [-f|--force] + [<message>]] 'git stash' clear -'git stash' create +'git stash' create [<message>] +'git stash' store [-m|--message <message>] [-q|--quiet] <commit> DESCRIPTION ----------- @@ -43,7 +45,7 @@ is also possible). OPTIONS ------- -save [-p|--patch] [--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]:: +save [-p|--patch] [--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-f|--force] [<message>]:: Save your local modifications to a new 'stash', and run `git reset --hard` to revert them. The <message> part is optional and gives @@ -70,6 +72,13 @@ linkgit:git-add[1] to learn how to operate the `--patch` mode. + The `--patch` option implies `--keep-index`. You can use `--no-keep-index` to override this. ++ +In some cases, saving a stash could mean irretrievably removing some +data - if a directory with untracked files replaces a tracked file of +the same name, the new untracked files are not saved (except in case +of `--include-untracked`) but the original tracked file shall be restored. +By default, `stash save` will abort in such a case; `--force` will allow +it to remove the untracked files. list [<options>]:: @@ -151,7 +160,15 @@ create:: Create a stash (which is a regular commit object) and return its object name, without storing it anywhere in the ref namespace. + This is intended to be useful for scripts. It is probably not + the command you want to use; see "save" above. + +store:: + Store a given stash created via 'git stash create' (which is a + dangling merge commit) in the stash ref, updating the stash + reflog. This is intended to be useful for scripts. It is + probably not the command you want to use; see "save" above. DISCUSSION ---------- diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index e5767134b1..bfef8a0c62 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -10,12 +10,12 @@ SYNOPSIS -------- [verse] 'git submodule' [--quiet] add [-b <branch>] [-f|--force] [--name <name>] - [--reference <repository>] [--] <repository> [<path>] + [--reference <repository>] [--depth <depth>] [--] <repository> [<path>] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...] 'git submodule' [--quiet] init [--] [<path>...] 'git submodule' [--quiet] deinit [-f|--force] [--] <path>... 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] - [-f|--force] [--rebase] [--reference <repository>] + [-f|--force] [--rebase] [--reference <repository>] [--depth <depth>] [--merge] [--recursive] [--] [<path>...] 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>] [commit] [--] [<path>...] @@ -159,7 +159,9 @@ update:: This will make the submodules HEAD be detached unless `--rebase` or `--merge` is specified or the key `submodule.$name.update` is set to `rebase`, `merge` or `none`. `none` can be overridden by specifying - `--checkout`. + `--checkout`. Setting the key `submodule.$name.update` to `!command` + will cause `command` to be run. `command` can be any arbitrary shell + command that takes a single argument, namely the sha1 to update to. + If the submodule is not yet initialized, and you just want to use the setting as stored in .gitmodules, you can automatically initialize the @@ -262,7 +264,7 @@ OPTIONS --remote:: This option is only valid for the update command. Instead of using the superproject's recorded SHA-1 to update the submodule, use the - status of the submodule's remote tracking branch. The remote used + status of the submodule's remote-tracking branch. The remote used is branch's remote (`branch.<name>.remote`), defaulting to `origin`. The remote branch used defaults to `master`, but the branch name may be overridden by setting the `submodule.<name>.branch` option in @@ -328,6 +330,12 @@ for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully. only in the submodules of the current repo, but also in any nested submodules inside those submodules (and so on). +--depth:: + This option is valid for add and update commands. Create a 'shallow' + clone with a history truncated to the specified number of revisions. + See linkgit:git-clone[1] + + <path>...:: Paths to submodule(s). When specified this will restrict the command to only operate on the submodules found at the specified paths. diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt index ba79cb4f35..5aec4ecffb 100644 --- a/Documentation/git-web--browse.txt +++ b/Documentation/git-web--browse.txt @@ -34,6 +34,7 @@ The following browsers (or commands) are currently supported: * dillo * open (this is the default under Mac OS X GUI) * start (this is the default under MinGW) +* cygstart (this is the default under Cygwin) Custom commands may also be specified. diff --git a/Documentation/git.txt b/Documentation/git.txt index 2e23cbb224..3bdd56e8f4 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,12 @@ unreleased) version of Git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.8.3.1/git.html[documentation for release 1.8.3.1] +* link:v1.8.3.4/git.html[documentation for release 1.8.3.4] * release notes for + link:RelNotes/1.8.3.4.txt[1.8.3.4], + link:RelNotes/1.8.3.3.txt[1.8.3.3], + link:RelNotes/1.8.3.2.txt[1.8.3.2], link:RelNotes/1.8.3.1.txt[1.8.3.1], link:RelNotes/1.8.3.txt[1.8.3]. @@ -837,6 +840,19 @@ for further details. as a file path and will try to write the trace messages into it. +'GIT_TRACE_PACK_ACCESS':: + If this variable is set to a path, a file will be created at + the given path logging all accesses to any packs. For each + access, the pack file name and an offset in the pack is + recorded. This may be helpful for troubleshooting some + pack-related performance problems. + +'GIT_TRACE_PACKET':: + If this variable is set, it shows a trace of all packets + coming in or out of a given program. This can help with + debugging object negotiation or other protocol issues. Tracing + is turned off at a packet starting with "PACK". + GIT_LITERAL_PATHSPECS:: Setting this variable to `1` will cause Git to treat all pathspecs literally, rather than as glob patterns. For example, diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt index ea0526ecc4..305db633cc 100644 --- a/Documentation/gitweb.conf.txt +++ b/Documentation/gitweb.conf.txt @@ -336,8 +336,26 @@ $home_link_str:: used as the first component of gitweb's "breadcrumb trail": `<home link> / <project> / <action>`. Can be set at build time using the `GITWEB_HOME_LINK_STR` variable. By default it is set to "projects", - as this link leads to the list of projects. Other popular choice it to - set it to the name of site. + as this link leads to the list of projects. Another popular choice is to + set it to the name of site. Note that it is treated as raw HTML so it + should not be set from untrusted sources. + +@extra_breadcrumbs:: + Additional links to be added to the start of the breadcrumb trail before + the home link, to pages that are logically "above" the gitweb projects + list, such as the organization and department which host the gitweb + server. Each element of the list is a reference to an array, in which + element 0 is the link text (equivalent to `$home_link_str`) and element + 1 is the target URL (equivalent to `$home_link`). ++ +For example, the following setting produces a breadcrumb trail like +"home / dev / projects / ..." where "projects" is the home link. +---------------------------------------------------------------------------- + our @extra_breadcrumbs = ( + [ 'home' => 'https://www.example.org/' ], + [ 'dev' => 'https://dev.example.org/' ], + ); +---------------------------------------------------------------------------- $logo_url:: $logo_label:: diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index db2a74df93..dba5062b37 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -113,7 +113,7 @@ Note that commands that operate on the history of the current branch while the HEAD is detached. They update the HEAD to point at the tip of the updated history without affecting any branch. Commands that update or inquire information _about_ the current branch (e.g. `git -branch --set-upstream-to` that sets what remote tracking branch the +branch --set-upstream-to` that sets what remote-tracking branch the current branch integrates with) obviously do not work, as there is no (real) current branch to ask about in this state. @@ -267,7 +267,7 @@ This commit is referred to as a "merge commit", or sometimes just a The default upstream <<def_repository,repository>>. Most projects have at least one upstream project which they track. By default 'origin' is used for that purpose. New upstream updates - will be fetched into remote <<def_remote_tracking_branch,remote-tracking branches>> named + will be fetched into <<def_remote_tracking_branch,remote-tracking branches>> named origin/name-of-upstream-branch, which you can see using `git branch -r`. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index b462f17f62..e632e8576e 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -625,6 +625,10 @@ By default, the commits are shown in reverse chronological order. Show no parents before all of its children are shown, but otherwise show commits in the commit timestamp order. +--author-date-order:: + Show no parents before all of its children are shown, but + otherwise show commits in the author timestamp order. + --topo-order:: Show no parents before all of its children are shown, and avoid showing commits on multiple lines of history @@ -845,7 +849,4 @@ options may be given. See linkgit:git-diff-files[1] for more options. -t:: Show the tree objects in the diff output. This implies '-r'. - --s:: - Suppress diff output. endif::git-rev-list[] diff --git a/Documentation/technical/api-builtin.txt b/Documentation/technical/api-builtin.txt index 4a4228b896..f3c1357b7c 100644 --- a/Documentation/technical/api-builtin.txt +++ b/Documentation/technical/api-builtin.txt @@ -39,7 +39,7 @@ where options is the bitwise-or of: on bare repositories. This only makes sense when `RUN_SETUP` is also set. -. Add `builtin-foo.o` to `BUILTIN_OBJS` in `Makefile`. +. Add `builtin/foo.o` to `BUILTIN_OBJS` in `Makefile`. Additionally, if `foo` is a new command, there are 3 more things to do: diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index 1317db4d6c..0be2b5159f 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -275,10 +275,10 @@ Examples -------- See `test-parse-options.c` and -`builtin-add.c`, -`builtin-clone.c`, -`builtin-commit.c`, -`builtin-fetch.c`, -`builtin-fsck.c`, -`builtin-rm.c` +`builtin/add.c`, +`builtin/clone.c`, +`builtin/commit.c`, +`builtin/fetch.c`, +`builtin/fsck.c`, +`builtin/rm.c` for real-world examples. diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt index 6dc82ca5a8..f716d6d97f 100644 --- a/Documentation/technical/racy-git.txt +++ b/Documentation/technical/racy-git.txt @@ -135,9 +135,9 @@ them, and give the same timestamp to the index file: $ git ls-files | git update-index --stdin $ touch -r .datestamp .git/index -This will make all index entries racily clean. The linux-2.6 -project, for example, there are over 20,000 files in the working -tree. On my Athlon 64 X2 3800+, after the above: +This will make all index entries racily clean. The linux project, for +example, there are over 20,000 files in the working tree. On my +Athlon 64 X2 3800+, after the above: $ /usr/bin/time git diff-files 1.68user 0.54system 0:02.22elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 5e185f87c2..9ccb24677e 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -11,6 +11,9 @@ and ftps can be used for fetching and rsync can be used for fetching and pushing, but these are inefficient and deprecated; do not use them). +The native transport (i.e. git:// URL) does no authentication and +should be used with caution on unsecured networks. + The following syntaxes may be used with them: - ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/path/to/repo.git/ diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index e831cc2020..fe723e4722 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -57,17 +57,17 @@ download a copy of an existing repository. If you don't already have a project in mind, here are some interesting examples: ------------------------------------------------ - # Git itself (approx. 10MB download): + # Git itself (approx. 40MB download): $ git clone git://git.kernel.org/pub/scm/git/git.git - # the Linux kernel (approx. 150MB download): -$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git + # the Linux kernel (approx. 640MB download): +$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git ------------------------------------------------ The initial clone may be time-consuming for a large project, but you will only need to clone once. -The clone command creates a new directory named after the project (`git` -or `linux-2.6` in the examples above). After you cd into this +The clone command creates a new directory named after the project +(`git` or `linux` in the examples above). After you cd into this directory, you will see that it contains a copy of the project files, called the <<def_working_tree,working tree>>, together with a special top-level directory named `.git`, which contains all the information @@ -431,19 +431,25 @@ You can also track branches from repositories other than the one you cloned from, using linkgit:git-remote[1]: ------------------------------------------------- -$ git remote add linux-nfs git://linux-nfs.org/pub/nfs-2.6.git -$ git fetch linux-nfs -* refs/remotes/linux-nfs/master: storing branch 'master' ... - commit: bf81b46 +$ git remote add staging git://git.kernel.org/.../gregkh/staging.git +$ git fetch staging +... +From git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging + * [new branch] master -> staging/master + * [new branch] staging-linus -> staging/staging-linus + * [new branch] staging-next -> staging/staging-next ------------------------------------------------- New remote-tracking branches will be stored under the shorthand name -that you gave `git remote add`, in this case `linux-nfs`: +that you gave `git remote add`, in this case `staging`: ------------------------------------------------- $ git branch -r -linux-nfs/master -origin/master + origin/HEAD -> origin/master + origin/master + staging/master + staging/staging-linus + staging/staging-next ------------------------------------------------- If you run `git fetch <remote>` later, the remote-tracking branches @@ -455,9 +461,9 @@ a new stanza: ------------------------------------------------- $ cat .git/config ... -[remote "linux-nfs"] - url = git://linux-nfs.org/pub/nfs-2.6.git - fetch = +refs/heads/*:refs/remotes/linux-nfs/* +[remote "staging"] + url = git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git + fetch = +refs/heads/*:refs/remotes/staging/* ... ------------------------------------------------- @@ -1835,7 +1841,7 @@ Once the index is updated with the results of the conflict resolution, instead of creating a new commit, just run ------------------------------------------------- -$ git am --resolved +$ git am --continue ------------------------------------------------- and Git will create the commit for you and continue applying the @@ -2156,7 +2162,7 @@ To set this up, first create your work tree by cloning Linus's public tree: ------------------------------------------------- -$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git work +$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git work $ cd work ------------------------------------------------- @@ -2198,7 +2204,7 @@ make it easy to push both branches to your public tree. (See ------------------------------------------------- $ cat >> .git/config <<EOF [remote "mytree"] - url = master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git + url = master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux.git push = release push = test EOF @@ -4256,15 +4262,16 @@ no longer need to call `setup_pager()` directly). Nowadays, `git log` is a builtin, which means that it is _contained_ in the command `git`. The source side of a builtin is -- a function called `cmd_<bla>`, typically defined in `builtin-<bla>.c`, - and declared in `builtin.h`, +- a function called `cmd_<bla>`, typically defined in `builtin/<bla.c>` + (note that older versions of Git used to have it in `builtin-<bla>.c` + instead), and declared in `builtin.h`. - an entry in the `commands[]` array in `git.c`, and - an entry in `BUILTIN_OBJECTS` in the `Makefile`. Sometimes, more than one builtin is contained in one source file. For -example, `cmd_whatchanged()` and `cmd_log()` both reside in `builtin-log.c`, +example, `cmd_whatchanged()` and `cmd_log()` both reside in `builtin/log.c`, since they share quite a bit of code. In that case, the commands which are _not_ named like the `.c` file in which they live have to be listed in `BUILT_INS` in the `Makefile`. @@ -4287,10 +4294,10 @@ For the sake of clarity, let's stay with `git cat-file`, because it - is plumbing, and - was around even in the initial commit (it literally went only through - some 20 revisions as `cat-file.c`, was renamed to `builtin-cat-file.c` + some 20 revisions as `cat-file.c`, was renamed to `builtin/cat-file.c` when made a builtin, and then saw less than 10 versions). -So, look into `builtin-cat-file.c`, search for `cmd_cat_file()` and look what +So, look into `builtin/cat-file.c`, search for `cmd_cat_file()` and look what it does. ------------------------------------------------------------------ @@ -4366,7 +4373,7 @@ Another example: Find out what to do in order to make some script a builtin: ------------------------------------------------- -$ git log --no-merges --diff-filter=A builtin-*.c +$ git log --no-merges --diff-filter=A builtin/*.c ------------------------------------------------- You see, Git is actually the best tool to find out about the source of Git @@ -4668,5 +4675,5 @@ Write a chapter on using plumbing and writing scripts. Alternates, clone -reference, etc. More on recovery from repository corruption. See: - http://marc.theaimsgroup.com/?l=git&m=117263864820799&w=2 - http://marc.theaimsgroup.com/?l=git&m=117147855503798&w=2 + http://marc.info/?l=git&m=117263864820799&w=2 + http://marc.info/?l=git&m=117147855503798&w=2 diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 390782fa12..b4d4e5045f 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -11,7 +11,7 @@ LF=' if test -f version then VN=$(cat version) || VN="$DEF_VER" -elif test -d .git -o -f .git && +elif test -d ${GIT_DIR:-.git} -o -f .git && VN=$(git describe --match "v[0-9]*" --abbrev=7 HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; @@ -109,7 +109,7 @@ all:: # Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the # FNM_CASEFOLD GNU extension. # -# Define USE_WILDMATCH if you want to use Git's wildmatch +# Define NO_WILDMATCH if you do not want to use Git's wildmatch # implementation as fnmatch # # Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd @@ -491,6 +491,7 @@ SCRIPT_PERL += git-svn.perl SCRIPT_PYTHON += git-remote-testpy.py SCRIPT_PYTHON += git-p4.py +NO_INSTALL += git-remote-testgit NO_INSTALL += git-remote-testpy # Generated files for scripts @@ -568,6 +569,8 @@ TEST_PROGRAMS_NEED_X += test-mergesort TEST_PROGRAMS_NEED_X += test-mktemp TEST_PROGRAMS_NEED_X += test-parse-options TEST_PROGRAMS_NEED_X += test-path-utils +TEST_PROGRAMS_NEED_X += test-prio-queue +TEST_PROGRAMS_NEED_X += test-read-cache TEST_PROGRAMS_NEED_X += test-regex TEST_PROGRAMS_NEED_X += test-revision-walking TEST_PROGRAMS_NEED_X += test-run-command @@ -694,6 +697,7 @@ LIB_H += merge-recursive.h LIB_H += mergesort.h LIB_H += notes-cache.h LIB_H += notes-merge.h +LIB_H += notes-utils.h LIB_H += notes.h LIB_H += object.h LIB_H += pack-revindex.h @@ -702,6 +706,7 @@ LIB_H += parse-options.h LIB_H += patch-ids.h LIB_H += pathspec.h LIB_H += pkt-line.h +LIB_H += prio-queue.h LIB_H += progress.h LIB_H += prompt.h LIB_H += quote.h @@ -828,6 +833,7 @@ LIB_OBJS += name-hash.o LIB_OBJS += notes.o LIB_OBJS += notes-cache.o LIB_OBJS += notes-merge.o +LIB_OBJS += notes-utils.o LIB_OBJS += object.o LIB_OBJS += pack-check.o LIB_OBJS += pack-revindex.o @@ -842,6 +848,7 @@ LIB_OBJS += pathspec.o LIB_OBJS += pkt-line.o LIB_OBJS += preload-index.o LIB_OBJS += pretty.o +LIB_OBJS += prio-queue.o LIB_OBJS += progress.o LIB_OBJS += prompt.o LIB_OBJS += quote.o @@ -905,6 +912,7 @@ BUILTIN_OBJS += builtin/bundle.o BUILTIN_OBJS += builtin/cat-file.o BUILTIN_OBJS += builtin/check-attr.o BUILTIN_OBJS += builtin/check-ignore.o +BUILTIN_OBJS += builtin/check-mailmap.o BUILTIN_OBJS += builtin/check-ref-format.o BUILTIN_OBJS += builtin/checkout-index.o BUILTIN_OBJS += builtin/checkout.o @@ -1278,7 +1286,7 @@ ifdef NO_FNMATCH_CASEFOLD COMPAT_OBJS += compat/fnmatch/fnmatch.o endif endif -ifdef USE_WILDMATCH +ifndef NO_WILDMATCH COMPAT_CFLAGS += -DUSE_WILDMATCH endif ifdef NO_SETENV @@ -2067,13 +2075,13 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIB_FILE): $(LIB_OBJS) - $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) + $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^ $(XDIFF_LIB): $(XDIFF_OBJS) - $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS) + $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^ $(VCSSVN_LIB): $(VCSSVN_OBJS) - $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS) + $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^ export DEFAULT_EDITOR DEFAULT_PAGER @@ -2233,6 +2241,7 @@ endif test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X)) all:: $(TEST_PROGRAMS) $(test_bindir_programs) +all:: $(NO_INSTALL) bin-wrappers/%: wrap-for-bin.sh @mkdir -p bin-wrappers @@ -2297,9 +2306,6 @@ check: common-cmds.h exit 1; \ fi -remove-dashes: - ./fixup-builtins $(BUILT_INS) $(PROGRAMS) $(SCRIPTS) - ### Installation rules ifneq ($(filter /%,$(firstword $(template_dir))),) @@ -2482,7 +2488,7 @@ clean: profile-clean coverage-clean $(RM) *.o *.res block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \ builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X - $(RM) $(TEST_PROGRAMS) + $(RM) $(TEST_PROGRAMS) $(NO_INSTALL) $(RM) -r bin-wrappers $(dep_dirs) $(RM) -r po/build/ $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope* @@ -15,6 +15,7 @@ int advice_implicit_identity = 1; int advice_detached_head = 1; int advice_set_upstream_failure = 1; int advice_object_name_warning = 1; +int advice_rm_hints = 1; static struct { const char *name; @@ -35,6 +36,7 @@ static struct { { "detachedhead", &advice_detached_head }, { "setupstreamfailure", &advice_set_upstream_failure }, { "object_name_warning", &advice_object_name_warning }, + { "rmhints", &advice_rm_hints }, /* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected } @@ -18,8 +18,10 @@ extern int advice_implicit_identity; extern int advice_detached_head; extern int advice_set_upstream_failure; extern int advice_object_name_warning; +extern int advice_rm_hints; int git_default_advice_config(const char *var, const char *value); +__attribute__((format (printf, 1, 2))) void advise(const char *advice, ...); int error_resolve_conflict(const char *me); extern void NORETURN die_resolve_conflict(const char *me); diff --git a/argv-array.h b/argv-array.h index 40248d424c..85ba438ac1 100644 --- a/argv-array.h +++ b/argv-array.h @@ -15,6 +15,7 @@ void argv_array_init(struct argv_array *); void argv_array_push(struct argv_array *, const char *); __attribute__((format (printf,2,3))) void argv_array_pushf(struct argv_array *, const char *fmt, ...); +LAST_ARG_MUST_BE_NULL void argv_array_pushl(struct argv_array *, ...); void argv_array_pop(struct argv_array *); void argv_array_clear(struct argv_array *); @@ -5,7 +5,6 @@ #include "strbuf.h" #include "cache.h" #include "commit.h" -#include "notes.h" #define DEFAULT_MERGE_LOG_LEN 20 @@ -26,21 +25,6 @@ struct fmt_merge_msg_opts { extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out, struct fmt_merge_msg_opts *); -struct notes_rewrite_cfg { - struct notes_tree **trees; - const char *cmd; - int enabled; - combine_notes_fn combine; - struct string_list *refs; - int refs_from_env; - int mode_from_env; -}; - -struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd); -int copy_note_for_rewrite(struct notes_rewrite_cfg *c, - const unsigned char *from_obj, const unsigned char *to_obj); -void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c); - extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, int sha1_valid, char **buf, unsigned long *buf_size); extern int cmd_add(int argc, const char **argv, const char *prefix); @@ -56,6 +40,7 @@ extern int cmd_checkout(int argc, const char **argv, const char *prefix); extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_attr(int argc, const char **argv, const char *prefix); extern int cmd_check_ignore(int argc, const char **argv, const char *prefix); +extern int cmd_check_mailmap(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); extern int cmd_cherry(int argc, const char **argv, const char *prefix); extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index f45d9d4865..8266a9cb70 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -343,6 +343,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) argc = setup_revisions(argc, argv, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_PATCH; + rev.diffopt.use_color = 0; DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES); out = open(file, O_CREAT | O_WRONLY, 0666); if (out < 0) diff --git a/builtin/apply.c b/builtin/apply.c index 30eefc3c7b..50912c928f 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -473,7 +473,7 @@ static char *find_name_gnu(const char *line, const char *def, int p_value) /* * Proposed "new-style" GNU patch/diff format; see - * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 + * http://marc.info/?l=git&m=112927316408690&w=2 */ if (unquote_c_style(&name, line, NULL)) { strbuf_release(&name); @@ -722,7 +722,7 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) static char *find_name_traditional(const char *line, char *def, int p_value) { - size_t len = strlen(line); + size_t len; size_t date_len; if (*line == '"') { @@ -906,7 +906,7 @@ static void parse_traditional_patch(const char *first, const char *second, struc patch->old_name = name; } else { patch->old_name = name; - patch->new_name = xstrdup(name); + patch->new_name = null_strdup(name); } } if (!name) @@ -2999,7 +2999,7 @@ static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsig return 0; } -static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) +static int read_file_or_gitlink(const struct cache_entry *ce, struct strbuf *buf) { if (!ce) return 0; @@ -3117,7 +3117,7 @@ static struct patch *previous_patch(struct patch *patch, int *gone) return previous; } -static int verify_index_match(struct cache_entry *ce, struct stat *st) +static int verify_index_match(const struct cache_entry *ce, struct stat *st) { if (S_ISGITLINK(ce->ce_mode)) { if (!S_ISDIR(st->st_mode)) @@ -3130,7 +3130,7 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st) #define SUBMODULE_PATCH_WITHOUT_INDEX 1 static int load_patch_target(struct strbuf *buf, - struct cache_entry *ce, + const struct cache_entry *ce, struct stat *st, const char *name, unsigned expected_mode) @@ -3160,7 +3160,8 @@ static int load_patch_target(struct strbuf *buf, * we read from the result of a previous diff. */ static int load_preimage(struct image *image, - struct patch *patch, struct stat *st, struct cache_entry *ce) + struct patch *patch, struct stat *st, + const struct cache_entry *ce) { struct strbuf buf = STRBUF_INIT; size_t len; @@ -3273,7 +3274,7 @@ static int load_current(struct image *image, struct patch *patch) } static int try_threeway(struct image *image, struct patch *patch, - struct stat *st, struct cache_entry *ce) + struct stat *st, const struct cache_entry *ce) { unsigned char pre_sha1[20], post_sha1[20], our_sha1[20]; struct strbuf buf = STRBUF_INIT; @@ -3343,7 +3344,7 @@ static int try_threeway(struct image *image, struct patch *patch, return 0; } -static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) +static int apply_data(struct patch *patch, struct stat *st, const struct cache_entry *ce) { struct image image; @@ -3525,7 +3526,7 @@ static int check_patch(struct patch *patch) ok_if_exists = 0; if (new_name && - ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) { + ((0 < patch->is_new) || patch->is_rename || patch->is_copy)) { int err = check_to_create(new_name, ok_if_exists); if (err && threeway) { @@ -3847,7 +3848,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned const char *s = buf; if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1)) - die(_("corrupt patch for subproject %s"), path); + die(_("corrupt patch for submodule %s"), path); } else { if (!cached) { if (lstat(path, &st) < 0) diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 045cee7bce..0e64b4159c 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -13,9 +13,6 @@ #include "userdiff.h" #include "streaming.h" -#define BATCH 1 -#define BATCH_CHECK 2 - static int cat_one_file(int opt, const char *exp_type, const char *obj_name) { unsigned char sha1[20]; @@ -117,54 +114,174 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) return 0; } -static int batch_one_object(const char *obj_name, int print_contents) -{ +struct expand_data { unsigned char sha1[20]; - enum object_type type = 0; + enum object_type type; unsigned long size; - void *contents = NULL; + unsigned long disk_size; + const char *rest; + + /* + * If mark_query is true, we do not expand anything, but rather + * just mark the object_info with items we wish to query. + */ + int mark_query; + + /* + * After a mark_query run, this object_info is set up to be + * passed to sha1_object_info_extended. It will point to the data + * elements above, so you can retrieve the response from there. + */ + struct object_info info; +}; + +static int is_atom(const char *atom, const char *s, int slen) +{ + int alen = strlen(atom); + return alen == slen && !memcmp(atom, s, alen); +} + +static void expand_atom(struct strbuf *sb, const char *atom, int len, + void *vdata) +{ + struct expand_data *data = vdata; + + if (is_atom("objectname", atom, len)) { + if (!data->mark_query) + strbuf_addstr(sb, sha1_to_hex(data->sha1)); + } else if (is_atom("objecttype", atom, len)) { + if (!data->mark_query) + strbuf_addstr(sb, typename(data->type)); + } else if (is_atom("objectsize", atom, len)) { + if (data->mark_query) + data->info.sizep = &data->size; + else + strbuf_addf(sb, "%lu", data->size); + } else if (is_atom("objectsize:disk", atom, len)) { + if (data->mark_query) + data->info.disk_sizep = &data->disk_size; + else + strbuf_addf(sb, "%lu", data->disk_size); + } else if (is_atom("rest", atom, len)) { + if (!data->mark_query && data->rest) + strbuf_addstr(sb, data->rest); + } else + die("unknown format element: %.*s", len, atom); +} + +static size_t expand_format(struct strbuf *sb, const char *start, void *data) +{ + const char *end; + + if (*start != '(') + return 0; + end = strchr(start + 1, ')'); + if (!end) + die("format element '%s' does not end in ')'", start); + + expand_atom(sb, start + 1, end - start - 1, data); + + return end - start + 1; +} + +static void print_object_or_die(int fd, const unsigned char *sha1, + enum object_type type, unsigned long size) +{ + if (type == OBJ_BLOB) { + if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0) + die("unable to stream %s to stdout", sha1_to_hex(sha1)); + } + else { + enum object_type rtype; + unsigned long rsize; + void *contents; + + contents = read_sha1_file(sha1, &rtype, &rsize); + if (!contents) + die("object %s disappeared", sha1_to_hex(sha1)); + if (rtype != type) + die("object %s changed type!?", sha1_to_hex(sha1)); + if (rsize != size) + die("object %s change size!?", sha1_to_hex(sha1)); + + write_or_die(fd, contents, size); + free(contents); + } +} + +struct batch_options { + int enabled; + int print_contents; + const char *format; +}; + +static int batch_one_object(const char *obj_name, struct batch_options *opt, + struct expand_data *data) +{ + struct strbuf buf = STRBUF_INIT; if (!obj_name) return 1; - if (get_sha1(obj_name, sha1)) { + if (get_sha1(obj_name, data->sha1)) { printf("%s missing\n", obj_name); fflush(stdout); return 0; } - if (print_contents == BATCH) - contents = read_sha1_file(sha1, &type, &size); - else - type = sha1_object_info(sha1, &size); - - if (type <= 0) { + data->type = sha1_object_info_extended(data->sha1, &data->info); + if (data->type <= 0) { printf("%s missing\n", obj_name); fflush(stdout); - if (print_contents == BATCH) - free(contents); return 0; } - printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size); - fflush(stdout); + strbuf_expand(&buf, opt->format, expand_format, data); + strbuf_addch(&buf, '\n'); + write_or_die(1, buf.buf, buf.len); + strbuf_release(&buf); - if (print_contents == BATCH) { - write_or_die(1, contents, size); - printf("\n"); - fflush(stdout); - free(contents); + if (opt->print_contents) { + print_object_or_die(1, data->sha1, data->type, data->size); + write_or_die(1, "\n", 1); } - return 0; } -static int batch_objects(int print_contents) +static int batch_objects(struct batch_options *opt) { struct strbuf buf = STRBUF_INIT; + struct expand_data data; + + if (!opt->format) + opt->format = "%(objectname) %(objecttype) %(objectsize)"; + + /* + * Expand once with our special mark_query flag, which will prime the + * object_info to be handed to sha1_object_info_extended for each + * object. + */ + memset(&data, 0, sizeof(data)); + data.mark_query = 1; + strbuf_expand(&buf, opt->format, expand_format, &data); + data.mark_query = 0; while (strbuf_getline(&buf, stdin, '\n') != EOF) { - int error = batch_one_object(buf.buf, print_contents); + char *p; + int error; + + /* + * Split at first whitespace, tying off the beginning of the + * string and saving the remainder (or NULL) in data.rest. + */ + p = strpbrk(buf.buf, " \t"); + if (p) { + while (*p && strchr(" \t", *p)) + *p++ = '\0'; + } + data.rest = p; + + error = batch_one_object(buf.buf, opt, &data); if (error) return error; } @@ -186,10 +303,29 @@ static int git_cat_file_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int batch_option_callback(const struct option *opt, + const char *arg, + int unset) +{ + struct batch_options *bo = opt->value; + + if (unset) { + memset(bo, 0, sizeof(*bo)); + return 0; + } + + bo->enabled = 1; + bo->print_contents = !strcmp(opt->long_name, "batch"); + bo->format = arg; + + return 0; +} + int cmd_cat_file(int argc, const char **argv, const char *prefix) { - int opt = 0, batch = 0; + int opt = 0; const char *exp_type = NULL, *obj_name = NULL; + struct batch_options batch = {0}; const struct option options[] = { OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")), @@ -200,12 +336,12 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'), OPT_SET_INT(0, "textconv", &opt, N_("for blob objects, run textconv on object's content"), 'c'), - OPT_SET_INT(0, "batch", &batch, - N_("show info and content of objects fed from the standard input"), - BATCH), - OPT_SET_INT(0, "batch-check", &batch, - N_("show info about objects fed from the standard input"), - BATCH_CHECK), + { OPTION_CALLBACK, 0, "batch", &batch, "format", + N_("show info and content of objects fed from the standard input"), + PARSE_OPT_OPTARG, batch_option_callback }, + { OPTION_CALLBACK, 0, "batch-check", &batch, "format", + N_("show info about objects fed from the standard input"), + PARSE_OPT_OPTARG, batch_option_callback }, OPT_END() }; @@ -222,19 +358,19 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) else usage_with_options(cat_file_usage, options); } - if (!opt && !batch) { + if (!opt && !batch.enabled) { if (argc == 2) { exp_type = argv[0]; obj_name = argv[1]; } else usage_with_options(cat_file_usage, options); } - if (batch && (opt || argc)) { + if (batch.enabled && (opt || argc)) { usage_with_options(cat_file_usage, options); } - if (batch) - return batch_objects(batch); + if (batch.enabled) + return batch_objects(&batch); return cat_one_file(opt, exp_type, obj_name); } diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c new file mode 100644 index 0000000000..8f4d809bd8 --- /dev/null +++ b/builtin/check-mailmap.c @@ -0,0 +1,66 @@ +#include "builtin.h" +#include "mailmap.h" +#include "parse-options.h" +#include "string-list.h" + +static int use_stdin; +static const char * const check_mailmap_usage[] = { +N_("git check-mailmap [options] <contact>..."), +NULL +}; + +static const struct option check_mailmap_options[] = { + OPT_BOOL(0, "stdin", &use_stdin, N_("also read contacts from stdin")), + OPT_END() +}; + +static void check_mailmap(struct string_list *mailmap, const char *contact) +{ + const char *name, *mail; + size_t namelen, maillen; + struct ident_split ident; + + if (split_ident_line(&ident, contact, strlen(contact))) + die(_("unable to parse contact: %s"), contact); + + name = ident.name_begin; + namelen = ident.name_end - ident.name_begin; + mail = ident.mail_begin; + maillen = ident.mail_end - ident.mail_begin; + + map_user(mailmap, &mail, &maillen, &name, &namelen); + + if (namelen) + printf("%.*s ", (int)namelen, name); + printf("<%.*s>\n", (int)maillen, mail); +} + +int cmd_check_mailmap(int argc, const char **argv, const char *prefix) +{ + int i; + struct string_list mailmap = STRING_LIST_INIT_NODUP; + + git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, check_mailmap_options, + check_mailmap_usage, 0); + if (argc == 0 && !use_stdin) + die(_("no contacts specified")); + + read_mailmap(&mailmap, NULL); + + for (i = 0; i < argc; ++i) + check_mailmap(&mailmap, argv[i]); + maybe_flush_or_die(stdout, "stdout"); + + if (use_stdin) { + struct strbuf buf = STRBUF_INIT; + while (strbuf_getline(&buf, stdin, '\n') != EOF) { + check_mailmap(&mailmap, buf.buf); + maybe_flush_or_die(stdout, "stdout"); + } + strbuf_release(&buf); + } + + clear_mailmap(&mailmap); + return 0; +} diff --git a/builtin/checkout.c b/builtin/checkout.c index f5b50e520f..7025938ae3 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -97,7 +97,7 @@ static int read_tree_some(struct tree *tree, const char **pathspec) return 0; } -static int skip_same_name(struct cache_entry *ce, int pos) +static int skip_same_name(const struct cache_entry *ce, int pos) { while (++pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) @@ -105,7 +105,7 @@ static int skip_same_name(struct cache_entry *ce, int pos) return pos; } -static int check_stage(int stage, struct cache_entry *ce, int pos) +static int check_stage(int stage, const struct cache_entry *ce, int pos) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@ -119,7 +119,7 @@ static int check_stage(int stage, struct cache_entry *ce, int pos) return error(_("path '%s' does not have their version"), ce->name); } -static int check_stages(unsigned stages, struct cache_entry *ce, int pos) +static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) { unsigned seen = 0; const char *name = ce->name; @@ -321,7 +321,7 @@ static int checkout_paths(const struct checkout_opts *opts, /* Any unmerged paths? */ for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) continue; @@ -587,7 +587,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, struct branch_info *new) { struct strbuf msg = STRBUF_INIT; - const char *old_desc; + const char *old_desc, *reflog_msg; if (opts->new_branch) { if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { @@ -620,8 +620,13 @@ static void update_refs_for_switch(const struct checkout_opts *opts, old_desc = old->name; if (!old_desc && old->commit) old_desc = sha1_to_hex(old->commit->object.sha1); - strbuf_addf(&msg, "checkout: moving from %s to %s", - old_desc ? old_desc : "(invalid)", new->name); + + reflog_msg = getenv("GIT_REFLOG_ACTION"); + if (!reflog_msg) + strbuf_addf(&msg, "checkout: moving from %s to %s", + old_desc ? old_desc : "(invalid)", new->name); + else + strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg)); if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) { /* Nothing to do. */ @@ -838,13 +843,16 @@ static int check_tracking_name(struct remote *remote, void *cb_data) memset(&query, 0, sizeof(struct refspec)); query.src = cb->src_ref; if (remote_find_tracking(remote, &query) || - get_sha1(query.dst, cb->dst_sha1)) + get_sha1(query.dst, cb->dst_sha1)) { + free(query.dst); return 0; + } if (cb->dst_ref) { + free(query.dst); cb->unique = 0; return 0; } - cb->dst_ref = xstrdup(query.dst); + cb->dst_ref = query.dst; return 0; } diff --git a/builtin/clean.c b/builtin/clean.c index 04e396b17a..dba8387747 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -13,11 +13,16 @@ #include "refs.h" #include "string-list.h" #include "quote.h" +#include "column.h" +#include "color.h" static int force = -1; /* unset */ +static int interactive; +static struct string_list del_list = STRING_LIST_INIT_DUP; +static unsigned int colopts; static const char *const builtin_clean_usage[] = { - N_("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."), + N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."), NULL }; @@ -27,11 +32,112 @@ static const char *msg_skip_git_dir = N_("Skipping repository %s\n"); static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); static const char *msg_warn_remove_failed = N_("failed to remove %s"); +static int clean_use_color = -1; +static char clean_colors[][COLOR_MAXLEN] = { + GIT_COLOR_RESET, + GIT_COLOR_NORMAL, /* PLAIN */ + GIT_COLOR_BOLD_BLUE, /* PROMPT */ + GIT_COLOR_BOLD, /* HEADER */ + GIT_COLOR_BOLD_RED, /* HELP */ + GIT_COLOR_BOLD_RED, /* ERROR */ +}; +enum color_clean { + CLEAN_COLOR_RESET = 0, + CLEAN_COLOR_PLAIN = 1, + CLEAN_COLOR_PROMPT = 2, + CLEAN_COLOR_HEADER = 3, + CLEAN_COLOR_HELP = 4, + CLEAN_COLOR_ERROR = 5, +}; + +#define MENU_OPTS_SINGLETON 01 +#define MENU_OPTS_IMMEDIATE 02 +#define MENU_OPTS_LIST_ONLY 04 + +struct menu_opts { + const char *header; + const char *prompt; + int flags; +}; + +#define MENU_RETURN_NO_LOOP 10 + +struct menu_item { + char hotkey; + const char *title; + int selected; + int (*fn)(); +}; + +enum menu_stuff_type { + MENU_STUFF_TYPE_STRING_LIST = 1, + MENU_STUFF_TYPE_MENU_ITEM +}; + +struct menu_stuff { + enum menu_stuff_type type; + int nr; + void *stuff; +}; + +static int parse_clean_color_slot(const char *var) +{ + if (!strcasecmp(var, "reset")) + return CLEAN_COLOR_RESET; + if (!strcasecmp(var, "plain")) + return CLEAN_COLOR_PLAIN; + if (!strcasecmp(var, "prompt")) + return CLEAN_COLOR_PROMPT; + if (!strcasecmp(var, "header")) + return CLEAN_COLOR_HEADER; + if (!strcasecmp(var, "help")) + return CLEAN_COLOR_HELP; + if (!strcasecmp(var, "error")) + return CLEAN_COLOR_ERROR; + return -1; +} + static int git_clean_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, "clean.requireforce")) + if (!prefixcmp(var, "column.")) + return git_column_config(var, value, "clean", &colopts); + + /* honors the color.interactive* config variables which also + applied in git-add--interactive and git-stash */ + if (!strcmp(var, "color.interactive")) { + clean_use_color = git_config_colorbool(var, value); + return 0; + } + if (!prefixcmp(var, "color.interactive.")) { + int slot = parse_clean_color_slot(var + + strlen("color.interactive.")); + if (slot < 0) + return 0; + if (!value) + return config_error_nonbool(var); + color_parse(value, var, clean_colors[slot]); + return 0; + } + + if (!strcmp(var, "clean.requireforce")) { force = !git_config_bool(var, value); - return git_default_config(var, value, cb); + return 0; + } + + /* inspect the color.ui config variable and others */ + return git_color_default_config(var, value, cb); +} + +static const char *clean_get_color(enum color_clean ix) +{ + if (want_color(clean_use_color)) + return clean_colors[ix]; + return ""; +} + +static void clean_print_color(enum color_clean ix) +{ + printf("%s", clean_get_color(ix)); } static int exclude_cb(const struct option *opt, const char *arg, int unset) @@ -56,7 +162,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) { if (!quiet) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), quoted.buf); } @@ -70,7 +176,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, /* an empty dir could be removed even if it is unreadble */ res = dry_run ? 0 : rmdir(path->buf); if (res) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); warning(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; } @@ -94,7 +200,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone)) ret = 1; if (gone) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); string_list_append(&dels, quoted.buf); } else *dir_gone = 0; @@ -102,10 +208,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, } else { res = dry_run ? 0 : unlink(path->buf); if (!res) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); string_list_append(&dels, quoted.buf); } else { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); warning(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; ret = 1; @@ -127,7 +233,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, if (!res) *dir_gone = 1; else { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); warning(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; ret = 1; @@ -142,24 +248,609 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, return ret; } +static void pretty_print_dels(void) +{ + struct string_list list = STRING_LIST_INIT_DUP; + struct string_list_item *item; + struct strbuf buf = STRBUF_INIT; + const char *qname; + struct column_options copts; + + for_each_string_list_item(item, &del_list) { + qname = quote_path_relative(item->string, NULL, &buf); + string_list_append(&list, qname); + } + + /* + * always enable column display, we only consult column.* + * about layout strategy and stuff + */ + colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED; + memset(&copts, 0, sizeof(copts)); + copts.indent = " "; + copts.padding = 2; + print_columns(&list, colopts, &copts); + strbuf_release(&buf); + string_list_clear(&list, 0); +} + +static void pretty_print_menus(struct string_list *menu_list) +{ + unsigned int local_colopts = 0; + struct column_options copts; + + local_colopts = COL_ENABLED | COL_ROW; + memset(&copts, 0, sizeof(copts)); + copts.indent = " "; + copts.padding = 2; + print_columns(menu_list, local_colopts, &copts); +} + +static void prompt_help_cmd(int singleton) +{ + clean_print_color(CLEAN_COLOR_HELP); + printf_ln(singleton ? + _("Prompt help:\n" + "1 - select a numbered item\n" + "foo - select item based on unique prefix\n" + " - (empty) select nothing") : + _("Prompt help:\n" + "1 - select a single item\n" + "3-5 - select a range of items\n" + "2-3,6-9 - select multiple ranges\n" + "foo - select item based on unique prefix\n" + "-... - unselect specified items\n" + "* - choose all items\n" + " - (empty) finish selecting")); + clean_print_color(CLEAN_COLOR_RESET); +} + +/* + * display menu stuff with number prefix and hotkey highlight + */ +static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen) +{ + struct string_list menu_list = STRING_LIST_INIT_DUP; + struct strbuf menu = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct menu_item *menu_item; + struct string_list_item *string_list_item; + int i; + + switch (stuff->type) { + default: + die("Bad type of menu_staff when print menu"); + case MENU_STUFF_TYPE_MENU_ITEM: + menu_item = (struct menu_item *)stuff->stuff; + for (i = 0; i < stuff->nr; i++, menu_item++) { + const char *p; + int highlighted = 0; + + p = menu_item->title; + if ((*chosen)[i] < 0) + (*chosen)[i] = menu_item->selected ? 1 : 0; + strbuf_addf(&menu, "%s%2d: ", (*chosen)[i] ? "*" : " ", i+1); + for (; *p; p++) { + if (!highlighted && *p == menu_item->hotkey) { + strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT)); + strbuf_addch(&menu, *p); + strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET)); + highlighted = 1; + } else { + strbuf_addch(&menu, *p); + } + } + string_list_append(&menu_list, menu.buf); + strbuf_reset(&menu); + } + break; + case MENU_STUFF_TYPE_STRING_LIST: + i = 0; + for_each_string_list_item(string_list_item, (struct string_list *)stuff->stuff) { + if ((*chosen)[i] < 0) + (*chosen)[i] = 0; + strbuf_addf(&menu, "%s%2d: %s", + (*chosen)[i] ? "*" : " ", i+1, string_list_item->string); + string_list_append(&menu_list, menu.buf); + strbuf_reset(&menu); + i++; + } + break; + } + + pretty_print_menus(&menu_list); + + strbuf_release(&menu); + strbuf_release(&buf); + string_list_clear(&menu_list, 0); +} + +/* + * Parse user input, and return choice(s) for menu (menu_stuff). + * + * Input + * (for single choice) + * 1 - select a numbered item + * foo - select item based on menu title + * - (empty) select nothing + * + * (for multiple choice) + * 1 - select a single item + * 3-5 - select a range of items + * 2-3,6-9 - select multiple ranges + * foo - select item based on menu title + * -... - unselect specified items + * * - choose all items + * - (empty) finish selecting + * + * The parse result will be saved in array **chosen, and + * return number of total selections. + */ +static int parse_choice(struct menu_stuff *menu_stuff, + int is_single, + struct strbuf input, + int **chosen) +{ + struct strbuf **choice_list, **ptr; + struct menu_item *menu_item; + struct string_list_item *string_list_item; + int nr = 0; + int i; + + if (is_single) { + choice_list = strbuf_split_max(&input, '\n', 0); + } else { + char *p = input.buf; + do { + if (*p == ',') + *p = ' '; + } while (*p++); + choice_list = strbuf_split_max(&input, ' ', 0); + } + + for (ptr = choice_list; *ptr; ptr++) { + char *p; + int choose = 1; + int bottom = 0, top = 0; + int is_range, is_number; + + strbuf_trim(*ptr); + if (!(*ptr)->len) + continue; + + /* Input that begins with '-'; unchoose */ + if (*(*ptr)->buf == '-') { + choose = 0; + strbuf_remove((*ptr), 0, 1); + } + + is_range = 0; + is_number = 1; + for (p = (*ptr)->buf; *p; p++) { + if ('-' == *p) { + if (!is_range) { + is_range = 1; + is_number = 0; + } else { + is_number = 0; + is_range = 0; + break; + } + } else if (!isdigit(*p)) { + is_number = 0; + is_range = 0; + break; + } + } + + if (is_number) { + bottom = atoi((*ptr)->buf); + top = bottom; + } else if (is_range) { + bottom = atoi((*ptr)->buf); + /* a range can be specified like 5-7 or 5- */ + if (!*(strchr((*ptr)->buf, '-') + 1)) + top = menu_stuff->nr; + else + top = atoi(strchr((*ptr)->buf, '-') + 1); + } else if (!strcmp((*ptr)->buf, "*")) { + bottom = 1; + top = menu_stuff->nr; + } else { + switch (menu_stuff->type) { + default: + die("Bad type of menu_stuff when parse choice"); + case MENU_STUFF_TYPE_MENU_ITEM: + menu_item = (struct menu_item *)menu_stuff->stuff; + for (i = 0; i < menu_stuff->nr; i++, menu_item++) { + if (((*ptr)->len == 1 && + *(*ptr)->buf == menu_item->hotkey) || + !strcasecmp((*ptr)->buf, menu_item->title)) { + bottom = i + 1; + top = bottom; + break; + } + } + break; + case MENU_STUFF_TYPE_STRING_LIST: + string_list_item = ((struct string_list *)menu_stuff->stuff)->items; + for (i = 0; i < menu_stuff->nr; i++, string_list_item++) { + if (!strcasecmp((*ptr)->buf, string_list_item->string)) { + bottom = i + 1; + top = bottom; + break; + } + } + break; + } + } + + if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || + (is_single && bottom != top)) { + clean_print_color(CLEAN_COLOR_ERROR); + printf_ln(_("Huh (%s)?"), (*ptr)->buf); + clean_print_color(CLEAN_COLOR_RESET); + continue; + } + + for (i = bottom; i <= top; i++) + (*chosen)[i-1] = choose; + } + + strbuf_list_free(choice_list); + + for (i = 0; i < menu_stuff->nr; i++) + nr += (*chosen)[i]; + return nr; +} + +/* + * Implement a git-add-interactive compatible UI, which is borrowed + * from git-add--interactive.perl. + * + * Return value: + * + * - Return an array of integers + * - , and it is up to you to free the allocated memory. + * - The array ends with EOF. + * - If user pressed CTRL-D (i.e. EOF), no selection returned. + */ +static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) +{ + struct strbuf choice = STRBUF_INIT; + int *chosen, *result; + int nr = 0; + int eof = 0; + int i; + + chosen = xmalloc(sizeof(int) * stuff->nr); + /* set chosen as uninitialized */ + for (i = 0; i < stuff->nr; i++) + chosen[i] = -1; + + for (;;) { + if (opts->header) { + printf_ln("%s%s%s", + clean_get_color(CLEAN_COLOR_HEADER), + _(opts->header), + clean_get_color(CLEAN_COLOR_RESET)); + } + + /* chosen will be initialized by print_highlight_menu_stuff */ + print_highlight_menu_stuff(stuff, &chosen); + + if (opts->flags & MENU_OPTS_LIST_ONLY) + break; + + if (opts->prompt) { + printf("%s%s%s%s", + clean_get_color(CLEAN_COLOR_PROMPT), + _(opts->prompt), + opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ", + clean_get_color(CLEAN_COLOR_RESET)); + } + + if (strbuf_getline(&choice, stdin, '\n') != EOF) { + strbuf_trim(&choice); + } else { + eof = 1; + break; + } + + /* help for prompt */ + if (!strcmp(choice.buf, "?")) { + prompt_help_cmd(opts->flags & MENU_OPTS_SINGLETON); + continue; + } + + /* for a multiple-choice menu, press ENTER (empty) will return back */ + if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len) + break; + + nr = parse_choice(stuff, + opts->flags & MENU_OPTS_SINGLETON, + choice, + &chosen); + + if (opts->flags & MENU_OPTS_SINGLETON) { + if (nr) + break; + } else if (opts->flags & MENU_OPTS_IMMEDIATE) { + break; + } + } + + if (eof) { + result = xmalloc(sizeof(int)); + *result = EOF; + } else { + int j = 0; + + /* + * recalculate nr, if return back from menu directly with + * default selections. + */ + if (!nr) { + for (i = 0; i < stuff->nr; i++) + nr += chosen[i]; + } + + result = xmalloc(sizeof(int) * (nr + 1)); + memset(result, 0, sizeof(int) * (nr + 1)); + for (i = 0; i < stuff->nr && j < nr; i++) { + if (chosen[i]) + result[j++] = i; + } + result[j] = EOF; + } + + free(chosen); + strbuf_release(&choice); + return result; +} + +static int clean_cmd(void) +{ + return MENU_RETURN_NO_LOOP; +} + +static int filter_by_patterns_cmd(void) +{ + struct dir_struct dir; + struct strbuf confirm = STRBUF_INIT; + struct strbuf **ignore_list; + struct string_list_item *item; + struct exclude_list *el; + int changed = -1, i; + + for (;;) { + if (!del_list.nr) + break; + + if (changed) + pretty_print_dels(); + + clean_print_color(CLEAN_COLOR_PROMPT); + printf(_("Input ignore patterns>> ")); + clean_print_color(CLEAN_COLOR_RESET); + if (strbuf_getline(&confirm, stdin, '\n') != EOF) + strbuf_trim(&confirm); + else + putchar('\n'); + + /* quit filter_by_pattern mode if press ENTER or Ctrl-D */ + if (!confirm.len) + break; + + memset(&dir, 0, sizeof(dir)); + el = add_exclude_list(&dir, EXC_CMDL, "manual exclude"); + ignore_list = strbuf_split_max(&confirm, ' ', 0); + + for (i = 0; ignore_list[i]; i++) { + strbuf_trim(ignore_list[i]); + if (!ignore_list[i]->len) + continue; + + add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1)); + } + + changed = 0; + for_each_string_list_item(item, &del_list) { + int dtype = DT_UNKNOWN; + + if (is_excluded(&dir, item->string, &dtype)) { + *item->string = '\0'; + changed++; + } + } + + if (changed) { + string_list_remove_empty_items(&del_list, 0); + } else { + clean_print_color(CLEAN_COLOR_ERROR); + printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf); + clean_print_color(CLEAN_COLOR_RESET); + } + + strbuf_list_free(ignore_list); + clear_directory(&dir); + } + + strbuf_release(&confirm); + return 0; +} + +static int select_by_numbers_cmd(void) +{ + struct menu_opts menu_opts; + struct menu_stuff menu_stuff; + struct string_list_item *items; + int *chosen; + int i, j; + + menu_opts.header = NULL; + menu_opts.prompt = N_("Select items to delete"); + menu_opts.flags = 0; + + menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST; + menu_stuff.stuff = &del_list; + menu_stuff.nr = del_list.nr; + + chosen = list_and_choose(&menu_opts, &menu_stuff); + items = del_list.items; + for (i = 0, j = 0; i < del_list.nr; i++) { + if (i < chosen[j]) { + *(items[i].string) = '\0'; + } else if (i == chosen[j]) { + /* delete selected item */ + j++; + continue; + } else { + /* end of chosen (chosen[j] == EOF), won't delete */ + *(items[i].string) = '\0'; + } + } + + string_list_remove_empty_items(&del_list, 0); + + free(chosen); + return 0; +} + +static int ask_each_cmd(void) +{ + struct strbuf confirm = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct string_list_item *item; + const char *qname; + int changed = 0, eof = 0; + + for_each_string_list_item(item, &del_list) { + /* Ctrl-D should stop removing files */ + if (!eof) { + qname = quote_path_relative(item->string, NULL, &buf); + printf(_("remove %s? "), qname); + if (strbuf_getline(&confirm, stdin, '\n') != EOF) { + strbuf_trim(&confirm); + } else { + putchar('\n'); + eof = 1; + } + } + if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) { + *item->string = '\0'; + changed++; + } + } + + if (changed) + string_list_remove_empty_items(&del_list, 0); + + strbuf_release(&buf); + strbuf_release(&confirm); + return MENU_RETURN_NO_LOOP; +} + +static int quit_cmd(void) +{ + string_list_clear(&del_list, 0); + printf_ln(_("Bye.")); + return MENU_RETURN_NO_LOOP; +} + +static int help_cmd(void) +{ + clean_print_color(CLEAN_COLOR_HELP); + printf_ln(_( + "clean - start cleaning\n" + "filter by pattern - exclude items from deletion\n" + "select by numbers - select items to be deleted by numbers\n" + "ask each - confirm each deletion (like \"rm -i\")\n" + "quit - stop cleaning\n" + "help - this screen\n" + "? - help for prompt selection" + )); + clean_print_color(CLEAN_COLOR_RESET); + return 0; +} + +static void interactive_main_loop(void) +{ + while (del_list.nr) { + struct menu_opts menu_opts; + struct menu_stuff menu_stuff; + struct menu_item menus[] = { + {'c', "clean", 0, clean_cmd}, + {'f', "filter by pattern", 0, filter_by_patterns_cmd}, + {'s', "select by numbers", 0, select_by_numbers_cmd}, + {'a', "ask each", 0, ask_each_cmd}, + {'q', "quit", 0, quit_cmd}, + {'h', "help", 0, help_cmd}, + }; + int *chosen; + + menu_opts.header = N_("*** Commands ***"); + menu_opts.prompt = N_("What now"); + menu_opts.flags = MENU_OPTS_SINGLETON; + + menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM; + menu_stuff.stuff = menus; + menu_stuff.nr = sizeof(menus) / sizeof(struct menu_item); + + clean_print_color(CLEAN_COLOR_HEADER); + printf_ln(Q_("Would remove the following item:", + "Would remove the following items:", + del_list.nr)); + clean_print_color(CLEAN_COLOR_RESET); + + pretty_print_dels(); + + chosen = list_and_choose(&menu_opts, &menu_stuff); + + if (*chosen != EOF) { + int ret; + ret = menus[*chosen].fn(); + if (ret != MENU_RETURN_NO_LOOP) { + free(chosen); + chosen = NULL; + if (!del_list.nr) { + clean_print_color(CLEAN_COLOR_ERROR); + printf_ln(_("No more files to clean, exiting.")); + clean_print_color(CLEAN_COLOR_RESET); + break; + } + continue; + } + } else { + quit_cmd(); + } + + free(chosen); + chosen = NULL; + break; + } +} + int cmd_clean(int argc, const char **argv, const char *prefix) { int i, res; int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0; int ignored_only = 0, config_set = 0, errors = 0, gone = 1; int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; - struct strbuf directory = STRBUF_INIT; + struct strbuf abs_path = STRBUF_INIT; struct dir_struct dir; static const char **pathspec; struct strbuf buf = STRBUF_INIT; struct string_list exclude_list = STRING_LIST_INIT_NODUP; struct exclude_list *el; + struct string_list_item *item; const char *qname; char *seen = NULL; struct option options[] = { OPT__QUIET(&quiet, N_("do not print names of files removed")), OPT__DRY_RUN(&dry_run, N_("dry run")), OPT__FORCE(&force, N_("force")), + OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")), OPT_BOOLEAN('d', NULL, &remove_directories, N_("remove whole directories")), { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"), @@ -186,12 +877,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (ignored && ignored_only) die(_("-x and -X cannot be used together")); - if (!dry_run && !force) { + if (!interactive && !dry_run && !force) { if (config_set) - die(_("clean.requireForce set to true and neither -n nor -f given; " + die(_("clean.requireForce set to true and neither -i, -n nor -f given; " "refusing to clean")); else - die(_("clean.requireForce defaults to true and neither -n nor -f given; " + die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; " "refusing to clean")); } @@ -221,8 +912,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) struct dir_entry *ent = dir.entries[i]; int len, pos; int matches = 0; - struct cache_entry *ce; + const struct cache_entry *ce; struct stat st; + const char *rel; /* * Remove the '/' at the end that directory @@ -242,13 +934,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) continue; /* Yup, this one exists unmerged */ } - /* - * we might have removed this as part of earlier - * recursive directory removal, so lstat() here could - * fail with ENOENT. - */ if (lstat(ent->name, &st)) - continue; + die_errno("Cannot lstat '%s'", ent->name); if (pathspec) { memset(seen, 0, argc > 0 ? argc : 1); @@ -257,33 +944,62 @@ int cmd_clean(int argc, const char **argv, const char *prefix) } if (S_ISDIR(st.st_mode)) { - strbuf_addstr(&directory, ent->name); if (remove_directories || (matches == MATCHED_EXACTLY)) { - if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone)) - errors++; - if (gone && !quiet) { - qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); - printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); - } + rel = relative_path(ent->name, prefix, &buf); + string_list_append(&del_list, rel); } - strbuf_reset(&directory); } else { if (pathspec && !matches) continue; - res = dry_run ? 0 : unlink(ent->name); + rel = relative_path(ent->name, prefix, &buf); + string_list_append(&del_list, rel); + } + } + + if (interactive && del_list.nr > 0) + interactive_main_loop(); + + for_each_string_list_item(item, &del_list) { + struct stat st; + + if (prefix) + strbuf_addstr(&abs_path, prefix); + + strbuf_addstr(&abs_path, item->string); + + /* + * we might have removed this as part of earlier + * recursive directory removal, so lstat() here could + * fail with ENOENT. + */ + if (lstat(abs_path.buf, &st)) + continue; + + if (S_ISDIR(st.st_mode)) { + if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone)) + errors++; + if (gone && !quiet) { + qname = quote_path_relative(item->string, NULL, &buf); + printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); + } + } else { + res = dry_run ? 0 : unlink(abs_path.buf); if (res) { - qname = quote_path_relative(ent->name, -1, &buf, prefix); + qname = quote_path_relative(item->string, NULL, &buf); warning(_(msg_warn_remove_failed), qname); errors++; } else if (!quiet) { - qname = quote_path_relative(ent->name, -1, &buf, prefix); + qname = quote_path_relative(item->string, NULL, &buf); printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } + strbuf_reset(&abs_path); } free(seen); - strbuf_release(&directory); + strbuf_release(&abs_path); + strbuf_release(&buf); + string_list_clear(&del_list, 0); string_list_clear(&exclude_list, 0); return (errors != 0); } diff --git a/builtin/clone.c b/builtin/clone.c index 14b1323568..430307b298 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -545,17 +545,20 @@ static void update_remote_refs(const struct ref *refs, const struct ref *remote_head_points_at, const char *branch_top, const char *msg, - struct transport *transport) + struct transport *transport, + int check_connectivity) { const struct ref *rm = mapped_refs; - if (0 <= option_verbosity) - printf(_("Checking connectivity... ")); - if (check_everything_connected_with_transport(iterate_ref_map, - 0, &rm, transport)) - die(_("remote did not send all necessary objects")); - if (0 <= option_verbosity) - printf(_("done\n")); + if (check_connectivity) { + if (0 <= option_verbosity) + printf(_("Checking connectivity... ")); + if (check_everything_connected_with_transport(iterate_ref_map, + 0, &rm, transport)) + die(_("remote did not send all necessary objects")); + if (0 <= option_verbosity) + printf(_("done\n")); + } if (refs) { write_remote_refs(mapped_refs); @@ -701,7 +704,7 @@ static void write_refspec_config(const char* src_ref_prefix, /* * otherwise, the next "git fetch" will * simply fetch from HEAD without updating - * any remote tracking branch, which is what + * any remote-tracking branch, which is what * we want. */ } else { @@ -963,7 +966,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_fetch_refs(transport, mapped_refs); update_remote_refs(refs, mapped_refs, remote_head_points_at, - branch_top.buf, reflog_msg.buf, transport); + branch_top.buf, reflog_msg.buf, transport, !is_local); update_head(our_head_points_at, remote_head, reflog_msg.buf); diff --git a/builtin/commit.c b/builtin/commit.c index 1621dfcd40..003bd7dbec 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -29,6 +29,7 @@ #include "gpg-interface.h" #include "column.h" #include "sequencer.h" +#include "notes-utils.h" static const char * const builtin_commit_usage[] = { N_("git commit [options] [--] <pathspec>..."), @@ -111,12 +112,14 @@ static int show_ignored_in_status, have_option_m; static const char *only_include_assumed; static struct strbuf message = STRBUF_INIT; -static enum { +static enum status_format { STATUS_FORMAT_NONE = 0, STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN -} status_format; + STATUS_FORMAT_PORCELAIN, + + STATUS_FORMAT_UNSPECIFIED +} status_format = STATUS_FORMAT_UNSPECIFIED; static int opt_parse_m(const struct option *opt, const char *arg, int unset) { @@ -204,7 +207,7 @@ static int list_paths(struct string_list *list, const char *with_tree, } for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; struct string_list_item *item; if (ce->ce_flags & CE_UPDATE) @@ -459,6 +462,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(s); break; + case STATUS_FORMAT_UNSPECIFIED: + die("BUG: finalize_deferred_config() should have been called"); + break; case STATUS_FORMAT_NONE: case STATUS_FORMAT_LONG: wt_status_print(s); @@ -528,7 +534,6 @@ static void determine_author_info(struct strbuf *author_ident) (lb - strlen(" ") - (a + strlen("\nauthor ")))); email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<"))); - date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> "))); len = eol - (rb + strlen("> ")); date = xmalloc(len + 2); *date = '@'; @@ -960,6 +965,42 @@ static const char *read_commit_message(const char *name) return logmsg_reencode(commit, NULL, out_enc); } +/* + * Enumerate what needs to be propagated when --porcelain + * is not in effect here. + */ +static struct status_deferred_config { + enum status_format status_format; + int show_branch; +} status_deferred_config = { + STATUS_FORMAT_UNSPECIFIED, + -1 /* unspecified */ +}; + +static void finalize_deferred_config(struct wt_status *s) +{ + int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN && + !s->null_termination); + + if (s->null_termination) { + if (status_format == STATUS_FORMAT_NONE || + status_format == STATUS_FORMAT_UNSPECIFIED) + status_format = STATUS_FORMAT_PORCELAIN; + else if (status_format == STATUS_FORMAT_LONG) + die(_("--long and -z are incompatible")); + } + + if (use_deferred_config && status_format == STATUS_FORMAT_UNSPECIFIED) + status_format = status_deferred_config.status_format; + if (status_format == STATUS_FORMAT_UNSPECIFIED) + status_format = STATUS_FORMAT_NONE; + + if (use_deferred_config && s->show_branch < 0) + s->show_branch = status_deferred_config.show_branch; + if (s->show_branch < 0) + s->show_branch = 0; +} + static int parse_and_validate_options(int argc, const char *argv[], const struct option *options, const char * const usage[], @@ -970,6 +1011,7 @@ static int parse_and_validate_options(int argc, const char *argv[], int f = 0; argc = parse_options(argc, argv, prefix, options, usage, 0); + finalize_deferred_config(s); if (force_author && !strchr(force_author, '>')) force_author = find_author_by_nickname(force_author); @@ -1054,12 +1096,6 @@ static int parse_and_validate_options(int argc, const char *argv[], if (all && argc > 0) die(_("Paths with -a does not make sense.")); - if (s->null_termination) { - if (status_format == STATUS_FORMAT_NONE) - status_format = STATUS_FORMAT_PORCELAIN; - else if (status_format == STATUS_FORMAT_LONG) - die(_("--long and -z are incompatible")); - } if (status_format != STATUS_FORMAT_NONE) dry_run = 1; @@ -1112,6 +1148,17 @@ static int git_status_config(const char *k, const char *v, void *cb) s->submodule_summary = -1; return 0; } + if (!strcmp(k, "status.short")) { + if (git_config_bool(k, v)) + status_deferred_config.status_format = STATUS_FORMAT_SHORT; + else + status_deferred_config.status_format = STATUS_FORMAT_NONE; + return 0; + } + if (!strcmp(k, "status.branch")) { + status_deferred_config.show_branch = git_config_bool(k, v); + return 0; + } if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { s->use_color = git_config_colorbool(k, v); return 0; @@ -1154,8 +1201,8 @@ int cmd_status(int argc, const char **argv, const char *prefix) OPT__VERBOSE(&verbose, N_("be verbose")), OPT_SET_INT('s', "short", &status_format, N_("show status concisely"), STATUS_FORMAT_SHORT), - OPT_BOOLEAN('b', "branch", &s.show_branch, - N_("show branch information")), + OPT_BOOL('b', "branch", &s.show_branch, + N_("show branch information")), OPT_SET_INT(0, "porcelain", &status_format, N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), @@ -1188,13 +1235,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) builtin_status_options, builtin_status_usage, 0); finalize_colopts(&s.colopts, -1); - - if (s.null_termination) { - if (status_format == STATUS_FORMAT_NONE) - status_format = STATUS_FORMAT_PORCELAIN; - else if (status_format == STATUS_FORMAT_LONG) - die(_("--long and -z are incompatible")); - } + finalize_deferred_config(&s); handle_untracked_files_arg(&s); if (show_ignored_in_status) @@ -1223,6 +1264,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(&s); break; + case STATUS_FORMAT_UNSPECIFIED: + die("BUG: finalize_deferred_config() should have been called"); + break; case STATUS_FORMAT_NONE: case STATUS_FORMAT_LONG: s.verbose = verbose; @@ -1391,7 +1435,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")), OPT_SET_INT(0, "short", &status_format, N_("show status concisely"), STATUS_FORMAT_SHORT), - OPT_BOOLEAN(0, "branch", &s.show_branch, N_("show branch information")), + OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")), OPT_SET_INT(0, "porcelain", &status_format, N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), OPT_SET_INT(0, "long", &status_format, @@ -1432,6 +1476,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) wt_status_prepare(&s); gitmodules_config(); git_config(git_commit_config, &s); + status_format = STATUS_FORMAT_NONE; /* Ignore status.short */ determine_whence(&s); s.colopts = 0; @@ -1593,7 +1638,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (cfg) { /* we are amending, so current_head is not NULL */ copy_note_for_rewrite(cfg, current_head->object.sha1, sha1); - finish_copy_notes_for_rewrite(cfg); + finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'"); } run_rewrite_hook(current_head->object.sha1, sha1); } diff --git a/builtin/config.c b/builtin/config.c index 19ffcaf187..4010c4320a 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -21,6 +21,7 @@ static char term = '\n'; static int use_global_config, use_system_config, use_local_config; static const char *given_config_file; +static const char *given_config_blob; static int actions, types; static const char *get_color_slot, *get_colorbool_slot; static int end_null; @@ -53,6 +54,7 @@ static struct option builtin_config_options[] = { OPT_BOOLEAN(0, "system", &use_system_config, N_("use system config file")), OPT_BOOLEAN(0, "local", &use_local_config, N_("use repository config file")), OPT_STRING('f', "file", &given_config_file, N_("file"), N_("use given config file")), + OPT_STRING(0, "blob", &given_config_blob, N_("blob-id"), N_("read config from given blob object")), OPT_GROUP(N_("Action")), OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET), OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL), @@ -218,7 +220,8 @@ static int get_value(const char *key_, const char *regex_) } git_config_with_options(collect_config, &values, - given_config_file, respect_includes); + given_config_file, given_config_blob, + respect_includes); ret = !values.nr; @@ -302,7 +305,8 @@ static void get_color(const char *def_color) get_color_found = 0; parsed_color[0] = '\0'; git_config_with_options(git_get_color_config, NULL, - given_config_file, respect_includes); + given_config_file, given_config_blob, + respect_includes); if (!get_color_found && def_color) color_parse(def_color, "command line", parsed_color); @@ -329,8 +333,10 @@ static int get_colorbool(int print) { get_colorbool_found = -1; get_diff_color_found = -1; + get_color_ui_found = -1; git_config_with_options(git_get_colorbool_config, NULL, - given_config_file, respect_includes); + given_config_file, given_config_blob, + respect_includes); if (get_colorbool_found < 0) { if (!strcmp(get_colorbool_slot, "color.diff")) @@ -339,6 +345,10 @@ static int get_colorbool(int print) get_colorbool_found = get_color_ui_found; } + if (get_colorbool_found < 0) + /* default value if none found in config */ + get_colorbool_found = GIT_COLOR_AUTO; + get_colorbool_found = want_color(get_colorbool_found); if (print) { @@ -348,6 +358,12 @@ static int get_colorbool(int print) return get_colorbool_found ? 0 : 1; } +static void check_blob_write(void) +{ + if (given_config_blob) + die("writing config blobs is not supported"); +} + int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = !startup_info->have_repository; @@ -359,7 +375,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) builtin_config_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) { + if (use_global_config + use_system_config + use_local_config + + !!given_config_file + !!given_config_blob > 1) { error("only one config file at a time."); usage_with_options(builtin_config_usage, builtin_config_options); } @@ -438,6 +455,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_argc(argc, 0, 0); if (git_config_with_options(show_all_config, NULL, given_config_file, + given_config_blob, respect_includes) < 0) { if (given_config_file) die_errno("unable to read config file '%s'", @@ -450,6 +468,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_argc(argc, 0, 0); if (!given_config_file && nongit) die("not in a git directory"); + if (given_config_blob) + die("editing blobs is not supported"); git_config(git_default_config, NULL); launch_editor(given_config_file ? given_config_file : git_path("config"), @@ -457,6 +477,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) } else if (actions == ACTION_SET) { int ret; + check_blob_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); ret = git_config_set_in_file(given_config_file, argv[0], value); @@ -466,18 +487,21 @@ int cmd_config(int argc, const char **argv, const char *prefix) return ret; } else if (actions == ACTION_SET_ALL) { + check_blob_write(); check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); return git_config_set_multivar_in_file(given_config_file, argv[0], value, argv[2], 0); } else if (actions == ACTION_ADD) { + check_blob_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); return git_config_set_multivar_in_file(given_config_file, argv[0], value, "^$", 0); } else if (actions == ACTION_REPLACE_ALL) { + check_blob_write(); check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); return git_config_set_multivar_in_file(given_config_file, @@ -500,6 +524,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) return get_value(argv[0], argv[1]); } else if (actions == ACTION_UNSET) { + check_blob_write(); check_argc(argc, 1, 2); if (argc == 2) return git_config_set_multivar_in_file(given_config_file, @@ -509,12 +534,14 @@ int cmd_config(int argc, const char **argv, const char *prefix) argv[0], NULL); } else if (actions == ACTION_UNSET_ALL) { + check_blob_write(); check_argc(argc, 1, 2); return git_config_set_multivar_in_file(given_config_file, argv[0], NULL, argv[1], 1); } else if (actions == ACTION_RENAME_SECTION) { int ret; + check_blob_write(); check_argc(argc, 2, 2); ret = git_config_rename_section_in_file(given_config_file, argv[0], argv[1]); @@ -525,6 +552,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) } else if (actions == ACTION_REMOVE_SECTION) { int ret; + check_blob_write(); check_argc(argc, 1, 1); ret = git_config_rename_section_in_file(given_config_file, argv[0], NULL); diff --git a/builtin/describe.c b/builtin/describe.c index 4e675c3d0d..7d73722f59 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -7,6 +7,7 @@ #include "parse-options.h" #include "diff.h" #include "hash.h" +#include "argv-array.h" #define SEEN (1u<<0) #define MAX_TAGS (FLAG_BITS - 1) @@ -442,24 +443,24 @@ int cmd_describe(int argc, const char **argv, const char *prefix) die(_("--long is incompatible with --abbrev=0")); if (contains) { - const char **args = xmalloc((7 + argc) * sizeof(char *)); - int i = 0; - args[i++] = "name-rev"; - args[i++] = "--name-only"; - args[i++] = "--no-undefined"; + struct argv_array args; + + argv_array_init(&args); + argv_array_pushl(&args, "name-rev", + "--peel-tag", "--name-only", "--no-undefined", + NULL); if (always) - args[i++] = "--always"; + argv_array_push(&args, "--always"); if (!all) { - args[i++] = "--tags"; - if (pattern) { - char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1); - sprintf(s, "--refs=refs/tags/%s", pattern); - args[i++] = s; - } + argv_array_push(&args, "--tags"); + if (pattern) + argv_array_pushf(&args, "--refs=refs/tags/%s", pattern); + } + while (*argv) { + argv_array_push(&args, *argv); + argv++; } - memcpy(args + i, argv, argc * sizeof(char *)); - args[i + argc] = NULL; - return cmd_name_rev(i + argc, args, prefix); + return cmd_name_rev(args.argc, args.argv, prefix); } init_hash(&names); diff --git a/builtin/grep.c b/builtin/grep.c index 159e65d47a..d3b3b1db11 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -286,8 +286,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, struct strbuf pathbuf = STRBUF_INIT; if (opt->relative && opt->prefix_length) { - quote_path_relative(filename + tree_name_len, -1, &pathbuf, - opt->prefix); + quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf); strbuf_insert(&pathbuf, 0, filename, tree_name_len); } else { strbuf_addstr(&pathbuf, filename); @@ -318,7 +317,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) struct strbuf buf = STRBUF_INIT; if (opt->relative && opt->prefix_length) - quote_path_relative(filename, -1, &buf, opt->prefix); + quote_path_relative(filename, opt->prefix, &buf); else strbuf_addstr(&buf, filename); @@ -376,7 +375,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int read_cache(); for (nr = 0; nr < active_nr; nr++) { - struct cache_entry *ce = active_cache[nr]; + const struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) continue; if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL)) diff --git a/builtin/help.c b/builtin/help.c index 062957f629..f1e236b912 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -1,6 +1,4 @@ /* - * builtin-help.c - * * Builtin help command */ #include "cache.h" diff --git a/builtin/log.c b/builtin/log.c index 9e2123295f..2625f9881a 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -237,7 +237,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) int i = revs->early_output; int show_header = 1; - sort_in_topological_order(&list, revs->lifo); + sort_in_topological_order(&list, revs->sort_order); while (list && i) { struct commit *commit = list->item; switch (simplify_commit(revs, commit)) { @@ -1112,6 +1112,21 @@ static int cc_callback(const struct option *opt, const char *arg, int unset) return 0; } +static int from_callback(const struct option *opt, const char *arg, int unset) +{ + char **from = opt->value; + + free(*from); + + if (unset) + *from = NULL; + else if (arg) + *from = xstrdup(arg); + else + *from = xstrdup(git_committer_info(IDENT_NO_DATE)); + return 0; +} + int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@ -1134,6 +1149,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int quiet = 0; int reroll_count = -1; char *branch_name = NULL; + char *from = NULL; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, N_("use [PATCH n/m] even with a single patch"), @@ -1177,6 +1193,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) 0, to_callback }, { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"), 0, cc_callback }, + { OPTION_CALLBACK, 0, "from", &from, N_("ident"), + N_("set From address to <ident> (or committer ident if absent)"), + PARSE_OPT_OPTARG, from_callback }, OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"), N_("make first mail a reply to <message-id>")), { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"), @@ -1264,6 +1283,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.extra_headers = strbuf_detach(&buf, NULL); + if (from) { + if (split_ident_line(&rev.from_ident, from, strlen(from))) + die(_("invalid ident line: %s"), from); + } + if (start_number < 0) start_number = 1; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 6a0730f720..5cf3e31370 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -46,10 +46,14 @@ static const char *tag_modified = ""; static const char *tag_skip_worktree = ""; static const char *tag_resolve_undo = ""; -static void write_name(const char* name, size_t len) +static void write_name(const char *name) { - write_name_quoted_relative(name, len, prefix, prefix_len, stdout, - line_terminator); + /* + * With "--full-name", prefix_len=0; this caller needs to pass + * an empty string in that case (a NULL is good for ""). + */ + write_name_quoted_relative(name, prefix_len ? prefix : NULL, + stdout, line_terminator); } static void show_dir_entry(const char *tag, struct dir_entry *ent) @@ -63,7 +67,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) return; fputs(tag, stdout); - write_name(ent->name, ent->len); + write_name(ent->name); } static void show_other_files(struct dir_struct *dir) @@ -127,7 +131,7 @@ static void show_killed_files(struct dir_struct *dir) } } -static void show_ce_entry(const char *tag, struct cache_entry *ce) +static void show_ce_entry(const char *tag, const struct cache_entry *ce) { int len = max_prefix_len; @@ -163,9 +167,9 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) find_unique_abbrev(ce->sha1,abbrev), ce_stage(ce)); } - write_name(ce->name, ce_namelen(ce)); + write_name(ce->name); if (debug_mode) { - struct stat_data *sd = &ce->ce_stat_data; + const struct stat_data *sd = &ce->ce_stat_data; printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); @@ -198,12 +202,12 @@ static void show_ru_info(void) printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], find_unique_abbrev(ui->sha1[i], abbrev), i + 1); - write_name(path, len); + write_name(path); } } } -static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce) +static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce) { int dtype = ce_to_dtype(ce); return is_excluded(dir, ce->name, &dtype); @@ -221,9 +225,9 @@ static void show_files(struct dir_struct *dir) if (show_killed) show_killed_files(dir); } - if (show_cached | show_stage) { + if (show_cached || show_stage) { for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if ((dir->flags & DIR_SHOW_IGNORED) && !ce_excluded(dir, ce)) continue; @@ -235,9 +239,9 @@ static void show_files(struct dir_struct *dir) (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce); } } - if (show_deleted | show_modified) { + if (show_deleted || show_modified) { for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; struct stat st; int err; if ((dir->flags & DIR_SHOW_IGNORED) && @@ -273,7 +277,7 @@ static void prune_cache(const char *prefix) last = active_nr; while (last > first) { int next = (last + first) >> 1; - struct cache_entry *ce = active_cache[next]; + const struct cache_entry *ce = active_cache[next]; if (!strncmp(ce->name, prefix, max_prefix_len)) { first = next+1; continue; @@ -391,7 +395,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char if (found_dup) continue; - name = quote_path_relative(pathspec[num], -1, &sb, prefix); + name = quote_path_relative(pathspec[num], prefix, &sb); error("pathspec '%s' did not match any file(s) known to git.", name); errors++; @@ -573,8 +577,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) die("ls-files --ignored needs some exclude pattern"); /* With no flags, we default to showing the cached files */ - if (!(show_stage | show_deleted | show_others | show_unmerged | - show_killed | show_modified | show_resolve_undo)) + if (!(show_stage || show_deleted || show_others || show_unmerged || + show_killed || show_modified || show_resolve_undo)) show_cached = 1; if (max_prefix) diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 1bc7991048..0c4cd2f9f7 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -107,7 +107,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0); if (!octopus && !reduce && argc < 2) usage_with_options(merge_base_usage, options); - if (is_ancestor && (show_all | octopus | reduce)) + if (is_ancestor && (show_all || octopus || reduce)) die("--is-ancestor cannot be used with other options"); if (is_ancestor) return handle_is_ancestor(argc, argv); diff --git a/builtin/merge-index.c b/builtin/merge-index.c index be5e514324..b416d92849 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -16,7 +16,7 @@ static int merge_entry(int pos, const char *path) die("git merge-index: %s not in the cache", path); found = 0; do { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; int stage = ce_stage(ce); if (strcmp(ce->name, path)) @@ -58,7 +58,7 @@ static void merge_all(void) { int i; for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; i += merge_entry(i, ce->name)-1; diff --git a/builtin/merge.c b/builtin/merge.c index 2ebe732896..34a6166b52 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -47,8 +47,8 @@ static const char * const builtin_merge_usage[] = { }; static int show_diffstat = 1, shortlog_len = -1, squash; -static int option_commit = 1, allow_fast_forward = 1; -static int fast_forward_only, option_edit = -1; +static int option_commit = 1; +static int option_edit = -1; static int allow_trivial = 1, have_message, verify_signatures; static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; @@ -76,6 +76,14 @@ static struct strategy all_strategy[] = { static const char *pull_twohead, *pull_octopus; +enum ff_type { + FF_NO, + FF_ALLOW, + FF_ONLY +}; + +static enum ff_type fast_forward = FF_ALLOW; + static int option_parse_message(const struct option *opt, const char *arg, int unset) { @@ -178,6 +186,13 @@ static int option_parse_n(const struct option *opt, return 0; } +static int option_parse_ff_only(const struct option *opt, + const char *arg, int unset) +{ + fast_forward = FF_ONLY; + return 0; +} + static struct option builtin_merge_options[] = { { OPTION_CALLBACK, 'n', NULL, NULL, NULL, N_("do not show a diffstat at the end of the merge"), @@ -194,10 +209,10 @@ static struct option builtin_merge_options[] = { N_("perform a commit if the merge succeeds (default)")), OPT_BOOL('e', "edit", &option_edit, N_("edit message before committing")), - OPT_BOOLEAN(0, "ff", &allow_fast_forward, - N_("allow fast-forward (default)")), - OPT_BOOLEAN(0, "ff-only", &fast_forward_only, - N_("abort if fast-forward is not possible")), + OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW), + { OPTION_CALLBACK, 0, "ff-only", NULL, NULL, + N_("abort if fast-forward is not possible"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, option_parse_ff_only }, OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_BOOL(0, "verify-signatures", &verify_signatures, N_("Verify that the named commit has a valid GPG signature")), @@ -581,10 +596,9 @@ static int git_merge_config(const char *k, const char *v, void *cb) else if (!strcmp(k, "merge.ff")) { int boolval = git_config_maybe_bool(k, v); if (0 <= boolval) { - allow_fast_forward = boolval; + fast_forward = boolval ? FF_ALLOW : FF_NO; } else if (v && !strcmp(v, "only")) { - allow_fast_forward = 1; - fast_forward_only = 1; + fast_forward = FF_ONLY; } /* do not barf on values from future versions of git */ return 0; } else if (!strcmp(k, "merge.defaulttoupstream")) { @@ -863,7 +877,7 @@ static int finish_automerge(struct commit *head, free_commit_list(common); parents = remoteheads; - if (!head_subsumed || !allow_fast_forward) + if (!head_subsumed || fast_forward == FF_NO) commit_list_insert(head, &parents); strbuf_addch(&merge_msg, '\n'); prepare_to_commit(remoteheads); @@ -889,7 +903,7 @@ static int suggest_conflicts(int renormalizing) die_errno(_("Could not open '%s' for writing"), filename); fprintf(fp, "\nConflicts:\n"); for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce_stage(ce)) { fprintf(fp, "\t%s\n", ce->name); @@ -948,7 +962,7 @@ static int evaluate_result(void) } /* - * Pretend as if the user told us to merge with the tracking + * Pretend as if the user told us to merge with the remote-tracking * branch we have for the upstream of the current branch */ static int setup_with_upstream(const char ***argv) @@ -967,7 +981,7 @@ static int setup_with_upstream(const char ***argv) args = xcalloc(branch->merge_nr + 1, sizeof(char *)); for (i = 0; i < branch->merge_nr; i++) { if (!branch->merge[i]->dst) - die(_("No remote tracking branch for %s from %s"), + die(_("No remote-tracking branch for %s from %s"), branch->merge[i]->src, branch->remote_name); args[i] = branch->merge[i]->dst; } @@ -1008,7 +1022,7 @@ static void write_merge_state(struct commit_list *remoteheads) if (fd < 0) die_errno(_("Could not open '%s' for writing"), filename); strbuf_reset(&buf); - if (!allow_fast_forward) + if (fast_forward == FF_NO) strbuf_addf(&buf, "no-ff"); if (write_in_full(fd, buf.buf, buf.len) != buf.len) die_errno(_("Could not write to '%s'"), filename); @@ -1157,14 +1171,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) show_diffstat = 0; if (squash) { - if (!allow_fast_forward) + if (fast_forward == FF_NO) die(_("You cannot combine --squash with --no-ff.")); option_commit = 0; } - if (!allow_fast_forward && fast_forward_only) - die(_("You cannot combine --no-ff with --ff-only.")); - if (!abort_current_merge) { if (!argc) { if (default_to_upstream) @@ -1206,7 +1217,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) "empty head")); if (squash) die(_("Squash commit into empty head not supported yet")); - if (!allow_fast_forward) + if (fast_forward == FF_NO) die(_("Non-fast-forward commit does not make sense into " "an empty head")); remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); @@ -1294,11 +1305,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) sha1_to_hex(commit->object.sha1)); setenv(buf.buf, merge_remote_util(commit)->name, 1); strbuf_reset(&buf); - if (!fast_forward_only && + if (fast_forward != FF_ONLY && merge_remote_util(commit) && merge_remote_util(commit)->obj && merge_remote_util(commit)->obj->type == OBJ_TAG) - allow_fast_forward = 0; + fast_forward = FF_NO; } if (option_edit < 0) @@ -1315,7 +1326,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (i = 0; i < use_strategies_nr; i++) { if (use_strategies[i]->attr & NO_FAST_FORWARD) - allow_fast_forward = 0; + fast_forward = FF_NO; if (use_strategies[i]->attr & NO_TRIVIAL) allow_trivial = 0; } @@ -1345,7 +1356,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) */ finish_up_to_date("Already up-to-date."); goto done; - } else if (allow_fast_forward && !remoteheads->next && + } else if (fast_forward != FF_NO && !remoteheads->next && !common->next && !hashcmp(common->item->object.sha1, head_commit->object.sha1)) { /* Again the most common case of merging one remote. */ @@ -1392,7 +1403,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * only one common. */ refresh_cache(REFRESH_QUIET); - if (allow_trivial && !fast_forward_only) { + if (allow_trivial && fast_forward != FF_ONLY) { /* See if it is really trivial. */ git_committer_info(IDENT_STRICT); printf(_("Trying really trivial in-index merge...\n")); @@ -1433,7 +1444,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } } - if (fast_forward_only) + if (fast_forward == FF_ONLY) die(_("Not possible to fast-forward, aborting.")); /* We are going to make a new commit. */ diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 6238247974..0aaa19e4ab 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -4,6 +4,7 @@ #include "tag.h" #include "refs.h" #include "parse-options.h" +#include "sha1-lookup.h" #define CUTOFF_DATE_SLOP 86400 /* one day */ @@ -82,23 +83,88 @@ copy_data: } } +static int subpath_matches(const char *path, const char *filter) +{ + const char *subpath = path; + + while (subpath) { + if (!fnmatch(filter, subpath, 0)) + return subpath - path; + subpath = strchr(subpath, '/'); + if (subpath) + subpath++; + } + return -1; +} + +static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) +{ + if (shorten_unambiguous) + refname = shorten_unambiguous_ref(refname, 0); + else if (!prefixcmp(refname, "refs/heads/")) + refname = refname + 11; + else if (!prefixcmp(refname, "refs/")) + refname = refname + 5; + return refname; +} + struct name_ref_data { int tags_only; int name_only; const char *ref_filter; }; +static struct tip_table { + struct tip_table_entry { + unsigned char sha1[20]; + const char *refname; + } *table; + int nr; + int alloc; + int sorted; +} tip_table; + +static void add_to_tip_table(const unsigned char *sha1, const char *refname, + int shorten_unambiguous) +{ + refname = name_ref_abbrev(refname, shorten_unambiguous); + + ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc); + hashcpy(tip_table.table[tip_table.nr].sha1, sha1); + tip_table.table[tip_table.nr].refname = xstrdup(refname); + tip_table.nr++; + tip_table.sorted = 0; +} + +static int tipcmp(const void *a_, const void *b_) +{ + const struct tip_table_entry *a = a_, *b = b_; + return hashcmp(a->sha1, b->sha1); +} + static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) { struct object *o = parse_object(sha1); struct name_ref_data *data = cb_data; + int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; if (data->tags_only && prefixcmp(path, "refs/tags/")) return 0; - if (data->ref_filter && fnmatch(data->ref_filter, path, 0)) - return 0; + if (data->ref_filter) { + switch (subpath_matches(path, data->ref_filter)) { + case -1: /* did not match */ + return 0; + case 0: /* matched fully */ + break; + default: /* matched subpath */ + can_abbreviate_output = 1; + break; + } + } + + add_to_tip_table(sha1, path, can_abbreviate_output); while (o && o->type == OBJ_TAG) { struct tag *t = (struct tag *) o; @@ -110,20 +176,38 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; - if (!prefixcmp(path, "refs/heads/")) - path = path + 11; - else if (data->tags_only - && data->name_only - && !prefixcmp(path, "refs/tags/")) - path = path + 10; - else if (!prefixcmp(path, "refs/")) - path = path + 5; - + path = name_ref_abbrev(path, can_abbreviate_output); name_rev(commit, xstrdup(path), 0, 0, deref); } return 0; } +static const unsigned char *nth_tip_table_ent(size_t ix, void *table_) +{ + struct tip_table_entry *table = table_; + return table[ix].sha1; +} + +static const char *get_exact_ref_match(const struct object *o) +{ + int found; + + if (!tip_table.table || !tip_table.nr) + return NULL; + + if (!tip_table.sorted) { + qsort(tip_table.table, tip_table.nr, sizeof(*tip_table.table), + tipcmp); + tip_table.sorted = 1; + } + + found = sha1_pos(o->sha1, tip_table.table, tip_table.nr, + nth_tip_table_ent); + if (0 <= found) + return tip_table.table[found].refname; + return NULL; +} + /* returns a static buffer */ static const char *get_rev_name(const struct object *o) { @@ -132,7 +216,7 @@ static const char *get_rev_name(const struct object *o) struct commit *c; if (o->type != OBJ_COMMIT) - return NULL; + return get_exact_ref_match(o); c = (struct commit *) o; n = c->util; if (!n) @@ -223,7 +307,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = OBJECT_ARRAY_INIT; - int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0; + int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; struct name_ref_data data = { 0, 0, NULL }; struct option opts[] = { OPT_BOOLEAN(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")), @@ -236,6 +320,12 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "undefined", &allow_undefined, N_("allow to print `undefined` names")), OPT_BOOLEAN(0, "always", &always, N_("show abbreviated commit object as fallback")), + { + /* A Hidden OPT_BOOL */ + OPTION_SET_INT, 0, "peel-tag", &peel_tag, NULL, + N_("dereference tags in the input (internal use)"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1, + }, OPT_END(), }; @@ -250,7 +340,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) for (; argc; argc--, argv++) { unsigned char sha1[20]; - struct object *o; + struct object *object; struct commit *commit; if (get_sha1(*argv, sha1)) { @@ -259,17 +349,34 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) continue; } - o = deref_tag(parse_object(sha1), *argv, 0); - if (!o || o->type != OBJ_COMMIT) { - fprintf(stderr, "Could not get commit for %s. Skipping.\n", + commit = NULL; + object = parse_object(sha1); + if (object) { + struct object *peeled = deref_tag(object, *argv, 0); + if (peeled && peeled->type == OBJ_COMMIT) + commit = (struct commit *)peeled; + } + + if (!object) { + fprintf(stderr, "Could not get object for %s. Skipping.\n", *argv); continue; } - commit = (struct commit *)o; - if (cutoff > commit->date) - cutoff = commit->date; - add_object_array((struct object *)commit, *argv, &revs); + if (commit) { + if (cutoff > commit->date) + cutoff = commit->date; + } + + if (peel_tag) { + if (!commit) { + fprintf(stderr, "Could not get commit for %s. Skipping.\n", + *argv); + continue; + } + object = (struct object *)commit; + } + add_object_array(object, *argv, &revs); } if (cutoff) diff --git a/builtin/notes.c b/builtin/notes.c index 57748a6fb6..e4100c4982 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -4,7 +4,7 @@ * Copyright (c) 2010 Johan Herland <johan@herland.net> * * Based on git-notes.sh by Johannes Schindelin, - * and builtin-tag.c by Kristian Høgsberg and Carlos Rica. + * and builtin/tag.c by Kristian Høgsberg and Carlos Rica. */ #include "cache.h" @@ -18,9 +18,7 @@ #include "parse-options.h" #include "string-list.h" #include "notes-merge.h" - -static void commit_notes(struct notes_tree *t, const char *msg); -static combine_notes_fn parse_combine_notes_fn(const char *v); +#include "notes-utils.h" static const char * const git_notes_usage[] = { N_("git notes [--ref <notes_ref>] [list [<object>]]"), @@ -287,139 +285,13 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset return parse_reuse_arg(opt, arg, unset); } -static void commit_notes(struct notes_tree *t, const char *msg) -{ - struct strbuf buf = STRBUF_INIT; - unsigned char commit_sha1[20]; - - if (!t) - t = &default_notes_tree; - if (!t->initialized || !t->ref || !*t->ref) - die(_("Cannot commit uninitialized/unreferenced notes tree")); - if (!t->dirty) - return; /* don't have to commit an unchanged tree */ - - /* Prepare commit message and reflog message */ - strbuf_addstr(&buf, msg); - if (buf.buf[buf.len - 1] != '\n') - strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */ - - create_notes_commit(t, NULL, &buf, commit_sha1); - strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */ - update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR); - - strbuf_release(&buf); -} - -static combine_notes_fn parse_combine_notes_fn(const char *v) -{ - if (!strcasecmp(v, "overwrite")) - return combine_notes_overwrite; - else if (!strcasecmp(v, "ignore")) - return combine_notes_ignore; - else if (!strcasecmp(v, "concatenate")) - return combine_notes_concatenate; - else if (!strcasecmp(v, "cat_sort_uniq")) - return combine_notes_cat_sort_uniq; - else - return NULL; -} - -static int notes_rewrite_config(const char *k, const char *v, void *cb) -{ - struct notes_rewrite_cfg *c = cb; - if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) { - c->enabled = git_config_bool(k, v); - return 0; - } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) { - if (!v) - config_error_nonbool(k); - c->combine = parse_combine_notes_fn(v); - if (!c->combine) { - error(_("Bad notes.rewriteMode value: '%s'"), v); - return 1; - } - return 0; - } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) { - /* note that a refs/ prefix is implied in the - * underlying for_each_glob_ref */ - if (!prefixcmp(v, "refs/notes/")) - string_list_add_refs_by_glob(c->refs, v); - else - warning(_("Refusing to rewrite notes in %s" - " (outside of refs/notes/)"), v); - return 0; - } - - return 0; -} - - -struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) -{ - struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg)); - const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT); - const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT); - c->cmd = cmd; - c->enabled = 1; - c->combine = combine_notes_concatenate; - c->refs = xcalloc(1, sizeof(struct string_list)); - c->refs->strdup_strings = 1; - c->refs_from_env = 0; - c->mode_from_env = 0; - if (rewrite_mode_env) { - c->mode_from_env = 1; - c->combine = parse_combine_notes_fn(rewrite_mode_env); - if (!c->combine) - /* TRANSLATORS: The first %s is the name of the - environment variable, the second %s is its value */ - error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT, - rewrite_mode_env); - } - if (rewrite_refs_env) { - c->refs_from_env = 1; - string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env); - } - git_config(notes_rewrite_config, c); - if (!c->enabled || !c->refs->nr) { - string_list_clear(c->refs, 0); - free(c->refs); - free(c); - return NULL; - } - c->trees = load_notes_trees(c->refs); - string_list_clear(c->refs, 0); - free(c->refs); - return c; -} - -int copy_note_for_rewrite(struct notes_rewrite_cfg *c, - const unsigned char *from_obj, const unsigned char *to_obj) -{ - int ret = 0; - int i; - for (i = 0; c->trees[i]; i++) - ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret; - return ret; -} - -void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c) -{ - int i; - for (i = 0; c->trees[i]; i++) { - commit_notes(c->trees[i], "Notes added by 'git notes copy'"); - free_notes(c->trees[i]); - } - free(c->trees); - free(c); -} - static int notes_copy_from_stdin(int force, const char *rewrite_cmd) { struct strbuf buf = STRBUF_INIT; struct notes_rewrite_cfg *c = NULL; struct notes_tree *t = NULL; int ret = 0; + const char *msg = "Notes added by 'git notes copy'"; if (rewrite_cmd) { c = init_copy_notes_for_rewrite(rewrite_cmd); @@ -461,10 +333,10 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) } if (!rewrite_cmd) { - commit_notes(t, "Notes added by 'git notes copy'"); + commit_notes(t, msg); free_notes(t); } else { - finish_copy_notes_for_rewrite(c); + finish_copy_notes_for_rewrite(c, msg); } return ret; } diff --git a/builtin/push.c b/builtin/push.c index 2d84d10720..6d36c24268 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -120,10 +120,11 @@ static const char message_detached_head_die[] = "\n" " git push %s HEAD:<name-of-remote-branch>\n"); -static void setup_push_upstream(struct remote *remote, int simple) +static void setup_push_upstream(struct remote *remote, struct branch *branch, + int triangular) { struct strbuf refspec = STRBUF_INIT; - struct branch *branch = branch_get(NULL); + if (!branch) die(_(message_detached_head_die), remote->name); if (!branch->merge_nr || !branch->merge || !branch->remote_name) @@ -137,18 +138,29 @@ static void setup_push_upstream(struct remote *remote, int simple) if (branch->merge_nr != 1) die(_("The current branch %s has multiple upstream branches, " "refusing to push."), branch->name); - if (strcmp(branch->remote_name, remote->name)) + if (triangular) die(_("You are pushing to remote '%s', which is not the upstream of\n" "your current branch '%s', without telling me what to push\n" "to update which remote branch."), remote->name, branch->name); - if (simple && strcmp(branch->refname, branch->merge[0]->src)) - die_push_simple(branch, remote); + + if (push_default == PUSH_DEFAULT_SIMPLE) { + /* Additional safety */ + if (strcmp(branch->refname, branch->merge[0]->src)) + die_push_simple(branch, remote); + } strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); add_refspec(refspec.buf); } +static void setup_push_current(struct remote *remote, struct branch *branch) +{ + if (!branch) + die(_(message_detached_head_die), remote->name); + add_refspec(branch->name); +} + static char warn_unspecified_push_default_msg[] = N_("push.default is unset; its implicit value is changing in\n" "Git 2.0 from 'matching' to 'simple'. To squelch this message\n" @@ -173,9 +185,16 @@ static void warn_unspecified_push_default_configuration(void) warning("%s\n", _(warn_unspecified_push_default_msg)); } +static int is_workflow_triangular(struct remote *remote) +{ + struct remote *fetch_remote = remote_get(NULL); + return (fetch_remote && fetch_remote != remote); +} + static void setup_default_push_refspecs(struct remote *remote) { - struct branch *branch; + struct branch *branch = branch_get(NULL); + int triangular = is_workflow_triangular(remote); switch (push_default) { default: @@ -188,18 +207,18 @@ static void setup_default_push_refspecs(struct remote *remote) break; case PUSH_DEFAULT_SIMPLE: - setup_push_upstream(remote, 1); + if (triangular) + setup_push_current(remote, branch); + else + setup_push_upstream(remote, branch, triangular); break; case PUSH_DEFAULT_UPSTREAM: - setup_push_upstream(remote, 0); + setup_push_upstream(remote, branch, triangular); break; case PUSH_DEFAULT_CURRENT: - branch = branch_get(NULL); - if (!branch) - die(_(message_detached_head_die), remote->name); - add_refspec(branch->name); + setup_push_current(remote, branch); break; case PUSH_DEFAULT_NOTHING: @@ -211,8 +230,8 @@ static void setup_default_push_refspecs(struct remote *remote) static const char message_advice_pull_before_push[] = N_("Updates were rejected because the tip of your current branch is behind\n" - "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n" - "before pushing again.\n" + "its remote counterpart. Integrate the remote changes (e.g.\n" + "'git pull ...') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); static const char message_advice_use_upstream[] = @@ -223,15 +242,15 @@ static const char message_advice_use_upstream[] = static const char message_advice_checkout_pull_push[] = N_("Updates were rejected because a pushed branch tip is behind its remote\n" - "counterpart. Check out this branch and merge the remote changes\n" - "(e.g. 'git pull') before pushing again.\n" + "counterpart. Check out this branch and integrate the remote changes\n" + "(e.g. 'git pull ...') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); static const char message_advice_ref_fetch_first[] = N_("Updates were rejected because the remote contains work that you do\n" "not have locally. This is usually caused by another repository pushing\n" - "to the same ref. You may want to first merge the remote changes (e.g.,\n" - "'git pull') before pushing again.\n" + "to the same ref. You may want to first integrate the remote changes\n" + "(e.g., 'git pull ...') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); static const char message_advice_ref_already_exists[] = diff --git a/builtin/replace.c b/builtin/replace.c index 398ccd5eaa..59d31152d0 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -3,7 +3,7 @@ * * Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org> * - * Based on builtin-tag.c by Kristian Høgsberg <krh@redhat.com> + * Based on builtin/tag.c by Kristian Høgsberg <krh@redhat.com> * and Carlos Rica <jasampler@gmail.com> that was itself based on * git-tag.sh and mktag.c by Linus Torvalds. */ diff --git a/builtin/reset.c b/builtin/reset.c index 6032131a90..afa6e020e8 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -93,10 +93,12 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet) static void print_new_head_line(struct commit *commit) { const char *hex, *body; + char *msg; hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); printf(_("HEAD is now at %s"), hex); - body = strstr(commit->buffer, "\n\n"); + msg = logmsg_reencode(commit, NULL, get_log_output_encoding()); + body = strstr(msg, "\n\n"); if (body) { const char *eol; size_t len; @@ -107,6 +109,7 @@ static void print_new_head_line(struct commit *commit) } else printf("\n"); + logmsg_free(msg, commit); } static void update_index_from_diff(struct diff_queue_struct *q, diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 67701be551..a5ec30d74e 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -111,6 +111,7 @@ static void show_commit(struct commit *commit, void *data) ctx.date_mode = revs->date_mode; ctx.date_mode_explicit = revs->date_mode_explicit; ctx.fmt = revs->commit_format; + ctx.output_encoding = get_log_output_encoding(); pretty_print_commit(&ctx, commit, &buf); if (revs->graph) { if (buf.len) { diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index f267a1d3b5..de894c7577 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -212,11 +212,17 @@ static void show_datestring(const char *flag, const char *datestr) show(buffer); } -static int show_file(const char *arg) +static int show_file(const char *arg, int output_prefix) { show_default(); if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) { - show(arg); + if (output_prefix) { + const char *prefix = startup_info->prefix; + show(prefix_filename(prefix, + prefix ? strlen(prefix) : 0, + arg)); + } else + show(arg); return 1; } return 0; @@ -470,6 +476,7 @@ N_("git rev-parse --parseopt [options] -- [<args>...]\n" 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; + int output_prefix = 0; unsigned char sha1[20]; const char *name = NULL; @@ -503,7 +510,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *arg = argv[i]; if (as_is) { - if (show_file(arg) && as_is < 2) + if (show_file(arg, output_prefix) && as_is < 2) verify_filename(prefix, arg, 0); continue; } @@ -527,7 +534,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) as_is = 2; /* Pass on the "--" if we show anything but files.. */ if (filter & (DO_FLAGS | DO_REVS)) - show_file(arg); + show_file(arg, 0); continue; } if (!strcmp(arg, "--default")) { @@ -535,6 +542,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) i++; continue; } + if (!strcmp(arg, "--prefix")) { + prefix = argv[i+1]; + startup_info->prefix = prefix; + output_prefix = 1; + i++; + continue; + } if (!strcmp(arg, "--revs-only")) { filter &= ~DO_NOREV; continue; @@ -754,7 +768,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (verify) die_no_single_rev(quiet); as_is = 1; - if (!show_file(arg)) + if (!show_file(arg, output_prefix)) continue; verify_filename(prefix, arg, 1); } diff --git a/builtin/revert.c b/builtin/revert.c index 0401fdb02c..1d2648b756 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -54,6 +54,7 @@ static int option_parse_x(const struct option *opt, return 0; } +LAST_ARG_MUST_BE_NULL static void verify_opt_compatible(const char *me, const char *base_opt, ...) { const char *this_opt; @@ -70,6 +71,7 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...) die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); } +LAST_ARG_MUST_BE_NULL static void verify_opt_mutually_compatible(const char *me, ...) { const char *opt1, *opt2 = NULL; diff --git a/builtin/rm.c b/builtin/rm.c index 7b91d52f39..18916e022a 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -9,6 +9,7 @@ #include "cache-tree.h" #include "tree-walk.h" #include "parse-options.h" +#include "string-list.h" #include "submodule.h" static const char * const builtin_rm_usage[] = { @@ -36,15 +37,37 @@ static int get_ours_cache_pos(const char *path, int pos) return -1; } +static void print_error_files(struct string_list *files_list, + const char *main_msg, + const char *hints_msg, + int *errs) +{ + if (files_list->nr) { + int i; + struct strbuf err_msg = STRBUF_INIT; + + strbuf_addstr(&err_msg, main_msg); + for (i = 0; i < files_list->nr; i++) + strbuf_addf(&err_msg, + "\n %s", + files_list->items[i].string); + if (advice_rm_hints) + strbuf_addstr(&err_msg, hints_msg); + *errs = error("%s", err_msg.buf); + strbuf_release(&err_msg); + } +} + static int check_submodules_use_gitfiles(void) { int i; int errs = 0; + struct string_list files = STRING_LIST_INIT_NODUP; for (i = 0; i < list.nr; i++) { const char *name = list.entry[i].name; int pos; - struct cache_entry *ce; + const struct cache_entry *ce; struct stat st; pos = cache_name_pos(name, strlen(name)); @@ -61,11 +84,18 @@ static int check_submodules_use_gitfiles(void) continue; if (!submodule_uses_gitfile(name)) - errs = error(_("submodule '%s' (or one of its nested " - "submodules) uses a .git directory\n" - "(use 'rm -rf' if you really want to remove " - "it including all of its history)"), name); + string_list_append(&files, name); } + print_error_files(&files, + Q_("the following submodule (or one of its nested " + "submodules)\n uses a .git directory:", + "the following submodules (or one of its nested " + "submodules)\n use a .git directory:", + files.nr), + _("\n(use 'rm -rf' if you really want to remove " + "it including all of its history)"), + &errs); + string_list_clear(&files, 0); return errs; } @@ -81,12 +111,16 @@ static int check_local_mod(unsigned char *head, int index_only) */ int i, no_head; int errs = 0; + struct string_list files_staged = STRING_LIST_INIT_NODUP; + struct string_list files_cached = STRING_LIST_INIT_NODUP; + struct string_list files_submodule = STRING_LIST_INIT_NODUP; + struct string_list files_local = STRING_LIST_INIT_NODUP; no_head = is_null_sha1(head); for (i = 0; i < list.nr; i++) { struct stat st; int pos; - struct cache_entry *ce; + const struct cache_entry *ce; const char *name = list.entry[i].name; unsigned char sha1[20]; unsigned mode; @@ -171,29 +205,58 @@ static int check_local_mod(unsigned char *head, int index_only) */ if (local_changes && staged_changes) { if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD)) - errs = error(_("'%s' has staged content different " - "from both the file and the HEAD\n" - "(use -f to force removal)"), name); + string_list_append(&files_staged, name); } else if (!index_only) { if (staged_changes) - errs = error(_("'%s' has changes staged in the index\n" - "(use --cached to keep the file, " - "or -f to force removal)"), name); + string_list_append(&files_cached, name); if (local_changes) { if (S_ISGITLINK(ce->ce_mode) && - !submodule_uses_gitfile(name)) { - errs = error(_("submodule '%s' (or one of its nested " - "submodules) uses a .git directory\n" - "(use 'rm -rf' if you really want to remove " - "it including all of its history)"), name); - } else - errs = error(_("'%s' has local modifications\n" - "(use --cached to keep the file, " - "or -f to force removal)"), name); + !submodule_uses_gitfile(name)) + string_list_append(&files_submodule, name); + else + string_list_append(&files_local, name); } } } + print_error_files(&files_staged, + Q_("the following file has staged content different " + "from both the\nfile and the HEAD:", + "the following files have staged content different" + " from both the\nfile and the HEAD:", + files_staged.nr), + _("\n(use -f to force removal)"), + &errs); + string_list_clear(&files_staged, 0); + print_error_files(&files_cached, + Q_("the following file has changes " + "staged in the index:", + "the following files have changes " + "staged in the index:", files_cached.nr), + _("\n(use --cached to keep the file," + " or -f to force removal)"), + &errs); + string_list_clear(&files_cached, 0); + print_error_files(&files_submodule, + Q_("the following submodule (or one of its nested " + "submodule)\nuses a .git directory:", + "the following submodules (or one of its nested " + "submodule)\nuse a .git directory:", + files_submodule.nr), + _("\n(use 'rm -rf' if you really " + "want to remove it including all " + "of its history)"), + &errs); + string_list_clear(&files_submodule, 0); + print_error_files(&files_local, + Q_("the following file has local modifications:", + "the following files have local modifications:", + files_local.nr), + _("\n(use --cached to keep the file," + " or -f to force removal)"), + &errs); + string_list_clear(&files_local, 0); + return errs; } @@ -258,7 +321,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) seen = xcalloc(i, 1); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen)) continue; ALLOC_GROW(list.entry, list.nr + 1, list.alloc); diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 1fd6f8ac59..1434f8fee4 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -137,6 +137,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.subject = ""; ctx.after_subject = ""; ctx.date_mode = DATE_NORMAL; + ctx.output_encoding = get_log_output_encoding(); pretty_print_commit(&ctx, commit, &ufbuf); buffer = ufbuf.buf; } else if (*buffer) { diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 90fc6b1b9d..9788eb115b 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -630,7 +630,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) int num_rev, i, extra = 0; int all_heads = 0, all_remotes = 0; int all_mask, all_revs; - int lifo = 1; + enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER; char head[128]; const char *head_p; int head_len; @@ -665,15 +665,17 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) N_("show possible merge bases")), OPT_BOOLEAN(0, "independent", &independent, N_("show refs unreachable from any other ref")), - OPT_BOOLEAN(0, "topo-order", &lifo, - N_("show commits in topological order")), + OPT_SET_INT(0, "topo-order", &sort_order, + N_("show commits in topological order"), + REV_SORT_IN_GRAPH_ORDER), OPT_BOOLEAN(0, "topics", &topics, N_("show only commits not on the first branch")), OPT_SET_INT(0, "sparse", &dense, N_("show merges reachable from only one tip"), 0), - OPT_SET_INT(0, "date-order", &lifo, - N_("show commits where no parent comes before its " - "children"), 0), + OPT_SET_INT(0, "date-order", &sort_order, + N_("topologically sort, maintaining date order " + "where possible"), + REV_SORT_BY_COMMIT_DATE), { OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"), N_("show <n> most recent ref-log entries starting at " "base"), @@ -900,7 +902,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) exit(0); /* Sort topologically */ - sort_in_topological_order(&seen, lifo); + sort_in_topological_order(&seen, sort_order); /* Give names to commits */ if (!sha1_name && !no_name) diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 4a0310da37..87806ad5b0 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -31,6 +31,9 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo const char *hex; unsigned char peeled[20]; + if (show_head && !strcmp(refname, "HEAD")) + goto match; + if (tags_only || heads_only) { int match; @@ -167,9 +170,10 @@ static const struct option show_ref_options[] = { OPT_BOOLEAN(0, "verify", &verify, N_("stricter reference checking, " "requires exact ref path")), { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL, - N_("show the HEAD reference"), + N_("show the HEAD reference, even if it would be filtered out"), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - OPT_BOOLEAN(0, "head", &show_head, N_("show the HEAD reference")), + OPT_BOOLEAN(0, "head", &show_head, + N_("show the HEAD reference, even if it would be filtered out")), OPT_BOOLEAN('d', "dereference", &deref_tags, N_("dereference tags into object IDs")), { OPTION_CALLBACK, 's', "hash", &abbrev, N_("n"), diff --git a/builtin/update-index.c b/builtin/update-index.c index 5c7762eef4..c317981516 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -83,7 +83,7 @@ static int process_lstat_error(const char *path, int err) return error("lstat(\"%s\"): %s", path, strerror(errno)); } -static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st) +static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st) { int option, size; struct cache_entry *ce; @@ -142,7 +142,7 @@ static int process_directory(const char *path, int len, struct stat *st) /* Exact match: file or existing gitlink */ if (pos >= 0) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (S_ISGITLINK(ce->ce_mode)) { /* Do nothing to the index if there is no HEAD! */ @@ -158,7 +158,7 @@ static int process_directory(const char *path, int len, struct stat *st) /* Inexact match: is there perhaps a subdirectory match? */ pos = -pos-1; while (pos < active_nr) { - struct cache_entry *ce = active_cache[pos++]; + const struct cache_entry *ce = active_cache[pos++]; if (strncmp(ce->name, path, len)) break; @@ -183,7 +183,7 @@ static int process_path(const char *path) { int pos, len; struct stat st; - struct cache_entry *ce; + const struct cache_entry *ce; len = strlen(path); if (has_symlink_leading_path(path, len)) @@ -448,7 +448,7 @@ static int unresolve_one(const char *path) /* already merged */ pos = unmerge_cache_entry_at(pos); if (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce_stage(ce) && ce_namelen(ce) == namelen && !memcmp(ce->name, path, namelen)) @@ -462,7 +462,7 @@ static int unresolve_one(const char *path) */ pos = -pos-1; if (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce_namelen(ce) == namelen && !memcmp(ce->name, path, namelen)) { fprintf(stderr, @@ -558,7 +558,7 @@ static int do_reupdate(int ac, const char **av, has_head = 0; redo: for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; struct cache_entry *old = NULL; int save_nr; diff --git a/cache-tree.c b/cache-tree.c index 37e4d008b5..0bbec43216 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -149,7 +149,7 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path) cache_tree_invalidate_path(down->cache_tree, slash + 1); } -static int verify_cache(struct cache_entry **cache, +static int verify_cache(const struct cache_entry * const *cache, int entries, int flags) { int i, funny; @@ -158,7 +158,7 @@ static int verify_cache(struct cache_entry **cache, /* Verify that the tree is merged */ funny = 0; for (i = 0; i < entries; i++) { - struct cache_entry *ce = cache[i]; + const struct cache_entry *ce = cache[i]; if (ce_stage(ce)) { if (silent) return -1; @@ -234,7 +234,7 @@ int cache_tree_fully_valid(struct cache_tree *it) } static int update_one(struct cache_tree *it, - struct cache_entry **cache, + const struct cache_entry * const *cache, int entries, const char *base, int baselen, @@ -265,7 +265,7 @@ static int update_one(struct cache_tree *it, */ i = 0; while (i < entries) { - struct cache_entry *ce = cache[i]; + const struct cache_entry *ce = cache[i]; struct cache_tree_sub *sub; const char *path, *slash; int pathlen, sublen, subcnt, subskip; @@ -312,7 +312,7 @@ static int update_one(struct cache_tree *it, i = 0; while (i < entries) { - struct cache_entry *ce = cache[i]; + const struct cache_entry *ce = cache[i]; struct cache_tree_sub *sub; const char *path, *slash; int pathlen, entlen; @@ -397,7 +397,7 @@ static int update_one(struct cache_tree *it, } int cache_tree_update(struct cache_tree *it, - struct cache_entry **cache, + const struct cache_entry * const *cache, int entries, int flags) { @@ -599,8 +599,8 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix) was_valid = cache_tree_fully_valid(active_cache_tree); if (!was_valid) { if (cache_tree_update(active_cache_tree, - active_cache, active_nr, - flags) < 0) + (const struct cache_entry * const *)active_cache, + active_nr, flags) < 0) return WRITE_TREE_UNMERGED_INDEX; if (0 <= newfd) { if (!write_cache(newfd, active_cache, active_nr) && @@ -701,5 +701,6 @@ int update_main_cache_tree(int flags) if (!the_index.cache_tree) the_index.cache_tree = cache_tree(); return cache_tree_update(the_index.cache_tree, - the_index.cache, the_index.cache_nr, flags); + (const struct cache_entry * const *)the_index.cache, + the_index.cache_nr, flags); } diff --git a/cache-tree.h b/cache-tree.h index 55d0f59f2b..f1923ad1e9 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -30,7 +30,7 @@ void cache_tree_write(struct strbuf *, struct cache_tree *root); struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); int cache_tree_fully_valid(struct cache_tree *); -int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int); +int cache_tree_update(struct cache_tree *, const struct cache_entry * const *, int, int); int update_main_cache_tree(int); @@ -425,6 +425,8 @@ extern int path_inside_repo(const char *prefix, const char *path); extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int); extern int init_db(const char *template_dir, unsigned int flags); +extern void sanitize_stdfds(void); + #define alloc_nr(x) (((x)+16)*3/2) /* @@ -476,7 +478,7 @@ extern int remove_file_from_index(struct index_state *, const char *path); extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags); extern int add_file_to_index(struct index_state *, const char *path, int flags); extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh); -extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); +extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b); extern int index_name_is_other(const struct index_state *, const char *, int); extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *); @@ -758,7 +760,7 @@ int is_directory(const char *); const char *real_path(const char *path); const char *real_path_if_valid(const char *path); const char *absolute_path(const char *path); -const char *relative_path(const char *abs, const char *base); +const char *relative_path(const char *in, const char *prefix, struct strbuf *sb); int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, struct string_list *prefixes); char *strip_path_suffix(const char *path, const char *suffix); @@ -793,9 +795,6 @@ extern int parse_sha1_header(const char *hdr, unsigned long *sizep); /* global flag to enable extra checks when accessing packed objects */ extern int do_check_packed_object_crc; -/* for development: log offset of pack access */ -extern const char *log_pack_access; - extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); extern int move_temp_to_file(const char *tmpfile, const char *filename); @@ -1133,6 +1132,7 @@ extern int unpack_object_header(struct packed_git *, struct pack_window **, off_ struct object_info { /* Request */ unsigned long *sizep; + unsigned long *disk_sizep; /* Response */ enum { @@ -1176,11 +1176,15 @@ extern int update_server_info(int); typedef int (*config_fn_t)(const char *, const char *, void *); extern int git_default_config(const char *, const char *, void *); extern int git_config_from_file(config_fn_t fn, const char *, void *); +extern int git_config_from_buf(config_fn_t fn, const char *name, + const char *buf, size_t len, void *data); extern void git_config_push_parameter(const char *text); extern int git_config_from_parameters(config_fn_t fn, void *data); extern int git_config(config_fn_t fn, void *); extern int git_config_with_options(config_fn_t fn, void *, - const char *filename, int respect_includes); + const char *filename, + const char *blob_ref, + int respect_includes); extern int git_config_early(config_fn_t fn, void *, const char *repo_config); extern int git_parse_ulong(const char *, unsigned long *); extern int git_config_int(const char *, const char *); @@ -1,7 +1,7 @@ #include "cache.h" #include "color.h" -static int git_use_color_default = 0; +static int git_use_color_default = GIT_COLOR_AUTO; int color_stdout_is_tty = -1; /* diff --git a/command-list.txt b/command-list.txt index bf83303c48..08b04e2c66 100644 --- a/command-list.txt +++ b/command-list.txt @@ -13,6 +13,7 @@ git-bundle mainporcelain git-cat-file plumbinginterrogators git-check-attr purehelpers git-check-ignore purehelpers +git-check-mailmap purehelpers git-checkout mainporcelain common git-checkout-index plumbingmanipulators git-check-ref-format purehelpers diff --git a/commit-slab.h b/commit-slab.h new file mode 100644 index 0000000000..7d481638af --- /dev/null +++ b/commit-slab.h @@ -0,0 +1,98 @@ +#ifndef COMMIT_SLAB_H +#define COMMIT_SLAB_H + +/* + * define_commit_slab(slabname, elemtype) creates boilerplate code to define + * a new struct (struct slabname) that is used to associate a piece of data + * of elemtype to commits, and a few functions to use that struct. + * + * After including this header file, using: + * + * define_commit_slab(indegee, int); + * + * will let you call the following functions: + * + * - int *indegree_at(struct indegree *, struct commit *); + * + * This function locates the data associated with the given commit in + * the indegree slab, and returns the pointer to it. + * + * - void init_indegree(struct indegree *); + * void init_indegree_with_stride(struct indegree *, int); + * + * Initializes the indegree slab that associates an array of integers + * to each commit. 'stride' specifies how big each array is. The slab + * that id initialied by the variant without "_with_stride" associates + * each commit with an array of one integer. + */ + +/* allocate ~512kB at once, allowing for malloc overhead */ +#ifndef COMMIT_SLAB_SIZE +#define COMMIT_SLAB_SIZE (512*1024-32) +#endif + +#define define_commit_slab(slabname, elemtype) \ + \ +struct slabname { \ + unsigned slab_size; \ + unsigned stride; \ + unsigned slab_count; \ + elemtype **slab; \ +}; \ +static int stat_ ##slabname## realloc; \ + \ +static void init_ ##slabname## _with_stride(struct slabname *s, \ + unsigned stride) \ +{ \ + unsigned int elem_size; \ + if (!stride) \ + stride = 1; \ + s->stride = stride; \ + elem_size = sizeof(struct slabname) * stride; \ + s->slab_size = COMMIT_SLAB_SIZE / elem_size; \ + s->slab_count = 0; \ + s->slab = NULL; \ +} \ + \ +static void init_ ##slabname(struct slabname *s) \ +{ \ + init_ ##slabname## _with_stride(s, 1); \ +} \ + \ +static void clear_ ##slabname(struct slabname *s) \ +{ \ + int i; \ + for (i = 0; i < s->slab_count; i++) \ + free(s->slab[i]); \ + s->slab_count = 0; \ + free(s->slab); \ + s->slab = NULL; \ +} \ + \ +static elemtype *slabname## _at(struct slabname *s, \ + const struct commit *c) \ +{ \ + int nth_slab, nth_slot, ix; \ + \ + ix = c->index * s->stride; \ + nth_slab = ix / s->slab_size; \ + nth_slot = ix % s->slab_size; \ + \ + if (s->slab_count <= nth_slab) { \ + int i; \ + s->slab = xrealloc(s->slab, \ + (nth_slab + 1) * sizeof(s->slab)); \ + stat_ ##slabname## realloc++; \ + for (i = s->slab_count; i <= nth_slab; i++) \ + s->slab[i] = NULL; \ + s->slab_count = nth_slab + 1; \ + } \ + if (!s->slab[nth_slab]) \ + s->slab[nth_slab] = xcalloc(s->slab_size, \ + sizeof(**s->slab)); \ + return &s->slab[nth_slab][nth_slot]; \ +} \ + \ +static int stat_ ##slabname## realloc + +#endif /* COMMIT_SLAB_H */ @@ -8,12 +8,15 @@ #include "notes.h" #include "gpg-interface.h" #include "mergesort.h" +#include "commit-slab.h" +#include "prio-queue.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); int save_commit_buffer = 1; const char *commit_type = "commit"; +static int commit_count; static struct commit *check_commit(struct object *obj, const unsigned char *sha1, @@ -58,8 +61,11 @@ struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_n struct commit *lookup_commit(const unsigned char *sha1) { struct object *obj = lookup_object(sha1); - if (!obj) - return create_object(sha1, OBJ_COMMIT, alloc_commit_node()); + if (!obj) { + struct commit *c = alloc_commit_node(); + c->index = commit_count++; + return create_object(sha1, OBJ_COMMIT, c); + } if (!obj->type) obj->type = OBJ_COMMIT; return check_commit(obj, sha1, 0); @@ -507,32 +513,136 @@ struct commit *pop_commit(struct commit_list **stack) } /* + * Topological sort support + */ + +/* count number of children that have not been emitted */ +define_commit_slab(indegree_slab, int); + +/* record author-date for each commit object */ +define_commit_slab(author_date_slab, unsigned long); + +static void record_author_date(struct author_date_slab *author_date, + struct commit *commit) +{ + const char *buf, *line_end; + char *buffer = NULL; + struct ident_split ident; + char *date_end; + unsigned long date; + + if (!commit->buffer) { + unsigned long size; + enum object_type type; + buffer = read_sha1_file(commit->object.sha1, &type, &size); + if (!buffer) + return; + } + + for (buf = commit->buffer ? commit->buffer : buffer; + buf; + buf = line_end + 1) { + line_end = strchrnul(buf, '\n'); + if (prefixcmp(buf, "author ")) { + if (!line_end[0] || line_end[1] == '\n') + return; /* end of header */ + continue; + } + if (split_ident_line(&ident, + buf + strlen("author "), + line_end - (buf + strlen("author "))) || + !ident.date_begin || !ident.date_end) + goto fail_exit; /* malformed "author" line */ + break; + } + + date = strtoul(ident.date_begin, &date_end, 10); + if (date_end != ident.date_end) + goto fail_exit; /* malformed date */ + *(author_date_slab_at(author_date, commit)) = date; + +fail_exit: + free(buffer); +} + +static int compare_commits_by_author_date(const void *a_, const void *b_, + void *cb_data) +{ + const struct commit *a = a_, *b = b_; + struct author_date_slab *author_date = cb_data; + unsigned long a_date = *(author_date_slab_at(author_date, a)); + unsigned long b_date = *(author_date_slab_at(author_date, b)); + + /* newer commits with larger date first */ + if (a_date < b_date) + return 1; + else if (a_date > b_date) + return -1; + return 0; +} + +int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused) +{ + const struct commit *a = a_, *b = b_; + /* newer commits with larger date first */ + if (a->date < b->date) + return 1; + else if (a->date > b->date) + return -1; + return 0; +} + +/* * Performs an in-place topological sort on the list supplied. */ -void sort_in_topological_order(struct commit_list ** list, int lifo) +void sort_in_topological_order(struct commit_list **list, enum rev_sort_order sort_order) { struct commit_list *next, *orig = *list; - struct commit_list *work, **insert; struct commit_list **pptr; + struct indegree_slab indegree; + struct prio_queue queue; + struct commit *commit; + struct author_date_slab author_date; if (!orig) return; *list = NULL; + init_indegree_slab(&indegree); + memset(&queue, '\0', sizeof(queue)); + + switch (sort_order) { + default: /* REV_SORT_IN_GRAPH_ORDER */ + queue.compare = NULL; + break; + case REV_SORT_BY_COMMIT_DATE: + queue.compare = compare_commits_by_commit_date; + break; + case REV_SORT_BY_AUTHOR_DATE: + init_author_date_slab(&author_date); + queue.compare = compare_commits_by_author_date; + queue.cb_data = &author_date; + break; + } + /* Mark them and clear the indegree */ for (next = orig; next; next = next->next) { struct commit *commit = next->item; - commit->indegree = 1; + *(indegree_slab_at(&indegree, commit)) = 1; + /* also record the author dates, if needed */ + if (sort_order == REV_SORT_BY_AUTHOR_DATE) + record_author_date(&author_date, commit); } /* update the indegree */ for (next = orig; next; next = next->next) { - struct commit_list * parents = next->item->parents; + struct commit_list *parents = next->item->parents; while (parents) { struct commit *parent = parents->item; + int *pi = indegree_slab_at(&indegree, parent); - if (parent->indegree) - parent->indegree++; + if (*pi) + (*pi)++; parents = parents->next; } } @@ -544,34 +654,33 @@ void sort_in_topological_order(struct commit_list ** list, int lifo) * * the tips serve as a starting set for the work queue. */ - work = NULL; - insert = &work; for (next = orig; next; next = next->next) { struct commit *commit = next->item; - if (commit->indegree == 1) - insert = &commit_list_insert(commit, insert)->next; + if (*(indegree_slab_at(&indegree, commit)) == 1) + prio_queue_put(&queue, commit); } - /* process the list in topological order */ - if (!lifo) - commit_list_sort_by_date(&work); + /* + * This is unfortunate; the initial tips need to be shown + * in the order given from the revision traversal machinery. + */ + if (sort_order == REV_SORT_IN_GRAPH_ORDER) + prio_queue_reverse(&queue); + + /* We no longer need the commit list */ + free_commit_list(orig); pptr = list; *list = NULL; - while (work) { - struct commit *commit; - struct commit_list *parents, *work_item; - - work_item = work; - work = work_item->next; - work_item->next = NULL; + while ((commit = prio_queue_get(&queue)) != NULL) { + struct commit_list *parents; - commit = work_item->item; for (parents = commit->parents; parents ; parents = parents->next) { struct commit *parent = parents->item; + int *pi = indegree_slab_at(&indegree, parent); - if (!parent->indegree) + if (!*pi) continue; /* @@ -579,21 +688,22 @@ void sort_in_topological_order(struct commit_list ** list, int lifo) * when all their children have been emitted thereby * guaranteeing topological order. */ - if (--parent->indegree == 1) { - if (!lifo) - commit_list_insert_by_date(parent, &work); - else - commit_list_insert(parent, &work); - } + if (--(*pi) == 1) + prio_queue_put(&queue, parent); } /* - * work_item is a commit all of whose children - * have already been emitted. we can emit it now. + * all children of commit have already been + * emitted. we can emit it now. */ - commit->indegree = 0; - *pptr = work_item; - pptr = &work_item->next; + *(indegree_slab_at(&indegree, commit)) = 0; + + pptr = &commit_list_insert(commit, pptr)->next; } + + clear_indegree_slab(&indegree); + clear_prio_queue(&queue); + if (sort_order == REV_SORT_BY_AUTHOR_DATE) + clear_author_date_slab(&author_date); } /* merge-base stuff */ @@ -1240,10 +1350,15 @@ int commit_tree(const struct strbuf *msg, unsigned char *tree, static int find_invalid_utf8(const char *buf, int len) { int offset = 0; + static const unsigned int max_codepoint[] = { + 0x7f, 0x7ff, 0xffff, 0x10ffff + }; while (len) { unsigned char c = *buf++; int bytes, bad_offset; + unsigned int codepoint; + unsigned int min_val, max_val; len--; offset++; @@ -1264,24 +1379,48 @@ static int find_invalid_utf8(const char *buf, int len) bytes++; } - /* Must be between 1 and 5 more bytes */ - if (bytes < 1 || bytes > 5) + /* + * Must be between 1 and 3 more bytes. Longer sequences result in + * codepoints beyond U+10FFFF, which are guaranteed never to exist. + */ + if (bytes < 1 || 3 < bytes) return bad_offset; /* Do we *have* that many bytes? */ if (len < bytes) return bad_offset; + /* + * Place the encoded bits at the bottom of the value and compute the + * valid range. + */ + codepoint = (c & 0x7f) >> bytes; + min_val = max_codepoint[bytes-1] + 1; + max_val = max_codepoint[bytes]; + offset += bytes; len -= bytes; /* And verify that they are good continuation bytes */ do { + codepoint <<= 6; + codepoint |= *buf & 0x3f; if ((*buf++ & 0xc0) != 0x80) return bad_offset; } while (--bytes); - /* We could/should check the value and length here too */ + /* Reject codepoints that are out of range for the sequence length. */ + if (codepoint < min_val || codepoint > max_val) + return bad_offset; + /* Surrogates are only for UTF-16 and cannot be encoded in UTF-8. */ + if ((codepoint & 0x1ff800) == 0xd800) + return bad_offset; + /* U+xxFFFE and U+xxFFFF are guaranteed non-characters. */ + if ((codepoint & 0xffffe) == 0xfffe) + return bad_offset; + /* So are anything in the range U+FDD0..U+FDEF. */ + if (codepoint >= 0xfdd0 && codepoint <= 0xfdef) + return bad_offset; } return -1; } @@ -1291,9 +1430,6 @@ static int find_invalid_utf8(const char *buf, int len) * * If it isn't, it assumes any non-utf8 characters are Latin1, * and does the conversion. - * - * Fixme: we should probably also disallow overlong forms and - * invalid characters. But we don't do that currently. */ static int verify_utf8(struct strbuf *buf) { @@ -6,6 +6,7 @@ #include "strbuf.h" #include "decorate.h" #include "gpg-interface.h" +#include "string-list.h" struct commit_list { struct commit *item; @@ -15,7 +16,7 @@ struct commit_list { struct commit { struct object object; void *util; - unsigned int indegree; + unsigned int index; unsigned long date; struct commit_list *parents; struct tree *tree; @@ -79,6 +80,9 @@ enum cmit_fmt { }; struct pretty_print_context { + /* + * Callers should tweak these to change the behavior of pp_* functions. + */ enum cmit_fmt fmt; int abbrev; const char *subject; @@ -92,6 +96,15 @@ struct pretty_print_context { const char *output_encoding; struct string_list *mailmap; int color; + struct ident_split *from_ident; + + /* + * Fields below here are manipulated internally by pp_* functions and + * should not be counted on by callers. + */ + + /* Manipulated by the pp_* functions internally. */ + struct string_list in_body_headers; }; struct userformat_want { @@ -111,20 +124,20 @@ extern void userformat_find_requirements(const char *fmt, struct userformat_want extern void format_commit_message(const struct commit *commit, const char *format, struct strbuf *sb, const struct pretty_print_context *context); -extern void pretty_print_commit(const struct pretty_print_context *pp, +extern void pretty_print_commit(struct pretty_print_context *pp, const struct commit *commit, struct strbuf *sb); extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit, struct strbuf *sb); -void pp_user_info(const struct pretty_print_context *pp, +void pp_user_info(struct pretty_print_context *pp, const char *what, struct strbuf *sb, const char *line, const char *encoding); -void pp_title_line(const struct pretty_print_context *pp, +void pp_title_line(struct pretty_print_context *pp, const char **msg_p, struct strbuf *sb, const char *encoding, int need_8bit_cte); -void pp_remainder(const struct pretty_print_context *pp, +void pp_remainder(struct pretty_print_context *pp, const char **msg_p, struct strbuf *sb, int indent); @@ -142,15 +155,24 @@ void clear_commit_marks(struct commit *commit, unsigned int mark); void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark); void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark); + +enum rev_sort_order { + REV_SORT_IN_GRAPH_ORDER = 0, + REV_SORT_BY_COMMIT_DATE, + REV_SORT_BY_AUTHOR_DATE +}; + /* * Performs an in-place topological sort of list supplied. * * invariant of resulting list is: * a reachable from b => ord(b) < ord(a) - * in addition, when lifo == 0, commits on parallel tracks are - * sorted in the dates order. + * sort_order further specifies: + * REV_SORT_IN_GRAPH_ORDER: try to show a commit on a single-parent + * chain together. + * REV_SORT_BY_COMMIT_DATE: show eligible commits in committer-date order. */ -void sort_in_topological_order(struct commit_list ** list, int lifo); +void sort_in_topological_order(struct commit_list **, enum rev_sort_order); struct commit_graft { unsigned char sha1[20]; @@ -245,4 +267,6 @@ extern void print_commit_list(struct commit_list *list, */ extern void check_commit_signature(const struct commit* commit, struct signature_check *sigc); +int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused); + #endif /* COMMIT_H */ @@ -10,20 +10,69 @@ #include "strbuf.h" #include "quote.h" -typedef struct config_file { - struct config_file *prev; - FILE *f; +struct config_source { + struct config_source *prev; + union { + FILE *file; + struct config_buf { + const char *buf; + size_t len; + size_t pos; + } buf; + } u; const char *name; + int die_on_error; int linenr; int eof; struct strbuf value; struct strbuf var; -} config_file; -static config_file *cf; + int (*fgetc)(struct config_source *c); + int (*ungetc)(int c, struct config_source *conf); + long (*ftell)(struct config_source *c); +}; + +static struct config_source *cf; static int zlib_compression_seen; +static int config_file_fgetc(struct config_source *conf) +{ + return fgetc(conf->u.file); +} + +static int config_file_ungetc(int c, struct config_source *conf) +{ + return ungetc(c, conf->u.file); +} + +static long config_file_ftell(struct config_source *conf) +{ + return ftell(conf->u.file); +} + + +static int config_buf_fgetc(struct config_source *conf) +{ + if (conf->u.buf.pos < conf->u.buf.len) + return conf->u.buf.buf[conf->u.buf.pos++]; + + return EOF; +} + +static int config_buf_ungetc(int c, struct config_source *conf) +{ + if (conf->u.buf.pos > 0) + return conf->u.buf.buf[--conf->u.buf.pos]; + + return EOF; +} + +static long config_buf_ftell(struct config_source *conf) +{ + return conf->u.buf.pos; +} + #define MAX_INCLUDE_DEPTH 10 static const char include_depth_advice[] = "exceeded maximum include depth (%d) while including\n" @@ -168,27 +217,22 @@ int git_config_from_parameters(config_fn_t fn, void *data) static int get_next_char(void) { - int c; - FILE *f; - - c = '\n'; - if (cf && ((f = cf->f) != NULL)) { - c = fgetc(f); - if (c == '\r') { - /* DOS like systems */ - c = fgetc(f); - if (c != '\n') { - ungetc(c, f); - c = '\r'; - } - } - if (c == '\n') - cf->linenr++; - if (c == EOF) { - cf->eof = 1; - c = '\n'; + int c = cf->fgetc(cf); + + if (c == '\r') { + /* DOS like systems */ + c = cf->fgetc(cf); + if (c != '\n') { + cf->ungetc(c, cf); + c = '\r'; } } + if (c == '\n') + cf->linenr++; + if (c == EOF) { + cf->eof = 1; + c = '\n'; + } return c; } @@ -339,7 +383,7 @@ static int get_base_var(struct strbuf *name) } } -static int git_parse_file(config_fn_t fn, void *data) +static int git_parse_source(config_fn_t fn, void *data) { int comment = 0; int baselen = 0; @@ -399,7 +443,10 @@ static int git_parse_file(config_fn_t fn, void *data) if (get_value(fn, data, var) < 0) break; } - die("bad config file line %d in %s", cf->linenr, cf->name); + if (cf->die_on_error) + die("bad config file line %d in %s", cf->linenr, cf->name); + else + return error("bad config file line %d in %s", cf->linenr, cf->name); } static int parse_unit_factor(const char *end, uintmax_t *val) @@ -688,9 +735,6 @@ static int git_default_core_config(const char *var, const char *value) return 0; } - if (!strcmp(var, "core.logpackaccess")) - return git_config_string(&log_pack_access, var, value); - if (!strcmp(var, "core.autocrlf")) { if (value && !strcasecmp(value, "input")) { if (core_eol == EOL_CRLF) @@ -909,6 +953,33 @@ int git_default_config(const char *var, const char *value, void *dummy) return 0; } +/* + * All source specific fields in the union, die_on_error, name and the callbacks + * fgetc, ungetc, ftell of top need to be initialized before calling + * this function. + */ +static int do_config_from(struct config_source *top, config_fn_t fn, void *data) +{ + int ret; + + /* push config-file parsing state stack */ + top->prev = cf; + top->linenr = 1; + top->eof = 0; + strbuf_init(&top->value, 1024); + strbuf_init(&top->var, 1024); + cf = top; + + ret = git_parse_source(fn, data); + + /* pop config-file parsing state stack */ + strbuf_release(&top->value); + strbuf_release(&top->var); + cf = top->prev; + + return ret; +} + int git_config_from_file(config_fn_t fn, const char *filename, void *data) { int ret; @@ -916,30 +987,74 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data) ret = -1; if (f) { - config_file top; + struct config_source top; - /* push config-file parsing state stack */ - top.prev = cf; - top.f = f; + top.u.file = f; top.name = filename; - top.linenr = 1; - top.eof = 0; - strbuf_init(&top.value, 1024); - strbuf_init(&top.var, 1024); - cf = ⊤ + top.die_on_error = 1; + top.fgetc = config_file_fgetc; + top.ungetc = config_file_ungetc; + top.ftell = config_file_ftell; - ret = git_parse_file(fn, data); - - /* pop config-file parsing state stack */ - strbuf_release(&top.value); - strbuf_release(&top.var); - cf = top.prev; + ret = do_config_from(&top, fn, data); fclose(f); } return ret; } +int git_config_from_buf(config_fn_t fn, const char *name, const char *buf, + size_t len, void *data) +{ + struct config_source top; + + top.u.buf.buf = buf; + top.u.buf.len = len; + top.u.buf.pos = 0; + top.name = name; + top.die_on_error = 0; + top.fgetc = config_buf_fgetc; + top.ungetc = config_buf_ungetc; + top.ftell = config_buf_ftell; + + return do_config_from(&top, fn, data); +} + +static int git_config_from_blob_sha1(config_fn_t fn, + const char *name, + const unsigned char *sha1, + void *data) +{ + enum object_type type; + char *buf; + unsigned long size; + int ret; + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return error("unable to load config blob object '%s'", name); + if (type != OBJ_BLOB) { + free(buf); + return error("reference '%s' does not point to a blob", name); + } + + ret = git_config_from_buf(fn, name, buf, size, data); + free(buf); + + return ret; +} + +static int git_config_from_blob_ref(config_fn_t fn, + const char *name, + void *data) +{ + unsigned char sha1[20]; + + if (get_sha1(name, sha1) < 0) + return error("unable to resolve config blob '%s'", name); + return git_config_from_blob_sha1(fn, name, sha1, data); +} + const char *git_etc_gitconfig(void) { static const char *system_wide; @@ -1005,7 +1120,9 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) } int git_config_with_options(config_fn_t fn, void *data, - const char *filename, int respect_includes) + const char *filename, + const char *blob_ref, + int respect_includes) { char *repo_config = NULL; int ret; @@ -1024,6 +1141,8 @@ int git_config_with_options(config_fn_t fn, void *data, */ if (filename) return git_config_from_file(fn, filename, data); + else if (blob_ref) + return git_config_from_blob_ref(fn, blob_ref, data); repo_config = git_pathdup("config"); ret = git_config_early(fn, data, repo_config); @@ -1034,7 +1153,7 @@ int git_config_with_options(config_fn_t fn, void *data, int git_config(config_fn_t fn, void *data) { - return git_config_with_options(fn, data, NULL, 1); + return git_config_with_options(fn, data, NULL, NULL, 1); } /* @@ -1066,7 +1185,6 @@ static int store_aux(const char *key, const char *value, void *cb) { const char *ep; size_t section_len; - FILE *f = cf->f; switch (store.state) { case KEY_SEEN: @@ -1078,7 +1196,7 @@ static int store_aux(const char *key, const char *value, void *cb) return 1; } - store.offset[store.seen] = ftell(f); + store.offset[store.seen] = cf->ftell(cf); store.seen++; } break; @@ -1105,19 +1223,19 @@ static int store_aux(const char *key, const char *value, void *cb) * Do not increment matches: this is no match, but we * just made sure we are in the desired section. */ - store.offset[store.seen] = ftell(f); + store.offset[store.seen] = cf->ftell(cf); /* fallthru */ case SECTION_END_SEEN: case START: if (matches(key, value)) { - store.offset[store.seen] = ftell(f); + store.offset[store.seen] = cf->ftell(cf); store.state = KEY_SEEN; store.seen++; } else { if (strrchr(key, '.') - key == store.baselen && !strncmp(key, store.key, store.baselen)) { store.state = SECTION_SEEN; - store.offset[store.seen] = ftell(f); + store.offset[store.seen] = cf->ftell(cf); } } } diff --git a/configure.ac b/configure.ac index f3462d9c81..2f433939dc 100644 --- a/configure.ac +++ b/configure.ac @@ -193,7 +193,7 @@ AC_ARG_ENABLE([pthreads], [FLAGS is the value to pass to the compiler to enable POSIX Threads.] [The default if FLAGS is not specified is to try first -pthread] [and then -lpthread.] - [--without-pthreads will disable threading.])], + [--disable-pthreads will disable threading.])], [ if test "x$enableval" = "xyes"; then AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads]) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 56c52c6654..cd509a5d63 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -33,8 +33,6 @@ esac # returns location of .git repo __gitdir () { - # Note: this function is duplicated in git-prompt.sh - # When updating it, make sure you update the other one to match. if [ -z "${1-}" ]; then if [ -n "${__git_dir-}" ]; then echo "$__git_dir" @@ -650,6 +648,7 @@ __git_list_porcelain_commands () cat-file) : plumbing;; check-attr) : plumbing;; check-ignore) : plumbing;; + check-mailmap) : plumbing;; check-ref-format) : plumbing;; checkout-index) : plumbing;; commit-tree) : plumbing;; @@ -1163,7 +1162,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary --no-prefix --src-prefix= --dst-prefix= --inter-hunk-context= --patience --histogram --minimal - --raw + --raw --word-diff --dirstat --dirstat= --dirstat-by-file --dirstat-by-file= --cumulative --diff-algorithm= @@ -1211,7 +1210,7 @@ _git_difftool () return ;; esac - __git_complete_file + __git_complete_revlist_file } __git_fetch_options=" @@ -2277,7 +2276,7 @@ _git_show () return ;; esac - __git_complete_file + __git_complete_revlist_file } _git_show_branch () @@ -2492,9 +2491,10 @@ __git_main () i="${words[c]}" case "$i" in --git-dir=*) __git_dir="${i#--git-dir=}" ;; + --git-dir) ((c++)) ; __git_dir="${words[c]}" ;; --bare) __git_dir="." ;; --help) command="help"; break ;; - -c) c=$((++c)) ;; + -c|--work-tree|--namespace) ((c++)) ;; -*) ;; *) command="$i"; break ;; esac @@ -2512,6 +2512,7 @@ __git_main () --exec-path --exec-path= --html-path + --man-path --info-path --work-tree= --namespace= diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index 86a4f3fa49..a81ef5a482 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -3,7 +3,7 @@ # Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org> # Distributed under the GNU General Public License, version 2.0. # -# This script allows you to see the current branch in your prompt. +# This script allows you to see repository status in your prompt. # # To enable: # @@ -13,24 +13,27 @@ # 3a) Change your PS1 to call __git_ps1 as # command-substitution: # Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' -# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ ' +# ZSH: setopt PROMPT_SUBST ; PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ ' # the optional argument will be used as format string. -# 3b) Alternatively, if you are using bash, __git_ps1 can be -# used for PROMPT_COMMAND with two parameters, <pre> and -# <post>, which are strings you would put in $PS1 before -# and after the status string generated by the git-prompt -# machinery. e.g. +# 3b) Alternatively, for a slightly faster prompt, __git_ps1 can +# be used for PROMPT_COMMAND in Bash or for precmd() in Zsh +# with two parameters, <pre> and <post>, which are strings +# you would put in $PS1 before and after the status string +# generated by the git-prompt machinery. e.g. # Bash: PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "' +# will show username, at-sign, host, colon, cwd, then +# various status string, followed by dollar and SP, as +# your prompt. # ZSH: precmd () { __git_ps1 "%n" ":%~$ " "|%s" } -# will show username, at-sign, host, colon, cwd, then -# various status string, followed by dollar and SP, as -# your prompt. +# will show username, pipe, then various status string, +# followed by colon, cwd, dollar and SP, as your prompt. # Optionally, you can supply a third argument with a printf # format string to finetune the output of the branch status # -# The argument to __git_ps1 will be displayed only if you are currently -# in a git repository. The %s token will be the name of the current -# branch. +# The repository status will be displayed only if you are currently in a +# git repository. The %s token is the placeholder for the shown status. +# +# The prompt status always includes the current branch name. # # In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value, # unstaged (*) and staged (+) changes will be shown next to the branch @@ -78,31 +81,8 @@ # # If you would like a colored hint about the current dirty state, set # GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on -# the colored output of "git status -sb". - -# __gitdir accepts 0 or 1 arguments (i.e., location) -# returns location of .git repo -__gitdir () -{ - # Note: this function is duplicated in git-completion.bash - # When updating it, make sure you update the other one to match. - if [ -z "${1-}" ]; then - if [ -n "${__git_dir-}" ]; then - echo "$__git_dir" - elif [ -n "${GIT_DIR-}" ]; then - test -d "${GIT_DIR-}" || return 1 - echo "$GIT_DIR" - elif [ -d .git ]; then - echo .git - else - git rev-parse --git-dir 2>/dev/null - fi - elif [ -d "$1/.git" ]; then - echo "$1/.git" - else - echo "$1" - fi -} +# the colored output of "git status -sb" and are available only when +# using __git_ps1 for PROMPT_COMMAND or precmd. # stores the divergence from upstream in $p # used by GIT_PS1_SHOWUPSTREAM @@ -225,8 +205,8 @@ __git_ps1_show_upstream () } # Helper function that is meant to be called from __git_ps1. It -# builds up a gitstring injecting color codes into the appropriate -# places. +# injects color codes into the appropriate gitstring variables used +# to build a gitstring. __git_ps1_colorize_gitstring () { if [[ -n ${ZSH_VERSION-} ]]; then @@ -234,74 +214,40 @@ __git_ps1_colorize_gitstring () local c_green='%F{green}' local c_lblue='%F{blue}' local c_clear='%f' - local bad_color=$c_red - local ok_color=$c_green - local branch_color="$c_clear" - local flags_color="$c_lblue" - local branchstring="$c${b##refs/heads/}" - - if [ $detached = no ]; then - branch_color="$ok_color" - else - branch_color="$bad_color" - fi - - gitstring="$branch_color$branchstring$c_clear" - - if [ -n "$w$i$s$u$r$p" ]; then - gitstring="$gitstring$z" - fi - if [ "$w" = "*" ]; then - gitstring="$gitstring$bad_color$w" - fi - if [ -n "$i" ]; then - gitstring="$gitstring$ok_color$i" - fi - if [ -n "$s" ]; then - gitstring="$gitstring$flags_color$s" - fi - if [ -n "$u" ]; then - gitstring="$gitstring$bad_color$u" - fi - gitstring="$gitstring$c_clear$r$p" - return + else + # Using \[ and \] around colors is necessary to prevent + # issues with command line editing/browsing/completion! + local c_red='\[\e[31m\]' + local c_green='\[\e[32m\]' + local c_lblue='\[\e[1;34m\]' + local c_clear='\[\e[0m\]' fi - local c_red='\e[31m' - local c_green='\e[32m' - local c_lblue='\e[1;34m' - local c_clear='\e[0m' local bad_color=$c_red local ok_color=$c_green - local branch_color="$c_clear" local flags_color="$c_lblue" - local branchstring="$c${b##refs/heads/}" + local branch_color="" if [ $detached = no ]; then branch_color="$ok_color" else branch_color="$bad_color" fi + c="$branch_color$c" - # Setting gitstring directly with \[ and \] around colors - # is necessary to prevent wrapping issues! - gitstring="\[$branch_color\]$branchstring\[$c_clear\]" - - if [ -n "$w$i$s$u$r$p" ]; then - gitstring="$gitstring$z" - fi + z="$c_clear$z" if [ "$w" = "*" ]; then - gitstring="$gitstring\[$bad_color\]$w" + w="$bad_color$w" fi if [ -n "$i" ]; then - gitstring="$gitstring\[$ok_color\]$i" + i="$ok_color$i" fi if [ -n "$s" ]; then - gitstring="$gitstring\[$flags_color\]$s" + s="$flags_color$s" fi if [ -n "$u" ]; then - gitstring="$gitstring\[$bad_color\]$u" + u="$bad_color$u" fi - gitstring="$gitstring\[$c_clear\]$r$p" + r="$c_clear$r" } # __git_ps1 accepts 0 or 1 arguments (i.e., format string) @@ -335,50 +281,83 @@ __git_ps1 () ;; esac - local g="$(__gitdir)" - if [ -z "$g" ]; then + local repo_info rev_parse_exit_code + repo_info="$(git rev-parse --git-dir --is-inside-git-dir \ + --is-bare-repository --is-inside-work-tree \ + --short HEAD 2>/dev/null)" + rev_parse_exit_code="$?" + + if [ -z "$repo_info" ]; then if [ $pcmode = yes ]; then #In PC mode PS1 always needs to be set PS1="$ps1pc_start$ps1pc_end" fi + return + fi + + local short_sha + if [ "$rev_parse_exit_code" = "0" ]; then + short_sha="${repo_info##*$'\n'}" + repo_info="${repo_info%$'\n'*}" + fi + local inside_worktree="${repo_info##*$'\n'}" + repo_info="${repo_info%$'\n'*}" + local bare_repo="${repo_info##*$'\n'}" + repo_info="${repo_info%$'\n'*}" + local inside_gitdir="${repo_info##*$'\n'}" + local g="${repo_info%$'\n'*}" + + local r="" + local b="" + local step="" + local total="" + if [ -d "$g/rebase-merge" ]; then + read b 2>/dev/null <"$g/rebase-merge/head-name" + read step 2>/dev/null <"$g/rebase-merge/msgnum" + read total 2>/dev/null <"$g/rebase-merge/end" + if [ -f "$g/rebase-merge/interactive" ]; then + r="|REBASE-i" + else + r="|REBASE-m" + fi else - local r="" - local b="" - local step="" - local total="" - if [ -d "$g/rebase-merge" ]; then - b="$(cat "$g/rebase-merge/head-name")" - step=$(cat "$g/rebase-merge/msgnum") - total=$(cat "$g/rebase-merge/end") - if [ -f "$g/rebase-merge/interactive" ]; then - r="|REBASE-i" + if [ -d "$g/rebase-apply" ]; then + read step 2>/dev/null <"$g/rebase-apply/next" + read total 2>/dev/null <"$g/rebase-apply/last" + if [ -f "$g/rebase-apply/rebasing" ]; then + read b 2>/dev/null <"$g/rebase-apply/head-name" + r="|REBASE" + elif [ -f "$g/rebase-apply/applying" ]; then + r="|AM" else - r="|REBASE-m" + r="|AM/REBASE" fi + elif [ -f "$g/MERGE_HEAD" ]; then + r="|MERGING" + elif [ -f "$g/CHERRY_PICK_HEAD" ]; then + r="|CHERRY-PICKING" + elif [ -f "$g/REVERT_HEAD" ]; then + r="|REVERTING" + elif [ -f "$g/BISECT_LOG" ]; then + r="|BISECTING" + fi + + if [ -n "$b" ]; then + : + elif [ -h "$g/HEAD" ]; then + # symlink symbolic ref + b="$(git symbolic-ref HEAD 2>/dev/null)" else - if [ -d "$g/rebase-apply" ]; then - step=$(cat "$g/rebase-apply/next") - total=$(cat "$g/rebase-apply/last") - if [ -f "$g/rebase-apply/rebasing" ]; then - b="$(cat "$g/rebase-apply/head-name")" - r="|REBASE" - elif [ -f "$g/rebase-apply/applying" ]; then - r="|AM" - else - r="|AM/REBASE" + local head="" + if ! read head 2>/dev/null <"$g/HEAD"; then + if [ $pcmode = yes ]; then + PS1="$ps1pc_start$ps1pc_end" fi - elif [ -f "$g/MERGE_HEAD" ]; then - r="|MERGING" - elif [ -f "$g/CHERRY_PICK_HEAD" ]; then - r="|CHERRY-PICKING" - elif [ -f "$g/REVERT_HEAD" ]; then - r="|REVERTING" - elif [ -f "$g/BISECT_LOG" ]; then - r="|BISECTING" + return fi - - test -n "$b" || - b="$(git symbolic-ref HEAD 2>/dev/null)" || { + # is it a symbolic ref? + b="${head#ref: }" + if [ "$head" = "$b" ]; then detached=yes b="$( case "${GIT_PS1_DESCRIBE_STYLE-}" in @@ -392,70 +371,75 @@ __git_ps1 () git describe --tags --exact-match HEAD ;; esac 2>/dev/null)" || - b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." || - b="unknown" + b="$short_sha..." b="($b)" - } + fi fi + fi - if [ -n "$step" ] && [ -n "$total" ]; then - r="$r $step/$total" - fi + if [ -n "$step" ] && [ -n "$total" ]; then + r="$r $step/$total" + fi - local w="" - local i="" - local s="" - local u="" - local c="" - local p="" + local w="" + local i="" + local s="" + local u="" + local c="" + local p="" - if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then - if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then - c="BARE:" + if [ "true" = "$inside_gitdir" ]; then + if [ "true" = "$bare_repo" ]; then + c="BARE:" + else + b="GIT_DIR!" + fi + elif [ "true" = "$inside_worktree" ]; then + if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] && + [ "$(git config --bool bash.showDirtyState)" != "false" ] + then + git diff --no-ext-diff --quiet --exit-code || w="*" + if [ -n "$short_sha" ]; then + git diff-index --cached --quiet HEAD -- || i="+" else - b="GIT_DIR!" - fi - elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then - if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] && - [ "$(git config --bool bash.showDirtyState)" != "false" ] - then - git diff --no-ext-diff --quiet --exit-code || w="*" - if git rev-parse --quiet --verify HEAD >/dev/null; then - git diff-index --cached --quiet HEAD -- || i="+" - else - i="#" - fi - fi - if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then - git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$" + i="#" fi + fi + if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ] && + [ -r "$g/refs/stash" ]; then + s="$" + fi - if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] && - [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] && - [ -n "$(git ls-files --others --exclude-standard)" ] - then - u="%${ZSH_VERSION+%}" - fi + if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] && + [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] && + git ls-files --others --exclude-standard --error-unmatch -- '*' >/dev/null 2>/dev/null + then + u="%${ZSH_VERSION+%}" + fi - if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then - __git_ps1_show_upstream - fi + if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then + __git_ps1_show_upstream fi + fi - local z="${GIT_PS1_STATESEPARATOR-" "}" - local f="$w$i$s$u" - if [ $pcmode = yes ]; then - local gitstring= - if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then - __git_ps1_colorize_gitstring - else - gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p" - fi + local z="${GIT_PS1_STATESEPARATOR-" "}" + + # NO color option unless in PROMPT_COMMAND mode + if [ $pcmode = yes ] && [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then + __git_ps1_colorize_gitstring + fi + + local f="$w$i$s$u" + local gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p" + + if [ $pcmode = yes ]; then + if [[ -n ${ZSH_VERSION-} ]]; then gitstring=$(printf -- "$printf_format" "$gitstring") - PS1="$ps1pc_start$gitstring$ps1pc_end" else - # NO color option unless in PROMPT_COMMAND mode - printf -- "$printf_format" "$c${b##refs/heads/}${f:+$z$f}$r$p" + printf -v gitstring -- "$printf_format" "$gitstring" fi + PS1="$ps1pc_start$gitstring$ps1pc_end" + else + printf -- "$printf_format" "$gitstring" fi } diff --git a/contrib/mw-to-git/.perlcriticrc b/contrib/mw-to-git/.perlcriticrc new file mode 100644 index 0000000000..5a9955d757 --- /dev/null +++ b/contrib/mw-to-git/.perlcriticrc @@ -0,0 +1,28 @@ +# These 3 rules demand to add the s, m and x flag to *every* regexp. This is +# overkill and would be harmful for readability. +[-RegularExpressions::RequireExtendedFormatting] +[-RegularExpressions::RequireDotMatchAnything] +[-RegularExpressions::RequireLineBoundaryMatching] + +# This rule says that builtin functions should not be called with parentheses +# e.g.: (taken from CPAN's documentation) +# open($handle, '>', $filename); #not ok +# open $handle, '>', $filename; #ok +# Applying such a rule would mean modifying a huge number of lines for a +# question of style. +[-CodeLayout::ProhibitParensWithBuiltins] + +# This rule states that each system call should have its return value checked +# The problem is that it includes the print call. Checking every print call's +# return value would be harmful to the code readabilty. +# This configuration keeps all default function but print. +[InputOutput::RequireCheckedSyscalls] +functions = open say close + +# This rules demands to add a dependancy for the Readonly module. This is not +# wished. +[-ValuesAndExpressions::ProhibitConstantPragma] + +# This rule is not really useful (rather a question of style) and produces many +# warnings among the code. +[-ValuesAndExpressions::ProhibitNoisyQuotes] diff --git a/contrib/mw-to-git/Git/Mediawiki.pm b/contrib/mw-to-git/Git/Mediawiki.pm new file mode 100644 index 0000000000..d13c4dfa7d --- /dev/null +++ b/contrib/mw-to-git/Git/Mediawiki.pm @@ -0,0 +1,100 @@ +package Git::Mediawiki; + +use 5.008; +use strict; +use Git; + +BEGIN { + +our ($VERSION, @ISA, @EXPORT, @EXPORT_OK); + +# Totally unstable API. +$VERSION = '0.01'; + +require Exporter; + +@ISA = qw(Exporter); + +@EXPORT = (); + +# Methods which can be called as standalone functions as well: +@EXPORT_OK = qw(clean_filename smudge_filename connect_maybe + EMPTY HTTP_CODE_OK HTTP_CODE_PAGE_NOT_FOUND); +} + +# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced +use constant SLASH_REPLACEMENT => '%2F'; + +# Used to test for empty strings +use constant EMPTY => q{}; + +# HTTP codes +use constant HTTP_CODE_OK => 200; +use constant HTTP_CODE_PAGE_NOT_FOUND => 404; + +sub clean_filename { + my $filename = shift; + $filename =~ s{@{[SLASH_REPLACEMENT]}}{/}g; + # [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded. + # Do a variant of URL-encoding, i.e. looks like URL-encoding, + # but with _ added to prevent MediaWiki from thinking this is + # an actual special character. + $filename =~ s/[\[\]\{\}\|]/sprintf("_%%_%x", ord($&))/ge; + # If we use the uri escape before + # we should unescape here, before anything + + return $filename; +} + +sub smudge_filename { + my $filename = shift; + $filename =~ s{/}{@{[SLASH_REPLACEMENT]}}g; + $filename =~ s/ /_/g; + # Decode forbidden characters encoded in clean_filename + $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf('%c', hex($1))/ge; + return $filename; +} + +sub connect_maybe { + my $wiki = shift; + if ($wiki) { + return $wiki; + } + + my $remote_name = shift; + my $remote_url = shift; + my ($wiki_login, $wiki_password, $wiki_domain); + + $wiki_login = Git::config("remote.${remote_name}.mwLogin"); + $wiki_password = Git::config("remote.${remote_name}.mwPassword"); + $wiki_domain = Git::config("remote.${remote_name}.mwDomain"); + + $wiki = MediaWiki::API->new; + $wiki->{config}->{api_url} = "${remote_url}/api.php"; + if ($wiki_login) { + my %credential = ( + 'url' => $remote_url, + 'username' => $wiki_login, + 'password' => $wiki_password + ); + Git::credential(\%credential); + my $request = {lgname => $credential{username}, + lgpassword => $credential{password}, + lgdomain => $wiki_domain}; + if ($wiki->login($request)) { + Git::credential(\%credential, 'approve'); + print {*STDERR} qq(Logged in mediawiki user "$credential{username}".\n); + } else { + print {*STDERR} qq(Failed to log in mediawiki user "$credential{username}" on ${remote_url}\n); + print {*STDERR} ' (error ' . + $wiki->{error}->{code} . ': ' . + $wiki->{error}->{details} . ")\n"; + Git::credential(\%credential, 'reject'); + exit 1; + } + } + + return $wiki; +} + +1; # Famous last words diff --git a/contrib/mw-to-git/Makefile b/contrib/mw-to-git/Makefile index f14971987c..76fcd4defc 100644 --- a/contrib/mw-to-git/Makefile +++ b/contrib/mw-to-git/Makefile @@ -2,16 +2,43 @@ # Copyright (C) 2013 # Matthieu Moy <Matthieu.Moy@imag.fr> # -## Build git-remote-mediawiki +# To build and test: +# +# make +# bin-wrapper/git mw preview Some_page.mw +# bin-wrapper/git clone mediawiki::http://example.com/wiki/ +# +# To install, run Git's toplevel 'make install' then run: +# +# make install +GIT_MEDIAWIKI_PM=Git/Mediawiki.pm SCRIPT_PERL=git-remote-mediawiki.perl +SCRIPT_PERL+=git-mw.perl GIT_ROOT_DIR=../.. HERE=contrib/mw-to-git/ SCRIPT_PERL_FULL=$(patsubst %,$(HERE)/%,$(SCRIPT_PERL)) +INSTLIBDIR=$(shell $(MAKE) -C $(GIT_ROOT_DIR)/perl \ + -s --no-print-directory instlibdir) all: build -build install clean: - $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL=$(SCRIPT_PERL_FULL) \ - $@-perl-script +install_pm: + install $(GIT_MEDIAWIKI_PM) $(INSTLIBDIR)/$(GIT_MEDIAWIKI_PM) + +build: + $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \ + build-perl-script + +install: install_pm + $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \ + install-perl-script + +clean: + $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \ + clean-perl-script + rm $(INSTLIBDIR)/$(GIT_MEDIAWIKI_PM) + +perlcritic: + perlcritic -2 *.perl diff --git a/contrib/mw-to-git/bin-wrapper/git b/contrib/mw-to-git/bin-wrapper/git new file mode 100755 index 0000000000..6663ae57e8 --- /dev/null +++ b/contrib/mw-to-git/bin-wrapper/git @@ -0,0 +1,14 @@ +#!/bin/sh + +# git executable wrapper script for Git-Mediawiki to run tests without +# installing all the scripts and perl packages. + +GIT_ROOT_DIR=../../.. +GIT_EXEC_PATH=$(cd "$(dirname "$0")" && cd ${GIT_ROOT_DIR} && pwd) + +GITPERLLIB="$GIT_EXEC_PATH"'/contrib/mw-to-git'"${GITPERLLIB:+:$GITPERLLIB}" +PATH="$GIT_EXEC_PATH"'/contrib/mw-to-git:'"$PATH" + +export GITPERLLIB PATH + +exec "${GIT_EXEC_PATH}/bin-wrappers/git" "$@" diff --git a/contrib/mw-to-git/git-mw.perl b/contrib/mw-to-git/git-mw.perl new file mode 100755 index 0000000000..28df3ee321 --- /dev/null +++ b/contrib/mw-to-git/git-mw.perl @@ -0,0 +1,368 @@ +#!/usr/bin/perl + +# Copyright (C) 2013 +# Benoit Person <benoit.person@ensimag.imag.fr> +# Celestin Matte <celestin.matte@ensimag.imag.fr> +# License: GPL v2 or later + +# Set of tools for git repo with a mediawiki remote. +# Documentation & bugtracker: https://github.com/moy/Git-Mediawiki/ + +use strict; +use warnings; + +use Getopt::Long; +use URI::URL qw(url); +use LWP::UserAgent; +use HTML::TreeBuilder; + +use Git; +use MediaWiki::API; +use Git::Mediawiki qw(clean_filename connect_maybe + EMPTY HTTP_CODE_PAGE_NOT_FOUND); + +# By default, use UTF-8 to communicate with Git and the user +binmode STDERR, ':encoding(UTF-8)'; +binmode STDOUT, ':encoding(UTF-8)'; + +# Global parameters +my $verbose = 0; +sub v_print { + if ($verbose) { + return print {*STDERR} @_; + } + return; +} + +# Preview parameters +my $file_name = EMPTY; +my $remote_name = EMPTY; +my $preview_file_name = EMPTY; +my $autoload = 0; +sub file { + $file_name = shift; + return $file_name; +} + +my %commands = ( + 'help' => + [\&help, {}, \&help], + 'preview' => + [\&preview, { + '<>' => \&file, + 'output|o=s' => \$preview_file_name, + 'remote|r=s' => \$remote_name, + 'autoload|a' => \$autoload + }, \&preview_help] +); + +# Search for sub-command +my $cmd = $commands{'help'}; +for (0..@ARGV-1) { + if (defined $commands{$ARGV[$_]}) { + $cmd = $commands{$ARGV[$_]}; + splice @ARGV, $_, 1; + last; + } +}; +GetOptions( %{$cmd->[1]}, + 'help|h' => \&{$cmd->[2]}, + 'verbose|v' => \$verbose); + +# Launch command +&{$cmd->[0]}; + +############################# Preview Functions ################################ + +sub preview_help { + print {*STDOUT} <<'END'; +USAGE: git mw preview [--remote|-r <remote name>] [--autoload|-a] + [--output|-o <output filename>] [--verbose|-v] + <blob> | <filename> + +DESCRIPTION: +Preview is an utiliy to preview local content of a mediawiki repo as if it was +pushed on the remote. + +For that, preview searches for the remote name of the current branch's +upstream if --remote is not set. If that remote is not found or if it +is not a mediawiki, it lists all mediawiki remotes configured and asks +you to replay your command with the --remote option set properly. + +Then, it searches for a file named 'filename'. If it's not found in +the current dir, it will assume it's a blob. + +The content retrieved in the file (or in the blob) will then be parsed +by the remote mediawiki and combined with a template retrieved from +the mediawiki. + +Finally, preview will save the HTML result in a file. and autoload it +in your default web browser if the option --autoload is present. + +OPTIONS: + -r <remote name>, --remote <remote name> + If the remote is a mediawiki, the template and the parse engine + used for the preview will be those of that remote. + If not, a list of valid remotes will be shown. + + -a, --autoload + Try to load the HTML output in a new tab (or new window) of your + default web browser. + + -o <output filename>, --output <output filename> + Change the HTML output filename. Default filename is based on the + input filename with its extension replaced by '.html'. + + -v, --verbose + Show more information on what's going on under the hood. +END + exit; +} + +sub preview { + my $wiki; + my ($remote_url, $wiki_page_name); + my ($new_content, $template); + my $file_content; + + if ($file_name eq EMPTY) { + die "Missing file argument, see `git mw help`\n"; + } + + v_print("### Selecting remote\n"); + if ($remote_name eq EMPTY) { + $remote_name = find_upstream_remote_name(); + if ($remote_name) { + $remote_url = mediawiki_remote_url_maybe($remote_name); + } + + if (! $remote_url) { + my @valid_remotes = find_mediawiki_remotes(); + + if ($#valid_remotes == 0) { + print {*STDERR} "No mediawiki remote in this repo. \n"; + exit 1; + } else { + my $remotes_list = join("\n\t", @valid_remotes); + print {*STDERR} <<"MESSAGE"; +There are multiple mediawiki remotes, which of: + ${remotes_list} +do you want ? Use the -r option to specify the remote. +MESSAGE + } + + exit 1; + } + } else { + if (!is_valid_remote($remote_name)) { + die "${remote_name} is not a remote\n"; + } + + $remote_url = mediawiki_remote_url_maybe($remote_name); + if (! $remote_url) { + die "${remote_name} is not a mediawiki remote\n"; + } + } + v_print("selected remote:\n\tname: ${remote_name}\n\turl: ${remote_url}\n"); + + $wiki = connect_maybe($wiki, $remote_name, $remote_url); + + # Read file content + if (! -e $file_name) { + $file_content = git_cmd_try { + Git::command('cat-file', 'blob', $file_name); } + "%s failed w/ code %d"; + + if ($file_name =~ /(.+):(.+)/) { + $file_name = $2; + } + } else { + open my $read_fh, "<", $file_name + or die "could not open ${file_name}: $!\n"; + $file_content = do { local $/ = undef; <$read_fh> }; + close $read_fh + or die "unable to close: $!\n"; + } + + v_print("### Retrieving template\n"); + ($wiki_page_name = clean_filename($file_name)) =~ s/\.[^.]+$//; + $template = get_template($remote_url, $wiki_page_name); + + v_print("### Parsing local content\n"); + $new_content = $wiki->api({ + action => 'parse', + text => $file_content, + title => $wiki_page_name + }, { + skip_encoding => 1 + }) or die "No response from remote mediawiki\n"; + $new_content = $new_content->{'parse'}->{'text'}->{'*'}; + + v_print("### Merging contents\n"); + if ($preview_file_name eq EMPTY) { + ($preview_file_name = $file_name) =~ s/\.[^.]+$/.html/; + } + open(my $save_fh, '>:encoding(UTF-8)', $preview_file_name) + or die "Could not open: $!\n"; + print {$save_fh} merge_contents($template, $new_content, $remote_url); + close($save_fh) + or die "Could not close: $!\n"; + + v_print("### Results\n"); + if ($autoload) { + v_print("Launching browser w/ file: ${preview_file_name}"); + system('git', 'web--browse', $preview_file_name); + } else { + print {*STDERR} "Preview file saved as: ${preview_file_name}\n"; + } + + exit; +} + +# uses global scope variable: $remote_name +sub merge_contents { + my $template = shift; + my $content = shift; + my $remote_url = shift; + my ($content_tree, $html_tree, $mw_content_text); + my $template_content_id = 'bodyContent'; + + $html_tree = HTML::TreeBuilder->new; + $html_tree->parse($template); + + $content_tree = HTML::TreeBuilder->new; + $content_tree->parse($content); + + $template_content_id = Git::config("remote.${remote_name}.mwIDcontent") + || $template_content_id; + v_print("Using '${template_content_id}' as the content ID\n"); + + $mw_content_text = $html_tree->look_down('id', $template_content_id); + if (!defined $mw_content_text) { + print {*STDERR} <<"CONFIG"; +Could not combine the new content with the template. You might want to +configure `mediawiki.IDContent` in your config: + git config --add remote.${remote_name}.mwIDcontent <id> +and re-run the command afterward. +CONFIG + exit 1; + } + $mw_content_text->delete_content(); + $mw_content_text->push_content($content_tree); + + make_links_absolute($html_tree, $remote_url); + + return $html_tree->as_HTML; +} + +sub make_links_absolute { + my $html_tree = shift; + my $remote_url = shift; + for (@{ $html_tree->extract_links() }) { + my ($link, $element, $attr) = @{ $_ }; + my $url = url($link)->canonical; + if ($url !~ /#/) { + $element->attr($attr, URI->new_abs($url, $remote_url)); + } + } + return $html_tree; +} + +sub is_valid_remote { + my $remote = shift; + my @remotes = git_cmd_try { + Git::command('remote') } + "%s failed w/ code %d"; + my $found_remote = 0; + foreach my $remote (@remotes) { + if ($remote eq $remote) { + $found_remote = 1; + last; + } + } + return $found_remote; +} + +sub find_mediawiki_remotes { + my @remotes = git_cmd_try { + Git::command('remote'); } + "%s failed w/ code %d"; + my $remote_url; + my @valid_remotes = (); + foreach my $remote (@remotes) { + $remote_url = mediawiki_remote_url_maybe($remote); + if ($remote_url) { + push(@valid_remotes, $remote); + } + } + return @valid_remotes; +} + +sub find_upstream_remote_name { + my $current_branch = git_cmd_try { + Git::command_oneline('symbolic-ref', '--short', 'HEAD') } + "%s failed w/ code %d"; + return Git::config("branch.${current_branch}.remote"); +} + +sub mediawiki_remote_url_maybe { + my $remote = shift; + + # Find remote url + my $remote_url = Git::config("remote.${remote}.url"); + if ($remote_url =~ s/mediawiki::(.*)/$1/) { + return url($remote_url)->canonical; + } + + return; +} + +sub get_template { + my $url = shift; + my $page_name = shift; + my ($req, $res, $code, $url_after); + + $req = LWP::UserAgent->new; + if ($verbose) { + $req->show_progress(1); + } + + $res = $req->get("${url}/index.php?title=${page_name}"); + if (!$res->is_success) { + $code = $res->code; + $url_after = $res->request()->uri(); # resolve all redirections + if ($code == HTTP_CODE_PAGE_NOT_FOUND) { + if ($verbose) { + print {*STDERR} <<"WARNING"; +Warning: Failed to retrieve '$page_name'. Create it on the mediawiki if you want +all the links to work properly. +Trying to use the mediawiki homepage as a fallback template ... +WARNING + } + + # LWP automatically redirects GET request + $res = $req->get("${url}/index.php"); + if (!$res->is_success) { + $url_after = $res->request()->uri(); # resolve all redirections + die "Failed to get homepage @ ${url_after} w/ code ${code}\n"; + } + } else { + die "Failed to get '${page_name}' @ ${url_after} w/ code ${code}\n"; + } + } + + return $res->decoded_content; +} + +############################## Help Functions ################################## + +sub help { + print {*STDOUT} <<'END'; +usage: git mw <command> <args> + +git mw commands are: + help Display help information about git mw + preview Parse and render local file into HTML +END + exit; +} diff --git a/contrib/mw-to-git/git-remote-mediawiki.perl b/contrib/mw-to-git/git-remote-mediawiki.perl index f0c348cd28..f8d7d2ca6c 100755 --- a/contrib/mw-to-git/git-remote-mediawiki.perl +++ b/contrib/mw-to-git/git-remote-mediawiki.perl @@ -13,19 +13,17 @@ use strict; use MediaWiki::API; +use Git; +use Git::Mediawiki qw(clean_filename smudge_filename connect_maybe + EMPTY HTTP_CODE_OK); use DateTime::Format::ISO8601; +use warnings; # By default, use UTF-8 to communicate with Git and the user -binmode STDERR, ":utf8"; -binmode STDOUT, ":utf8"; +binmode STDERR, ':encoding(UTF-8)'; +binmode STDOUT, ':encoding(UTF-8)'; use URI::Escape; -use IPC::Open2; - -use warnings; - -# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced -use constant SLASH_REPLACEMENT => "%2F"; # It's not always possible to delete pages (may require some # privileges). Deleted pages are replaced with this content. @@ -36,45 +34,57 @@ use constant DELETED_CONTENT => "[[Category:Deleted]]\n"; use constant EMPTY_CONTENT => "<!-- empty page -->\n"; # used to reflect file creation or deletion in diff. -use constant NULL_SHA1 => "0000000000000000000000000000000000000000"; +use constant NULL_SHA1 => '0000000000000000000000000000000000000000'; # Used on Git's side to reflect empty edit messages on the wiki use constant EMPTY_MESSAGE => '*Empty MediaWiki Message*'; +# Number of pages taken into account at once in submodule get_mw_page_list +use constant SLICE_SIZE => 50; + +# Number of linked mediafile to get at once in get_linked_mediafiles +# The query is split in small batches because of the MW API limit of +# the number of links to be returned (500 links max). +use constant BATCH_SIZE => 10; + +if (@ARGV != 2) { + exit_error_usage(); +} + my $remotename = $ARGV[0]; my $url = $ARGV[1]; # Accept both space-separated and multiple keys in config file. # Spaces should be written as _ anyway because we'll use chomp. -my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".pages")); +my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.${remotename}.pages")); chomp(@tracked_pages); # Just like @tracked_pages, but for MediaWiki categories. -my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".categories")); +my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.${remotename}.categories")); chomp(@tracked_categories); # Import media files on pull -my $import_media = run_git("config --get --bool remote.". $remotename .".mediaimport"); +my $import_media = run_git("config --get --bool remote.${remotename}.mediaimport"); chomp($import_media); -$import_media = ($import_media eq "true"); +$import_media = ($import_media eq 'true'); # Export media files on push -my $export_media = run_git("config --get --bool remote.". $remotename .".mediaexport"); +my $export_media = run_git("config --get --bool remote.${remotename}.mediaexport"); chomp($export_media); -$export_media = !($export_media eq "false"); +$export_media = !($export_media eq 'false'); -my $wiki_login = run_git("config --get remote.". $remotename .".mwLogin"); +my $wiki_login = run_git("config --get remote.${remotename}.mwLogin"); # Note: mwPassword is discourraged. Use the credential system instead. -my $wiki_passwd = run_git("config --get remote.". $remotename .".mwPassword"); -my $wiki_domain = run_git("config --get remote.". $remotename .".mwDomain"); +my $wiki_passwd = run_git("config --get remote.${remotename}.mwPassword"); +my $wiki_domain = run_git("config --get remote.${remotename}.mwDomain"); chomp($wiki_login); chomp($wiki_passwd); chomp($wiki_domain); # Import only last revisions (both for clone and fetch) -my $shallow_import = run_git("config --get --bool remote.". $remotename .".shallow"); +my $shallow_import = run_git("config --get --bool remote.${remotename}.shallow"); chomp($shallow_import); -$shallow_import = ($shallow_import eq "true"); +$shallow_import = ($shallow_import eq 'true'); # Fetch (clone and pull) by revisions instead of by pages. This behavior # is more efficient when we have a wiki with lots of pages and we fetch @@ -82,15 +92,18 @@ $shallow_import = ($shallow_import eq "true"); # Possible values: # - by_rev: perform one query per new revision on the remote wiki # - by_page: query each tracked page for new revision -my $fetch_strategy = run_git("config --get remote.$remotename.fetchStrategy"); -unless ($fetch_strategy) { - $fetch_strategy = run_git("config --get mediawiki.fetchStrategy"); +my $fetch_strategy = run_git("config --get remote.${remotename}.fetchStrategy"); +if (!$fetch_strategy) { + $fetch_strategy = run_git('config --get mediawiki.fetchStrategy'); } chomp($fetch_strategy); -unless ($fetch_strategy) { - $fetch_strategy = "by_page"; +if (!$fetch_strategy) { + $fetch_strategy = 'by_page'; } +# Remember the timestamp corresponding to a revision id. +my %basetimestamps; + # Dumb push: don't update notes and mediawiki ref to reflect the last push. # # Configurable with mediawiki.dumbPush, or per-remote with @@ -105,48 +118,25 @@ unless ($fetch_strategy) { # will get the history with information lost). If the import is # deterministic, this means everybody gets the same sha1 for each # MediaWiki revision. -my $dumb_push = run_git("config --get --bool remote.$remotename.dumbPush"); -unless ($dumb_push) { - $dumb_push = run_git("config --get --bool mediawiki.dumbPush"); +my $dumb_push = run_git("config --get --bool remote.${remotename}.dumbPush"); +if (!$dumb_push) { + $dumb_push = run_git('config --get --bool mediawiki.dumbPush'); } chomp($dumb_push); -$dumb_push = ($dumb_push eq "true"); +$dumb_push = ($dumb_push eq 'true'); my $wiki_name = $url; -$wiki_name =~ s/[^\/]*:\/\///; +$wiki_name =~ s{[^/]*://}{}; # If URL is like http://user:password@example.com/, we clearly don't # want the password in $wiki_name. While we're there, also remove user # and '@' sign, to avoid author like MWUser@HTTPUser@host.com $wiki_name =~ s/^.*@//; # Commands parser -my $entry; -my @cmd; while (<STDIN>) { chomp; - @cmd = split(/ /); - if (defined($cmd[0])) { - # Line not blank - if ($cmd[0] eq "capabilities") { - die("Too many arguments for capabilities") unless (!defined($cmd[1])); - mw_capabilities(); - } elsif ($cmd[0] eq "list") { - die("Too many arguments for list") unless (!defined($cmd[2])); - mw_list($cmd[1]); - } elsif ($cmd[0] eq "import") { - die("Invalid arguments for import") unless ($cmd[1] ne "" && !defined($cmd[2])); - mw_import($cmd[1]); - } elsif ($cmd[0] eq "option") { - die("Too many arguments for option") unless ($cmd[1] ne "" && $cmd[2] ne "" && !defined($cmd[3])); - mw_option($cmd[1],$cmd[2]); - } elsif ($cmd[0] eq "push") { - mw_push($cmd[1]); - } else { - print STDERR "Unknown command. Aborting...\n"; - last; - } - } else { - # blank line: we should terminate + + if (!parse_command($_)) { last; } @@ -156,88 +146,54 @@ while (<STDIN>) { ########################## Functions ############################## -## credential API management (generic functions) - -sub credential_read { - my %credential; - my $reader = shift; - my $op = shift; - while (<$reader>) { - my ($key, $value) = /([^=]*)=(.*)/; - if (not defined $key) { - die "ERROR receiving response from git credential $op:\n$_\n"; - } - $credential{$key} = $value; - } - return %credential; +## error handling +sub exit_error_usage { + die "ERROR: git-remote-mediawiki module was not called with a correct number of\n" . + "parameters\n" . + "You may obtain this error because you attempted to run the git-remote-mediawiki\n" . + "module directly.\n" . + "This module can be used the following way:\n" . + "\tgit clone mediawiki://<address of a mediawiki>\n" . + "Then, use git commit, push and pull as with every normal git repository.\n"; } -sub credential_write { - my $credential = shift; - my $writer = shift; - # url overwrites other fields, so it must come first - print $writer "url=$credential->{url}\n" if exists $credential->{url}; - while (my ($key, $value) = each(%$credential) ) { - if (length $value && $key ne 'url') { - print $writer "$key=$value\n"; - } +sub parse_command { + my ($line) = @_; + my @cmd = split(/ /, $line); + if (!defined $cmd[0]) { + return 0; } -} - -sub credential_run { - my $op = shift; - my $credential = shift; - my $pid = open2(my $reader, my $writer, "git credential $op"); - credential_write($credential, $writer); - print $writer "\n"; - close($writer); - - if ($op eq "fill") { - %$credential = credential_read($reader, $op); + if ($cmd[0] eq 'capabilities') { + die("Too many arguments for capabilities\n") + if (defined($cmd[1])); + mw_capabilities(); + } elsif ($cmd[0] eq 'list') { + die("Too many arguments for list\n") if (defined($cmd[2])); + mw_list($cmd[1]); + } elsif ($cmd[0] eq 'import') { + die("Invalid argument for import\n") + if ($cmd[1] eq EMPTY); + die("Too many arguments for import\n") + if (defined($cmd[2])); + mw_import($cmd[1]); + } elsif ($cmd[0] eq 'option') { + die("Invalid arguments for option\n") + if ($cmd[1] eq EMPTY || $cmd[2] eq EMPTY); + die("Too many arguments for option\n") + if (defined($cmd[3])); + mw_option($cmd[1],$cmd[2]); + } elsif ($cmd[0] eq 'push') { + mw_push($cmd[1]); } else { - if (<$reader>) { - die "ERROR while running git credential $op:\n$_"; - } - } - close($reader); - waitpid($pid, 0); - my $child_exit_status = $? >> 8; - if ($child_exit_status != 0) { - die "'git credential $op' failed with code $child_exit_status."; + print {*STDERR} "Unknown command. Aborting...\n"; + return 0; } + return 1; } # MediaWiki API instance, created lazily. my $mediawiki; -sub mw_connect_maybe { - if ($mediawiki) { - return; - } - $mediawiki = MediaWiki::API->new; - $mediawiki->{config}->{api_url} = "$url/api.php"; - if ($wiki_login) { - my %credential = (url => $url); - $credential{username} = $wiki_login; - $credential{password} = $wiki_passwd; - credential_run("fill", \%credential); - my $request = {lgname => $credential{username}, - lgpassword => $credential{password}, - lgdomain => $wiki_domain}; - if ($mediawiki->login($request)) { - credential_run("approve", \%credential); - print STDERR "Logged in mediawiki user \"$credential{username}\".\n"; - } else { - print STDERR "Failed to log in mediawiki user \"$credential{username}\" on $url\n"; - print STDERR " (error " . - $mediawiki->{error}->{code} . ': ' . - $mediawiki->{error}->{details} . ")\n"; - credential_run("reject", \%credential); - exit 1; - } - } -} - sub fatal_mw_error { my $action = shift; print STDERR "fatal: could not $action.\n"; @@ -258,21 +214,23 @@ sub fatal_mw_error { sub get_mw_tracked_pages { my $pages = shift; get_mw_page_list(\@tracked_pages, $pages); + return; } sub get_mw_page_list { my $page_list = shift; my $pages = shift; - my @some_pages = @$page_list; + my @some_pages = @{$page_list}; while (@some_pages) { - my $last = 50; - if ($#some_pages < $last) { - $last = $#some_pages; + my $last_page = SLICE_SIZE; + if ($#some_pages < $last_page) { + $last_page = $#some_pages; } - my @slice = @some_pages[0..$last]; + my @slice = @some_pages[0..$last_page]; get_mw_first_pages(\@slice, $pages); - @some_pages = @some_pages[51..$#some_pages]; + @some_pages = @some_pages[(SLICE_SIZE + 1)..$#some_pages]; } + return; } sub get_mw_tracked_categories { @@ -282,7 +240,7 @@ sub get_mw_tracked_categories { # Mediawiki requires the Category # prefix, but let's not force the user # to specify it. - $category = "Category:" . $category; + $category = "Category:${category}"; } my $mw_pages = $mediawiki->list( { action => 'query', @@ -290,11 +248,12 @@ sub get_mw_tracked_categories { cmtitle => $category, cmlimit => 'max' } ) || die $mediawiki->{error}->{code} . ': ' - . $mediawiki->{error}->{details}; + . $mediawiki->{error}->{details} . "\n"; foreach my $page (@{$mw_pages}) { $pages->{$page->{title}} = $page; } } + return; } sub get_mw_all_pages { @@ -311,6 +270,7 @@ sub get_mw_all_pages { foreach my $page (@{$mw_pages}) { $pages->{$page->{title}} = $page; } + return; } # queries the wiki for a set of pages. Meant to be used within a loop @@ -333,18 +293,19 @@ sub get_mw_first_pages { } while (my ($id, $page) = each(%{$mw_pages->{query}->{pages}})) { if ($id < 0) { - print STDERR "Warning: page $page->{title} not found on wiki\n"; + print {*STDERR} "Warning: page $page->{title} not found on wiki\n"; } else { $pages->{$page->{title}} = $page; } } + return; } # Get the list of pages to be fetched according to configuration. sub get_mw_pages { - mw_connect_maybe(); + $mediawiki = connect_maybe($mediawiki, $remotename, $url); - print STDERR "Listing pages on remote wiki...\n"; + print {*STDERR} "Listing pages on remote wiki...\n"; my %pages; # hash on page titles to avoid duplicates my $user_defined; @@ -362,14 +323,14 @@ sub get_mw_pages { get_mw_all_pages(\%pages); } if ($import_media) { - print STDERR "Getting media files for selected pages...\n"; + print {*STDERR} "Getting media files for selected pages...\n"; if ($user_defined) { get_linked_mediafiles(\%pages); } else { get_all_mediafiles(\%pages); } } - print STDERR (scalar keys %pages) . " pages found.\n"; + print {*STDERR} (scalar keys %pages) . " pages found.\n"; return %pages; } @@ -377,9 +338,13 @@ sub get_mw_pages { # $out = run_git("command args", "raw"); # don't interpret output as UTF-8. sub run_git { my $args = shift; - my $encoding = (shift || "encoding(UTF-8)"); - open(my $git, "-|:$encoding", "git " . $args); - my $res = do { local $/; <$git> }; + my $encoding = (shift || 'encoding(UTF-8)'); + open(my $git, "-|:${encoding}", "git ${args}") + or die "Unable to fork: $!\n"; + my $res = do { + local $/ = undef; + <$git> + }; close($git); return $res; @@ -394,27 +359,26 @@ sub get_all_mediafiles { my $mw_pages = $mediawiki->list({ action => 'query', list => 'allpages', - apnamespace => get_mw_namespace_id("File"), + apnamespace => get_mw_namespace_id('File'), aplimit => 'max' }); if (!defined($mw_pages)) { - print STDERR "fatal: could not get the list of pages for media files.\n"; - print STDERR "fatal: '$url' does not appear to be a mediawiki\n"; - print STDERR "fatal: make sure '$url/api.php' is a valid page.\n"; + print {*STDERR} "fatal: could not get the list of pages for media files.\n"; + print {*STDERR} "fatal: '$url' does not appear to be a mediawiki\n"; + print {*STDERR} "fatal: make sure '$url/api.php' is a valid page.\n"; exit 1; } foreach my $page (@{$mw_pages}) { $pages->{$page->{title}} = $page; } + return; } sub get_linked_mediafiles { my $pages = shift; - my @titles = map $_->{title}, values(%{$pages}); + my @titles = map { $_->{title} } values(%{$pages}); - # The query is split in small batches because of the MW API limit of - # the number of links to be returned (500 links max). - my $batch = 10; + my $batch = BATCH_SIZE; while (@titles) { if ($#titles < $batch) { $batch = $#titles; @@ -430,7 +394,7 @@ sub get_linked_mediafiles { action => 'query', prop => 'links|images', titles => $mw_titles, - plnamespace => get_mw_namespace_id("File"), + plnamespace => get_mw_namespace_id('File'), pllimit => 'max' }; my $result = $mediawiki->api($query); @@ -438,11 +402,13 @@ sub get_linked_mediafiles { while (my ($id, $page) = each(%{$result->{query}->{pages}})) { my @media_titles; if (defined($page->{links})) { - my @link_titles = map $_->{title}, @{$page->{links}}; + my @link_titles + = map { $_->{title} } @{$page->{links}}; push(@media_titles, @link_titles); } if (defined($page->{images})) { - my @image_titles = map $_->{title}, @{$page->{images}}; + my @image_titles + = map { $_->{title} } @{$page->{images}}; push(@media_titles, @image_titles); } if (@media_titles) { @@ -452,6 +418,7 @@ sub get_linked_mediafiles { @titles = @titles[($batch+1)..$#titles]; } + return; } sub get_mw_mediafile_for_page_revision { @@ -465,7 +432,7 @@ sub get_mw_mediafile_for_page_revision { my $query = { action => 'query', prop => 'imageinfo', - titles => "File:" . $filename, + titles => "File:${filename}", iistart => $timestamp, iiend => $timestamp, iiprop => 'timestamp|archivename|url', @@ -483,53 +450,50 @@ sub get_mw_mediafile_for_page_revision { $mediafile{timestamp} = $fileinfo->{timestamp}; # Mediawiki::API's download function doesn't support https URLs # and can't download old versions of files. - print STDERR "\tDownloading file $mediafile{title}, version $mediafile{timestamp}\n"; + print {*STDERR} "\tDownloading file $mediafile{title}, version $mediafile{timestamp}\n"; $mediafile{content} = download_mw_mediafile($fileinfo->{url}); } return %mediafile; } sub download_mw_mediafile { - my $url = shift; + my $download_url = shift; - my $response = $mediawiki->{ua}->get($url); - if ($response->code == 200) { + my $response = $mediawiki->{ua}->get($download_url); + if ($response->code == HTTP_CODE_OK) { return $response->decoded_content; } else { - print STDERR "Error downloading mediafile from :\n"; - print STDERR "URL: $url\n"; - print STDERR "Server response: " . $response->code . " " . $response->message . "\n"; + print {*STDERR} "Error downloading mediafile from :\n"; + print {*STDERR} "URL: ${download_url}\n"; + print {*STDERR} 'Server response: ' . $response->code . q{ } . $response->message . "\n"; exit 1; } } sub get_last_local_revision { # Get note regarding last mediawiki revision - my $note = run_git("notes --ref=$remotename/mediawiki show refs/mediawiki/$remotename/master 2>/dev/null"); + my $note = run_git("notes --ref=${remotename}/mediawiki show refs/mediawiki/${remotename}/master 2>/dev/null"); my @note_info = split(/ /, $note); my $lastrevision_number; - if (!(defined($note_info[0]) && $note_info[0] eq "mediawiki_revision:")) { - print STDERR "No previous mediawiki revision found"; + if (!(defined($note_info[0]) && $note_info[0] eq 'mediawiki_revision:')) { + print {*STDERR} 'No previous mediawiki revision found'; $lastrevision_number = 0; } else { # Notes are formatted : mediawiki_revision: #number $lastrevision_number = $note_info[1]; chomp($lastrevision_number); - print STDERR "Last local mediawiki revision found is $lastrevision_number"; + print {*STDERR} "Last local mediawiki revision found is ${lastrevision_number}"; } return $lastrevision_number; } -# Remember the timestamp corresponding to a revision id. -my %basetimestamps; - # Get the last remote revision without taking in account which pages are # tracked or not. This function makes a single request to the wiki thus # avoid a loop onto all tracked pages. This is useful for the fetch-by-rev # option. sub get_last_global_remote_rev { - mw_connect_maybe(); + $mediawiki = connect_maybe($mediawiki, $remotename, $url); my $query = { action => 'query', @@ -545,14 +509,14 @@ sub get_last_global_remote_rev { # Get the last remote revision concerning the tracked pages and the tracked # categories. sub get_last_remote_revision { - mw_connect_maybe(); + $mediawiki = connect_maybe($mediawiki, $remotename, $url); my %pages_hash = get_mw_pages(); my @pages = values(%pages_hash); my $max_rev_num = 0; - print STDERR "Getting last revision id on tracked pages...\n"; + print {*STDERR} "Getting last revision id on tracked pages...\n"; foreach my $page (@pages) { my $id = $page->{pageid}; @@ -573,7 +537,7 @@ sub get_last_remote_revision { $max_rev_num = ($lastrev->{revid} > $max_rev_num ? $lastrev->{revid} : $max_rev_num); } - print STDERR "Last remote revision found is $max_rev_num.\n"; + print {*STDERR} "Last remote revision found is $max_rev_num.\n"; return $max_rev_num; } @@ -584,7 +548,7 @@ sub mediawiki_clean { # Mediawiki does not allow blank space at the end of a page and ends with a single \n. # This function right trims a string and adds a \n at the end to follow this rule $string =~ s/\s+$//; - if ($string eq "" && $page_created) { + if ($string eq EMPTY && $page_created) { # Creating empty pages is forbidden. $string = EMPTY_CONTENT; } @@ -595,38 +559,16 @@ sub mediawiki_clean { sub mediawiki_smudge { my $string = shift; if ($string eq EMPTY_CONTENT) { - $string = ""; + $string = EMPTY; } # This \n is important. This is due to mediawiki's way to handle end of files. - return $string."\n"; -} - -sub mediawiki_clean_filename { - my $filename = shift; - $filename =~ s/@{[SLASH_REPLACEMENT]}/\//g; - # [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded. - # Do a variant of URL-encoding, i.e. looks like URL-encoding, - # but with _ added to prevent MediaWiki from thinking this is - # an actual special character. - $filename =~ s/[\[\]\{\}\|]/sprintf("_%%_%x", ord($&))/ge; - # If we use the uri escape before - # we should unescape here, before anything - - return $filename; -} - -sub mediawiki_smudge_filename { - my $filename = shift; - $filename =~ s/\//@{[SLASH_REPLACEMENT]}/g; - $filename =~ s/ /_/g; - # Decode forbidden characters encoded in mediawiki_clean_filename - $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf("%c", hex($1))/ge; - return $filename; + return "${string}\n"; } sub literal_data { my ($content) = @_; - print STDOUT "data ", bytes::length($content), "\n", $content; + print {*STDOUT} 'data ', bytes::length($content), "\n", $content; + return; } sub literal_data_raw { @@ -634,33 +576,37 @@ sub literal_data_raw { my ($content) = @_; # Avoid confusion between size in bytes and in characters utf8::downgrade($content); - binmode STDOUT, ":raw"; - print STDOUT "data ", bytes::length($content), "\n", $content; - binmode STDOUT, ":utf8"; + binmode STDOUT, ':raw'; + print {*STDOUT} 'data ', bytes::length($content), "\n", $content; + binmode STDOUT, ':encoding(UTF-8)'; + return; } sub mw_capabilities { # Revisions are imported to the private namespace # refs/mediawiki/$remotename/ by the helper and fetched into # refs/remotes/$remotename later by fetch. - print STDOUT "refspec refs/heads/*:refs/mediawiki/$remotename/*\n"; - print STDOUT "import\n"; - print STDOUT "list\n"; - print STDOUT "push\n"; - print STDOUT "\n"; + print {*STDOUT} "refspec refs/heads/*:refs/mediawiki/${remotename}/*\n"; + print {*STDOUT} "import\n"; + print {*STDOUT} "list\n"; + print {*STDOUT} "push\n"; + print {*STDOUT} "\n"; + return; } sub mw_list { # MediaWiki do not have branches, we consider one branch arbitrarily # called master, and HEAD pointing to it. - print STDOUT "? refs/heads/master\n"; - print STDOUT "\@refs/heads/master HEAD\n"; - print STDOUT "\n"; + print {*STDOUT} "? refs/heads/master\n"; + print {*STDOUT} "\@refs/heads/master HEAD\n"; + print {*STDOUT} "\n"; + return; } sub mw_option { - print STDERR "remote-helper command 'option $_[0]' not yet implemented\n"; - print STDOUT "unsupported\n"; + print {*STDERR} "remote-helper command 'option $_[0]' not yet implemented\n"; + print {*STDOUT} "unsupported\n"; + return; } sub fetch_mw_revisions_for_page { @@ -691,15 +637,15 @@ sub fetch_mw_revisions_for_page { push(@page_revs, $page_rev_ids); $revnum++; } - last unless $result->{'query-continue'}; + last if (!$result->{'query-continue'}); $query->{rvstartid} = $result->{'query-continue'}->{revisions}->{rvstartid}; } if ($shallow_import && @page_revs) { - print STDERR " Found 1 revision (shallow import).\n"; + print {*STDERR} " Found 1 revision (shallow import).\n"; @page_revs = sort {$b->{revid} <=> $a->{revid}} (@page_revs); return $page_revs[0]; } - print STDERR " Found ", $revnum, " revision(s).\n"; + print {*STDERR} " Found ${revnum} revision(s).\n"; return @page_revs; } @@ -711,8 +657,7 @@ sub fetch_mw_revisions { my $n = 1; foreach my $page (@pages) { my $id = $page->{pageid}; - - print STDERR "page $n/", scalar(@pages), ": ". $page->{title} ."\n"; + print {*STDERR} "page ${n}/", scalar(@pages), ': ', $page->{title}, "\n"; $n++; my @page_revs = fetch_mw_revisions_for_page($page, $id, $fetch_from); @revisions = (@page_revs, @revisions); @@ -726,7 +671,7 @@ sub fe_escape_path { $path =~ s/\\/\\\\/g; $path =~ s/"/\\"/g; $path =~ s/\n/\\n/g; - return '"' . $path . '"'; + return qq("${path}"); } sub import_file_revision { @@ -746,42 +691,43 @@ sub import_file_revision { my $author = $commit{author}; my $date = $commit{date}; - print STDOUT "commit refs/mediawiki/$remotename/master\n"; - print STDOUT "mark :$n\n"; - print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n"; + print {*STDOUT} "commit refs/mediawiki/${remotename}/master\n"; + print {*STDOUT} "mark :${n}\n"; + print {*STDOUT} "committer ${author} <${author}\@${wiki_name}> " . $date->epoch . " +0000\n"; literal_data($comment); # If it's not a clone, we need to know where to start from if (!$full_import && $n == 1) { - print STDOUT "from refs/mediawiki/$remotename/master^0\n"; + print {*STDOUT} "from refs/mediawiki/${remotename}/master^0\n"; } if ($content ne DELETED_CONTENT) { - print STDOUT "M 644 inline " . - fe_escape_path($title . ".mw") . "\n"; + print {*STDOUT} 'M 644 inline ' . + fe_escape_path("${title}.mw") . "\n"; literal_data($content); if (%mediafile) { - print STDOUT "M 644 inline " + print {*STDOUT} 'M 644 inline ' . fe_escape_path($mediafile{title}) . "\n"; literal_data_raw($mediafile{content}); } - print STDOUT "\n\n"; + print {*STDOUT} "\n\n"; } else { - print STDOUT "D " . fe_escape_path($title . ".mw") . "\n"; + print {*STDOUT} 'D ' . fe_escape_path("${title}.mw") . "\n"; } # mediawiki revision number in the git note if ($full_import && $n == 1) { - print STDOUT "reset refs/notes/$remotename/mediawiki\n"; + print {*STDOUT} "reset refs/notes/${remotename}/mediawiki\n"; } - print STDOUT "commit refs/notes/$remotename/mediawiki\n"; - print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n"; - literal_data("Note added by git-mediawiki during import"); + print {*STDOUT} "commit refs/notes/${remotename}/mediawiki\n"; + print {*STDOUT} "committer ${author} <${author}\@${wiki_name}> " . $date->epoch . " +0000\n"; + literal_data('Note added by git-mediawiki during import'); if (!$full_import && $n == 1) { - print STDOUT "from refs/notes/$remotename/mediawiki^0\n"; + print {*STDOUT} "from refs/notes/${remotename}/mediawiki^0\n"; } - print STDOUT "N inline :$n\n"; - literal_data("mediawiki_revision: " . $commit{mw_revision}); - print STDOUT "\n\n"; + print {*STDOUT} "N inline :${n}\n"; + literal_data("mediawiki_revision: $commit{mw_revision}"); + print {*STDOUT} "\n\n"; + return; } # parse a sequence of @@ -794,23 +740,25 @@ sub get_more_refs { my @refs; while (1) { my $line = <STDIN>; - if ($line =~ m/^$cmd (.*)$/) { + if ($line =~ /^$cmd (.*)$/) { push(@refs, $1); } elsif ($line eq "\n") { return @refs; } else { - die("Invalid command in a '$cmd' batch: ". $_); + die("Invalid command in a '$cmd' batch: $_\n"); } } + return; } sub mw_import { # multiple import commands can follow each other. - my @refs = (shift, get_more_refs("import")); + my @refs = (shift, get_more_refs('import')); foreach my $ref (@refs) { mw_import_ref($ref); } - print STDOUT "done\n"; + print {*STDOUT} "done\n"; + return; } sub mw_import_ref { @@ -820,40 +768,41 @@ sub mw_import_ref { # Since HEAD is a symbolic ref to master (by convention, # followed by the output of the command "list" that we gave), # we don't need to do anything in this case. - if ($ref eq "HEAD") { + if ($ref eq 'HEAD') { return; } - mw_connect_maybe(); + $mediawiki = connect_maybe($mediawiki, $remotename, $url); - print STDERR "Searching revisions...\n"; + print {*STDERR} "Searching revisions...\n"; my $last_local = get_last_local_revision(); my $fetch_from = $last_local + 1; if ($fetch_from == 1) { - print STDERR ", fetching from beginning.\n"; + print {*STDERR} ", fetching from beginning.\n"; } else { - print STDERR ", fetching from here.\n"; + print {*STDERR} ", fetching from here.\n"; } my $n = 0; - if ($fetch_strategy eq "by_rev") { - print STDERR "Fetching & writing export data by revs...\n"; + if ($fetch_strategy eq 'by_rev') { + print {*STDERR} "Fetching & writing export data by revs...\n"; $n = mw_import_ref_by_revs($fetch_from); - } elsif ($fetch_strategy eq "by_page") { - print STDERR "Fetching & writing export data by pages...\n"; + } elsif ($fetch_strategy eq 'by_page') { + print {*STDERR} "Fetching & writing export data by pages...\n"; $n = mw_import_ref_by_pages($fetch_from); } else { - print STDERR "fatal: invalid fetch strategy \"$fetch_strategy\".\n"; - print STDERR "Check your configuration variables remote.$remotename.fetchStrategy and mediawiki.fetchStrategy\n"; + print {*STDERR} qq(fatal: invalid fetch strategy "${fetch_strategy}".\n); + print {*STDERR} "Check your configuration variables remote.${remotename}.fetchStrategy and mediawiki.fetchStrategy\n"; exit 1; } if ($fetch_from == 1 && $n == 0) { - print STDERR "You appear to have cloned an empty MediaWiki.\n"; + print {*STDERR} "You appear to have cloned an empty MediaWiki.\n"; # Something has to be done remote-helper side. If nothing is done, an error is # thrown saying that HEAD is referring to unknown object 0000000000000000000 # and the clone fails. } + return; } sub mw_import_ref_by_pages { @@ -865,7 +814,7 @@ sub mw_import_ref_by_pages { my ($n, @revisions) = fetch_mw_revisions(\@pages, $fetch_from); @revisions = sort {$a->{revid} <=> $b->{revid}} @revisions; - my @revision_ids = map $_->{revid}, @revisions; + my @revision_ids = map { $_->{revid} } @revisions; return mw_import_revids($fetch_from, \@revision_ids, \%pages_hash); } @@ -892,7 +841,7 @@ sub mw_import_revids { my $n_actual = 0; my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined - foreach my $pagerevid (@$revision_ids) { + foreach my $pagerevid (@{$revision_ids}) { # Count page even if we skip it, since we display # $n/$total and $total includes skipped pages. $n++; @@ -908,7 +857,7 @@ sub mw_import_revids { my $result = $mediawiki->api($query); if (!$result) { - die "Failed to retrieve modified page for revision $pagerevid"; + die "Failed to retrieve modified page for revision $pagerevid\n"; } if (defined($result->{query}->{badrevids}->{$pagerevid})) { @@ -917,7 +866,7 @@ sub mw_import_revids { } if (!defined($result->{query}->{pages})) { - die "Invalid revision $pagerevid."; + die "Invalid revision ${pagerevid}.\n"; } my @result_pages = values(%{$result->{query}->{pages}}); @@ -927,8 +876,8 @@ sub mw_import_revids { my $page_title = $result_page->{title}; if (!exists($pages->{$page_title})) { - print STDERR "$n/", scalar(@$revision_ids), - ": Skipping revision #$rev->{revid} of $page_title\n"; + print {*STDERR} "${n}/", scalar(@{$revision_ids}), + ": Skipping revision #$rev->{revid} of ${page_title}\n"; next; } @@ -937,7 +886,7 @@ sub mw_import_revids { my %commit; $commit{author} = $rev->{user} || 'Anonymous'; $commit{comment} = $rev->{comment} || EMPTY_MESSAGE; - $commit{title} = mediawiki_smudge_filename($page_title); + $commit{title} = smudge_filename($page_title); $commit{mw_revision} = $rev->{revid}; $commit{content} = mediawiki_smudge($rev->{'*'}); @@ -953,14 +902,14 @@ sub mw_import_revids { my %mediafile; if ($namespace) { my $id = get_mw_namespace_id($namespace); - if ($id && $id == get_mw_namespace_id("File")) { + if ($id && $id == get_mw_namespace_id('File')) { %mediafile = get_mw_mediafile_for_page_revision($filename, $rev->{timestamp}); } } # If this is a revision of the media page for new version # of a file do one common commit for both file and media page. # Else do commit only for that page. - print STDERR "$n/", scalar(@$revision_ids), ": Revision #$rev->{revid} of $commit{title}\n"; + print {*STDERR} "${n}/", scalar(@{$revision_ids}), ": Revision #$rev->{revid} of $commit{title}\n"; import_file_revision(\%commit, ($fetch_from == 1), $n_actual, \%mediafile); } @@ -968,17 +917,17 @@ sub mw_import_revids { } sub error_non_fast_forward { - my $advice = run_git("config --bool advice.pushNonFastForward"); + my $advice = run_git('config --bool advice.pushNonFastForward'); chomp($advice); - if ($advice ne "false") { + if ($advice ne 'false') { # Native git-push would show this after the summary. # We can't ask it to display it cleanly, so print it # ourselves before. - print STDERR "To prevent you from losing history, non-fast-forward updates were rejected\n"; - print STDERR "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n"; - print STDERR "'Note about fast-forwards' section of 'git push --help' for details.\n"; + print {*STDERR} "To prevent you from losing history, non-fast-forward updates were rejected\n"; + print {*STDERR} "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n"; + print {*STDERR} "'Note about fast-forwards' section of 'git push --help' for details.\n"; } - print STDOUT "error $_[0] \"non-fast-forward\"\n"; + print {*STDOUT} qq(error $_[0] "non-fast-forward"\n); return 0; } @@ -989,34 +938,34 @@ sub mw_upload_file { my $file_deleted = shift; my $summary = shift; my $newrevid; - my $path = "File:" . $complete_file_name; + my $path = "File:${complete_file_name}"; my %hashFiles = get_allowed_file_extensions(); if (!exists($hashFiles{$extension})) { - print STDERR "$complete_file_name is not a permitted file on this wiki.\n"; - print STDERR "Check the configuration of file uploads in your mediawiki.\n"; + print {*STDERR} "${complete_file_name} is not a permitted file on this wiki.\n"; + print {*STDERR} "Check the configuration of file uploads in your mediawiki.\n"; return $newrevid; } # Deleting and uploading a file requires a priviledged user if ($file_deleted) { - mw_connect_maybe(); + $mediawiki = connect_maybe($mediawiki, $remotename, $url); my $query = { action => 'delete', title => $path, reason => $summary }; if (!$mediawiki->edit($query)) { - print STDERR "Failed to delete file on remote wiki\n"; - print STDERR "Check your permissions on the remote site. Error code:\n"; - print STDERR $mediawiki->{error}->{code} . ':' . $mediawiki->{error}->{details}; + print {*STDERR} "Failed to delete file on remote wiki\n"; + print {*STDERR} "Check your permissions on the remote site. Error code:\n"; + print {*STDERR} $mediawiki->{error}->{code} . ':' . $mediawiki->{error}->{details}; exit 1; } } else { # Don't let perl try to interpret file content as UTF-8 => use "raw" - my $content = run_git("cat-file blob $new_sha1", "raw"); - if ($content ne "") { - mw_connect_maybe(); + my $content = run_git("cat-file blob ${new_sha1}", 'raw'); + if ($content ne EMPTY) { + $mediawiki = connect_maybe($mediawiki, $remotename, $url); $mediawiki->{config}->{upload_url} = - "$url/index.php/Special:Upload"; + "${url}/index.php/Special:Upload"; $mediawiki->edit({ action => 'upload', filename => $complete_file_name, @@ -1028,12 +977,12 @@ sub mw_upload_file { }, { skip_encoding => 1 } ) || die $mediawiki->{error}->{code} . ':' - . $mediawiki->{error}->{details}; + . $mediawiki->{error}->{details} . "\n"; my $last_file_page = $mediawiki->get_page({title => $path}); $newrevid = $last_file_page->{revid}; - print STDERR "Pushed file: $new_sha1 - $complete_file_name.\n"; + print {*STDERR} "Pushed file: ${new_sha1} - ${complete_file_name}.\n"; } else { - print STDERR "Empty file $complete_file_name not pushed.\n"; + print {*STDERR} "Empty file ${complete_file_name} not pushed.\n"; } } return $newrevid; @@ -1055,24 +1004,24 @@ sub mw_push_file { my $newrevid; if ($summary eq EMPTY_MESSAGE) { - $summary = ''; + $summary = EMPTY; } my $new_sha1 = $diff_info_split[3]; my $old_sha1 = $diff_info_split[2]; my $page_created = ($old_sha1 eq NULL_SHA1); my $page_deleted = ($new_sha1 eq NULL_SHA1); - $complete_file_name = mediawiki_clean_filename($complete_file_name); + $complete_file_name = clean_filename($complete_file_name); my ($title, $extension) = $complete_file_name =~ /^(.*)\.([^\.]*)$/; if (!defined($extension)) { - $extension = ""; + $extension = EMPTY; } - if ($extension eq "mw") { + if ($extension eq 'mw') { my $ns = get_mw_namespace_id_for_page($complete_file_name); - if ($ns && $ns == get_mw_namespace_id("File") && (!$export_media)) { - print STDERR "Ignoring media file related page: $complete_file_name\n"; - return ($oldrevid, "ok"); + if ($ns && $ns == get_mw_namespace_id('File') && (!$export_media)) { + print {*STDERR} "Ignoring media file related page: ${complete_file_name}\n"; + return ($oldrevid, 'ok'); } my $file_content; if ($page_deleted) { @@ -1082,10 +1031,10 @@ sub mw_push_file { # with this content instead: $file_content = DELETED_CONTENT; } else { - $file_content = run_git("cat-file blob $new_sha1"); + $file_content = run_git("cat-file blob ${new_sha1}"); } - mw_connect_maybe(); + $mediawiki = connect_maybe($mediawiki, $remotename, $url); my $result = $mediawiki->edit( { action => 'edit', @@ -1099,49 +1048,49 @@ sub mw_push_file { if (!$result) { if ($mediawiki->{error}->{code} == 3) { # edit conflicts, considered as non-fast-forward - print STDERR 'Warning: Error ' . + print {*STDERR} 'Warning: Error ' . $mediawiki->{error}->{code} . - ' from mediwiki: ' . $mediawiki->{error}->{details} . + ' from mediawiki: ' . $mediawiki->{error}->{details} . ".\n"; - return ($oldrevid, "non-fast-forward"); + return ($oldrevid, 'non-fast-forward'); } else { # Other errors. Shouldn't happen => just die() die 'Fatal: Error ' . $mediawiki->{error}->{code} . - ' from mediwiki: ' . $mediawiki->{error}->{details}; + ' from mediawiki: ' . $mediawiki->{error}->{details} . "\n"; } } $newrevid = $result->{edit}->{newrevid}; - print STDERR "Pushed file: $new_sha1 - $title\n"; + print {*STDERR} "Pushed file: ${new_sha1} - ${title}\n"; } elsif ($export_media) { $newrevid = mw_upload_file($complete_file_name, $new_sha1, $extension, $page_deleted, $summary); } else { - print STDERR "Ignoring media file $title\n"; + print {*STDERR} "Ignoring media file ${title}\n"; } $newrevid = ($newrevid or $oldrevid); - return ($newrevid, "ok"); + return ($newrevid, 'ok'); } sub mw_push { # multiple push statements can follow each other - my @refsspecs = (shift, get_more_refs("push")); + my @refsspecs = (shift, get_more_refs('push')); my $pushed; for my $refspec (@refsspecs) { my ($force, $local, $remote) = $refspec =~ /^(\+)?([^:]*):([^:]*)$/ - or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>"); + or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>\n"); if ($force) { - print STDERR "Warning: forced push not allowed on a MediaWiki.\n"; + print {*STDERR} "Warning: forced push not allowed on a MediaWiki.\n"; } - if ($local eq "") { - print STDERR "Cannot delete remote branch on a MediaWiki\n"; - print STDOUT "error $remote cannot delete\n"; + if ($local eq EMPTY) { + print {*STDERR} "Cannot delete remote branch on a MediaWiki\n"; + print {*STDOUT} "error ${remote} cannot delete\n"; next; } - if ($remote ne "refs/heads/master") { - print STDERR "Only push to the branch 'master' is supported on a MediaWiki\n"; - print STDOUT "error $remote only master allowed\n"; + if ($remote ne 'refs/heads/master') { + print {*STDERR} "Only push to the branch 'master' is supported on a MediaWiki\n"; + print {*STDOUT} "error ${remote} only master allowed\n"; next; } if (mw_push_revision($local, $remote)) { @@ -1150,30 +1099,32 @@ sub mw_push { } # Notify Git that the push is done - print STDOUT "\n"; + print {*STDOUT} "\n"; if ($pushed && $dumb_push) { - print STDERR "Just pushed some revisions to MediaWiki.\n"; - print STDERR "The pushed revisions now have to be re-imported, and your current branch\n"; - print STDERR "needs to be updated with these re-imported commits. You can do this with\n"; - print STDERR "\n"; - print STDERR " git pull --rebase\n"; - print STDERR "\n"; + print {*STDERR} "Just pushed some revisions to MediaWiki.\n"; + print {*STDERR} "The pushed revisions now have to be re-imported, and your current branch\n"; + print {*STDERR} "needs to be updated with these re-imported commits. You can do this with\n"; + print {*STDERR} "\n"; + print {*STDERR} " git pull --rebase\n"; + print {*STDERR} "\n"; } + return; } sub mw_push_revision { my $local = shift; my $remote = shift; # actually, this has to be "refs/heads/master" at this point. my $last_local_revid = get_last_local_revision(); - print STDERR ".\n"; # Finish sentence started by get_last_local_revision() + print {*STDERR} ".\n"; # Finish sentence started by get_last_local_revision() my $last_remote_revid = get_last_remote_revision(); my $mw_revision = $last_remote_revid; # Get sha1 of commit pointed by local HEAD - my $HEAD_sha1 = run_git("rev-parse $local 2>/dev/null"); chomp($HEAD_sha1); + my $HEAD_sha1 = run_git("rev-parse ${local} 2>/dev/null"); + chomp($HEAD_sha1); # Get sha1 of commit pointed by remotes/$remotename/master - my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/$remotename/master 2>/dev/null"); + my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/${remotename}/master 2>/dev/null"); chomp($remoteorigin_sha1); if ($last_local_revid > 0 && @@ -1192,22 +1143,22 @@ sub mw_push_revision { if ($last_local_revid > 0) { my $parsed_sha1 = $remoteorigin_sha1; # Find a path from last MediaWiki commit to pushed commit - print STDERR "Computing path from local to remote ...\n"; - my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents $local ^$parsed_sha1")); + print {*STDERR} "Computing path from local to remote ...\n"; + my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents ${local} ^${parsed_sha1}")); my %local_ancestry; foreach my $line (@local_ancestry) { - if (my ($child, $parents) = $line =~ m/^-?([a-f0-9]+) ([a-f0-9 ]+)/) { - foreach my $parent (split(' ', $parents)) { + if (my ($child, $parents) = $line =~ /^-?([a-f0-9]+) ([a-f0-9 ]+)/) { + foreach my $parent (split(/ /, $parents)) { $local_ancestry{$parent} = $child; } - } elsif (!$line =~ m/^([a-f0-9]+)/) { - die "Unexpected output from git rev-list: $line"; + } elsif (!$line =~ /^([a-f0-9]+)/) { + die "Unexpected output from git rev-list: ${line}\n"; } } while ($parsed_sha1 ne $HEAD_sha1) { my $child = $local_ancestry{$parsed_sha1}; if (!$child) { - printf STDERR "Cannot find a path in history from remote commit to last commit\n"; + print {*STDERR} "Cannot find a path in history from remote commit to last commit\n"; return error_non_fast_forward($remote); } push(@commit_pairs, [$parsed_sha1, $child]); @@ -1216,12 +1167,12 @@ sub mw_push_revision { } else { # No remote mediawiki revision. Export the whole # history (linearized with --first-parent) - print STDERR "Warning: no common ancestor, pushing complete history\n"; - my $history = run_git("rev-list --first-parent --children $local"); - my @history = split('\n', $history); + print {*STDERR} "Warning: no common ancestor, pushing complete history\n"; + my $history = run_git("rev-list --first-parent --children ${local}"); + my @history = split(/\n/, $history); @history = @history[1..$#history]; foreach my $line (reverse @history) { - my @commit_info_split = split(/ |\n/, $line); + my @commit_info_split = split(/[ \n]/, $line); push(@commit_pairs, \@commit_info_split); } } @@ -1229,12 +1180,12 @@ sub mw_push_revision { foreach my $commit_info_split (@commit_pairs) { my $sha1_child = @{$commit_info_split}[0]; my $sha1_commit = @{$commit_info_split}[1]; - my $diff_infos = run_git("diff-tree -r --raw -z $sha1_child $sha1_commit"); + my $diff_infos = run_git("diff-tree -r --raw -z ${sha1_child} ${sha1_commit}"); # TODO: we could detect rename, and encode them with a #redirect on the wiki. # TODO: for now, it's just a delete+add my @diff_info_list = split(/\0/, $diff_infos); # Keep the subject line of the commit message as mediawiki comment for the revision - my $commit_msg = run_git("log --no-walk --format=\"%s\" $sha1_commit"); + my $commit_msg = run_git(qq(log --no-walk --format="%s" ${sha1_commit})); chomp($commit_msg); # Push every blob while (@diff_info_list) { @@ -1246,7 +1197,7 @@ sub mw_push_revision { my $info = shift(@diff_info_list); my $file = shift(@diff_info_list); ($mw_revision, $status) = mw_push_file($info, $file, $commit_msg, $mw_revision); - if ($status eq "non-fast-forward") { + if ($status eq 'non-fast-forward') { # we may already have sent part of the # commit to MediaWiki, but it's too # late to cancel it. Stop the push in @@ -1254,22 +1205,22 @@ sub mw_push_revision { # accurate error message. return error_non_fast_forward($remote); } - if ($status ne "ok") { - die("Unknown error from mw_push_file()"); + if ($status ne 'ok') { + die("Unknown error from mw_push_file()\n"); } } - unless ($dumb_push) { - run_git("notes --ref=$remotename/mediawiki add -f -m \"mediawiki_revision: $mw_revision\" $sha1_commit"); - run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child"); + if (!$dumb_push) { + run_git(qq(notes --ref=${remotename}/mediawiki add -f -m "mediawiki_revision: ${mw_revision}" ${sha1_commit})); + run_git(qq(update-ref -m "Git-MediaWiki push" refs/mediawiki/${remotename}/master ${sha1_commit} ${sha1_child})); } } - print STDOUT "ok $remote\n"; + print {*STDOUT} "ok ${remote}\n"; return 1; } sub get_allowed_file_extensions { - mw_connect_maybe(); + $mediawiki = connect_maybe($mediawiki, $remotename, $url); my $query = { action => 'query', @@ -1277,8 +1228,8 @@ sub get_allowed_file_extensions { siprop => 'fileextensions' }; my $result = $mediawiki->api($query); - my @file_extensions= map $_->{ext},@{$result->{query}->{fileextensions}}; - my %hashFile = map {$_ => 1}@file_extensions; + my @file_extensions = map { $_->{ext}} @{$result->{query}->{fileextensions}}; + my %hashFile = map { $_ => 1 } @file_extensions; return %hashFile; } @@ -1293,15 +1244,15 @@ my %cached_mw_namespace_id; # Return MediaWiki id for a canonical namespace name. # Ex.: "File", "Project". sub get_mw_namespace_id { - mw_connect_maybe(); + $mediawiki = connect_maybe($mediawiki, $remotename, $url); my $name = shift; if (!exists $namespace_id{$name}) { # Look at configuration file, if the record for that namespace is # already cached. Namespaces are stored in form: # "Name_of_namespace:Id_namespace", ex.: "File:6". - my @temp = split(/[\n]/, run_git("config --get-all remote." - . $remotename .".namespaceCache")); + my @temp = split(/\n/, + run_git("config --get-all remote.${remotename}.namespaceCache")); chomp(@temp); foreach my $ns (@temp) { my ($n, $id) = split(/:/, $ns); @@ -1315,7 +1266,7 @@ sub get_mw_namespace_id { } if (!exists $namespace_id{$name}) { - print STDERR "Namespace $name not found in cache, querying the wiki ...\n"; + print {*STDERR} "Namespace ${name} not found in cache, querying the wiki ...\n"; # NS not found => get namespace id from MW and store it in # configuration file. my $query = { @@ -1339,8 +1290,8 @@ sub get_mw_namespace_id { my $ns = $namespace_id{$name}; my $id; - unless (defined $ns) { - print STDERR "No such namespace $name on MediaWiki.\n"; + if (!defined $ns) { + print {*STDERR} "No such namespace ${name} on MediaWiki.\n"; $ns = {is_namespace => 0}; $namespace_id{$name} = $ns; } @@ -1354,15 +1305,15 @@ sub get_mw_namespace_id { # Store explicitely requested namespaces on disk if (!exists $cached_mw_namespace_id{$name}) { - run_git("config --add remote.". $remotename - .".namespaceCache \"". $name .":". $store_id ."\""); + run_git(qq(config --add remote.${remotename}.namespaceCache "${name}:${store_id}")); $cached_mw_namespace_id{$name} = 1; } return $id; } sub get_mw_namespace_id_for_page { - if (my ($namespace) = $_[0] =~ /^([^:]*):/) { + my $namespace = shift; + if ($namespace =~ /^([^:]*):/) { return get_mw_namespace_id($namespace); } else { return; diff --git a/contrib/mw-to-git/t/test-gitmw-lib.sh b/contrib/mw-to-git/t/test-gitmw-lib.sh index 3b2cfacf51..ca6860ff30 100755 --- a/contrib/mw-to-git/t/test-gitmw-lib.sh +++ b/contrib/mw-to-git/t/test-gitmw-lib.sh @@ -62,12 +62,8 @@ test_check_precond () { test_done fi - if [ ! -f "$GIT_BUILD_DIR"/git-remote-mediawiki ]; - then - echo "No remote mediawiki for git found. Copying it in git" - echo "cp $GIT_BUILD_DIR/contrib/mw-to-git/git-remote-mediawiki $GIT_BUILD_DIR/" - ln -s "$GIT_BUILD_DIR"/contrib/mw-to-git/git-remote-mediawiki "$GIT_BUILD_DIR" - fi + GIT_EXEC_PATH=$(cd "$(dirname "$0")" && cd "../.." && pwd) + PATH="$GIT_EXEC_PATH"'/bin-wrapper:'"$PATH" if [ ! -d "$WIKI_DIR_INST/$WIKI_DIR_NAME" ]; then @@ -336,20 +332,21 @@ wiki_install () { fi # Fetch MediaWiki's archive if not already present in the TMP directory + MW_FILENAME="mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz" cd "$TMP" - if [ ! -f "$MW_VERSION.tar.gz" ] ; then - echo "Downloading $MW_VERSION sources ..." - wget "http://download.wikimedia.org/mediawiki/1.19/mediawiki-1.19.0.tar.gz" || + if [ ! -f $MW_FILENAME ] ; then + echo "Downloading $MW_VERSION_MAJOR.$MW_VERSION_MINOR sources ..." + wget "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/$MW_FILENAME" || error "Unable to download "\ - "http://download.wikimedia.org/mediawiki/1.19/"\ - "mediawiki-1.19.0.tar.gz. "\ + "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/"\ + "$MW_FILENAME. "\ "Please fix your connection and launch the script again." - echo "$MW_VERSION.tar.gz downloaded in `pwd`. "\ + echo "$MW_FILENAME downloaded in `pwd`. "\ "You can delete it later if you want." else - echo "Reusing existing $MW_VERSION.tar.gz downloaded in `pwd`." + echo "Reusing existing $MW_FILENAME downloaded in `pwd`." fi - archive_abs_path=$(pwd)/"$MW_VERSION.tar.gz" + archive_abs_path=$(pwd)/$MW_FILENAME cd "$WIKI_DIR_INST/$WIKI_DIR_NAME/" || error "can't cd to $WIKI_DIR_INST/$WIKI_DIR_NAME/" tar xzf "$archive_abs_path" --strip-components=1 || @@ -431,5 +428,5 @@ wiki_delete () { # Delete the wiki's SQLite database rm -f "$TMP/$DB_FILE" || error "Database $TMP/$DB_FILE could not be deleted." rm -f "$FILES_FOLDER/$DB_FILE" - rm -rf "$TMP/$MW_VERSION" + rm -rf "$TMP/mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz" } diff --git a/contrib/mw-to-git/t/test.config b/contrib/mw-to-git/t/test.config index 958b37b4a7..4cfebe9c69 100644 --- a/contrib/mw-to-git/t/test.config +++ b/contrib/mw-to-git/t/test.config @@ -30,6 +30,8 @@ WEB_WWW=$WEB/www # The variables below are used by the script to install a wiki. # You should not modify these unless you are modifying the script itself. -MW_VERSION=mediawiki-1.19.0 +# tested versions: 1.19.X -> 1.21.1 +MW_VERSION_MAJOR=1.21 +MW_VERSION_MINOR=1 FILES_FOLDER=install-wiki DB_INSTALL_SCRIPT=db_install.php @@ -760,7 +760,7 @@ static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen) snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d", ntohs(sin_addr->sin_port)); #ifndef NO_IPV6 - } else if (addr && addr->sa_family == AF_INET6) { + } else if (addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6_addr = (void *) addr; char *buf = addrbuf + 12; @@ -1047,18 +1047,6 @@ static int service_loop(struct socketlist *socklist) } } -/* if any standard file descriptor is missing open it to /dev/null */ -static void sanitize_stdfds(void) -{ - int fd = open("/dev/null", O_RDWR, 0); - while (fd != -1 && fd < 2) - fd = dup(fd); - if (fd == -1) - die_errno("open /dev/null or dup failed"); - if (fd > 2) - close(fd); -} - #ifdef NO_POSIX_GOODIES struct credentials; @@ -1683,9 +1683,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) del = deleted; if (graph_width <= max_change) { - int total = add + del; - - total = scale_linear(add + del, graph_width, max_change); + int total = scale_linear(add + del, graph_width, max_change); if (total < 2 && add && del) /* width >= 2 due to the sanity check */ total = 2; @@ -2586,7 +2584,7 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, */ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file) { - struct cache_entry *ce; + const struct cache_entry *ce; struct stat st; int pos, len; @@ -2677,6 +2675,14 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only) int diff_populate_filespec(struct diff_filespec *s, int size_only) { int err = 0; + /* + * demote FAIL to WARN to allow inspecting the situation + * instead of refusing. + */ + enum safe_crlf crlf_warn = (safe_crlf == SAFE_CRLF_FAIL + ? SAFE_CRLF_WARN + : safe_crlf); + if (!DIFF_FILE_VALID(s)) die("internal error: asking to populate invalid file."); if (S_ISDIR(s->mode)) @@ -2732,7 +2738,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) /* * Convert from working tree format to canonical git format */ - if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) { + if (convert_to_git(s->path, s->data, s->size, &buf, crlf_warn)) { size_t size = 0; munmap(s->data, s->size); s->should_munmap = 0; @@ -3497,6 +3503,11 @@ static int parse_submodule_opt(struct diff_options *options, const char *value) return 1; } +static void enable_patch_output(int *fmt) { + *fmt &= ~DIFF_FORMAT_NO_OUTPUT; + *fmt |= DIFF_FORMAT_PATCH; +} + int diff_opt_parse(struct diff_options *options, const char **av, int ac) { const char *arg = av[0]; @@ -3504,15 +3515,15 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) int argcount; /* Output format options */ - if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch")) - options->output_format |= DIFF_FORMAT_PATCH; - else if (opt_arg(arg, 'U', "unified", &options->context)) - options->output_format |= DIFF_FORMAT_PATCH; + if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch") + || opt_arg(arg, 'U', "unified", &options->context)) + enable_patch_output(&options->output_format); else if (!strcmp(arg, "--raw")) options->output_format |= DIFF_FORMAT_RAW; - else if (!strcmp(arg, "--patch-with-raw")) - options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW; - else if (!strcmp(arg, "--numstat")) + else if (!strcmp(arg, "--patch-with-raw")) { + enable_patch_output(&options->output_format); + options->output_format |= DIFF_FORMAT_RAW; + } else if (!strcmp(arg, "--numstat")) options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; @@ -3534,13 +3545,14 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->output_format |= DIFF_FORMAT_CHECKDIFF; else if (!strcmp(arg, "--summary")) options->output_format |= DIFF_FORMAT_SUMMARY; - else if (!strcmp(arg, "--patch-with-stat")) - options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT; - else if (!strcmp(arg, "--name-only")) + else if (!strcmp(arg, "--patch-with-stat")) { + enable_patch_output(&options->output_format); + options->output_format |= DIFF_FORMAT_DIFFSTAT; + } else if (!strcmp(arg, "--name-only")) options->output_format |= DIFF_FORMAT_NAME; else if (!strcmp(arg, "--name-status")) options->output_format |= DIFF_FORMAT_NAME_STATUS; - else if (!strcmp(arg, "-s")) + else if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch")) options->output_format |= DIFF_FORMAT_NO_OUTPUT; else if (!prefixcmp(arg, "--stat")) /* --stat, --stat-width, --stat-name-width, or --stat-count */ @@ -3593,6 +3605,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE); else if (!strcmp(arg, "--ignore-space-at-eol")) DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); + else if (!strcmp(arg, "--ignore-blank-lines")) + DIFF_XDL_SET(options, IGNORE_BLANK_LINES); else if (!strcmp(arg, "--patience")) options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); else if (!strcmp(arg, "--histogram")) @@ -3611,7 +3625,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) /* flags options */ else if (!strcmp(arg, "--binary")) { - options->output_format |= DIFF_FORMAT_PATCH; + enable_patch_output(&options->output_format); DIFF_OPT_SET(options, BINARY); } else if (!strcmp(arg, "--full-index")) diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index c97ac9b463..401eb72c61 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -131,8 +131,7 @@ static void diffcore_pickaxe_grep(struct diff_options *o) return; } -static unsigned int contains(mmfile_t *mf, struct diff_options *o, - regex_t *regexp, kwset_t kws) +static unsigned int contains(mmfile_t *mf, regex_t *regexp, kwset_t kws) { unsigned int cnt; unsigned long sz; @@ -176,11 +175,9 @@ static int has_changes(mmfile_t *one, mmfile_t *two, struct diff_options *o, regex_t *regexp, kwset_t kws) { - if (!one) - return contains(two, o, regexp, kws) != 0; - if (!two) - return contains(one, o, regexp, kws) != 0; - return contains(one, o, regexp, kws) != contains(two, o, regexp, kws); + unsigned int one_contains = one ? contains(one, regexp, kws) : 0; + unsigned int two_contains = two ? contains(two, regexp, kws) : 0; + return one_contains != two_contains; } static int pickaxe_match(struct diff_filepair *p, struct diff_options *o, @@ -933,7 +933,7 @@ enum exist_status { */ static enum exist_status directory_exists_in_index_icase(const char *dirname, int len) { - struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case); + const struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case); unsigned char endchar; if (!ce) @@ -977,7 +977,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) if (pos < 0) pos = -pos-1; while (pos < active_nr) { - struct cache_entry *ce = active_cache[pos++]; + const struct cache_entry *ce = active_cache[pos++]; unsigned char endchar; if (strncmp(ce->name, dirname, len)) @@ -1036,9 +1036,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir, return path_recurse; case index_gitdir: - if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES) - return path_none; - return path_untracked; + return path_none; case index_nonexistent: if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES) @@ -1115,7 +1113,7 @@ static int exclude_matches_pathspec(const char *path, int len, static int get_index_dtype(const char *path, int len) { int pos; - struct cache_entry *ce; + const struct cache_entry *ce; ce = cache_name_exists(path, len, 0); if (ce) { @@ -79,7 +79,7 @@ static int create_file(const char *path, unsigned int mode) return open(path, O_WRONLY | O_CREAT | O_EXCL, mode); } -static void *read_blob_entry(struct cache_entry *ce, unsigned long *size) +static void *read_blob_entry(const struct cache_entry *ce, unsigned long *size) { enum object_type type; void *new = read_sha1_file(ce->sha1, &type, size); @@ -92,7 +92,7 @@ static void *read_blob_entry(struct cache_entry *ce, unsigned long *size) return NULL; } -static int open_output_fd(char *path, struct cache_entry *ce, int to_tempfile) +static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempfile) { int symlink = (ce->ce_mode & S_IFMT) != S_IFREG; if (to_tempfile) { @@ -115,7 +115,7 @@ static int fstat_output(int fd, const struct checkout *state, struct stat *st) return 0; } -static int streaming_write_entry(struct cache_entry *ce, char *path, +static int streaming_write_entry(const struct cache_entry *ce, char *path, struct stream_filter *filter, const struct checkout *state, int to_tempfile, int *fstat_done, struct stat *statbuf) @@ -136,7 +136,8 @@ static int streaming_write_entry(struct cache_entry *ce, char *path, return result; } -static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile) +static int write_entry(struct cache_entry *ce, + char *path, const struct checkout *state, int to_tempfile) { unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT; int fd, ret, fstat_done = 0; @@ -199,9 +200,9 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout break; case S_IFGITLINK: if (to_tempfile) - return error("cannot create temporary subproject %s", path); + return error("cannot create temporary submodule %s", path); if (mkdir(path, 0777) < 0) - return error("cannot create subproject directory %s", path); + return error("cannot create submodule directory %s", path); break; default: return error("unknown file mode for %s in index", path); @@ -233,7 +234,8 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen) return lstat(path, st); } -int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath) +int checkout_entry(struct cache_entry *ce, + const struct checkout *state, char *topath) { static char path[PATH_MAX + 1]; struct stat st; diff --git a/environment.c b/environment.c index e2e75c1660..0cb67b22cf 100644 --- a/environment.c +++ b/environment.c @@ -37,7 +37,6 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; size_t delta_base_cache_limit = 16 * 1024 * 1024; unsigned long big_file_threshold = 512 * 1024 * 1024; -const char *log_pack_access; const char *pager_program; int pager_use_color = 1; const char *editor_program; diff --git a/exec_cmd.h b/exec_cmd.h index e2b546b615..e4c9702f02 100644 --- a/exec_cmd.h +++ b/exec_cmd.h @@ -7,6 +7,7 @@ extern const char *git_exec_path(void); extern void setup_path(void); extern const char **prepare_git_cmd(const char **argv); extern int execv_git_cmd(const char **argv); /* NULL terminated */ +LAST_ARG_MUST_BE_NULL extern int execl_git_cmd(const char *cmd, ...); extern const char *system_path(const char *path); diff --git a/fetch-pack.c b/fetch-pack.c index abe5ffbba5..6684348c0e 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -11,6 +11,7 @@ #include "run-command.h" #include "transport.h" #include "version.h" +#include "prio-queue.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; @@ -37,7 +38,7 @@ static int marked; */ #define MAX_IN_VAIN 256 -static struct commit_list *rev_list; +static struct prio_queue rev_list = { compare_commits_by_commit_date }; static int non_common_revs, multi_ack, use_sideband, allow_tip_sha1_in_want; static void rev_list_push(struct commit *commit, int mark) @@ -49,7 +50,7 @@ static void rev_list_push(struct commit *commit, int mark) if (parse_commit(commit)) return; - commit_list_insert_by_date(commit, &rev_list); + prio_queue_put(&rev_list, commit); if (!(commit->object.flags & COMMON)) non_common_revs++; @@ -122,10 +123,10 @@ static const unsigned char *get_rev(void) unsigned int mark; struct commit_list *parents; - if (rev_list == NULL || non_common_revs == 0) + if (rev_list.nr == 0 || non_common_revs == 0) return NULL; - commit = rev_list->item; + commit = prio_queue_get(&rev_list); if (!commit->object.parsed) parse_commit(commit); parents = commit->parents; @@ -152,8 +153,6 @@ static const unsigned char *get_rev(void) mark_common(parents->item, 1, 0); parents = parents->next; } - - rev_list = rev_list->next; } return commit->object.sha1; @@ -442,7 +441,7 @@ static int find_common(struct fetch_pack_args *args, in_vain = 0; got_continue = 1; if (ack == ACK_ready) { - rev_list = NULL; + clear_prio_queue(&rev_list); got_ready = 1; } break; @@ -505,7 +504,7 @@ static int mark_complete(const char *refname, const unsigned char *sha1, int fla struct commit *commit = (struct commit *)o; if (!(commit->object.flags & COMPLETE)) { commit->object.flags |= COMPLETE; - commit_list_insert_by_date(commit, &complete); + commit_list_insert(commit, &complete); } } return 0; @@ -622,6 +621,7 @@ static int everything_local(struct fetch_pack_args *args, if (!args->depth) { for_each_ref(mark_complete, NULL); for_each_alternate_ref(mark_alternate_complete, NULL); + commit_list_sort_by_date(&complete); if (cutoff) mark_recent_complete_commits(args, cutoff); } diff --git a/fixup-builtins b/fixup-builtins deleted file mode 100755 index 63dfa4c475..0000000000 --- a/fixup-builtins +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -while [ "$1" ] -do - if [ "$1" != "git-sh-setup" -a "$1" != "git-parse-remote" -a "$1" != "git-svn" ]; then - old="$1" - new=$(echo "$1" | sed 's/git-/git /') - echo "Converting '$old' to '$new'" - sed -i "s/\\<$old\\>/$new/g" $(git ls-files '*.sh') - fi - shift -done - -sed -i 's/git merge-one-file/git-merge-one-file/g -s/git rebase-todo/git-rebase-todo/g' $(git ls-files '*.sh') -git update-index --refresh >& /dev/null -exit 0 diff --git a/git-add--interactive.perl b/git-add--interactive.perl index d2c4ce6e1e..75a991f7ec 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -44,6 +44,8 @@ my ($diff_new_color) = my $normal_color = $repo->get_color("", "reset"); +my $diff_algorithm = $repo->config('diff.algorithm'); + my $use_readkey = 0; my $use_termcap = 0; my %term_escapes; @@ -731,6 +733,9 @@ sub run_git_apply { sub parse_diff { my ($path) = @_; my @diff_cmd = split(" ", $patch_mode_flavour{DIFF}); + if (defined $diff_algorithm) { + splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}"; + } if (defined $patch_mode_revision) { push @diff_cmd, $patch_mode_revision; } @@ -6,7 +6,7 @@ SUBDIRECTORY_OK=Yes OPTIONS_KEEPDASHDASH= OPTIONS_SPEC="\ git am [options] [(<mbox>|<Maildir>)...] -git am [options] (--resolved | --skip | --abort) +git am [options] (--continue | --skip | --abort) -- i,interactive run interactively b,binary* (historical option -- no-op) @@ -102,7 +102,7 @@ stop_here_user_resolve () { printf '%s\n' "$resolvemsg" stop_here $1 fi - eval_gettextln "When you have resolved this problem, run \"\$cmdline --resolved\". + eval_gettextln "When you have resolved this problem, run \"\$cmdline --continue\". If you prefer to skip this patch, run \"\$cmdline --skip\" instead. To restore the original branch and stop patching, run \"\$cmdline --abort\"." @@ -506,7 +506,24 @@ then esac rm -f "$dotest/dirtyindex" else - # Make sure we are not given --skip, --resolved, nor --abort + # Possible stray $dotest directory in the independent-run + # case; in the --rebasing case, it is upto the caller + # (git-rebase--am) to take care of stray directories. + if test -d "$dotest" && test -z "$rebasing" + then + case "$skip,$resolved,$abort" in + ,,t) + rm -fr "$dotest" + exit 0 + ;; + *) + die "$(eval_gettext "Stray \$dotest directory found. +Use \"git am --abort\" to remove it.")" + ;; + esac + fi + + # Make sure we are not given --skip, --continue, nor --abort test "$skip$resolved$abort" = "" || die "$(gettext "Resolve operation not in progress, we are not resuming.")" @@ -653,7 +670,7 @@ do # - patch is the patch body. # # When we are resuming, these files are either already prepared - # by the user, or the user can tell us to do so by --resolved flag. + # by the user, or the user can tell us to do so by --continue flag. case "$resume" in '') if test -f "$dotest/rebasing" diff --git a/git-compat-util.h b/git-compat-util.h index ff193f4aa2..cc4ba4d18f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -303,6 +303,13 @@ extern char *gitbasename(char *); #endif #endif +/* The sentinel attribute is valid from gcc version 4.0 */ +#if defined(__GNUC__) && (__GNUC__ >= 4) +#define LAST_ARG_MUST_BE_NULL __attribute__((sentinel)) +#else +#define LAST_ARG_MUST_BE_NULL +#endif + #include "compat/bswap.h" #ifdef USE_WILDMATCH diff --git a/git-gui/GIT-VERSION-GEN b/git-gui/GIT-VERSION-GEN index 49b62ea6eb..0a1f8d4f8a 100755 --- a/git-gui/GIT-VERSION-GEN +++ b/git-gui/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=0.17.GITGUI +DEF_VER=0.18.GITGUI LF=' ' diff --git a/git-gui/Makefile b/git-gui/Makefile index e22ba5c321..e9c2bc347d 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -254,7 +254,7 @@ lib/tclIndex: $(ALL_LIBFILES) GIT-GUI-VARS auto_mkindex lib '*.tcl' \ | $(TCL_PATH) $(QUIET_2DEVNULL); then : ok; \ else \ - echo 1>&2 " * $(TCL_PATH) failed; using unoptimized loading"; \ + echo >&2 " * $(TCL_PATH) failed; using unoptimized loading"; \ rm -f $@ ; \ echo '# Autogenerated by git-gui Makefile' >$@ && \ echo >>$@ && \ @@ -274,8 +274,8 @@ TRACK_VARS = \ GIT-GUI-VARS: FORCE @VARS='$(TRACK_VARS)'; \ if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \ - echo 1>&2 " * new locations or Tcl/Tk interpreter"; \ - echo 1>$@ "$$VARS"; \ + echo >&2 " * new locations or Tcl/Tk interpreter"; \ + echo >$@ "$$VARS"; \ fi ifdef GITGUI_MACOSXAPP diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 89f636f496..153f85da06 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -137,6 +137,20 @@ unset oguimsg ###################################################################### ## +## On Mac, bring the current Wish process window to front + +if {[tk windowingsystem] eq "aqua"} { + catch { + exec osascript -e [format { + tell application "System Events" + set frontmost of processes whose unix id is %d to true + end tell + } [pid]] + } +} + +###################################################################### +## ## read only globals set _appname {Git Gui} @@ -3003,18 +3017,11 @@ blame { set jump_spec {} set is_path 0 foreach a $argv { - if {[file exists $a]} { - if {$path ne {}} usage - set path [normalize_relpath $a] - break - } elseif {[file exists $_prefix$a]} { - if {$path ne {}} usage - set path [normalize_relpath $_prefix$a] - break - } + set p [file join $_prefix $a] - if {$is_path} { + if {$is_path || [file exists $p]} { if {$path ne {}} usage + set path [normalize_relpath $p] break } elseif {$a eq {--}} { if {$path ne {}} { diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index 657f7d5dc1..ee58981f53 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -286,7 +286,9 @@ method _next {action} { destroy $w_body if {![winfo exists $w_next]} { ${NS}::button $w_next -default active - pack $w_next -side right -padx 5 -before $w_quit + set pos -before + if {[tk windowingsystem] eq "win32"} { set pos -after } + pack $w_next -side right -padx 5 $pos $w_quit } _do_$action $this } diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index ec4405567a..30d9a79776 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -764,8 +764,15 @@ proc apply_range_or_line {x y} { # context line set ln [$ui_diff get $i_l $next_l] set patch "$patch$pre_context$ln" - set n [expr $n+1] - set m [expr $m+1] + # Skip the "\ No newline at end of + # file". Depending on the locale setting + # we don't know what this line looks + # like exactly. The only thing we do + # know is that it starts with "\ " + if {![string match {\\ *} $ln]} { + set n [expr $n+1] + set m [expr $m+1] + } set pre_context {} } elseif {$c1 eq $to_context} { # turn change line into context line diff --git a/git-gui/lib/mergetool.tcl b/git-gui/lib/mergetool.tcl index 3c8e73bceb..120bc4064b 100644 --- a/git-gui/lib/mergetool.tcl +++ b/git-gui/lib/mergetool.tcl @@ -189,9 +189,9 @@ proc merge_resolve_tool2 {} { } bc3 { if {$base_stage ne {}} { - set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" -mergeoutput="$MERGED"] + set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "$BASE" "-mergeoutput=$MERGED"] } else { - set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -mergeoutput="$MERGED"] + set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" "-mergeoutput=$MERGED"] } } ecmerge { diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl index 5e4e7f4c83..4e5c784418 100644 --- a/git-gui/lib/remote.tcl +++ b/git-gui/lib/remote.tcl @@ -245,7 +245,8 @@ proc update_all_remotes_menu_entry {} { set prune_m $remote_m.prune if {$have_remote > 1} { make_sure_remote_submenues_exist $remote_m - if {[$fetch_m entrycget end -label] ne "All"} { + if {[$fetch_m type end] eq "command" \ + && [$fetch_m entrycget end -label] ne "All"} { $fetch_m insert end separator $fetch_m insert end command \ @@ -259,7 +260,8 @@ proc update_all_remotes_menu_entry {} { } } else { if {[winfo exists $fetch_m]} { - if {[$fetch_m entrycget end -label] eq "All"} { + if {[$fetch_m type end] eq "command" \ + && [$fetch_m entrycget end -label] eq "All"} { delete_from_menu $fetch_m end delete_from_menu $fetch_m end diff --git a/git-gui/po/fr.po b/git-gui/po/fr.po index 40441dbb0d..0aff18691d 100644 --- a/git-gui/po/fr.po +++ b/git-gui/po/fr.po @@ -1139,7 +1139,7 @@ msgstr "Standard (rapide, semi-redondant, liens durs)" #: lib/choose_repository.tcl:514 msgid "Full Copy (Slower, Redundant Backup)" -msgstr "Copy complète (plus lent, sauvegarde redondante)" +msgstr "Copie complète (plus lent, sauvegarde redondante)" #: lib/choose_repository.tcl:520 msgid "Shared (Fastest, Not Recommended, No Backup)" diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index e338be5e57..6a721064c2 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -114,6 +114,33 @@ valid_tool () { test -n "$cmd" } +setup_user_tool () { + merge_tool_cmd=$(get_merge_tool_cmd "$tool") + test -n "$merge_tool_cmd" || return 1 + + diff_cmd () { + ( eval $merge_tool_cmd ) + status=$? + return $status + } + + merge_cmd () { + trust_exit_code=$(git config --bool \ + "mergetool.$1.trustExitCode" || echo false) + if test "$trust_exit_code" = "false" + then + touch "$BACKUP" + ( eval $merge_tool_cmd ) + status=$? + check_unchanged + else + ( eval $merge_tool_cmd ) + status=$? + fi + return $status + } +} + setup_tool () { tool="$1" @@ -142,15 +169,15 @@ setup_tool () { if ! test -f "$MERGE_TOOLS_DIR/$tool" then - # Use a special return code for this case since we want to - # source "defaults" even when an explicit tool path is - # configured since the user can use that to override the - # default path in the scriptlet. - return 2 + setup_user_tool + return $? fi # Load the redefined functions . "$MERGE_TOOLS_DIR/$tool" + # Now let the user override the default command for the tool. If + # they have not done so then this will return 1 which we ignore. + setup_user_tool if merge_mode && ! can_merge then @@ -187,20 +214,7 @@ run_merge_tool () { status=0 # Bring tool-specific functions into scope - setup_tool "$1" - exitcode=$? - case $exitcode in - 0) - : - ;; - 2) - # The configured tool is not a built-in tool. - test -n "$merge_tool_path" || return 1 - ;; - *) - return $exitcode - ;; - esac + setup_tool "$1" || return 1 if merge_mode then @@ -213,38 +227,12 @@ run_merge_tool () { # Run a either a configured or built-in diff tool run_diff_cmd () { - merge_tool_cmd=$(get_merge_tool_cmd "$1") - if test -n "$merge_tool_cmd" - then - ( eval $merge_tool_cmd ) - status=$? - return $status - else - diff_cmd "$1" - fi + diff_cmd "$1" } # Run a either a configured or built-in merge tool run_merge_cmd () { - merge_tool_cmd=$(get_merge_tool_cmd "$1") - if test -n "$merge_tool_cmd" - then - trust_exit_code=$(git config --bool \ - "mergetool.$1.trustExitCode" || echo false) - if test "$trust_exit_code" = "false" - then - touch "$BACKUP" - ( eval $merge_tool_cmd ) - status=$? - check_unchanged - else - ( eval $merge_tool_cmd ) - status=$? - fi - return $status - else - merge_cmd "$1" - fi + merge_cmd "$1" } list_merge_tool_candidates () { @@ -3168,7 +3168,7 @@ class P4Rebase(Command): if os.system("git update-index --refresh") != 0: die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash."); if len(read_pipe("git diff-index HEAD --")) > 0: - die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash."); + die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash."); [upstream, settings] = findUpstreamBranchPoint() if len(upstream) == 0: diff --git a/git-pull.sh b/git-pull.sh index 638aabb7b3..f0df41c841 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -5,7 +5,7 @@ # Fetch one or more remote refs and merge it/them into the current HEAD. USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...' -LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.' +LONG_USAGE='Fetch one or more remote refs and integrate it/them with the current HEAD.' SUBDIRECTORY_OK=Yes OPTIONS_SPEC= . git-sh-setup @@ -266,10 +266,17 @@ case "$merge_head" in ;; esac +# Pulling into unborn branch: a shorthand for branching off +# FETCH_HEAD, for lazy typers. if test -z "$orig_head" then - git update-ref -m "initial pull" HEAD $merge_head "$curr_head" && - git read-tree -m -u HEAD || exit 1 + # Two-way merge: we claim the index is based on an empty tree, + # and try to fast-forward to HEAD. This ensures we will not + # lose index/worktree changes that the user already made on + # the unborn branch. + empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904 + git read-tree -m -u $empty_tree $merge_head && + git update-ref -m "initial pull" HEAD $merge_head "$curr_head" exit fi diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index f953d8d224..83d6d4676b 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -80,6 +80,18 @@ amend="$state_dir"/amend rewritten_list="$state_dir"/rewritten-list rewritten_pending="$state_dir"/rewritten-pending +strategy_args= +if test -n "$do_merge" +then + strategy_args=${strategy:+--strategy=$strategy} + eval ' + for strategy_opt in '"$strategy_opts"' + do + strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")" + done + ' +fi + GIT_CHERRY_PICK_HELP="$resolvemsg" export GIT_CHERRY_PICK_HELP @@ -239,7 +251,7 @@ pick_one () { test -d "$rewritten" && pick_one_preserving_merges "$@" && return - output git cherry-pick $empty_args $ff "$@" + output eval git cherry-pick "$strategy_args" $empty_args $ff "$@" } pick_one_preserving_merges () { @@ -340,9 +352,8 @@ pick_one_preserving_merges () { msg_content="$(commit_message $sha1)" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} - if ! do_with_author output \ - git merge --no-ff ${strategy:+-s $strategy} -m \ - "$msg_content" $new_parents + if ! do_with_author output eval \ + 'git merge --no-ff $strategy_args -m "$msg_content" $new_parents' then printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG die_with_patch $sha1 "Error redoing merge $sha1" @@ -350,7 +361,7 @@ pick_one_preserving_merges () { echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" ;; *) - output git cherry-pick "$@" || + output eval git cherry-pick "$strategy_args" "$@" || die_with_patch $sha1 "Could not pick $sha1" ;; esac @@ -689,8 +700,22 @@ rearrange_squash () { case "$message" in "squash! "*|"fixup! "*) action="${message%%!*}" - rest="${message#*! }" - echo "$sha1 $action $rest" + rest=$message + prefix= + # skip all squash! or fixup! (but save for later) + while : + do + case "$rest" in + "squash! "*|"fixup! "*) + prefix="$prefix${rest%%!*}," + rest="${rest#*! }" + ;; + *) + break + ;; + esac + done + echo "$sha1 $action $prefix $rest" # if it's a single word, try to resolve to a full sha1 and # emit a second copy. This allows us to match on both message # and on sha1 prefix @@ -699,7 +724,7 @@ rearrange_squash () { if test -n "$fullsha"; then # prefix the action to uniquely identify this line as # intended for full sha1 match - echo "$sha1 +$action $fullsha" + echo "$sha1 +$action $prefix $fullsha" fi fi esac @@ -714,7 +739,7 @@ rearrange_squash () { esac printf '%s\n' "$pick $sha1 $message" used="$used$sha1 " - while read -r squash action msg_content + while read -r squash action msg_prefix msg_content do case " $used" in *" $squash "*) continue ;; @@ -730,7 +755,8 @@ rearrange_squash () { case "$message" in "$msg_content"*) emit=1;; esac ;; esac if test $emit = 1; then - printf '%s\n' "$action $squash $action! $msg_content" + real_prefix=$(echo "$msg_prefix" | sed "s/,/! /g") + printf '%s\n' "$action $squash ${real_prefix}$msg_content" used="$used$squash " fi done <"$1.sq" @@ -838,8 +864,11 @@ comment_for_reflog start if test ! -z "$switch_to" then + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" output git checkout "$switch_to" -- || - die "Could not checkout $switch_to" + die "Could not checkout $switch_to" + + comment_for_reflog start fi orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?" @@ -981,6 +1010,7 @@ has_action "$todo" || test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks +GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" output git checkout $onto || die_abort "could not detach HEAD" git update-ref ORIG_HEAD $orig_head do_rest diff --git a/git-rebase.sh b/git-rebase.sh index d0c11a910a..0039ecfb40 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -84,6 +84,8 @@ keep_empty= test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t read_basic_state () { + test -f "$state_dir/head-name" && + test -f "$state_dir/onto" && head_name=$(cat "$state_dir"/head-name) && onto=$(cat "$state_dir"/onto) && # We always write to orig-head, but interactive rebase used to write to @@ -153,11 +155,8 @@ finish_rebase () { then echo "$(gettext 'Applied autostash.')" else - ref_stash=refs/stash && - >>"$GIT_DIR/logs/$ref_stash" && - git update-ref -m "autostash" $ref_stash $stash_sha1 || - die "$(eval_gettext 'Cannot store $stash_sha1')" - + git stash store -m "autostash" -q $stash_sha1 || + die "$(eval_gettext "Cannot store \$stash_sha1")" gettext 'Applying autostash resulted in conflicts. Your changes are safe in the stash. You can run "git stash pop" or "git stash drop" it at any time. @@ -434,7 +433,7 @@ then shift ;; esac - upstream=`git rev-parse --verify "${upstream_name}^0"` || + upstream=$(peel_committish "${upstream_name}") || die "$(eval_gettext "invalid upstream \$upstream_name")" upstream_arg="$upstream_name" else @@ -470,7 +469,7 @@ case "$onto_name" in fi ;; *) - onto=$(git rev-parse --verify "${onto_name}^0") || + onto=$(peel_committish "$onto_name") || die "$(eval_gettext "Does not point to a valid commit: \$onto_name")" ;; esac @@ -543,8 +542,11 @@ then if test -z "$force_rebase" then # Lazily switch to the target branch if needed... - test -z "$switch_to" || git checkout "$switch_to" -- + test -z "$switch_to" || + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" \ + git checkout "$switch_to" -- say "$(eval_gettext "Current branch \$branch_name is up to date.")" + finish_rebase exit 0 else say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")" @@ -568,7 +570,9 @@ test "$type" = interactive && run_specific_rebase # Detach HEAD and reset the tree say "$(gettext "First, rewinding head to replay your work on top of it...")" -git checkout -q "$onto^0" || die "could not detach HEAD" + +GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \ + git checkout -q "$onto^0" || die "could not detach HEAD" git update-ref ORIG_HEAD $orig_head # If the $onto is a proper descendant of the tip of the branch, then @@ -577,6 +581,7 @@ if test "$mb" = "$orig_head" then say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")" move_to_original_branch + finish_rebase exit 0 fi diff --git a/git-request-pull.sh b/git-request-pull.sh index d566015975..ebf1269d29 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -51,8 +51,18 @@ fi tag_name=$(git describe --exact "$head^0" 2>/dev/null) test -n "$base" && test -n "$url" || usage -baserev=$(git rev-parse --verify "$base"^0) && -headrev=$(git rev-parse --verify "$head"^0) || exit + +baserev=$(git rev-parse --verify --quiet "$base"^0) +if test -z "$baserev" +then + die "fatal: Not a valid revision: $base" +fi + +headrev=$(git rev-parse --verify --quiet "$head"^0) +if test -z "$headrev" +then + die "fatal: Not a valid revision: $head" +fi merge_base=$(git merge-base $baserev $headrev) || die "fatal: No commits in common between $base and $head" diff --git a/git-send-email.perl b/git-send-email.perl index ec1d6ce4eb..2162478392 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -69,6 +69,9 @@ git send-email [options] <file | directory | rev-list options > --smtp-pass <str> * Password for SMTP-AUTH; not necessary. --smtp-encryption <str> * tls or ssl; anything else disables. --smtp-ssl * Deprecated. Use '--smtp-encryption ssl'. + --smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file). + Pass an empty string to disable certificate + verification. --smtp-domain <str> * The domain name sent to HELO/EHLO handshake --smtp-debug <0|1> * Disable, enable Net::SMTP debug. @@ -194,7 +197,7 @@ sub do_edit { my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc); my ($to_cmd, $cc_cmd); my ($smtp_server, $smtp_server_port, @smtp_server_options); -my ($smtp_authuser, $smtp_encryption); +my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path); my ($identity, $aliasfiletype, @alias_files, $smtp_domain); my ($validate, $confirm); my (@suppress_cc); @@ -220,6 +223,7 @@ my %config_settings = ( "smtpserveroption" => \@smtp_server_options, "smtpuser" => \$smtp_authuser, "smtppass" => \$smtp_authpass, + "smtpsslcertpath" => \$smtp_ssl_cert_path, "smtpdomain" => \$smtp_domain, "to" => \@initial_to, "tocmd" => \$to_cmd, @@ -287,6 +291,7 @@ my $rc = GetOptions("h" => \$help, "smtp-pass:s" => \$smtp_authpass, "smtp-ssl" => sub { $smtp_encryption = 'ssl' }, "smtp-encryption=s" => \$smtp_encryption, + "smtp-ssl-cert-path" => \$smtp_ssl_cert_path, "smtp-debug:i" => \$debug_net_smtp, "smtp-domain:s" => \$smtp_domain, "identity=s" => \$identity, @@ -745,6 +750,11 @@ if (!defined $sender) { $sender = $repoauthor || $repocommitter || ''; } +# $sender could be an already sanitized address +# (e.g. sendemail.from could be manually sanitized by user). +# But it's a no-op to run sanitize_address on an already sanitized address. +$sender = sanitize_address($sender); + my $prompting = 0; if (!@initial_to && !defined $to_cmd) { my $to = ask("Who should the emails be sent to (if any)? ", @@ -1074,6 +1084,34 @@ sub smtp_auth_maybe { return $auth; } +sub ssl_verify_params { + eval { + require IO::Socket::SSL; + IO::Socket::SSL->import(qw/SSL_VERIFY_PEER SSL_VERIFY_NONE/); + }; + if ($@) { + print STDERR "Not using SSL_VERIFY_PEER due to out-of-date IO::Socket::SSL.\n"; + return; + } + + if (!defined $smtp_ssl_cert_path) { + $smtp_ssl_cert_path = "/etc/ssl/certs"; + } + + if ($smtp_ssl_cert_path eq "") { + return (SSL_verify_mode => SSL_VERIFY_NONE()); + } elsif (-d $smtp_ssl_cert_path) { + return (SSL_verify_mode => SSL_VERIFY_PEER(), + SSL_ca_path => $smtp_ssl_cert_path); + } elsif (-f $smtp_ssl_cert_path) { + return (SSL_verify_mode => SSL_VERIFY_PEER(), + SSL_ca_file => $smtp_ssl_cert_path); + } else { + print STDERR "Not using SSL_VERIFY_PEER because the CA path does not exist.\n"; + return (SSL_verify_mode => SSL_VERIFY_NONE()); + } +} + # Returns 1 if the message was sent, and 0 otherwise. # In actuality, the whole program dies when there # is an error sending a message. @@ -1098,10 +1136,9 @@ sub send_message { if ($cc ne '') { $ccline = "\nCc: $cc"; } - my $sanitized_sender = sanitize_address($sender); make_message_id() unless defined($message_id); - my $header = "From: $sanitized_sender + my $header = "From: $sender To: $to${ccline} Subject: $subject Date: $date @@ -1118,7 +1155,7 @@ X-Mailer: git-send-email $gitversion } my @sendmail_parameters = ('-i', @recipients); - my $raw_from = $sanitized_sender; + my $raw_from = $sender; if (defined $envelope_sender && $envelope_sender ne "auto") { $raw_from = $envelope_sender; } @@ -1179,20 +1216,24 @@ X-Mailer: git-send-email $gitversion $smtp_domain ||= maildomain(); $smtp ||= Net::SMTP::SSL->new($smtp_server, Hello => $smtp_domain, - Port => $smtp_server_port); + Port => $smtp_server_port, + ssl_verify_params()); } else { require Net::SMTP; $smtp_domain ||= maildomain(); - $smtp ||= Net::SMTP->new(smtp_host_string(), + $smtp_server_port ||= 25; + $smtp ||= Net::SMTP->new($smtp_server, Hello => $smtp_domain, - Debug => $debug_net_smtp); + Debug => $debug_net_smtp, + Port => $smtp_server_port); if ($smtp_encryption eq 'tls' && $smtp) { require Net::SMTP::SSL; $smtp->command('STARTTLS'); $smtp->response(); if ($smtp->code == 220) { - $smtp = Net::SMTP::SSL->start_SSL($smtp) + $smtp = Net::SMTP::SSL->start_SSL($smtp, + ssl_verify_params()) or die "STARTTLS failed! ".$smtp->message; $smtp_encryption = ''; # Send EHLO again to receive fresh @@ -1255,6 +1296,7 @@ foreach my $t (@files) { open my $fh, "<", $t or die "can't open file $t"; my $author = undef; + my $sauthor = undef; my $author_encoding; my $has_content_type; my $body_encoding; @@ -1293,8 +1335,9 @@ foreach my $t (@files) { } elsif (/^From:\s+(.*)$/i) { ($author, $author_encoding) = unquote_rfc2047($1); + $sauthor = sanitize_address($author); next if $suppress_cc{'author'}; - next if $suppress_cc{'self'} and $author eq $sender; + next if $suppress_cc{'self'} and $sauthor eq $sender; printf("(mbox) Adding cc: %s from line '%s'\n", $1, $_) unless $quiet; push @cc, $1; @@ -1308,7 +1351,9 @@ foreach my $t (@files) { } elsif (/^Cc:\s+(.*)$/i) { foreach my $addr (parse_address_line($1)) { - if (unquote_rfc2047($addr) eq $sender) { + my $qaddr = unquote_rfc2047($addr); + my $saddr = sanitize_address($qaddr); + if ($saddr eq $sender) { next if ($suppress_cc{'self'}); } else { next if ($suppress_cc{'cc'}); @@ -1355,7 +1400,8 @@ foreach my $t (@files) { chomp; my ($what, $c) = ($1, $2); chomp $c; - if ($c eq $sender) { + my $sc = sanitize_address($c); + if ($sc eq $sender) { next if ($suppress_cc{'self'}); } else { next if $suppress_cc{'sob'} and $what =~ /Signed-off-by/i; @@ -1385,7 +1431,7 @@ foreach my $t (@files) { $subject = quote_subject($subject, $auto_8bit_encoding); } - if (defined $author and $author ne $sender) { + if (defined $sauthor and $sauthor ne $sender) { $message = "From: $author\n\n$message"; if (defined $author_encoding) { if ($has_content_type) { @@ -1439,7 +1485,6 @@ foreach my $t (@files) { sub recipients_cmd { my ($prefix, $what, $cmd, $file) = @_; - my $sanitized_sender = sanitize_address($sender); my @addresses = (); open my $fh, "-|", "$cmd \Q$file\E" or die "($prefix) Could not execute '$cmd'"; @@ -1447,7 +1492,7 @@ sub recipients_cmd { $address =~ s/^\s*//g; $address =~ s/\s*$//g; $address = sanitize_address($address); - next if ($address eq $sanitized_sender and $suppress_from); + next if ($address eq $sender and $suppress_cc{'self'}); push @addresses, $address; printf("($prefix) Adding %s: %s from: '%s'\n", $what, $address, $cmd) unless $quiet; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 2f7835941e..7a964ad2ff 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -313,3 +313,15 @@ then } : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} fi + +peel_committish () { + case "$1" in + :/*) + peeltmp=$(git rev-parse --verify "$1") && + git rev-parse --verify "${peeltmp}^0" + ;; + *) + git rev-parse --verify "${1}^0" + ;; + esac +} diff --git a/git-stash.sh b/git-stash.sh index bbefdf6424..85c9e2c081 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -156,10 +156,46 @@ create_stash () { die "$(gettext "Cannot record working tree state")" } +store_stash () { + while test $# != 0 + do + case "$1" in + -m|--message) + shift + stash_msg="$1" + ;; + -q|--quiet) + quiet=t + ;; + *) + break + ;; + esac + shift + done + test $# = 1 || + die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")" + + w_commit="$1" + if test -z "$stash_msg" + then + stash_msg="Created via \"git stash store\"." + fi + + # Make sure the reflog for stash is kept. + : >>"$GIT_DIR/logs/$ref_stash" + git update-ref -m "$stash_msg" $ref_stash $w_commit + ret=$? + test $ret != 0 && test -z $quiet && + die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")" + return $ret +} + save_stash () { keep_index= patch_mode= untracked= + force= while test $# != 0 do case "$1" in @@ -180,6 +216,9 @@ save_stash () { -u|--include-untracked) untracked=untracked ;; + -f|--force) + force=t + ;; -a|--all) untracked=all ;; @@ -223,16 +262,20 @@ save_stash () { say "$(gettext "No local changes to save")" exit 0 fi + if test -z "$untracked$force" && + test -n "$(git ls-files --killed | head -n 1)" + then + say "$(gettext "The following untracked files would NOT be saved but need to be removed by stash save:")" + test -n "$GIT_QUIET" || git ls-files --killed | sed 's/^/\t/' + say "$(gettext "Aborting. Consider using either the --force or --include-untracked option.")" >&2 + exit 1 + fi test -f "$GIT_DIR/logs/$ref_stash" || clear_stash || die "$(gettext "Cannot initialize stash")" create_stash "$stash_msg" $untracked - - # Make sure the reflog for stash is kept. - : >>"$GIT_DIR/logs/$ref_stash" - - git update-ref -m "$stash_msg" $ref_stash $w_commit || - die "$(gettext "Cannot save the current status")" + store_stash -m "$stash_msg" -q $w_commit || + die "$(gettext "Cannot save the current status")" say Saved working directory and index state "$stash_msg" if test -z "$patch_mode" @@ -546,12 +589,13 @@ clear) clear_stash "$@" ;; create) - if test $# -gt 0 && test "$1" = create - then - shift - fi + shift create_stash "$*" && echo "$w_commit" ;; +store) + shift + store_stash "$@" + ;; drop) shift drop_stash "$@" diff --git a/git-submodule.sh b/git-submodule.sh index 79bfaac9d4..2979197087 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -14,10 +14,13 @@ USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <re or: $dashless [--quiet] foreach [--recursive] <command> or: $dashless [--quiet] sync [--recursive] [--] [<path>...]" OPTIONS_SPEC= +SUBDIRECTORY_OK=Yes . git-sh-setup . git-sh-i18n . git-parse-remote require_work_tree +wt_prefix=$(git rev-parse --show-prefix) +cd_to_toplevel command= branch= @@ -32,6 +35,7 @@ nofetch= update= prefix= custom_name= +depth= # The function takes at most 2 arguments. The first argument is the # URL that navigates to the submodule origin repo. When relative, this URL @@ -106,14 +110,50 @@ resolve_relative_url () echo "${is_relative:+${up_path}}${remoteurl#./}" } +# Resolve a path to be relative to another path. This is intended for +# converting submodule paths when git-submodule is run in a subdirectory +# and only handles paths where the directory separator is '/'. +# +# The output is the first argument as a path relative to the second argument, +# which defaults to $wt_prefix if it is omitted. +relative_path () +{ + local target curdir result + target=$1 + curdir=${2-$wt_prefix} + curdir=${curdir%/} + result= + + while test -n "$curdir" + do + case "$target" in + "$curdir/"*) + target=${target#"$curdir"/} + break + ;; + esac + + result="${result}../" + if test "$curdir" = "${curdir%/*}" + then + curdir= + else + curdir="${curdir%/*}" + fi + done + + echo "$result$target" +} + # # Get submodule info for registered submodules # $@ = path to limit submodule list # module_list() { + eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")" ( - git ls-files --error-unmatch --stage -- "$@" || + git ls-files -z --error-unmatch --stage -- "$@" || echo "unmatched pathspec exists" ) | perl -e ' @@ -121,6 +161,7 @@ module_list() my ($null_sha1) = ("0" x 40); my @out = (); my $unmatched = 0; + $/ = "\0"; while (<STDIN>) { if (/^unmatched pathspec/) { $unmatched = 1; @@ -211,6 +252,7 @@ module_clone() name=$2 url=$3 reference="$4" + depth="$5" quiet= if test -n "$GIT_QUIET" then @@ -233,7 +275,7 @@ module_clone() mkdir -p "$gitdir_base" ( clear_local_git_env - git clone $quiet -n ${reference:+"$reference"} \ + git clone $quiet ${depth:+"$depth"} -n ${reference:+"$reference"} \ --separate-git-dir "$gitdir" "$url" "$sm_path" ) || die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")" @@ -282,6 +324,7 @@ isnumber() cmd_add() { # parse $args after "submodule ... add". + reference_path= while test $# -ne 0 do case "$1" in @@ -298,17 +341,25 @@ cmd_add() ;; --reference) case "$2" in '') usage ;; esac - reference="--reference=$2" + reference_path=$2 shift ;; --reference=*) - reference="$1" + reference_path="${1#--reference=}" ;; --name) case "$2" in '') usage ;; esac custom_name=$2 shift ;; + --depth) + case "$2" in '') usage ;; esac + depth="--depth=$2" + shift + ;; + --depth=*) + depth=$1 + ;; --) shift break @@ -323,6 +374,14 @@ cmd_add() shift done + if test -n "$reference_path" + then + is_absolute_path "$reference_path" || + reference_path="$wt_prefix$reference_path" + + reference="--reference=$reference_path" + fi + repo=$1 sm_path=$2 @@ -335,9 +394,14 @@ cmd_add() usage fi + is_absolute_path "$sm_path" || sm_path="$wt_prefix$sm_path" + # assure repo is absolute or relative to parent case "$repo" in ./*|../*) + test -z "$wt_prefix" || + die "$(gettext "Relative path can only be used from the toplevel of the working tree")" + # dereference source url relative to parent's url realrepo=$(resolve_relative_url "$repo") || exit ;; @@ -405,7 +469,7 @@ Use -f if you really want to add it." >&2 echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")" fi fi - module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" || exit + module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" "$depth" || exit ( clear_local_git_env cd "$sm_path" && @@ -471,21 +535,23 @@ cmd_foreach() die_if_unmatched "$mode" if test -e "$sm_path"/.git then - say "$(eval_gettext "Entering '\$prefix\$sm_path'")" + displaypath=$(relative_path "$sm_path") + say "$(eval_gettext "Entering '\$prefix\$displaypath'")" name=$(module_name "$sm_path") ( prefix="$prefix$sm_path/" clear_local_git_env - # we make $path available to scripts ... - path=$sm_path cd "$sm_path" && + sm_path=$(relative_path "$sm_path") && + # we make $path available to scripts ... + path=$sm_path && eval "$@" && if test -n "$recursive" then cmd_foreach "--recursive" "$@" fi ) <&3 3<&- || - die "$(eval_gettext "Stopping at '\$sm_path'; script returned non-zero status.")" + die "$(eval_gettext "Stopping at '\$prefix\$displaypath'; script returned non-zero status.")" fi done } @@ -524,12 +590,14 @@ cmd_init() die_if_unmatched "$mode" name=$(module_name "$sm_path") || exit + displaypath=$(relative_path "$sm_path") + # Copy url setting when it is not set yet if test -z "$(git config "submodule.$name.url")" then url=$(git config -f .gitmodules submodule."$name".url) test -z "$url" && - die "$(eval_gettext "No url found for submodule path '\$sm_path' in .gitmodules")" + die "$(eval_gettext "No url found for submodule path '\$displaypath' in .gitmodules")" # Possibly a url relative to parent case "$url" in @@ -538,9 +606,9 @@ cmd_init() ;; esac git config submodule."$name".url "$url" || - die "$(eval_gettext "Failed to register url for submodule path '\$sm_path'")" + die "$(eval_gettext "Failed to register url for submodule path '\$displaypath'")" - say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$sm_path'")" + say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$displaypath'")" fi # Copy "update" setting when it is not set yet @@ -548,7 +616,7 @@ cmd_init() test -z "$upd" || test -n "$(git config submodule."$name".update)" || git config submodule."$name".update "$upd" || - die "$(eval_gettext "Failed to register update mode for submodule path '\$sm_path'")" + die "$(eval_gettext "Failed to register update mode for submodule path '\$displaypath'")" done } @@ -594,27 +662,29 @@ cmd_deinit() die_if_unmatched "$mode" name=$(module_name "$sm_path") || exit + displaypath=$(relative_path "$sm_path") + # Remove the submodule work tree (unless the user already did it) if test -d "$sm_path" then # Protect submodules containing a .git directory if test -d "$sm_path/.git" then - echo >&2 "$(eval_gettext "Submodule work tree '\$sm_path' contains a .git directory")" + echo >&2 "$(eval_gettext "Submodule work tree '\$displaypath' contains a .git directory")" die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")" fi if test -z "$force" then git rm -qn "$sm_path" || - die "$(eval_gettext "Submodule work tree '\$sm_path' contains local modifications; use '-f' to discard them")" + die "$(eval_gettext "Submodule work tree '\$displaypath' contains local modifications; use '-f' to discard them")" fi rm -rf "$sm_path" && - say "$(eval_gettext "Cleared directory '\$sm_path'")" || - say "$(eval_gettext "Could not remove submodule work tree '\$sm_path'")" + say "$(eval_gettext "Cleared directory '\$displaypath'")" || + say "$(eval_gettext "Could not remove submodule work tree '\$displaypath'")" fi - mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$sm_path'")" + mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$displaypath'")" # Remove the .git/config entries (unless the user already did it) if test -n "$(git config --get-regexp submodule."$name\.")" @@ -623,7 +693,7 @@ cmd_deinit() # the user later decides to init this submodule again url=$(git config submodule."$name".url) git config --remove-section submodule."$name" 2>/dev/null && - say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$sm_path'")" + say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$displaypath'")" fi done } @@ -676,6 +746,14 @@ cmd_update() --checkout) update="checkout" ;; + --depth) + case "$2" in '') usage ;; esac + depth="--depth=$2" + shift + ;; + --depth=*) + depth=$1 + ;; --) shift break @@ -717,9 +795,11 @@ cmd_update() update_module=$(git config submodule."$name".update) fi + displaypath=$(relative_path "$prefix$sm_path") + if test "$update_module" = "none" then - echo "Skipping submodule '$prefix$sm_path'" + echo "Skipping submodule '$displaypath'" continue fi @@ -728,20 +808,20 @@ cmd_update() # Only mention uninitialized submodules when its # path have been specified test "$#" != "0" && - say "$(eval_gettext "Submodule path '\$prefix\$sm_path' not initialized + say "$(eval_gettext "Submodule path '\$displaypath' not initialized Maybe you want to use 'update --init'?")" continue fi if ! test -d "$sm_path"/.git -o -f "$sm_path"/.git then - module_clone "$sm_path" "$name" "$url" "$reference" || exit + module_clone "$sm_path" "$name" "$url" "$reference" "$depth" || exit cloned_modules="$cloned_modules;$name" subsha1= else subsha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD) || - die "$(eval_gettext "Unable to find current revision in submodule path '\$prefix\$sm_path'")" + die "$(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")" fi if test -n "$remote" @@ -774,7 +854,7 @@ Maybe you want to use 'update --init'?")" (clear_local_git_env; cd "$sm_path" && ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) && test -z "$rev") || git-fetch)) || - die "$(eval_gettext "Unable to fetch in submodule path '\$prefix\$sm_path'")" + die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")" fi # Is this something we just cloned? @@ -788,20 +868,26 @@ Maybe you want to use 'update --init'?")" case "$update_module" in rebase) command="git rebase" - die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$prefix\$sm_path'")" - say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': rebased into '\$sha1'")" + die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$displaypath'")" + say_msg="$(eval_gettext "Submodule path '\$displaypath': rebased into '\$sha1'")" must_die_on_failure=yes ;; merge) command="git merge" - die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$prefix\$sm_path'")" - say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': merged in '\$sha1'")" + die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$displaypath'")" + say_msg="$(eval_gettext "Submodule path '\$displaypath': merged in '\$sha1'")" + must_die_on_failure=yes + ;; + !*) + command="${update_module#!}" + die_msg="$(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$prefix\$sm_path'")" + say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': '\$command \$sha1'")" must_die_on_failure=yes ;; *) command="git checkout $subforce -q" - die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$prefix\$sm_path'")" - say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': checked out '\$sha1'")" + die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$displaypath'")" + say_msg="$(eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'")" ;; esac @@ -828,7 +914,7 @@ Maybe you want to use 'update --init'?")" res=$? if test $res -gt 0 then - die_msg="$(eval_gettext "Failed to recurse into submodule path '\$prefix\$sm_path'")" + die_msg="$(eval_gettext "Failed to recurse into submodule path '\$displaypath'")" if test $res -eq 1 then err="${err};$die_msg" @@ -942,6 +1028,7 @@ cmd_summary() { fi cd_to_toplevel + eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")" # Get modified modules cared by user modules=$(git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- "$@" | sane_egrep '^:([0-7]* )?160000' | @@ -991,16 +1078,18 @@ cmd_summary() { ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null && missing_dst=t + display_name=$(relative_path "$name") + total_commits= case "$missing_src,$missing_dst" in t,) - errmsg="$(eval_gettext " Warn: \$name doesn't contain commit \$sha1_src")" + errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commit \$sha1_src")" ;; ,t) - errmsg="$(eval_gettext " Warn: \$name doesn't contain commit \$sha1_dst")" + errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commit \$sha1_dst")" ;; t,t) - errmsg="$(eval_gettext " Warn: \$name doesn't contain commits \$sha1_src and \$sha1_dst")" + errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commits \$sha1_src and \$sha1_dst")" ;; *) errmsg= @@ -1029,12 +1118,12 @@ cmd_summary() { submodule="$(gettext "submodule")" if test $mod_dst = 160000 then - echo "* $name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:" + echo "* $display_name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:" else - echo "* $name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:" + echo "* $display_name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:" fi else - echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:" + echo "* $display_name $sha1_abbr_src...$sha1_abbr_dst$total_commits:" fi if test -n "$errmsg" then @@ -1118,7 +1207,7 @@ cmd_status() die_if_unmatched "$mode" name=$(module_name "$sm_path") || exit url=$(git config submodule."$name".url) - displaypath="$prefix$sm_path" + displaypath=$(relative_path "$prefix$sm_path") if test "$stage" = U then say "U$sha1 $displaypath" @@ -1129,16 +1218,16 @@ cmd_status() say "-$sha1 $displaypath" continue; fi - set_name_rev "$sm_path" "$sha1" if git diff-files --ignore-submodules=dirty --quiet -- "$sm_path" then + set_name_rev "$sm_path" "$sha1" say " $sha1 $displaypath$revname" else if test -z "$cached" then sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD) - set_name_rev "$sm_path" "$sha1" fi + set_name_rev "$sm_path" "$sha1" say "+$sha1 $displaypath$revname" fi @@ -1213,7 +1302,8 @@ cmd_sync() if git config "submodule.$name.url" >/dev/null 2>/dev/null then - say "$(eval_gettext "Synchronizing submodule url for '\$prefix\$sm_path'")" + displaypath=$(relative_path "$prefix$sm_path") + say "$(eval_gettext "Synchronizing submodule url for '\$displaypath'")" git config submodule."$name".url "$super_config_url" if test -e "$sm_path"/.git diff --git a/git-svn.perl b/git-svn.perl index 36083c1072..ff1ce3d351 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1246,7 +1246,7 @@ sub cmd_rebase { return; } if (command(qw/diff-index HEAD --/)) { - print STDERR "Cannot rebase with uncommited changes:\n"; + print STDERR "Cannot rebase with uncommitted changes:\n"; command_noisy('status'); exit 1; } diff --git a/git-web--browse.sh b/git-web--browse.sh index 9f446798d4..1d72ec760e 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -32,8 +32,9 @@ valid_custom_tool() valid_tool() { case "$1" in firefox | iceweasel | seamonkey | iceape | \ - chrome | google-chrome | chromium | chromium-browser |\ - konqueror | opera | w3m | elinks | links | lynx | dillo | open | start) + chrome | google-chrome | chromium | chromium-browser | \ + konqueror | opera | w3m | elinks | links | lynx | dillo | open | \ + start | cygstart) ;; # happy *) valid_custom_tool "$1" || return 1 @@ -127,6 +128,10 @@ if test -z "$browser" ; then if test -x /bin/start; then browser_candidates="start $browser_candidates" fi + # /usr/bin/cygstart indicates Cygwin + if test -x /usr/bin/cygstart; then + browser_candidates="cygstart $browser_candidates" + fi for i in $browser_candidates; do init_browser_path $i @@ -174,7 +179,7 @@ konqueror) ;; esac ;; -w3m|elinks|links|lynx|open) +w3m|elinks|links|lynx|open|cygstart) "$browser_path" "$@" ;; start) @@ -324,6 +324,7 @@ static void handle_internal_command(int argc, const char **argv) { "cat-file", cmd_cat_file, RUN_SETUP }, { "check-attr", cmd_check_attr, RUN_SETUP }, { "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE }, + { "check-mailmap", cmd_check_mailmap, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE }, { "checkout-index", cmd_checkout_index, @@ -525,6 +526,13 @@ int main(int argc, char **av) if (!cmd) cmd = "git-help"; + /* + * Always open file descriptors 0/1/2 to avoid clobbering files + * in die(). It also avoids messing up when the pipes are dup'ed + * onto stdin/stdout/stderr in the child processes we spawn. + */ + sanitize_stdfds(); + git_setup_gettext(); /* diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8d69ada042..f429f75897 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -85,6 +85,9 @@ our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++"; # string of the home link on top of all pages our $home_link_str = "++GITWEB_HOME_LINK_STR++"; +# extra breadcrumbs preceding the home link +our @extra_breadcrumbs = (); + # name of your site or organization to appear in page titles # replace this with something more descriptive for clearer bookmarks our $site_name = "++GITWEB_SITENAME++" @@ -3982,7 +3985,9 @@ sub print_nav_breadcrumbs_path { sub print_nav_breadcrumbs { my %opts = @_; - print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; + for my $crumb (@extra_breadcrumbs, [ $home_link_str => $home_link ]) { + print $cgi->a({-href => esc_url($crumb->[1])}, $crumb->[0]) . " / "; + } if (defined $project) { my @dirname = split '/', $project; my $projectbasename = pop @dirname; diff --git a/http-push.c b/http-push.c index 395a8cfc10..6dad188b5f 100644 --- a/http-push.c +++ b/http-push.c @@ -663,7 +663,7 @@ static void add_fetch_request(struct object *obj) static int add_send_request(struct object *obj, struct remote_lock *lock) { - struct transfer_request *request = request_queue_head; + struct transfer_request *request; struct packed_git *target; /* Keep locks active */ @@ -228,9 +228,15 @@ static void init_curl_http_auth(CURL *result) #else { static struct strbuf up = STRBUF_INIT; - strbuf_reset(&up); - strbuf_addf(&up, "%s:%s", - http_auth.username, http_auth.password); + /* + * Note that we assume we only ever have a single set of + * credentials in a given program run, so we do not have + * to worry about updating this buffer, only setting its + * initial value. + */ + if (!up.len) + strbuf_addf(&up, "%s:%s", + http_auth.username, http_auth.password); curl_easy_setopt(result, CURLOPT_USERPWD, up.buf); } #endif diff --git a/line-log.c b/line-log.c index 4bbb09be59..c2d01dccc2 100644 --- a/line-log.c +++ b/line-log.c @@ -110,13 +110,16 @@ static void range_set_check_invariants(struct range_set *rs) static void sort_and_merge_range_set(struct range_set *rs) { int i; - int o = 1; /* output cursor */ + int o = 0; /* output cursor */ qsort(rs->ranges, rs->nr, sizeof(struct range), range_cmp); - for (i = 1; i < rs->nr; i++) { - if (rs->ranges[i].start <= rs->ranges[o-1].end) { - rs->ranges[o-1].end = rs->ranges[i].end; + for (i = 0; i < rs->nr; i++) { + if (rs->ranges[i].start == rs->ranges[i].end) + continue; + if (o > 0 && rs->ranges[i].start <= rs->ranges[o-1].end) { + if (rs->ranges[o-1].end < rs->ranges[i].end) + rs->ranges[o-1].end = rs->ranges[i].end; } else { rs->ranges[o].start = rs->ranges[i].start; rs->ranges[o].end = rs->ranges[i].end; @@ -296,6 +299,7 @@ static void line_log_data_insert(struct line_log_data **list, p = xcalloc(1, sizeof(struct line_log_data)); p->path = path; range_set_append(&p->ranges, begin, end); + sort_and_merge_range_set(&p->ranges); if (ip) { p->next = ip->next; ip->next = p; diff --git a/line-range.c b/line-range.c index 8faf943745..3942475c2f 100644 --- a/line-range.c +++ b/line-range.c @@ -211,6 +211,8 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb, void *cb_data, long lines, long *begin, long *end, const char *path) { + *begin = *end = 0; + if (*arg == ':') { arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path); if (!arg || *arg) @@ -226,6 +228,11 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb, if (*arg) return -1; + if (*begin && *end && *end < *begin) { + long tmp; + tmp = *end; *end = *begin; *begin = tmp; + } + return 0; } diff --git a/lockfile.c b/lockfile.c index c6fb77b26f..8fbcb6a98a 100644 --- a/lockfile.c +++ b/lockfile.c @@ -124,15 +124,17 @@ static char *resolve_symlink(char *p, size_t s) static int lock_file(struct lock_file *lk, const char *path, int flags) { - if (strlen(path) >= sizeof(lk->filename)) - return -1; - strcpy(lk->filename, path); /* * subtract 5 from size to make sure there's room for adding * ".lock" for the lock file name */ + static const size_t max_path_len = sizeof(lk->filename) - 5; + + if (strlen(path) >= max_path_len) + return -1; + strcpy(lk->filename, path); if (!(flags & LOCK_NODEREF)) - resolve_symlink(lk->filename, sizeof(lk->filename)-5); + resolve_symlink(lk->filename, max_path_len); strcat(lk->filename, ".lock"); lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666); if (0 <= lk->fd) { diff --git a/log-tree.c b/log-tree.c index 2eb69bcfed..a49d8e895d 100644 --- a/log-tree.c +++ b/log-tree.c @@ -617,6 +617,9 @@ void show_log(struct rev_info *opt) ctx.fmt = opt->commit_format; ctx.mailmap = opt->mailmap; ctx.color = opt->diffopt.use_color; + ctx.output_encoding = get_log_output_encoding(); + if (opt->from_ident.mail_begin && opt->from_ident.name_begin) + ctx.from_ident = &opt->from_ident; pretty_print_commit(&ctx, commit, &msgbuf); if (opt->add_signoff) @@ -5,8 +5,10 @@ #define DEBUG_MAILMAP 0 #if DEBUG_MAILMAP #define debug_mm(...) fprintf(stderr, __VA_ARGS__) +#define debug_str(X) ((X) ? (X) : "(none)") #else static inline void debug_mm(const char *format, ...) {} +static inline const char *debug_str(const char *s) { return s; } #endif const char *git_mailmap_file; @@ -29,7 +31,8 @@ struct mailmap_entry { static void free_mailmap_info(void *p, const char *s) { struct mailmap_info *mi = (struct mailmap_info *)p; - debug_mm("mailmap: -- complex: '%s' -> '%s' <%s>\n", s, mi->name, mi->email); + debug_mm("mailmap: -- complex: '%s' -> '%s' <%s>\n", + s, debug_str(mi->name), debug_str(mi->email)); free(mi->name); free(mi->email); } @@ -37,8 +40,11 @@ static void free_mailmap_info(void *p, const char *s) static void free_mailmap_entry(void *p, const char *s) { struct mailmap_entry *me = (struct mailmap_entry *)p; - debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n", s, me->namemap.nr); - debug_mm("mailmap: - simple: '%s' <%s>\n", me->name, me->email); + debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n", + s, me->namemap.nr); + debug_mm("mailmap: - simple: '%s' <%s>\n", + debug_str(me->name), debug_str(me->email)); + free(me->name); free(me->email); @@ -47,18 +53,11 @@ static void free_mailmap_entry(void *p, const char *s) } static void add_mapping(struct string_list *map, - char *new_name, char *new_email, char *old_name, char *old_email) + char *new_name, char *new_email, + char *old_name, char *old_email) { struct mailmap_entry *me; int index; - char *p; - - if (old_email) - for (p = old_email; *p; p++) - *p = tolower(*p); - if (new_email) - for (p = new_email; *p; p++) - *p = tolower(*p); if (old_email == NULL) { old_email = new_email; @@ -68,16 +67,21 @@ static void add_mapping(struct string_list *map, if ((index = string_list_find_insert_index(map, old_email, 1)) < 0) { /* mailmap entry exists, invert index value */ index = -1 - index; + me = (struct mailmap_entry *)map->items[index].util; } else { /* create mailmap entry */ - struct string_list_item *item = string_list_insert_at_index(map, index, old_email); - item->util = xcalloc(1, sizeof(struct mailmap_entry)); - ((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1; + struct string_list_item *item; + + item = string_list_insert_at_index(map, index, old_email); + me = xcalloc(1, sizeof(struct mailmap_entry)); + me->namemap.strdup_strings = 1; + me->namemap.cmp = strcasecmp; + item->util = me; } - me = (struct mailmap_entry *)map->items[index].util; if (old_name == NULL) { - debug_mm("mailmap: adding (simple) entry for %s at index %d\n", old_email, index); + debug_mm("mailmap: adding (simple) entry for %s at index %d\n", + old_email, index); /* Replace current name and new email for simple entry */ if (new_name) { free(me->name); @@ -89,7 +93,8 @@ static void add_mapping(struct string_list *map, } } else { struct mailmap_info *mi = xcalloc(1, sizeof(struct mailmap_info)); - debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index); + debug_mm("mailmap: adding (complex) entry for %s at index %d\n", + old_email, index); if (new_name) mi->name = xstrdup(new_name); if (new_email) @@ -98,11 +103,12 @@ static void add_mapping(struct string_list *map, } debug_mm("mailmap: '%s' <%s> -> '%s' <%s>\n", - old_name, old_email, new_name, new_email); + debug_str(old_name), old_email, + debug_str(new_name), debug_str(new_email)); } static char *parse_name_and_email(char *buffer, char **name, - char **email, int allow_empty_email) + char **email, int allow_empty_email) { char *left, *right, *nstart, *nend; *name = *email = NULL; @@ -122,7 +128,7 @@ static char *parse_name_and_email(char *buffer, char **name, while (nend > nstart && isspace(*nend)) --nend; - *name = (nstart < nend ? nstart : NULL); + *name = (nstart <= nend ? nstart : NULL); *email = left+1; *(nend+1) = '\0'; *right++ = '\0'; @@ -306,21 +312,25 @@ static struct string_list_item *lookup_prefix(struct string_list *map, } int map_user(struct string_list *map, - const char **email, size_t *emaillen, - const char **name, size_t *namelen) + const char **email, size_t *emaillen, + const char **name, size_t *namelen) { struct string_list_item *item; struct mailmap_entry *me; debug_mm("map_user: map '%.*s' <%.*s>\n", - *name, *namelen, *emaillen, *email); + (int)*namelen, debug_str(*name), + (int)*emaillen, debug_str(*email)); item = lookup_prefix(map, *email, *emaillen); if (item != NULL) { me = (struct mailmap_entry *)item->util; if (me->namemap.nr) { - /* The item has multiple items, so we'll look up on name too */ - /* If the name is not found, we choose the simple entry */ + /* + * The item has multiple items, so we'll look up on + * name too. If the name is not found, we choose the + * simple entry. + */ struct string_list_item *subitem; subitem = lookup_prefix(&me->namemap, *name, *namelen); if (subitem) @@ -341,8 +351,9 @@ int map_user(struct string_list *map, *name = mi->name; *namelen = strlen(*name); } - debug_mm("map_user: to '%.*s' <.*%s>\n", *namelen, *name, - *emaillen, *email); + debug_mm("map_user: to '%.*s' <%.*s>\n", + (int)*namelen, debug_str(*name), + (int)*emaillen, debug_str(*email)); return 1; } debug_mm("map_user: --\n"); diff --git a/match-trees.c b/match-trees.c index 2bb734d51c..7873cdec58 100644 --- a/match-trees.c +++ b/match-trees.c @@ -47,6 +47,22 @@ static int score_matches(unsigned mode1, unsigned mode2, const char *path) return score; } +static void *fill_tree_desc_strict(struct tree_desc *desc, + const unsigned char *hash) +{ + void *buffer; + enum object_type type; + unsigned long size; + + buffer = read_sha1_file(hash, &type, &size); + if (!buffer) + die("unable to read tree (%s)", sha1_to_hex(hash)); + if (type != OBJ_TREE) + die("%s is not a tree", sha1_to_hex(hash)); + init_tree_desc(desc, buffer, size); + return buffer; +} + static int base_name_entries_compare(const struct name_entry *a, const struct name_entry *b) { @@ -61,23 +77,10 @@ static int score_trees(const unsigned char *hash1, const unsigned char *hash2) { struct tree_desc one; struct tree_desc two; - void *one_buf, *two_buf; + void *one_buf = fill_tree_desc_strict(&one, hash1); + void *two_buf = fill_tree_desc_strict(&two, hash2); int score = 0; - enum object_type type; - unsigned long size; - one_buf = read_sha1_file(hash1, &type, &size); - if (!one_buf) - die("unable to read tree (%s)", sha1_to_hex(hash1)); - if (type != OBJ_TREE) - die("%s is not a tree", sha1_to_hex(hash1)); - init_tree_desc(&one, one_buf, size); - two_buf = read_sha1_file(hash2, &type, &size); - if (!two_buf) - die("unable to read tree (%s)", sha1_to_hex(hash2)); - if (type != OBJ_TREE) - die("%s is not a tree", sha1_to_hex(hash2)); - init_tree_desc(&two, two_buf, size); for (;;) { struct name_entry e1, e2; int got_entry_from_one = tree_entry(&one, &e1); @@ -124,16 +127,7 @@ static void match_trees(const unsigned char *hash1, int recurse_limit) { struct tree_desc one; - void *one_buf; - enum object_type type; - unsigned long size; - - one_buf = read_sha1_file(hash1, &type, &size); - if (!one_buf) - die("unable to read tree (%s)", sha1_to_hex(hash1)); - if (type != OBJ_TREE) - die("%s is not a tree", sha1_to_hex(hash1)); - init_tree_desc(&one, one_buf, size); + void *one_buf = fill_tree_desc_strict(&one, hash1); while (one.size) { const char *path; diff --git a/merge-recursive.c b/merge-recursive.c index ea9dbd307c..f95933b0aa 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -251,7 +251,7 @@ struct tree *write_tree_from_memory(struct merge_options *o) int i; fprintf(stderr, "BUG: There are unmerged index entries:\n"); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce), (int)ce_namelen(ce), ce->name); @@ -264,7 +264,8 @@ struct tree *write_tree_from_memory(struct merge_options *o) if (!cache_tree_fully_valid(active_cache_tree) && cache_tree_update(active_cache_tree, - active_cache, active_nr, 0) < 0) + (const struct cache_entry * const *)active_cache, + active_nr, 0) < 0) die(_("error building trees")); result = lookup_tree(active_cache_tree->sha1); @@ -339,7 +340,7 @@ static struct string_list *get_unmerged(void) for (i = 0; i < active_nr; i++) { struct string_list_item *item; struct stage_data *e; - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; diff --git a/notes-merge.c b/notes-merge.c index 0f67bd3f96..ab18857074 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -9,6 +9,7 @@ #include "notes.h" #include "notes-merge.h" #include "strbuf.h" +#include "notes-utils.h" struct notes_merge_pair { unsigned char obj[20], base[20], local[20], remote[20]; @@ -530,32 +531,6 @@ static int merge_from_diffs(struct notes_merge_options *o, return conflicts ? -1 : 1; } -void create_notes_commit(struct notes_tree *t, struct commit_list *parents, - const struct strbuf *msg, unsigned char *result_sha1) -{ - unsigned char tree_sha1[20]; - - assert(t->initialized); - - if (write_notes_tree(t, tree_sha1)) - die("Failed to write notes tree to database"); - - if (!parents) { - /* Deduce parent commit from t->ref */ - unsigned char parent_sha1[20]; - if (!read_ref(t->ref, parent_sha1)) { - struct commit *parent = lookup_commit(parent_sha1); - if (!parent || parse_commit(parent)) - die("Failed to find/parse commit %s", t->ref); - commit_list_insert(parent, &parents); - } - /* else: t->ref points to nothing, assume root/orphan commit */ - } - - if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL)) - die("Failed to commit notes tree to database"); -} - int notes_merge(struct notes_merge_options *o, struct notes_tree *local_tree, unsigned char *result_sha1) diff --git a/notes-merge.h b/notes-merge.h index 0c11b173a1..1d01f6aacf 100644 --- a/notes-merge.h +++ b/notes-merge.h @@ -26,20 +26,6 @@ struct notes_merge_options { void init_notes_merge_options(struct notes_merge_options *o); /* - * Create new notes commit from the given notes tree - * - * Properties of the created commit: - * - tree: the result of converting t to a tree object with write_notes_tree(). - * - parents: the given parents OR (if NULL) the commit referenced by t->ref. - * - author/committer: the default determined by commmit_tree(). - * - commit message: msg - * - * The resulting commit SHA1 is stored in result_sha1. - */ -void create_notes_commit(struct notes_tree *t, struct commit_list *parents, - const struct strbuf *msg, unsigned char *result_sha1); - -/* * Merge notes from o->remote_ref into o->local_ref * * The given notes_tree 'local_tree' must be the notes_tree referenced by the diff --git a/notes-utils.c b/notes-utils.c new file mode 100644 index 0000000000..9107c379d9 --- /dev/null +++ b/notes-utils.c @@ -0,0 +1,157 @@ +#include "cache.h" +#include "commit.h" +#include "refs.h" +#include "notes-utils.h" + +void create_notes_commit(struct notes_tree *t, struct commit_list *parents, + const struct strbuf *msg, unsigned char *result_sha1) +{ + unsigned char tree_sha1[20]; + + assert(t->initialized); + + if (write_notes_tree(t, tree_sha1)) + die("Failed to write notes tree to database"); + + if (!parents) { + /* Deduce parent commit from t->ref */ + unsigned char parent_sha1[20]; + if (!read_ref(t->ref, parent_sha1)) { + struct commit *parent = lookup_commit(parent_sha1); + if (!parent || parse_commit(parent)) + die("Failed to find/parse commit %s", t->ref); + commit_list_insert(parent, &parents); + } + /* else: t->ref points to nothing, assume root/orphan commit */ + } + + if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL)) + die("Failed to commit notes tree to database"); +} + +void commit_notes(struct notes_tree *t, const char *msg) +{ + struct strbuf buf = STRBUF_INIT; + unsigned char commit_sha1[20]; + + if (!t) + t = &default_notes_tree; + if (!t->initialized || !t->ref || !*t->ref) + die(_("Cannot commit uninitialized/unreferenced notes tree")); + if (!t->dirty) + return; /* don't have to commit an unchanged tree */ + + /* Prepare commit message and reflog message */ + strbuf_addstr(&buf, msg); + if (buf.buf[buf.len - 1] != '\n') + strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */ + + create_notes_commit(t, NULL, &buf, commit_sha1); + strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */ + update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR); + + strbuf_release(&buf); +} + +static combine_notes_fn parse_combine_notes_fn(const char *v) +{ + if (!strcasecmp(v, "overwrite")) + return combine_notes_overwrite; + else if (!strcasecmp(v, "ignore")) + return combine_notes_ignore; + else if (!strcasecmp(v, "concatenate")) + return combine_notes_concatenate; + else if (!strcasecmp(v, "cat_sort_uniq")) + return combine_notes_cat_sort_uniq; + else + return NULL; +} + +static int notes_rewrite_config(const char *k, const char *v, void *cb) +{ + struct notes_rewrite_cfg *c = cb; + if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) { + c->enabled = git_config_bool(k, v); + return 0; + } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) { + if (!v) + config_error_nonbool(k); + c->combine = parse_combine_notes_fn(v); + if (!c->combine) { + error(_("Bad notes.rewriteMode value: '%s'"), v); + return 1; + } + return 0; + } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) { + /* note that a refs/ prefix is implied in the + * underlying for_each_glob_ref */ + if (!prefixcmp(v, "refs/notes/")) + string_list_add_refs_by_glob(c->refs, v); + else + warning(_("Refusing to rewrite notes in %s" + " (outside of refs/notes/)"), v); + return 0; + } + + return 0; +} + + +struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) +{ + struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg)); + const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT); + const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT); + c->cmd = cmd; + c->enabled = 1; + c->combine = combine_notes_concatenate; + c->refs = xcalloc(1, sizeof(struct string_list)); + c->refs->strdup_strings = 1; + c->refs_from_env = 0; + c->mode_from_env = 0; + if (rewrite_mode_env) { + c->mode_from_env = 1; + c->combine = parse_combine_notes_fn(rewrite_mode_env); + if (!c->combine) + /* TRANSLATORS: The first %s is the name of the + environment variable, the second %s is its value */ + error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT, + rewrite_mode_env); + } + if (rewrite_refs_env) { + c->refs_from_env = 1; + string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env); + } + git_config(notes_rewrite_config, c); + if (!c->enabled || !c->refs->nr) { + string_list_clear(c->refs, 0); + free(c->refs); + free(c); + return NULL; + } + c->trees = load_notes_trees(c->refs); + string_list_clear(c->refs, 0); + free(c->refs); + return c; +} + +int copy_note_for_rewrite(struct notes_rewrite_cfg *c, + const unsigned char *from_obj, const unsigned char *to_obj) +{ + int ret = 0; + int i; + for (i = 0; c->trees[i]; i++) + ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret; + return ret; +} + +void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c, const char *msg) +{ + int i; + for (i = 0; c->trees[i]; i++) { + commit_notes(c->trees[i], msg); + free_notes(c->trees[i]); + } + free(c->trees); + free(c); +} diff --git a/notes-utils.h b/notes-utils.h new file mode 100644 index 0000000000..b4cb1bfb43 --- /dev/null +++ b/notes-utils.h @@ -0,0 +1,37 @@ +#ifndef NOTES_UTILS_H +#define NOTES_UTILS_H + +#include "notes.h" + +/* + * Create new notes commit from the given notes tree + * + * Properties of the created commit: + * - tree: the result of converting t to a tree object with write_notes_tree(). + * - parents: the given parents OR (if NULL) the commit referenced by t->ref. + * - author/committer: the default determined by commmit_tree(). + * - commit message: msg + * + * The resulting commit SHA1 is stored in result_sha1. + */ +void create_notes_commit(struct notes_tree *t, struct commit_list *parents, + const struct strbuf *msg, unsigned char *result_sha1); + +void commit_notes(struct notes_tree *t, const char *msg); + +struct notes_rewrite_cfg { + struct notes_tree **trees; + const char *cmd; + int enabled; + combine_notes_fn combine; + struct string_list *refs; + int refs_from_env; + int mode_from_env; +}; + +struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd); +int copy_note_for_rewrite(struct notes_rewrite_cfg *c, + const unsigned char *from_obj, const unsigned char *to_obj); +void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c, const char *msg); + +#endif @@ -145,7 +145,7 @@ struct object *lookup_unknown_object(const unsigned char *sha1) struct object *parse_object_buffer(const unsigned char *sha1, enum object_type type, unsigned long size, void *buffer, int *eaten_p) { struct object *obj; - int eaten = 0; + *eaten_p = 0; obj = NULL; if (type == OBJ_BLOB) { @@ -164,7 +164,7 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t if (!tree->object.parsed) { if (parse_tree_buffer(tree, buffer, size)) return NULL; - eaten = 1; + *eaten_p = 1; } } } else if (type == OBJ_COMMIT) { @@ -174,7 +174,7 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t return NULL; if (!commit->buffer) { commit->buffer = buffer; - eaten = 1; + *eaten_p = 1; } obj = &commit->object; } @@ -191,7 +191,6 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t } if (obj && obj->type == OBJ_NONE) obj->type = type; - *eaten_p = eaten; return obj; } diff --git a/pack-revindex.c b/pack-revindex.c index 77a0465be6..b4d2b35bb3 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -59,11 +59,101 @@ static void init_pack_revindex(void) /* revindex elements are lazily initialized */ } -static int cmp_offset(const void *a_, const void *b_) +/* + * This is a least-significant-digit radix sort. + * + * It sorts each of the "n" items in "entries" by its offset field. The "max" + * parameter must be at least as large as the largest offset in the array, + * and lets us quit the sort early. + */ +static void sort_revindex(struct revindex_entry *entries, unsigned n, off_t max) { - const struct revindex_entry *a = a_; - const struct revindex_entry *b = b_; - return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0; + /* + * We use a "digit" size of 16 bits. That keeps our memory + * usage reasonable, and we can generally (for a 4G or smaller + * packfile) quit after two rounds of radix-sorting. + */ +#define DIGIT_SIZE (16) +#define BUCKETS (1 << DIGIT_SIZE) + /* + * We want to know the bucket that a[i] will go into when we are using + * the digit that is N bits from the (least significant) end. + */ +#define BUCKET_FOR(a, i, bits) (((a)[(i)].offset >> (bits)) & (BUCKETS-1)) + + /* + * We need O(n) temporary storage. Rather than do an extra copy of the + * partial results into "entries", we sort back and forth between the + * real array and temporary storage. In each iteration of the loop, we + * keep track of them with alias pointers, always sorting from "from" + * to "to". + */ + struct revindex_entry *tmp = xmalloc(n * sizeof(*tmp)); + struct revindex_entry *from = entries, *to = tmp; + int bits; + unsigned *pos = xmalloc(BUCKETS * sizeof(*pos)); + + /* + * If (max >> bits) is zero, then we know that the radix digit we are + * on (and any higher) will be zero for all entries, and our loop will + * be a no-op, as everybody lands in the same zero-th bucket. + */ + for (bits = 0; max >> bits; bits += DIGIT_SIZE) { + struct revindex_entry *swap; + unsigned i; + + memset(pos, 0, BUCKETS * sizeof(*pos)); + + /* + * We want pos[i] to store the index of the last element that + * will go in bucket "i" (actually one past the last element). + * To do this, we first count the items that will go in each + * bucket, which gives us a relative offset from the last + * bucket. We can then cumulatively add the index from the + * previous bucket to get the true index. + */ + for (i = 0; i < n; i++) + pos[BUCKET_FOR(from, i, bits)]++; + for (i = 1; i < BUCKETS; i++) + pos[i] += pos[i-1]; + + /* + * Now we can drop the elements into their correct buckets (in + * our temporary array). We iterate the pos counter backwards + * to avoid using an extra index to count up. And since we are + * going backwards there, we must also go backwards through the + * array itself, to keep the sort stable. + * + * Note that we use an unsigned iterator to make sure we can + * handle 2^32-1 objects, even on a 32-bit system. But this + * means we cannot use the more obvious "i >= 0" loop condition + * for counting backwards, and must instead check for + * wrap-around with UINT_MAX. + */ + for (i = n - 1; i != UINT_MAX; i--) + to[--pos[BUCKET_FOR(from, i, bits)]] = from[i]; + + /* + * Now "to" contains the most sorted list, so we swap "from" and + * "to" for the next iteration. + */ + swap = from; + from = to; + to = swap; + } + + /* + * If we ended with our data in the original array, great. If not, + * we have to move it back from the temporary storage. + */ + if (from != entries) + memcpy(entries, tmp, n * sizeof(*entries)); + free(tmp); + free(pos); + +#undef BUCKET_FOR +#undef BUCKETS +#undef DIGIT_SIZE } /* @@ -72,8 +162,8 @@ static int cmp_offset(const void *a_, const void *b_) static void create_pack_revindex(struct pack_revindex *rix) { struct packed_git *p = rix->p; - int num_ent = p->num_objects; - int i; + unsigned num_ent = p->num_objects; + unsigned i; const char *index = p->index_data; rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1)); @@ -108,13 +198,13 @@ static void create_pack_revindex(struct pack_revindex *rix) */ rix->revindex[num_ent].offset = p->pack_size - 20; rix->revindex[num_ent].nr = -1; - qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset); + sort_revindex(rix->revindex, num_ent, p->pack_size); } struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs) { int num; - int lo, hi; + unsigned lo, hi; struct pack_revindex *rix; struct revindex_entry *revindex; @@ -132,7 +222,7 @@ struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs) lo = 0; hi = p->num_objects + 1; do { - int mi = (lo + hi) / 2; + unsigned mi = lo + (hi - lo) / 2; if (revindex[mi].offset == ofs) { return revindex + mi; } else if (ofs < revindex[mi].offset) @@ -441,42 +441,100 @@ int adjust_shared_perm(const char *path) return 0; } -const char *relative_path(const char *abs, const char *base) +/* + * Give path as relative to prefix. + * + * The strbuf may or may not be used, so do not assume it contains the + * returned path. + */ +const char *relative_path(const char *in, const char *prefix, + struct strbuf *sb) { - static char buf[PATH_MAX + 1]; + int in_len = in ? strlen(in) : 0; + int prefix_len = prefix ? strlen(prefix) : 0; + int in_off = 0; + int prefix_off = 0; int i = 0, j = 0; - if (!base || !base[0]) - return abs; - while (base[i]) { - if (is_dir_sep(base[i])) { - if (!is_dir_sep(abs[j])) - return abs; - while (is_dir_sep(base[i])) + if (!in_len) + return "./"; + else if (!prefix_len) + return in; + + while (i < prefix_len && j < in_len && prefix[i] == in[j]) { + if (is_dir_sep(prefix[i])) { + while (is_dir_sep(prefix[i])) i++; - while (is_dir_sep(abs[j])) + while (is_dir_sep(in[j])) j++; + prefix_off = i; + in_off = j; + } else { + i++; + j++; + } + } + + if ( + /* "prefix" seems like prefix of "in" */ + i >= prefix_len && + /* + * but "/foo" is not a prefix of "/foobar" + * (i.e. prefix not end with '/') + */ + prefix_off < prefix_len) { + if (j >= in_len) { + /* in="/a/b", prefix="/a/b" */ + in_off = in_len; + } else if (is_dir_sep(in[j])) { + /* in="/a/b/c", prefix="/a/b" */ + while (is_dir_sep(in[j])) + j++; + in_off = j; + } else { + /* in="/a/bbb/c", prefix="/a/b" */ + i = prefix_off; + } + } else if ( + /* "in" is short than "prefix" */ + j >= in_len && + /* "in" not end with '/' */ + in_off < in_len) { + if (is_dir_sep(prefix[i])) { + /* in="/a/b", prefix="/a/b/c/" */ + while (is_dir_sep(prefix[i])) + i++; + in_off = in_len; + } + } + in += in_off; + in_len -= in_off; + + if (i >= prefix_len) { + if (!in_len) + return "./"; + else + return in; + } + + strbuf_reset(sb); + strbuf_grow(sb, in_len); + + while (i < prefix_len) { + if (is_dir_sep(prefix[i])) { + strbuf_addstr(sb, "../"); + while (is_dir_sep(prefix[i])) + i++; continue; - } else if (abs[j] != base[i]) { - return abs; } i++; - j++; } - if ( - /* "/foo" is a prefix of "/foo" */ - abs[j] && - /* "/foo" is not a prefix of "/foobar" */ - !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j]) - ) - return abs; - while (is_dir_sep(abs[j])) - j++; - if (!abs[j]) - strcpy(buf, "."); - else - strcpy(buf, abs + j); - return buf; + if (!is_dir_sep(prefix[prefix_len - 1])) + strbuf_addstr(sb, "../"); + + strbuf_addstr(sb, in); + + return sb->buf; } /* diff --git a/pathspec.c b/pathspec.c index 284f3970a3..6ea0867493 100644 --- a/pathspec.c +++ b/pathspec.c @@ -32,7 +32,7 @@ void add_pathspec_matches_against_index(const char **pathspec, if (!num_unmatched) return; for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen); } } @@ -67,7 +67,7 @@ const char *check_path_for_gitlink(const char *path) { int i, path_len = strlen(path); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (S_ISGITLINK(ce->ce_mode)) { int ce_len = ce_namelen(ce); if (path_len <= ce_len || path[ce_len] != '/' || @@ -406,7 +406,7 @@ static const char *show_ident_date(const struct ident_split *ident, return show_date(date, tz, mode); } -void pp_user_info(const struct pretty_print_context *pp, +void pp_user_info(struct pretty_print_context *pp, const char *what, struct strbuf *sb, const char *line, const char *encoding) { @@ -432,6 +432,23 @@ void pp_user_info(const struct pretty_print_context *pp, map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen); if (pp->fmt == CMIT_FMT_EMAIL) { + if (pp->from_ident) { + struct strbuf buf = STRBUF_INIT; + + strbuf_addstr(&buf, "From: "); + strbuf_add(&buf, namebuf, namelen); + strbuf_addstr(&buf, " <"); + strbuf_add(&buf, mailbuf, maillen); + strbuf_addstr(&buf, ">\n"); + string_list_append(&pp->in_body_headers, + strbuf_detach(&buf, NULL)); + + mailbuf = pp->from_ident->mail_begin; + maillen = pp->from_ident->mail_end - mailbuf; + namebuf = pp->from_ident->name_begin; + namelen = pp->from_ident->name_end - namebuf; + } + strbuf_addstr(sb, "From: "); if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) { add_rfc2047(sb, namebuf, namelen, @@ -1514,7 +1531,7 @@ void format_commit_message(const struct commit *commit, free(context.signature_check.signer); } -static void pp_header(const struct pretty_print_context *pp, +static void pp_header(struct pretty_print_context *pp, const char *encoding, const struct commit *commit, const char **msg_p, @@ -1575,7 +1592,7 @@ static void pp_header(const struct pretty_print_context *pp, } } -void pp_title_line(const struct pretty_print_context *pp, +void pp_title_line(struct pretty_print_context *pp, const char **msg_p, struct strbuf *sb, const char *encoding, @@ -1602,6 +1619,16 @@ void pp_title_line(const struct pretty_print_context *pp, } strbuf_addch(sb, '\n'); + if (need_8bit_cte == 0) { + int i; + for (i = 0; i < pp->in_body_headers.nr; i++) { + if (has_non_ascii(pp->in_body_headers.items[i].string)) { + need_8bit_cte = 1; + break; + } + } + } + if (need_8bit_cte > 0) { const char *header_fmt = "MIME-Version: 1.0\n" @@ -1615,10 +1642,21 @@ void pp_title_line(const struct pretty_print_context *pp, if (pp->fmt == CMIT_FMT_EMAIL) { strbuf_addch(sb, '\n'); } + + if (pp->in_body_headers.nr) { + int i; + for (i = 0; i < pp->in_body_headers.nr; i++) { + strbuf_addstr(sb, pp->in_body_headers.items[i].string); + free(pp->in_body_headers.items[i].string); + } + string_list_clear(&pp->in_body_headers, 0); + strbuf_addch(sb, '\n'); + } + strbuf_release(&title); } -void pp_remainder(const struct pretty_print_context *pp, +void pp_remainder(struct pretty_print_context *pp, const char **msg_p, struct strbuf *sb, int indent) @@ -1650,7 +1688,7 @@ void pp_remainder(const struct pretty_print_context *pp, } } -void pretty_print_commit(const struct pretty_print_context *pp, +void pretty_print_commit(struct pretty_print_context *pp, const struct commit *commit, struct strbuf *sb) { diff --git a/prio-queue.c b/prio-queue.c new file mode 100644 index 0000000000..c9f8c6d253 --- /dev/null +++ b/prio-queue.c @@ -0,0 +1,84 @@ +#include "cache.h" +#include "commit.h" +#include "prio-queue.h" + +void prio_queue_reverse(struct prio_queue *queue) +{ + int i, j; + + if (queue->compare != NULL) + die("BUG: prio_queue_reverse() on non-LIFO queue"); + for (i = 0; i <= (j = (queue->nr - 1) - i); i++) { + struct commit *swap = queue->array[i]; + queue->array[i] = queue->array[j]; + queue->array[j] = swap; + } +} + +void clear_prio_queue(struct prio_queue *queue) +{ + free(queue->array); + queue->nr = 0; + queue->alloc = 0; + queue->array = NULL; +} + +void prio_queue_put(struct prio_queue *queue, void *thing) +{ + prio_queue_compare_fn compare = queue->compare; + int ix, parent; + + /* Append at the end */ + ALLOC_GROW(queue->array, queue->nr + 1, queue->alloc); + queue->array[queue->nr++] = thing; + if (!compare) + return; /* LIFO */ + + /* Bubble up the new one */ + for (ix = queue->nr - 1; ix; ix = parent) { + parent = (ix - 1) / 2; + if (compare(queue->array[parent], queue->array[ix], + queue->cb_data) <= 0) + break; + + thing = queue->array[parent]; + queue->array[parent] = queue->array[ix]; + queue->array[ix] = thing; + } +} + +void *prio_queue_get(struct prio_queue *queue) +{ + void *result, *swap; + int ix, child; + prio_queue_compare_fn compare = queue->compare; + + if (!queue->nr) + return NULL; + if (!compare) + return queue->array[--queue->nr]; /* LIFO */ + + result = queue->array[0]; + if (!--queue->nr) + return result; + + queue->array[0] = queue->array[queue->nr]; + + /* Push down the one at the root */ + for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) { + child = ix * 2 + 1; /* left */ + if ((child + 1 < queue->nr) && + (compare(queue->array[child], queue->array[child + 1], + queue->cb_data) >= 0)) + child++; /* use right child */ + + if (compare(queue->array[ix], queue->array[child], + queue->cb_data) <= 0) + break; + + swap = queue->array[child]; + queue->array[child] = queue->array[ix]; + queue->array[ix] = swap; + } + return result; +} diff --git a/prio-queue.h b/prio-queue.h new file mode 100644 index 0000000000..9c3cd1f875 --- /dev/null +++ b/prio-queue.h @@ -0,0 +1,48 @@ +#ifndef PRIO_QUEUE_H +#define PRIO_QUEUE_H + +/* + * A priority queue implementation, primarily for keeping track of + * commits in the 'date-order' so that we process them from new to old + * as they are discovered, but can be used to hold any pointer to + * struct. The caller is responsible for supplying a function to + * compare two "things". + * + * Alternatively, this data structure can also be used as a LIFO stack + * by specifying NULL as the comparison function. + */ + +/* + * Compare two "things", one and two; the third parameter is cb_data + * in the prio_queue structure. The result is returned as a sign of + * the return value, being the same as the sign of the result of + * subtracting "two" from "one" (i.e. negative if "one" sorts earlier + * than "two"). + */ +typedef int (*prio_queue_compare_fn)(const void *one, const void *two, void *cb_data); + +struct prio_queue { + prio_queue_compare_fn compare; + void *cb_data; + int alloc, nr; + void **array; +}; + +/* + * Add the "thing" to the queue. + */ +extern void prio_queue_put(struct prio_queue *, void *thing); + +/* + * Extract the "thing" that compares the smallest out of the queue, + * or NULL. If compare function is NULL, the queue acts as a LIFO + * stack. + */ +extern void *prio_queue_get(struct prio_queue *); + +extern void clear_prio_queue(struct prio_queue *); + +/* Reverse the LIFO elements */ +extern void prio_queue_reverse(struct prio_queue *); + +#endif /* PRIO_QUEUE_H */ @@ -312,82 +312,27 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen, fputc(terminator, fp); } -static const char *path_relative(const char *in, int len, - struct strbuf *sb, const char *prefix, - int prefix_len); - -void write_name_quoted_relative(const char *name, size_t len, - const char *prefix, size_t prefix_len, +void write_name_quoted_relative(const char *name, const char *prefix, FILE *fp, int terminator) { struct strbuf sb = STRBUF_INIT; - name = path_relative(name, len, &sb, prefix, prefix_len); + name = relative_path(name, prefix, &sb); write_name_quoted(name, fp, terminator); strbuf_release(&sb); } -/* - * Give path as relative to prefix. - * - * The strbuf may or may not be used, so do not assume it contains the - * returned path. - */ -static const char *path_relative(const char *in, int len, - struct strbuf *sb, const char *prefix, - int prefix_len) -{ - int off, i; - - if (len < 0) - len = strlen(in); - if (prefix_len < 0) { - if (prefix) - prefix_len = strlen(prefix); - else - prefix_len = 0; - } - - off = 0; - i = 0; - while (i < prefix_len && i < len && prefix[i] == in[i]) { - if (prefix[i] == '/') - off = i + 1; - i++; - } - in += off; - len -= off; - - if (i >= prefix_len) - return in; - - strbuf_reset(sb); - strbuf_grow(sb, len); - - while (i < prefix_len) { - if (prefix[i] == '/') - strbuf_addstr(sb, "../"); - i++; - } - strbuf_add(sb, in, len); - - return sb->buf; -} - /* quote path as relative to the given prefix */ -char *quote_path_relative(const char *in, int len, - struct strbuf *out, const char *prefix) +char *quote_path_relative(const char *in, const char *prefix, + struct strbuf *out) { struct strbuf sb = STRBUF_INIT; - const char *rel = path_relative(in, len, &sb, prefix, -1); + const char *rel = relative_path(in, prefix, &sb); strbuf_reset(out); quote_c_style_counted(rel, strlen(rel), out, NULL, 0); strbuf_release(&sb); - if (!out->len) - strbuf_addstr(out, "./"); - return out->buf; } @@ -60,13 +60,12 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int); extern void write_name_quoted(const char *name, FILE *, int terminator); extern void write_name_quotedpfx(const char *pfx, size_t pfxlen, const char *name, FILE *, int terminator); -extern void write_name_quoted_relative(const char *name, size_t len, - const char *prefix, size_t prefix_len, +extern void write_name_quoted_relative(const char *name, const char *prefix, FILE *fp, int terminator); /* quote path as relative to the given prefix */ -extern char *quote_path_relative(const char *in, int len, - struct strbuf *out, const char *prefix); +extern char *quote_path_relative(const char *in, const char *prefix, + struct strbuf *out); /* quoting as a string literal for other languages */ extern void perl_quote_print(FILE *stream, const char *src); diff --git a/read-cache.c b/read-cache.c index b15bc096ea..d4474eab7b 100644 --- a/read-cache.c +++ b/read-cache.c @@ -722,7 +722,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, return ce; } -int ce_same_name(struct cache_entry *a, struct cache_entry *b) +int ce_same_name(const struct cache_entry *a, const struct cache_entry *b) { int len = ce_namelen(a); return ce_namelen(b) == len && !memcmp(a->name, b->name, len); @@ -1535,8 +1535,9 @@ int discard_index(struct index_state *istate) free_name_hash(istate); cache_tree_free(&(istate->cache_tree)); istate->initialized = 0; - - /* no need to throw away allocated active_cache */ + free(istate->cache); + istate->cache = NULL; + istate->cache_alloc = 0; return 0; } @@ -1295,6 +1295,37 @@ static struct ref_entry *get_packed_ref(const char *refname) return find_ref(get_packed_refs(&ref_cache), refname); } +/* + * A loose ref file doesn't exist; check for a packed ref. The + * options are forwarded from resolve_safe_unsafe(). + */ +static const char *handle_missing_loose_ref(const char *refname, + unsigned char *sha1, + int reading, + int *flag) +{ + struct ref_entry *entry; + + /* + * The loose reference file does not exist; check for a packed + * reference. + */ + entry = get_packed_ref(refname); + if (entry) { + hashcpy(sha1, entry->u.value.sha1); + if (flag) + *flag |= REF_ISPACKED; + return refname; + } + /* The reference is not a packed reference, either. */ + if (reading) { + return NULL; + } else { + hashclr(sha1); + return refname; + } +} + const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag) { int depth = MAXDEPTH; @@ -1319,36 +1350,34 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea git_snpath(path, sizeof(path), "%s", refname); + /* + * We might have to loop back here to avoid a race + * condition: first we lstat() the file, then we try + * to read it as a link or as a file. But if somebody + * changes the type of the file (file <-> directory + * <-> symlink) between the lstat() and reading, then + * we don't want to report that as an error but rather + * try again starting with the lstat(). + */ + stat_ref: if (lstat(path, &st) < 0) { - struct ref_entry *entry; - - if (errno != ENOENT) + if (errno == ENOENT) + return handle_missing_loose_ref(refname, sha1, + reading, flag); + else return NULL; - /* - * The loose reference file does not exist; - * check for a packed reference. - */ - entry = get_packed_ref(refname); - if (entry) { - hashcpy(sha1, entry->u.value.sha1); - if (flag) - *flag |= REF_ISPACKED; - return refname; - } - /* The reference is not a packed reference, either. */ - if (reading) { - return NULL; - } else { - hashclr(sha1); - return refname; - } } /* Follow "normalized" - ie "refs/.." symlinks by hand */ if (S_ISLNK(st.st_mode)) { len = readlink(path, buffer, sizeof(buffer)-1); - if (len < 0) - return NULL; + if (len < 0) { + if (errno == ENOENT || errno == EINVAL) + /* inconsistent with lstat; retry */ + goto stat_ref; + else + return NULL; + } buffer[len] = 0; if (!prefixcmp(buffer, "refs/") && !check_refname_format(buffer, 0)) { @@ -1371,8 +1400,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea * a ref */ fd = open(path, O_RDONLY); - if (fd < 0) - return NULL; + if (fd < 0) { + if (errno == ENOENT) + /* inconsistent with lstat; retry */ + goto stat_ref; + else + return NULL; + } len = read_in_full(fd, buffer, sizeof(buffer)-1); close(fd); if (len < 0) @@ -1384,8 +1418,19 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea /* * Is it a symbolic ref? */ - if (prefixcmp(buffer, "ref:")) - break; + if (prefixcmp(buffer, "ref:")) { + /* + * Please note that FETCH_HEAD has a second + * line containing other data. + */ + if (get_sha1_hex(buffer, sha1) || + (buffer[40] != '\0' && !isspace(buffer[40]))) { + if (flag) + *flag |= REF_ISBROKEN; + return NULL; + } + return refname; + } if (flag) *flag |= REF_ISSYMREF; buf = buffer + 4; @@ -1398,13 +1443,6 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea } refname = strcpy(refname_buffer, buf); } - /* Please note that FETCH_HEAD has a second line containing other data. */ - if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) { - if (flag) - *flag |= REF_ISBROKEN; - return NULL; - } - return refname; } char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag) diff --git a/remote-curl.c b/remote-curl.c index 60eda63081..5b3ce9eed2 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -7,6 +7,7 @@ #include "run-command.h" #include "pkt-line.h" #include "sideband.h" +#include "argv-array.h" static struct remote *remote; static const char *url; /* always ends with a trailing slash */ @@ -787,36 +788,35 @@ static int push_dav(int nr_spec, char **specs) static int push_git(struct discovery *heads, int nr_spec, char **specs) { struct rpc_state rpc; - const char **argv; - int argc = 0, i, err; + int i, err; + struct argv_array args; + + argv_array_init(&args); + argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status", + NULL); - 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"; + argv_array_push(&args, "--thin"); if (options.dry_run) - argv[argc++] = "--dry-run"; + argv_array_push(&args, "--dry-run"); if (options.verbosity == 0) - argv[argc++] = "--quiet"; + argv_array_push(&args, "--quiet"); else if (options.verbosity > 1) - argv[argc++] = "--verbose"; - argv[argc++] = options.progress ? "--progress" : "--no-progress"; - argv[argc++] = url; + argv_array_push(&args, "--verbose"); + argv_array_push(&args, options.progress ? "--progress" : "--no-progress"); + argv_array_push(&args, url); for (i = 0; i < nr_spec; i++) - argv[argc++] = specs[i]; - argv[argc++] = NULL; + argv_array_push(&args, specs[i]); memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-receive-pack", - rpc.argv = argv; + rpc.argv = args.argv; err = rpc_service(&rpc, heads); if (rpc.result.len) write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); - free(argv); + argv_array_clear(&args); return err; } @@ -276,10 +276,9 @@ static void read_remotes_file(struct remote *remote) static void read_branches_file(struct remote *remote) { - const char *slash = strchr(remote->name, '/'); char *frag; struct strbuf branch = STRBUF_INIT; - int n = slash ? slash - remote->name : 1000; + int n = 1000; FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r"); char *s, *p; int len; @@ -299,21 +298,11 @@ static void read_branches_file(struct remote *remote) while (isspace(p[-1])) *--p = 0; len = p - s; - if (slash) - len += strlen(slash); p = xmalloc(len + 1); strcpy(p, s); - if (slash) - strcat(p, slash); /* - * With "slash", e.g. "git fetch jgarzik/netdev-2.6" when - * reading from $GIT_DIR/branches/jgarzik fetches "HEAD" from - * the partial URL obtained from the branches file plus - * "/netdev-2.6" and does not store it in any tracking ref. - * #branch specifier in the file is ignored. - * - * Otherwise, the branches file would have URL and optionally + * The branches file would have URL and optionally * #branch specified. The "master" (or specified) branch is * fetched and stored in the local branch of the same name. */ @@ -323,12 +312,8 @@ static void read_branches_file(struct remote *remote) strbuf_addf(&branch, "refs/heads/%s", frag); } else strbuf_addstr(&branch, "refs/heads/master"); - if (!slash) { - strbuf_addf(&branch, ":refs/heads/%s", remote->name); - } else { - strbuf_reset(&branch); - strbuf_addstr(&branch, "HEAD:"); - } + + strbuf_addf(&branch, ":refs/heads/%s", remote->name); add_url_alias(remote, p); add_fetch_refspec(remote, strbuf_detach(&branch, NULL)); /* @@ -1317,6 +1302,14 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds free(sent_tips.tip); } +static void prepare_ref_index(struct string_list *ref_index, struct ref *ref) +{ + for ( ; ref; ref = ref->next) + string_list_append_nodup(ref_index, ref->name)->util = ref; + + sort_string_list(ref_index); +} + /* * Given the set of refs the local repository has, the set of refs the * remote repository has, and the refspec used for push, determine @@ -1335,6 +1328,7 @@ int match_push_refs(struct ref *src, struct ref **dst, int errs; static const char *default_refspec[] = { ":", NULL }; struct ref *ref, **dst_tail = tail_ref(dst); + struct string_list dst_ref_index = STRING_LIST_INIT_NODUP; if (!nr_refspec) { nr_refspec = 1; @@ -1345,6 +1339,7 @@ int match_push_refs(struct ref *src, struct ref **dst, /* pick the remainder */ for (ref = src; ref; ref = ref->next) { + struct string_list_item *dst_item; struct ref *dst_peer; const struct refspec *pat = NULL; char *dst_name; @@ -1353,7 +1348,11 @@ int match_push_refs(struct ref *src, struct ref **dst, if (!dst_name) continue; - dst_peer = find_ref_by_name(*dst, dst_name); + if (!dst_ref_index.nr) + prepare_ref_index(&dst_ref_index, *dst); + + dst_item = string_list_lookup(&dst_ref_index, dst_name); + dst_peer = dst_item ? dst_item->util : NULL; if (dst_peer) { if (dst_peer->peer_ref) /* We're already sending something to this ref. */ @@ -1370,6 +1369,8 @@ int match_push_refs(struct ref *src, struct ref **dst, /* Create a new one and link it */ dst_peer = make_linked_ref(dst_name, &dst_tail); hashcpy(dst_peer->new_sha1, ref->new_sha1); + string_list_insert(&dst_ref_index, + dst_peer->name)->util = dst_peer; } dst_peer->peer_ref = copy_ref(ref); dst_peer->force = pat->force; @@ -1377,10 +1378,13 @@ int match_push_refs(struct ref *src, struct ref **dst, free(dst_name); } + string_list_clear(&dst_ref_index, 0); + if (flags & MATCH_REFS_FOLLOW_TAGS) add_missing_tags(src, dst, &dst_tail); if (send_prune) { + struct string_list src_ref_index = STRING_LIST_INIT_NODUP; /* check for missing refs on the remote */ for (ref = *dst; ref; ref = ref->next) { char *src_name; @@ -1391,11 +1395,15 @@ int match_push_refs(struct ref *src, struct ref **dst, src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL); if (src_name) { - if (!find_ref_by_name(src, src_name)) + if (!src_ref_index.nr) + prepare_ref_index(&src_ref_index, src); + if (!string_list_has_string(&src_ref_index, + src_name)) ref->peer_ref = alloc_delete_ref(); free(src_name); } } + string_list_clear(&src_ref_index, 0); } if (errs) return -1; @@ -299,7 +299,7 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu { mmfile_t mmfile[3] = {{NULL}}; mmbuffer_t result = {NULL, 0}; - struct cache_entry *ce; + const struct cache_entry *ce; int pos, len, i, hunk_no; struct rerere_io_mem io; int marker_size = ll_merge_marker_size(path); @@ -359,7 +359,7 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu static int check_one_conflict(int i, int *type) { - struct cache_entry *e = active_cache[i]; + const struct cache_entry *e = active_cache[i]; if (!ce_stage(e)) { *type = RESOLVED; @@ -374,8 +374,8 @@ static int check_one_conflict(int i, int *type) /* Only handle regular files with both stages #2 and #3 */ if (i + 1 < active_nr) { - struct cache_entry *e2 = active_cache[i]; - struct cache_entry *e3 = active_cache[i + 1]; + const struct cache_entry *e2 = active_cache[i]; + const struct cache_entry *e3 = active_cache[i + 1]; if (ce_stage(e2) == 2 && ce_stage(e3) == 3 && ce_same_name(e, e3) && @@ -398,7 +398,7 @@ static int find_conflict(struct string_list *conflict) for (i = 0; i < active_nr;) { int conflict_type; - struct cache_entry *e = active_cache[i]; + const struct cache_entry *e = active_cache[i]; i = check_one_conflict(i, &conflict_type); if (conflict_type == THREE_STAGED) string_list_insert(conflict, (const char *)e->name); @@ -414,7 +414,7 @@ int rerere_remaining(struct string_list *merge_rr) for (i = 0; i < active_nr;) { int conflict_type; - struct cache_entry *e = active_cache[i]; + const struct cache_entry *e = active_cache[i]; i = check_one_conflict(i, &conflict_type); if (conflict_type == PUNTED) string_list_insert(merge_rr, (const char *)e->name); diff --git a/resolve-undo.c b/resolve-undo.c index 639eb9c59f..77101f51c1 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -115,7 +115,7 @@ void resolve_undo_clear_index(struct index_state *istate) int unmerge_index_entry_at(struct index_state *istate, int pos) { - struct cache_entry *ce; + const struct cache_entry *ce; struct string_list_item *item; struct resolve_undo_info *ru; int i, err = 0, matched; @@ -167,7 +167,7 @@ void unmerge_marked_index(struct index_state *istate) return; for (i = 0; i < istate->cache_nr; i++) { - struct cache_entry *ce = istate->cache[i]; + const struct cache_entry *ce = istate->cache[i]; if (ce->ce_flags & CE_MATCHED) i = unmerge_index_entry_at(istate, i); } @@ -181,7 +181,7 @@ void unmerge_index(struct index_state *istate, const char **pathspec) return; for (i = 0; i < istate->cache_nr; i++) { - struct cache_entry *ce = istate->cache[i]; + const struct cache_entry *ce = istate->cache[i]; if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) continue; i = unmerge_index_entry_at(istate, i); diff --git a/revision.c b/revision.c index f1bb731fd7..84ccc0529b 100644 --- a/revision.c +++ b/revision.c @@ -1296,7 +1296,7 @@ void init_revisions(struct rev_info *revs, const char *prefix) DIFF_OPT_SET(&revs->pruning, QUICK); revs->pruning.add_remove = file_add_remove; revs->pruning.change = file_change; - revs->lifo = 1; + revs->sort_order = REV_SORT_IN_GRAPH_ORDER; revs->dense = 1; revs->prefix = prefix; revs->max_age = -1; @@ -1358,7 +1358,7 @@ static void prepare_show_merge(struct rev_info *revs) if (!active_nr) read_cache(); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; if (ce_path_match(ce, &revs->prune_data)) { @@ -1638,7 +1638,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--merge")) { revs->show_merge = 1; } else if (!strcmp(arg, "--topo-order")) { - revs->lifo = 1; + revs->sort_order = REV_SORT_IN_GRAPH_ORDER; revs->topo_order = 1; } else if (!strcmp(arg, "--simplify-merges")) { revs->simplify_merges = 1; @@ -1656,7 +1656,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->prune = 1; load_ref_decorations(DECORATE_SHORT_REFS); } else if (!strcmp(arg, "--date-order")) { - revs->lifo = 0; + revs->sort_order = REV_SORT_BY_COMMIT_DATE; + revs->topo_order = 1; + } else if (!strcmp(arg, "--author-date-order")) { + revs->sort_order = REV_SORT_BY_AUTHOR_DATE; revs->topo_order = 1; } else if (!prefixcmp(arg, "--early-output")) { int count = 100; @@ -2606,7 +2609,7 @@ int prepare_revision_walk(struct rev_info *revs) if (limit_list(revs) < 0) return -1; if (revs->topo_order) - sort_in_topological_order(&revs->commits, revs->lifo); + sort_in_topological_order(&revs->commits, revs->sort_order); if (revs->line_level_traverse) line_log_filter(revs); if (revs->simplify_merges) @@ -2924,7 +2927,7 @@ static void create_boundary_commit_list(struct rev_info *revs) * If revs->topo_order is set, sort the boundary commits * in topological order */ - sort_in_topological_order(&revs->commits, revs->lifo); + sort_in_topological_order(&revs->commits, revs->sort_order); } static struct commit *get_revision_internal(struct rev_info *revs) diff --git a/revision.h b/revision.h index eeea6fba3c..95859ba119 100644 --- a/revision.h +++ b/revision.h @@ -4,6 +4,7 @@ #include "parse-options.h" #include "grep.h" #include "notes.h" +#include "commit.h" #define SEEN (1u<<0) #define UNINTERESTING (1u<<1) @@ -62,6 +63,10 @@ struct rev_info { const char *prefix; const char *def; struct pathspec prune_data; + + /* topo-sort */ + enum rev_sort_order sort_order; + unsigned int early_output:1, ignore_missing:1; @@ -72,7 +77,6 @@ struct rev_info { show_all:1, remove_empty_trees:1, simplify_history:1, - lifo:1, topo_order:1, simplify_merges:1, simplify_by_decoration:1, @@ -140,6 +144,7 @@ struct rev_info { int numbered_files; int reroll_count; char *message_id; + struct ident_split from_ident; struct string_list *ref_message_ids; int add_signoff; const char *extra_headers; diff --git a/run-command.c b/run-command.c index aece872e33..1b7f88eeb1 100644 --- a/run-command.c +++ b/run-command.c @@ -76,7 +76,10 @@ static inline void close_pair(int fd[2]) static inline void dup_devnull(int to) { int fd = open("/dev/null", O_RDWR); - dup2(fd, to); + if (fd < 0) + die_errno(_("open /dev/null failed")); + if (dup2(fd, to) < 0) + die_errno(_("dup2(%d,%d) failed"), fd, to); close(fd); } #endif diff --git a/run-command.h b/run-command.h index 221ce33140..6b985afd07 100644 --- a/run-command.h +++ b/run-command.h @@ -46,6 +46,7 @@ int finish_command(struct child_process *); int run_command(struct child_process *); extern char *find_hook(const char *name); +LAST_ARG_MUST_BE_NULL extern int run_hook(const char *index_file, const char *name, ...); #define RUN_COMMAND_NO_STDIN 1 diff --git a/sequencer.c b/sequencer.c index ab6f8a722d..351548f57d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -270,15 +270,20 @@ static int error_dirty_index(struct replay_opts *opts) } static int fast_forward_to(const unsigned char *to, const unsigned char *from, - int unborn) + int unborn, struct replay_opts *opts) { struct ref_lock *ref_lock; + struct strbuf sb = STRBUF_INIT; + int ret; read_cache(); if (checkout_fast_forward(from, to, 1)) exit(1); /* the callee should have complained already */ ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0); - return write_ref_sha1(ref_lock, to, "cherry-pick"); + strbuf_addf(&sb, "%s: fast-forward", action_name(opts)); + ret = write_ref_sha1(ref_lock, to, sb.buf); + strbuf_release(&sb); + return ret; } static int do_recursive_merge(struct commit *base, struct commit *next, @@ -326,7 +331,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, int i; strbuf_addstr(msgbuf, "\nConflicts:\n"); for (i = 0; i < active_nr;) { - struct cache_entry *ce = active_cache[i++]; + const struct cache_entry *ce = active_cache[i++]; if (ce_stage(ce)) { strbuf_addch(msgbuf, '\t'); strbuf_addstr(msgbuf, ce->name); @@ -366,8 +371,9 @@ static int is_index_unchanged(void) active_cache_tree = cache_tree(); if (!cache_tree_fully_valid(active_cache_tree)) - if (cache_tree_update(active_cache_tree, active_cache, - active_nr, 0)) + if (cache_tree_update(active_cache_tree, + (const struct cache_entry * const *)active_cache, + active_nr, 0)) return error(_("Unable to update cache tree\n")); return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1); @@ -474,7 +480,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0; + int res, unborn = 0, allow; if (opts->no_commit) { /* @@ -523,7 +529,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) if (opts->allow_ff && ((parent && !hashcmp(parent->object.sha1, head)) || (!parent && unborn))) - return fast_forward_to(commit->object.sha1, head, unborn); + return fast_forward_to(commit->object.sha1, head, unborn, opts); if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be "revert" or @@ -624,14 +630,18 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) msg.subject); print_advice(res == 1, opts); rerere(opts->allow_rerere_auto); - } else { - int allow = allow_empty(opts, commit); - if (allow < 0) - return allow; - if (!opts->no_commit) - res = run_git_commit(defmsg, opts, allow); + goto leave; + } + + allow = allow_empty(opts, commit); + if (allow < 0) { + res = allow; + goto leave; } + if (!opts->no_commit) + res = run_git_commit(defmsg, opts, allow); +leave: free_message(&msg); free(defmsg); @@ -360,6 +360,7 @@ int is_inside_work_tree(void) void setup_work_tree(void) { + struct strbuf sb = STRBUF_INIT; const char *work_tree, *git_dir; static int initialized = 0; @@ -379,8 +380,10 @@ void setup_work_tree(void) if (getenv(GIT_WORK_TREE_ENVIRONMENT)) setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1); - set_git_dir(relative_path(git_dir, work_tree)); + set_git_dir(relative_path(git_dir, work_tree, &sb)); initialized = 1; + + strbuf_release(&sb); } static int check_repository_format_gently(const char *gitdir, int *nongit_ok) @@ -908,3 +911,15 @@ const char *resolve_gitdir(const char *suspect) return suspect; return read_gitfile(suspect); } + +/* if any standard file descriptor is missing open it to /dev/null */ +void sanitize_stdfds(void) +{ + int fd = open("/dev/null", O_RDWR, 0); + while (fd != -1 && fd < 2) + fd = dup(fd); + if (fd == -1) + die_errno("open /dev/null or dup failed"); + if (fd > 2) + close(fd); +} diff --git a/sha1_file.c b/sha1_file.c index 5c08701ae8..4c2365f48f 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -36,6 +36,9 @@ static inline uintmax_t sz_fmt(size_t s) { return s; } const unsigned char null_sha1[20]; +static const char *no_log_pack_access = "no_log_pack_access"; +static const char *log_pack_access; + /* * This is meant to hold a *small* number of objects that you would * want read_sha1_file() to be able to return, but yet you do not want @@ -1694,7 +1697,8 @@ static int retry_bad_packed_offset(struct packed_git *p, off_t obj_offset) #define POI_STACK_PREALLOC 64 static int packed_object_info(struct packed_git *p, off_t obj_offset, - unsigned long *sizep, int *rtype) + unsigned long *sizep, int *rtype, + unsigned long *disk_sizep) { struct pack_window *w_curs = NULL; unsigned long size; @@ -1728,6 +1732,11 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset, } } + if (disk_sizep) { + struct revindex_entry *revidx = find_pack_revindex(p, obj_offset); + *disk_sizep = revidx[1].offset - obj_offset; + } + while (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) { off_t base_offset; /* Push the object we're going to leave behind */ @@ -1956,12 +1965,19 @@ static void write_pack_access_log(struct packed_git *p, off_t obj_offset) { static FILE *log_file; + if (!log_pack_access) + log_pack_access = getenv("GIT_TRACE_PACK_ACCESS"); + if (!log_pack_access) + log_pack_access = no_log_pack_access; + if (log_pack_access == no_log_pack_access) + return; + if (!log_file) { log_file = fopen(log_pack_access, "w"); if (!log_file) { error("cannot open pack access log '%s' for writing: %s", log_pack_access, strerror(errno)); - log_pack_access = NULL; + log_pack_access = no_log_pack_access; return; } } @@ -1992,7 +2008,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, int delta_stack_nr = 0, delta_stack_alloc = UNPACK_ENTRY_STACK_PREALLOC; int base_from_cache = 0; - if (log_pack_access) + if (log_pack_access != no_log_pack_access) write_pack_access_log(p, obj_offset); /* PHASE 1: drill down to the innermost base object */ @@ -2135,8 +2151,17 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, data = patch_delta(base, base_size, delta_data, delta_size, &size); + + /* + * We could not apply the delta; warn the user, but keep going. + * Our failure will be noticed either in the next iteration of + * the loop, or if this is the final delta, in the caller when + * we return NULL. Those code paths will take care of making + * a more explicit warning and retrying with another copy of + * the object. + */ if (!data) - die("failed to apply delta"); + error("failed to apply delta"); free(delta_data); } @@ -2338,7 +2363,8 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1, } -static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *sizep) +static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *sizep, + unsigned long *disk_sizep) { int status; unsigned long mapsize, size; @@ -2349,6 +2375,8 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size map = map_sha1_file(sha1, &mapsize); if (!map) return -1; + if (disk_sizep) + *disk_sizep = mapsize; if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) status = error("unable to unpack %s header", sha1_to_hex(sha1)); @@ -2372,13 +2400,15 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi) if (co) { if (oi->sizep) *(oi->sizep) = co->size; + if (oi->disk_sizep) + *(oi->disk_sizep) = 0; oi->whence = OI_CACHED; return co->type; } if (!find_pack_entry(sha1, &e)) { /* Most likely it's a loose object. */ - status = sha1_loose_object_info(sha1, oi->sizep); + status = sha1_loose_object_info(sha1, oi->sizep, oi->disk_sizep); if (status >= 0) { oi->whence = OI_LOOSE; return status; @@ -2390,7 +2420,8 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi) return status; } - status = packed_object_info(e.p, e.offset, oi->sizep, &rtype); + status = packed_object_info(e.p, e.offset, oi->sizep, &rtype, + oi->disk_sizep); if (status < 0) { mark_bad_packed_object(e.p, sha1); status = sha1_object_info_extended(sha1, oi); @@ -2409,7 +2440,7 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi) int sha1_object_info(const unsigned char *sha1, unsigned long *sizep) { - struct object_info oi; + struct object_info oi = {0}; oi.sizep = sizep; return sha1_object_info_extended(sha1, &oi); diff --git a/sha1_name.c b/sha1_name.c index 90419efe10..543bf9d9ec 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -241,7 +241,7 @@ static int disambiguate_committish_only(const unsigned char *sha1, void *cb_data return 0; /* We need to do this the hard way... */ - obj = deref_tag(lookup_object(sha1), NULL, 0); + obj = deref_tag(parse_object(sha1), NULL, 0); if (obj && obj->type == OBJ_COMMIT) return 1; return 0; @@ -1234,7 +1234,7 @@ static void diagnose_invalid_index_path(int stage, const char *filename) { struct stat st; - struct cache_entry *ce; + const struct cache_entry *ce; int pos; unsigned namelen = strlen(filename); unsigned fullnamelen; @@ -1328,7 +1328,7 @@ static int get_sha1_with_context_1(const char *name, */ if (name[0] == ':') { int stage = 0; - struct cache_entry *ce; + const struct cache_entry *ce; char *new_path = NULL; int pos; if (!only_to_die && namelen > 2 && name[1] == '/') { @@ -110,17 +110,12 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, continue; *pointer = cur_depth; } - if (cur_depth < depth) { - if (p->next) - add_object_array(&p->item->object, - NULL, &stack); - else { - commit = p->item; - cur_depth = *(int *)commit->util; - } - } else { - commit_list_insert(p->item, &result); - p->item->object.flags |= shallow_flag; + if (p->next) + add_object_array(&p->item->object, + NULL, &stack); + else { + commit = p->item; + cur_depth = *(int *)commit->util; } } } @@ -147,7 +147,6 @@ int main(int argc, char **argv) char *prog; const char **user_argv; struct commands *cmd; - int devnull_fd; int count; git_setup_gettext(); @@ -156,15 +155,10 @@ int main(int argc, char **argv) /* * Always open file descriptors 0/1/2 to avoid clobbering files - * in die(). It also avoids not messing up when the pipes are - * dup'ed onto stdin/stdout/stderr in the child processes we spawn. + * in die(). It also avoids messing up when the pipes are dup'ed + * onto stdin/stdout/stderr in the child processes we spawn. */ - devnull_fd = open("/dev/null", O_RDWR); - while (devnull_fd >= 0 && devnull_fd <= 2) - devnull_fd = dup(devnull_fd); - if (devnull_fd == -1) - die_errno("opening /dev/null failed"); - close (devnull_fd); + sanitize_stdfds(); /* * Special hack to pretend to be a CVS server diff --git a/streaming.c b/streaming.c index cabcd9d157..efbc3babf1 100644 --- a/streaming.c +++ b/streaming.c @@ -135,7 +135,7 @@ struct git_istream *open_istream(const unsigned char *sha1, struct stream_filter *filter) { struct git_istream *st; - struct object_info oi; + struct object_info oi = {0}; const unsigned char *real = lookup_replace_object(sha1); enum input_source src = istream_source(real, type, &oi); @@ -149,7 +149,7 @@ struct git_istream *open_istream(const unsigned char *sha1, return NULL; } } - if (st && filter) { + if (filter) { /* Add "&& !is_null_stream_filter(filter)" for performance */ struct git_istream *nst = attach_stream_filter(st, filter); if (!nst) diff --git a/submodule.c b/submodule.c index 8685424898..3f0a3f9419 100644 --- a/submodule.c +++ b/submodule.c @@ -226,6 +226,7 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, while ((commit = get_revision(rev))) { struct pretty_print_context ctx = {0}; ctx.date_mode = rev->date_mode; + ctx.output_encoding = get_log_output_encoding(); strbuf_setlen(&sb, 0); strbuf_addstr(&sb, line_prefix); if (commit->object.flags & SYMMETRIC_LEFT) { @@ -623,7 +624,7 @@ int fetch_populated_submodules(const struct argv_array *options, struct strbuf submodule_path = STRBUF_INIT; struct strbuf submodule_git_dir = STRBUF_INIT; struct strbuf submodule_prefix = STRBUF_INIT; - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; const char *git_dir, *name, *default_argv; if (!S_ISGITLINK(ce->ce_mode)) @@ -76,6 +76,11 @@ appropriately before running "make". command being run and their output if any are also output. +--verbose-only=<pattern>:: + Like --verbose, but the effect is limited to tests with + numbers matching <pattern>. The number matched against is + simply the running count of the test within the file. + --debug:: This may help the person who is developing a new test. It causes the command defined with test_debug to run. @@ -121,6 +126,11 @@ appropriately before running "make". the 't/valgrind/' directory and use the commands under 't/valgrind/bin/'. +--valgrind-only=<pattern>:: + Like --valgrind, but the effect is limited to tests with + numbers matching <pattern>. The number matched against is + simply the running count of the test within the file. + --tee:: In addition to printing the test output to the terminal, write it to files named 't/test-results/$TEST_NAME.out'. @@ -595,6 +605,20 @@ library for your script to use. test_cmp expected actual ' + - test_ln_s_add <path1> <path2> + + This function helps systems whose filesystem does not support symbolic + links. Use it to add a symbolic link entry to the index when it is not + important that the file system entry is a symbolic link, i.e., instead + of the sequence + + ln -s foo bar && + git add bar + + Sometimes it is possible to split a test in a part that does not need + the symbolic link in the file system and a part that does; then only + the latter part need be protected by a SYMLINKS prerequisite (see below). + Prerequisites ------------- diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index c56a77d237..0bfee001b4 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -2,11 +2,21 @@ # sourced from t8001-annotate.sh and t8002-blame.sh. check_count () { - head= - case "$1" in -h) head="$2"; shift; shift ;; esac - echo "$PROG file $head" >&4 - $PROG file $head >.result || return 1 - cat .result | perl -e ' + head= && + file='file' && + options= && + while : + do + case "$1" in + -h) head="$2"; shift; shift ;; + -f) file="$2"; shift; shift ;; + -*) options="$options $1"; shift ;; + *) break ;; + esac + done && + echo "$PROG $options $file $head" >&4 && + $PROG $options $file $head >actual && + perl -e ' my %expect = (@ARGV); my %count = map { $_ => 0 } keys %expect; while (<STDIN>) { @@ -31,107 +41,246 @@ check_count () { print STDERR "Author $author (expected $value, attributed $count) $ok\n"; } exit($bad); - ' "$@" + ' "$@" <actual } -test_expect_success \ - 'prepare reference tree' \ - 'echo "1A quick brown fox jumps over the" >file && - echo "lazy dog" >>file && - git add file && - GIT_AUTHOR_NAME="A" GIT_AUTHOR_EMAIL="A@test.git" git commit -a -m "Initial."' - -test_expect_success \ - 'check all lines blamed on A' \ - 'check_count A 2' - -test_expect_success \ - 'Setup new lines blamed on B' \ - 'echo "2A quick brown fox jumps over the" >>file && - echo "lazy dog" >> file && - GIT_AUTHOR_NAME="B" GIT_AUTHOR_EMAIL="B@test.git" git commit -a -m "Second."' - -test_expect_success \ - 'Two lines blamed on A, two on B' \ - 'check_count A 2 B 2' - -test_expect_success \ - 'merge-setup part 1' \ - 'git checkout -b branch1 master && - echo "3A slow green fox jumps into the" >> file && - echo "well." >> file && - GIT_AUTHOR_NAME="B1" GIT_AUTHOR_EMAIL="B1@test.git" git commit -a -m "Branch1-1"' - -test_expect_success \ - 'Two lines blamed on A, two on B, two on B1' \ - 'check_count A 2 B 2 B1 2' - -test_expect_success \ - 'merge-setup part 2' \ - 'git checkout -b branch2 master && - sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new && - mv file.new file && - GIT_AUTHOR_NAME="B2" GIT_AUTHOR_EMAIL="B2@test.git" git commit -a -m "Branch2-1"' - -test_expect_success \ - 'Two lines blamed on A, one on B, one on B2' \ - 'check_count A 2 B 1 B2 1' - -test_expect_success \ - 'merge-setup part 3' \ - 'git pull . branch1' - -test_expect_success \ - 'Two lines blamed on A, one on B, two on B1, one on B2' \ - 'check_count A 2 B 1 B1 2 B2 1' - -test_expect_success \ - 'Annotating an old revision works' \ - 'check_count -h master A 2 B 2' - -test_expect_success \ - 'Annotating an old revision works' \ - 'check_count -h master^ A 2' - -test_expect_success \ - 'merge-setup part 4' \ - 'echo "evil merge." >>file && - git commit -a --amend' - -test_expect_success \ - 'Two lines blamed on A, one on B, two on B1, one on B2, one on A U Thor' \ - 'check_count A 2 B 1 B1 2 B2 1 "A U Thor" 1' - -test_expect_success \ - 'an incomplete line added' \ - 'echo "incomplete" | tr -d "\\012" >>file && - GIT_AUTHOR_NAME="C" GIT_AUTHOR_EMAIL="C@test.git" git commit -a -m "Incomplete"' - -test_expect_success \ - 'With incomplete lines.' \ - 'check_count A 2 B 1 B1 2 B2 1 "A U Thor" 1 C 1' - -test_expect_success \ - 'some edit' \ - 'mv file file.orig && - { - cat file.orig && - echo - } | sed -e "s/^3A/99/" -e "/^1A/d" -e "/^incomplete/d" > file && - echo "incomplete" | tr -d "\\012" >>file && - GIT_AUTHOR_NAME="D" GIT_AUTHOR_EMAIL="D@test.git" git commit -a -m "edit"' - -test_expect_success \ - 'some edit' \ - 'check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1' - -test_expect_success \ - 'an obfuscated email added' \ - 'echo "No robots allowed" > file.new && - cat file >> file.new && - mv file.new file && - GIT_AUTHOR_NAME="E" GIT_AUTHOR_EMAIL="E at test dot git" git commit -a -m "norobots"' - -test_expect_success \ - 'obfuscated email parsed' \ - 'check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1 E 1' +test_expect_success 'setup A lines' ' + echo "1A quick brown fox jumps over the" >file && + echo "lazy dog" >>file && + git add file && + GIT_AUTHOR_NAME="A" GIT_AUTHOR_EMAIL="A@test.git" \ + git commit -a -m "Initial." +' + +test_expect_success 'blame 1 author' ' + check_count A 2 +' + +test_expect_success 'setup B lines' ' + echo "2A quick brown fox jumps over the" >>file && + echo "lazy dog" >>file && + GIT_AUTHOR_NAME="B" GIT_AUTHOR_EMAIL="B@test.git" \ + git commit -a -m "Second." +' + +test_expect_success 'blame 2 authors' ' + check_count A 2 B 2 +' + +test_expect_success 'setup B1 lines (branch1)' ' + git checkout -b branch1 master && + echo "3A slow green fox jumps into the" >>file && + echo "well." >>file && + GIT_AUTHOR_NAME="B1" GIT_AUTHOR_EMAIL="B1@test.git" \ + git commit -a -m "Branch1-1" +' + +test_expect_success 'blame 2 authors + 1 branch1 author' ' + check_count A 2 B 2 B1 2 +' + +test_expect_success 'setup B2 lines (branch2)' ' + git checkout -b branch2 master && + sed -e "s/2A quick brown/4A quick brown lazy dog/" <file >file.new && + mv file.new file && + GIT_AUTHOR_NAME="B2" GIT_AUTHOR_EMAIL="B2@test.git" \ + git commit -a -m "Branch2-1" +' + +test_expect_success 'blame 2 authors + 1 branch2 author' ' + check_count A 2 B 1 B2 1 +' + +test_expect_success 'merge branch1 & branch2' ' + git pull . branch1 +' + +test_expect_success 'blame 2 authors + 2 merged-in authors' ' + check_count A 2 B 1 B1 2 B2 1 +' + +test_expect_success 'blame ancestor' ' + check_count -h master A 2 B 2 +' + +test_expect_success 'blame great-ancestor' ' + check_count -h master^ A 2 +' + +test_expect_success 'setup evil merge' ' + echo "evil merge." >>file && + git commit -a --amend +' + +test_expect_success 'blame evil merge' ' + check_count A 2 B 1 B1 2 B2 1 "A U Thor" 1 +' + +test_expect_success 'setup incomplete line' ' + echo "incomplete" | tr -d "\\012" >>file && + GIT_AUTHOR_NAME="C" GIT_AUTHOR_EMAIL="C@test.git" \ + git commit -a -m "Incomplete" +' + +test_expect_success 'blame incomplete line' ' + check_count A 2 B 1 B1 2 B2 1 "A U Thor" 1 C 1 +' + +test_expect_success 'setup edits' ' + mv file file.orig && + { + cat file.orig && + echo + } | sed -e "s/^3A/99/" -e "/^1A/d" -e "/^incomplete/d" >file && + echo "incomplete" | tr -d "\\012" >>file && + GIT_AUTHOR_NAME="D" GIT_AUTHOR_EMAIL="D@test.git" \ + git commit -a -m "edit" +' + +test_expect_success 'blame edits' ' + check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1 +' + +test_expect_success 'setup obfuscated email' ' + echo "No robots allowed" >file.new && + cat file >>file.new && + mv file.new file && + GIT_AUTHOR_NAME="E" GIT_AUTHOR_EMAIL="E at test dot git" \ + git commit -a -m "norobots" +' + +test_expect_success 'blame obfuscated email' ' + check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1 E 1 +' + +test_expect_success 'blame -L 1 (all)' ' + check_count -L1 A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1 E 1 +' + +test_expect_success 'blame -L , (all)' ' + check_count -L, A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1 E 1 +' + +test_expect_success 'blame -L X (X to end)' ' + check_count -L5 B1 1 C 1 D 1 "A U Thor" 1 +' + +test_expect_success 'blame -L X, (X to end)' ' + check_count -L5, B1 1 C 1 D 1 "A U Thor" 1 +' + +test_expect_success 'blame -L ,Y (up to Y)' ' + check_count -L,3 A 1 B2 1 E 1 +' + +test_expect_success 'blame -L X,X' ' + check_count -L3,3 B2 1 +' + +test_expect_success 'blame -L X,Y' ' + check_count -L3,6 B 1 B1 1 B2 1 D 1 +' + +test_expect_success 'blame -L Y,X (undocumented)' ' + check_count -L6,3 B 1 B1 1 B2 1 D 1 +' + +test_expect_success 'blame -L X,+1' ' + check_count -L3,+1 B2 1 +' + +test_expect_success 'blame -L X,+N' ' + check_count -L3,+4 B 1 B1 1 B2 1 D 1 +' + +test_expect_success 'blame -L X,-1' ' + check_count -L3,-1 B2 1 +' + +test_expect_success 'blame -L X,-N' ' + check_count -L6,-4 B 1 B1 1 B2 1 D 1 +' + +test_expect_success 'blame -L /RE/ (RE to end)' ' + check_count -L/evil/ C 1 "A U Thor" 1 +' + +test_expect_success 'blame -L /RE/,/RE2/' ' + check_count -L/robot/,/green/ A 1 B 1 B2 1 D 1 E 1 +' + +test_expect_success 'blame -L X,/RE/' ' + check_count -L5,/evil/ B1 1 D 1 "A U Thor" 1 +' + +test_expect_success 'blame -L /RE/,Y' ' + check_count -L/99/,7 B1 1 D 1 "A U Thor" 1 +' + +test_expect_success 'blame -L /RE/,+N' ' + check_count -L/99/,+3 B1 1 D 1 "A U Thor" 1 +' + +test_expect_success 'blame -L /RE/,-N' ' + check_count -L/99/,-3 B 1 B2 1 D 1 +' + +test_expect_success 'blame -L X (X > nlines)' ' + test_must_fail $PROG -L12345 file +' + +test_expect_success 'blame -L ,Y (Y > nlines)' ' + test_must_fail $PROG -L,12345 file +' + +test_expect_success 'setup -L :regex' ' + tr Q "\\t" >hello.c <<-\EOF && + int main(int argc, const char *argv[]) + { + Qputs("hello"); + } + EOF + git add hello.c && + GIT_AUTHOR_NAME="F" GIT_AUTHOR_EMAIL="F@test.git" \ + git commit -m "hello" && + + mv hello.c hello.orig && + sed -e "/}/i\\ + Qputs(\"goodbye\");" <hello.orig | tr Q "\\t" >hello.c && + GIT_AUTHOR_NAME="G" GIT_AUTHOR_EMAIL="G@test.git" \ + git commit -a -m "goodbye" && + + mv hello.c hello.orig && + echo "#include <stdio.h>" >hello.c && + cat hello.orig >>hello.c && + tr Q "\\t" >>hello.c <<-\EOF + void mail() + { + Qputs("mail"); + } + EOF + GIT_AUTHOR_NAME="H" GIT_AUTHOR_EMAIL="H@test.git" \ + git commit -a -m "mail" +' + +test_expect_success 'blame -L :literal' ' + check_count -f hello.c -L:main F 4 G 1 +' + +test_expect_success 'blame -L :regex' ' + check_count -f hello.c "-L:m[a-z][a-z]l" H 4 +' + +test_expect_success 'blame -L :nomatch' ' + test_must_fail $PROG -L:nomatch hello.c +' + +test_expect_success 'blame -L bogus' ' + test_must_fail $PROG -L file && + test_must_fail $PROG -L1,+ file && + test_must_fail $PROG -L1,- file && + test_must_fail $PROG -LX file && + test_must_fail $PROG -L1,X file && + test_must_fail $PROG -L1,+N file && + test_must_fail $PROG -L1,-N file +' diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl index 8b5a71dc05..45971f43b7 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl @@ -21,6 +21,7 @@ while (<>) { /^\s*declare\s+/ and err 'arrays/declare not portable'; /^\s*[^#]\s*which\s/ and err 'which is not portable (please use type)'; /test\s+[^=]*==/ and err '"test a == b" is not portable (please use =)'; + /^\s*export\s+[^=]*=/ and err '"export FOO=bar" is not portable (please use FOO=bar && export FOO)'; # this resets our $. for each file close ARGV if eof; } diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index b5bce459b6..dd17e3a09d 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -1,5 +1,4 @@ ServerName dummy -LockFile accept.lock PidFile httpd.pid DocumentRoot www LogFormat "%h %l %u %t \"%r\" %>s %b" common @@ -24,6 +23,10 @@ ErrorLog error.log LoadModule version_module modules/mod_version.so </IfModule> +<IfVersion < 2.4> +LockFile accept.lock +</IfVersion> + <IfVersion < 2.1> <IfModule !mod_auth.c> LoadModule auth_module modules/mod_auth.so @@ -45,6 +48,21 @@ ErrorLog error.log </IfModule> </IfVersion> +<IfVersion >= 2.4> +<IfModule !mod_authn_core.c> + LoadModule authn_core_module modules/mod_authn_core.so +</IfModule> +<IfModule !mod_authz_core.c> + LoadModule authz_core_module modules/mod_authz_core.so +</IfModule> +<IfModule !mod_access_compat.c> + LoadModule access_compat_module modules/mod_access_compat.so +</IfModule> +<IfModule !mod_mpm_prefork.c> + LoadModule mpm_prefork_module modules/mod_mpm_prefork.so +</IfModule> +</IfVersion> + PassEnv GIT_VALGRIND PassEnv GIT_VALGRIND_OPTIONS diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 6ccf797091..8ff87fb3f9 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -17,51 +17,98 @@ # ("squash", "fixup", "edit", or "reword") and the SHA1 taken # from the specified line. # +# "exec_cmd_with_args" -- add an "exec cmd with args" line. +# # "#" -- Add a comment line. # # ">" -- Add a blank line. set_fake_editor () { - echo "#!$SHELL_PATH" >fake-editor.sh - cat >> fake-editor.sh <<\EOF -case "$1" in -*/COMMIT_EDITMSG) - test -z "$EXPECT_HEADER_COUNT" || - test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" || + write_script fake-editor.sh <<-\EOF + case "$1" in + */COMMIT_EDITMSG) + test -z "$EXPECT_HEADER_COUNT" || + test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" || + exit + test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" + test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" exit - test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" - test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" - exit - ;; -esac -test -z "$EXPECT_COUNT" || - test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) || - exit -test -z "$FAKE_LINES" && exit -grep -v '^#' < "$1" > "$1".tmp -rm -f "$1" -echo 'rebase -i script before editing:' -cat "$1".tmp -action=pick -for line in $FAKE_LINES; do - case $line in - squash|fixup|edit|reword) - action="$line";; - exec*) - echo "$line" | sed 's/_/ /g' >> "$1";; - "#") - echo '# comment' >> "$1";; - ">") - echo >> "$1";; - *) - sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1" - action=pick;; + ;; esac -done -echo 'rebase -i script after editing:' -cat "$1" -EOF + test -z "$EXPECT_COUNT" || + test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) || + exit + test -z "$FAKE_LINES" && exit + grep -v '^#' < "$1" > "$1".tmp + rm -f "$1" + echo 'rebase -i script before editing:' + cat "$1".tmp + action=pick + for line in $FAKE_LINES; do + case $line in + squash|fixup|edit|reword) + action="$line";; + exec*) + echo "$line" | sed 's/_/ /g' >> "$1";; + "#") + echo '# comment' >> "$1";; + ">") + echo >> "$1";; + *) + sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1" + action=pick;; + esac + done + echo 'rebase -i script after editing:' + cat "$1" + EOF + + test_set_editor "$(pwd)/fake-editor.sh" +} + +# After set_cat_todo_editor, rebase -i will write the todo list (ignoring +# blank lines and comments) to stdout, and exit failure (so you should run +# it with test_must_fail). This can be used to verify the expected user +# experience, for todo list changes that do not affect the outcome of +# rebase; or as an extra check in addition to checking the outcome. +set_cat_todo_editor () { + write_script fake-editor.sh <<-\EOF + grep "^[^#]" "$1" + exit 1 + EOF test_set_editor "$(pwd)/fake-editor.sh" - chmod a+x fake-editor.sh +} + +# checks that the revisions in "$2" represent a linear range with the +# subjects in "$1" +test_linear_range () { + revlist_merges=$(git rev-list --merges "$2") && + test -z "$revlist_merges" && + expected=$1 + set -- $(git log --reverse --format=%s "$2") + test "$expected" = "$*" +} + +reset_rebase () { + test_might_fail git rebase --abort && + git reset --hard && + git clean -f +} + +cherry_pick () { + git cherry-pick -n "$2" && + git commit -m "$1" && + git tag "$1" +} + +revert () { + git revert -n "$2" && + git commit -m "$1" && + git tag "$1" +} + +make_empty () { + git commit --allow-empty -m "$1" && + git tag "$1" } diff --git a/t/lib-t6000.sh b/t/lib-t6000.sh index ea25dd89e5..4ffd90127e 100644 --- a/t/lib-t6000.sh +++ b/t/lib-t6000.sh @@ -1,55 +1,50 @@ : included from 6002 and others -[ -d .git/refs/tags ] || mkdir -p .git/refs/tags +mkdir -p .git/refs/tags -:> sed.script +>sed.script -# Answer the sha1 has associated with the tag. The tag must exist in .git or .git/refs/tags -tag() -{ +# Answer the sha1 has associated with the tag. The tag must exist in .git/refs/tags +tag () { _tag=$1 - [ -f .git/refs/tags/$_tag ] || error "tag: \"$_tag\" does not exist" - cat .git/refs/tags/$_tag + test -f ".git/refs/tags/$_tag" || error "tag: \"$_tag\" does not exist" + cat ".git/refs/tags/$_tag" } # Generate a commit using the text specified to make it unique and the tree # named by the tag specified. -unique_commit() -{ +unique_commit () { _text=$1 - _tree=$2 + _tree=$2 shift 2 - echo $_text | git commit-tree $(tag $_tree) "$@" + echo "$_text" | git commit-tree $(tag "$_tree") "$@" } # Save the output of a command into the tag specified. Prepend # a substitution script for the tag onto the front of sed.script -save_tag() -{ +save_tag () { _tag=$1 - [ -n "$_tag" ] || error "usage: save_tag tag commit-args ..." + test -n "$_tag" || error "usage: save_tag tag commit-args ..." shift 1 - "$@" >.git/refs/tags/$_tag + "$@" >".git/refs/tags/$_tag" - echo "s/$(tag $_tag)/$_tag/g" > sed.script.tmp - cat sed.script >> sed.script.tmp + echo "s/$(tag $_tag)/$_tag/g" >sed.script.tmp + cat sed.script >>sed.script.tmp rm sed.script mv sed.script.tmp sed.script } # Replace unhelpful sha1 hashses with their symbolic equivalents -entag() -{ +entag () { sed -f sed.script } # Execute a command after first saving, then setting the GIT_AUTHOR_EMAIL # tag to a specified value. Restore the original value on return. -as_author() -{ +as_author () { _author=$1 shift 1 - _save=$GIT_AUTHOR_EMAIL + _save=$GIT_AUTHOR_EMAIL GIT_AUTHOR_EMAIL="$_author" export GIT_AUTHOR_EMAIL @@ -63,45 +58,58 @@ as_author() fi } -commit_date() -{ - _commit=$1 - git cat-file commit $_commit | sed -n "s/^committer .*> \([0-9]*\) .*/\1/p" +commit_date () { + _commit=$1 + git cat-file commit $_commit | + sed -n "s/^committer .*> \([0-9]*\) .*/\1/p" } -on_committer_date() -{ - _date=$1 - shift 1 - GIT_COMMITTER_DATE="$_date" - export GIT_COMMITTER_DATE - "$@" - unset GIT_COMMITTER_DATE +# Assign the value of fake date to a variable, but +# allow fairly common "1971-08-16 00:00" to be omittd +assign_fake_date () { + case "$2" in + ??:??:??) eval "$1='1971-08-16 $2'" ;; + ??:??) eval "$1='1971-08-16 00:$2'" ;; + ??) eval "$1='1971-08-16 00:00:$2'" ;; + *) eval "$1='$2'" ;; + esac +} + +on_committer_date () { + assign_fake_date GIT_COMMITTER_DATE "$1" + export GIT_COMMITTER_DATE + shift 1 + "$@" +} + +on_dates () { + assign_fake_date GIT_COMMITTER_DATE "$1" + assign_fake_date GIT_AUTHOR_DATE "$2" + export GIT_COMMITTER_DATE GIT_AUTHOR_DATE + shift 2 + "$@" } # Execute a command and suppress any error output. -hide_error() -{ +hide_error () { "$@" 2>/dev/null } -check_output() -{ +check_output () { _name=$1 shift 1 - if eval "$*" | entag > $_name.actual + if eval "$*" | entag >"$_name.actual" then - test_cmp $_name.expected $_name.actual + test_cmp "$_name.expected" "$_name.actual" else - return 1; + return 1 fi } # Turn a reasonable test description into a reasonable test name. # All alphanums translated into -'s which are then compressed and stripped # from front and back. -name_from_description() -{ +name_from_description () { perl -pe ' s/[^A-Za-z0-9.]/-/g; s/-+/-/g; @@ -119,9 +127,11 @@ name_from_description() test_output_expect_success() { _description=$1 - _test=$2 - [ $# -eq 2 ] || error "usage: test_output_expect_success description test <<EOF ... EOF" - _name=$(echo $_description | name_from_description) - cat > $_name.expected + _test=$2 + test $# -eq 2 || + error "usage: test_output_expect_success description test <<EOF ... EOF" + + _name=$(echo $_description | name_from_description) + cat >"$_name.expected" test_expect_success "$_description" "check_output $_name \"$_test\"" } diff --git a/t/perf/README b/t/perf/README index c552f561bf..8848c14619 100644 --- a/t/perf/README +++ b/t/perf/README @@ -66,7 +66,7 @@ You can set the following variables (also in your config.mak): GIT_PERF_LARGE_REPO Repositories to copy for the performance tests. The normal repo should be at least git.git size. The large repo should - probably be about linux-2.6.git size for optimal results. + probably be about linux.git size for optimal results. Both default to the git.git you are running from. You can also pass the options taken by ordinary git tests; the most diff --git a/t/perf/p0002-read-cache.sh b/t/perf/p0002-read-cache.sh new file mode 100755 index 0000000000..9180ae9343 --- /dev/null +++ b/t/perf/p0002-read-cache.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +test_description="Tests performance of reading the index" + +. ./perf-lib.sh + +test_perf_default_repo + +count=1000 +test_perf "read_cache/discard_cache $count times" " + test-read-cache $count +" + +test_done diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index a816fbcb76..c61d5351e1 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -150,6 +150,7 @@ exit $ret' >&3 2>&4 test_perf () { + test_start_ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test-expect-success" @@ -187,7 +188,7 @@ test_perf () { base="$perf_results_dir"/"$perf_results_prefix$(basename "$0" .sh)"."$test_count" "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times fi - echo >&3 "" + test_finish_ } # We extend test_done to print timings at the end (./run disables this diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index cefe33d6d1..10be52beed 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -47,8 +47,14 @@ test_expect_failure 'pretend we have a known breakage' ' run_sub_test_lib_test () { name="$1" descr="$2" # stdin is the body of the test code + shift 2 mkdir "$name" && ( + # Pretend we're a test harness. This prevents + # test-lib from writing the counts to a file that will + # later be summarized, showing spurious "failed" tests + HARNESS_ACTIVE=t && + export HARNESS_ACTIVE && cd "$name" && cat >"$name.sh" <<-EOF && #!$SHELL_PATH @@ -65,7 +71,7 @@ run_sub_test_lib_test () { cat >>"$name.sh" && chmod +x "$name.sh" && export TEST_DIRECTORY && - ./"$name.sh" >out 2>err + ./"$name.sh" "$@" >out 2>err ) } @@ -215,6 +221,60 @@ test_expect_success 'pretend we have a mix of all possible results' " EOF " +test_expect_success 'test --verbose' ' + test_must_fail run_sub_test_lib_test \ + test-verbose "test verbose" --verbose <<-\EOF && + test_expect_success "passing test" true + test_expect_success "test with output" "echo foo" + test_expect_success "failing test" false + test_done + EOF + mv test-verbose/out test-verbose/out+ + grep -v "^Initialized empty" test-verbose/out+ >test-verbose/out && + check_sub_test_lib_test test-verbose <<-\EOF + > expecting success: true + > Z + > ok 1 - passing test + > Z + > expecting success: echo foo + > foo + > Z + > ok 2 - test with output + > Z + > expecting success: false + > Z + > not ok 3 - failing test + > # false + > Z + > # failed 1 among 3 test(s) + > 1..3 + EOF +' + +test_expect_success 'test --verbose-only' ' + test_must_fail run_sub_test_lib_test \ + test-verbose-only-2 "test verbose-only=2" \ + --verbose-only=2 <<-\EOF && + test_expect_success "passing test" true + test_expect_success "test with output" "echo foo" + test_expect_success "failing test" false + test_done + EOF + check_sub_test_lib_test test-verbose-only-2 <<-\EOF + > ok 1 - passing test + > Z + > expecting success: echo foo + > foo + > Z + > ok 2 - test with output + > Z + > not ok 3 - failing test + > # false + > # failed 1 among 3 test(s) + > 1..3 + EOF +' + test_set_prereq HAVEIT haveit=no test_expect_success HAVEIT 'test runs if prerequisite is satisfied' ' @@ -367,22 +427,6 @@ test_expect_success 'validate object ID of a known tree' ' # Various types of objects -# Some filesystems do not support symblic links; on such systems -# some expected values are different -if test_have_prereq SYMLINKS -then - expectfilter=cat - expectedtree=087704a96baf1c2d1c869a8b084481e121c88b5b - expectedptree1=21ae8269cacbe57ae09138dcc3a2887f904d02b3 - expectedptree2=3c5e5399f3a333eddecce7a9b9465b63f65f51e2 -else - expectfilter='grep -v sym' - expectedtree=8e18edf7d7edcf4371a3ac6ae5f07c2641db7c46 - expectedptree1=cfb8591b2f65de8b8cc1020cd7d9e67e7793b325 - expectedptree2=ce580448f0148b985a513b693fdf7d802cacb44f -fi - - test_expect_success 'adding various types of objects with git update-index --add' ' mkdir path2 path3 path3/subp3 && paths="path0 path2/file2 path3/file3 path3/subp3/file3" && @@ -390,10 +434,7 @@ test_expect_success 'adding various types of objects with git update-index --add for p in $paths do echo "hello $p" >$p || exit 1 - if test_have_prereq SYMLINKS - then - ln -s "hello $p" ${p}sym || exit 1 - fi + test_ln_s_add "hello $p" ${p}sym || exit 1 done ) && find path* ! -type d -print | xargs git update-index --add @@ -405,7 +446,7 @@ test_expect_success 'showing stage with git ls-files --stage' ' ' test_expect_success 'validate git ls-files output for a known tree' ' - $expectfilter >expected <<-\EOF && + cat >expected <<-\EOF && 100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0 path0sym 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0 path2/file2 @@ -423,14 +464,14 @@ test_expect_success 'writing tree out with git write-tree' ' ' test_expect_success 'validate object ID for a known tree' ' - test "$tree" = "$expectedtree" + test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b ' test_expect_success 'showing tree with git ls-tree' ' git ls-tree $tree >current ' -test_expect_success SYMLINKS 'git ls-tree output for a known tree' ' +test_expect_success 'git ls-tree output for a known tree' ' cat >expected <<-\EOF && 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym @@ -447,7 +488,7 @@ test_expect_success 'showing tree with git ls-tree -r' ' ' test_expect_success 'git ls-tree -r output for a known tree' ' - $expectfilter >expected <<-\EOF && + cat >expected <<-\EOF && 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym 100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2 @@ -465,7 +506,7 @@ test_expect_success 'showing tree with git ls-tree -r -t' ' git ls-tree -r -t $tree >current ' -test_expect_success SYMLINKS 'git ls-tree -r output for a known tree' ' +test_expect_success 'git ls-tree -r output for a known tree' ' cat >expected <<-\EOF && 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym @@ -487,7 +528,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' ' ' test_expect_success 'validate object ID for a known tree' ' - test "$ptree" = "$expectedptree1" + test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3 ' test_expect_success 'writing partial tree out with git write-tree --prefix' ' @@ -495,7 +536,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' ' ' test_expect_success 'validate object ID for a known tree' ' - test "$ptree" = "$expectedptree2" + test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 ' test_expect_success 'put invalid objects into the index' ' @@ -529,7 +570,7 @@ test_expect_success 'git read-tree followed by write-tree should be idempotent' ' test_expect_success 'validate git diff-files output for a know cache/work tree state' ' - $expectfilter >expected <<\EOF && + cat >expected <<\EOF && :100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M path0 :120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M path0sym :100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M path2/file2 @@ -553,7 +594,7 @@ test_expect_success 'no diff after checkout and git update-index --refresh' ' ' ################################################################ -P=$expectedtree +P=087704a96baf1c2d1c869a8b084481e121c88b5b test_expect_success 'git commit-tree records the correct tree in a commit' ' commit0=$(echo NO | git commit-tree $P) && diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh index a56db804cb..c29342d6bc 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -697,13 +697,21 @@ test_expect_success PIPE 'streaming support for --stdin' ' # shell, and then echo to the fd. We make sure to close it at # the end, so that the subprocess does get EOF and dies # properly. + # + # Similarly, we must keep "out" open so that check-ignore does + # not ever get SIGPIPE trying to write to us. Not only would that + # produce incorrect results, but then there would be no writer on the + # other end of the pipe, and we would potentially block forever trying + # to open it. exec 9>in && + exec 8<out && test_when_finished "exec 9>&-" && + test_when_finished "exec 8<&-" && echo >&9 one && - read response <out && + read response <&8 && echo "$response" | grep "^\.gitignore:1:one one" && echo >&9 two && - read response <out && + read response <&8 && echo "$response" | grep "^:: two" ' diff --git a/t/t0009-prio-queue.sh b/t/t0009-prio-queue.sh new file mode 100755 index 0000000000..94045c3fad --- /dev/null +++ b/t/t0009-prio-queue.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +test_description='basic tests for priority queue implementation' +. ./test-lib.sh + +cat >expect <<'EOF' +1 +2 +3 +4 +5 +5 +6 +7 +8 +9 +10 +EOF +test_expect_success 'basic ordering' ' + test-prio-queue 2 6 3 10 9 5 7 4 5 8 1 dump >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +2 +3 +4 +1 +5 +6 +EOF +test_expect_success 'mixed put and get' ' + test-prio-queue 6 2 4 get 5 3 get get 1 dump >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +1 +2 +NULL +1 +2 +NULL +EOF +test_expect_success 'notice empty queue' ' + test-prio-queue 1 2 get get get 1 2 get get get >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 1a8f44c44c..e526184a0b 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -81,6 +81,14 @@ test_expect_success 'safecrlf: print warning only once' ' test $(git add doublewarn 2>&1 | grep "CRLF will be replaced by LF" | wc -l) = 1 ' + +test_expect_success 'safecrlf: git diff demotes safecrlf=true to warn' ' + git config core.autocrlf input && + git config core.safecrlf true && + git diff HEAD +' + + test_expect_success 'switch off autocrlf, safecrlf, reset HEAD' ' git config core.autocrlf false && git config core.safecrlf false && diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 244a43c920..65606df3ed 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -50,7 +50,7 @@ EOF test_expect_success 'test help' ' test_must_fail test-parse-options -h > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_i18ncmp expect output ' @@ -75,7 +75,7 @@ check() { shift && sed "s/^$what .*/$what $expect/" <expect.template >expect && test-parse-options $* >output 2>output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output } @@ -86,7 +86,7 @@ check_i18n() { shift && sed "s/^$what .*/$what $expect/" <expect.template >expect && test-parse-options $* >output 2>output.err && - test ! -s output.err && + test_must_be_empty output.err && test_i18ncmp expect output } @@ -99,7 +99,7 @@ check_unknown() { esac && cat expect.err >>expect && test_must_fail test-parse-options $* >output 2>output.err && - test ! -s output && + test_must_be_empty output && test_cmp expect output.err } @@ -112,7 +112,7 @@ check_unknown_i18n() { esac && cat expect.err >>expect && test_must_fail test-parse-options $* >output 2>output.err && - test ! -s output && + test_must_be_empty output && test_i18ncmp expect output.err } @@ -149,7 +149,7 @@ test_expect_success 'short options' ' test-parse-options -s123 -b -i 1729 -b -vv -n -F my.file \ > output 2> output.err && test_cmp expect output && - test ! -s output.err + test_must_be_empty output.err ' cat > expect << EOF @@ -168,7 +168,7 @@ test_expect_success 'long options' ' test-parse-options --boolean --integer 1729 --boolean --string2=321 \ --verbose --verbose --no-dry-run --abbrev=10 --file fi.le\ --obsolete > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' @@ -199,7 +199,7 @@ EOF test_expect_success 'intermingled arguments' ' test-parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \ > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' @@ -217,13 +217,13 @@ EOF test_expect_success 'unambiguously abbreviated option' ' test-parse-options --int 2 --boolean --no-bo > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'unambiguously abbreviated option with "="' ' test-parse-options --int=2 > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' @@ -246,7 +246,7 @@ EOF test_expect_success 'non ambiguous option (after two options it abbreviates)' ' test-parse-options --st 123 > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' @@ -256,7 +256,7 @@ EOF test_expect_success 'detect possible typos' ' test_must_fail test-parse-options -boolean > output 2> output.err && - test ! -s output && + test_must_be_empty output && test_cmp typo.err output.err ' @@ -266,7 +266,7 @@ EOF test_expect_success 'detect possible typos' ' test_must_fail test-parse-options -ambiguous > output 2> output.err && - test ! -s output && + test_must_be_empty output && test_cmp typo.err output.err ' @@ -285,7 +285,7 @@ EOF test_expect_success 'keep some options as arguments' ' test-parse-options --quux > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' @@ -305,7 +305,7 @@ EOF test_expect_success 'OPT_DATE() and OPT_SET_PTR() work' ' test-parse-options -t "1970-01-01 00:00:01 +0000" --default-string \ foo -q > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' @@ -324,7 +324,7 @@ EOF test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' ' test-parse-options --length=four -b -4 > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' @@ -352,13 +352,13 @@ EOF test_expect_success 'OPT_BIT() and OPT_SET_INT() work' ' test-parse-options --set23 -bbbbb --no-or4 > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' ' test-parse-options --set23 -bbbbb --neg-or4 > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' @@ -376,19 +376,19 @@ EOF test_expect_success 'OPT_BIT() works' ' test-parse-options -bb --or4 > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'OPT_NEGBIT() works' ' test-parse-options -bb --no-neg-or4 > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' ' test-parse-options + + + + + + > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' @@ -406,7 +406,7 @@ EOF test_expect_success 'OPT_NUMBER_CALLBACK() works' ' test-parse-options -12345 > output 2> output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' @@ -424,7 +424,7 @@ EOF test_expect_success 'negation of OPT_NONEG flags is not ambiguous' ' test-parse-options --no-ambig >output 2>output.err && - test ! -s output.err && + test_must_be_empty output.err && test_cmp expect output ' diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 09a42a428e..3a48de20d8 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -8,8 +8,15 @@ test_description='Test various path utilities' . ./test-lib.sh norm_path() { + expected=$(test-path-utils mingw_path "$2") test_expect_success $3 "normalize path: $1 => $2" \ - "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'" + "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$expected'" +} + +relative_path() { + expected=$(test-path-utils mingw_path "$3") + test_expect_success $4 "relative path: $1 $2 => $3" \ + "test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'" } # On Windows, we are using MSYS's bash, which mangles the paths. @@ -34,8 +41,8 @@ ancestor() { test \"\$actual\" = '$expected'" } -# Absolute path tests must be skipped on Windows because due to path mangling -# the test program never sees a POSIX-style absolute path +# Some absolute path tests should be skipped on Windows due to path mangling +# on POSIX-style absolute paths case $(uname -s) in *MINGW*) ;; @@ -68,30 +75,30 @@ norm_path d1/s1//../s2/../../d2 d2 norm_path d1/.../d2 d1/.../d2 norm_path d1/..././../d2 d1/d2 -norm_path / / POSIX +norm_path / / norm_path // / POSIX norm_path /// / POSIX -norm_path /. / POSIX +norm_path /. / norm_path /./ / POSIX norm_path /./.. ++failed++ POSIX -norm_path /../. ++failed++ POSIX +norm_path /../. ++failed++ norm_path /./../.// ++failed++ POSIX norm_path /dir/.. / POSIX norm_path /dir/sub/../.. / POSIX norm_path /dir/sub/../../.. ++failed++ POSIX -norm_path /dir /dir POSIX -norm_path /dir// /dir/ POSIX -norm_path /./dir /dir POSIX -norm_path /dir/. /dir/ POSIX -norm_path /dir///./ /dir/ POSIX -norm_path /dir//sub/.. /dir/ POSIX -norm_path /dir/sub/../ /dir/ POSIX +norm_path /dir /dir +norm_path /dir// /dir/ +norm_path /./dir /dir +norm_path /dir/. /dir/ +norm_path /dir///./ /dir/ +norm_path /dir//sub/.. /dir/ +norm_path /dir/sub/../ /dir/ norm_path //dir/sub/../. /dir/ POSIX -norm_path /dir/s1/../s2/ /dir/s2/ POSIX -norm_path /d1/s1///s2/..//../s3/ /d1/s3/ POSIX -norm_path /d1/s1//../s2/../../d2 /d2 POSIX -norm_path /d1/.../d2 /d1/.../d2 POSIX -norm_path /d1/..././../d2 /d1/d2 POSIX +norm_path /dir/s1/../s2/ /dir/s2/ +norm_path /d1/s1///s2/..//../s3/ /d1/s3/ +norm_path /d1/s1//../s2/../../d2 /d2 +norm_path /d1/.../d2 /d1/.../d2 +norm_path /d1/..././../d2 /d1/d2 ancestor / / -1 ancestor /foo / 0 @@ -183,4 +190,33 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' test "$sym" = "$(test-path-utils real_path "$dir2/syml")" ' +relative_path /a/b/c/ /a/b/ c/ +relative_path /a/b/c/ /a/b c/ +relative_path /a//b//c/ //a/b// c/ POSIX +relative_path /a/b /a/b ./ +relative_path /a/b/ /a/b ./ +relative_path /a /a/b ../ +relative_path / /a/b/ ../../ +relative_path /a/c /a/b/ ../c +relative_path /a/c /a/b ../c +relative_path /x/y /a/b/ ../../x/y +relative_path /a/b "<empty>" /a/b +relative_path /a/b "<null>" /a/b +relative_path a/b/c/ a/b/ c/ +relative_path a/b/c/ a/b c/ +relative_path a/b//c a//b c +relative_path a/b/ a/b/ ./ +relative_path a/b/ a/b ./ +relative_path a a/b ../ +relative_path x/y a/b ../../x/y +relative_path a/c a/b ../c +relative_path a/b "<empty>" a/b +relative_path a/b "<null>" a/b +relative_path "<empty>" /a/b ./ +relative_path "<empty>" "<empty>" ./ +relative_path "<empty>" "<null>" ./ +relative_path "<null>" "<empty>" ./ +relative_path "<null>" "<null>" ./ +relative_path "<null>" /a/b ./ + test_done diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index b3ae7d52c6..3e72aff470 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -158,7 +158,7 @@ test_expect_success '3-way not overwriting local changes (their side)' ' ' -test_expect_success SYMLINKS 'funny symlink in work tree' ' +test_expect_success 'funny symlink in work tree' ' git reset --hard && git checkout -b sym-b side-b && @@ -170,15 +170,14 @@ test_expect_success SYMLINKS 'funny symlink in work tree' ' rm -fr a && git checkout -b sym-a side-a && mkdir -p a && - ln -s ../b a/b && - git add a/b && + test_ln_s_add ../b a/b && git commit -m "we add a/b" && read_tree_u_must_succeed -m -u sym-a sym-a sym-b ' -test_expect_success SYMLINKS,SANITY 'funny symlink in work tree, un-unlink-able' ' +test_expect_success SANITY 'funny symlink in work tree, un-unlink-able' ' rm -fr a b && git reset --hard && diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 9cc5c6bf4d..d499d02a29 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -36,66 +36,54 @@ $content" ' test_expect_success "Type of $type is correct" ' - test $type = "$(git cat-file -t $sha1)" + echo $type >expect && + git cat-file -t $sha1 >actual && + test_cmp expect actual ' test_expect_success "Size of $type is correct" ' - test $size = "$(git cat-file -s $sha1)" + echo $size >expect && + git cat-file -s $sha1 >actual && + test_cmp expect actual ' test -z "$content" || test_expect_success "Content of $type is correct" ' - expect="$(maybe_remove_timestamp "$content" $no_ts)" - actual="$(maybe_remove_timestamp "$(git cat-file $type $sha1)" $no_ts)" - - if test "z$expect" = "z$actual" - then - : happy - else - echo "Oops: expected $expect" - echo "but got $actual" - false - fi + maybe_remove_timestamp "$content" $no_ts >expect && + maybe_remove_timestamp "$(git cat-file $type $sha1)" $no_ts >actual && + test_cmp expect actual ' test_expect_success "Pretty content of $type is correct" ' - expect="$(maybe_remove_timestamp "$pretty_content" $no_ts)" - actual="$(maybe_remove_timestamp "$(git cat-file -p $sha1)" $no_ts)" - if test "z$expect" = "z$actual" - then - : happy - else - echo "Oops: expected $expect" - echo "but got $actual" - false - fi + maybe_remove_timestamp "$pretty_content" $no_ts >expect && + maybe_remove_timestamp "$(git cat-file -p $sha1)" $no_ts >actual && + test_cmp expect actual ' test -z "$content" || test_expect_success "--batch output of $type is correct" ' - expect="$(maybe_remove_timestamp "$batch_output" $no_ts)" - actual="$(maybe_remove_timestamp "$(echo $sha1 | git cat-file --batch)" $no_ts)" - if test "z$expect" = "z$actual" - then - : happy - else - echo "Oops: expected $expect" - echo "but got $actual" - false - fi + maybe_remove_timestamp "$batch_output" $no_ts >expect && + maybe_remove_timestamp "$(echo $sha1 | git cat-file --batch)" $no_ts >actual && + test_cmp expect actual ' test_expect_success "--batch-check output of $type is correct" ' - expect="$sha1 $type $size" - actual="$(echo_without_newline $sha1 | git cat-file --batch-check)" - if test "z$expect" = "z$actual" - then - : happy - else - echo "Oops: expected $expect" - echo "but got $actual" - false - fi + echo "$sha1 $type $size" >expect && + echo_without_newline $sha1 | git cat-file --batch-check >actual && + test_cmp expect actual + ' + + test_expect_success "custom --batch-check format" ' + echo "$type $sha1" >expect && + echo $sha1 | git cat-file --batch-check="%(objecttype) %(objectname)" >actual && + test_cmp expect actual + ' + + test_expect_success '--batch-check with %(rest)' ' + echo "$type this is some extra content" >expect && + echo "$sha1 this is some extra content" | + git cat-file --batch-check="%(objecttype) %(rest)" >actual && + test_cmp expect actual ' } diff --git a/t/t1307-config-blob.sh b/t/t1307-config-blob.sh new file mode 100755 index 0000000000..fdc257e66f --- /dev/null +++ b/t/t1307-config-blob.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +test_description='support for reading config from a blob' +. ./test-lib.sh + +test_expect_success 'create config blob' ' + cat >config <<-\EOF && + [some] + value = 1 + EOF + git add config && + git commit -m foo +' + +test_expect_success 'list config blob contents' ' + echo some.value=1 >expect && + git config --blob=HEAD:config --list >actual && + test_cmp expect actual +' + +test_expect_success 'fetch value from blob' ' + echo true >expect && + git config --blob=HEAD:config --bool some.value >actual && + test_cmp expect actual +' + +test_expect_success 'reading non-existing value from blob is an error' ' + test_must_fail git config --blob=HEAD:config non.existing +' + +test_expect_success 'reading from blob and file is an error' ' + test_must_fail git config --blob=HEAD:config --system --list +' + +test_expect_success 'reading from missing ref is an error' ' + test_must_fail git config --blob=HEAD:doesnotexist --list +' + +test_expect_success 'reading from non-blob is an error' ' + test_must_fail git config --blob=HEAD --list +' + +test_expect_success 'setting a value in a blob is an error' ' + test_must_fail git config --blob=HEAD:config some.value foo +' + +test_expect_success 'deleting a value in a blob is an error' ' + test_must_fail git config --blob=HEAD:config --unset some.value +' + +test_expect_success 'editing a blob is an error' ' + test_must_fail git config --blob=HEAD:config --edit +' + +test_expect_success 'parse errors in blobs are properly attributed' ' + cat >config <<-\EOF && + [some] + value = " + EOF + git add config && + git commit -m broken && + + test_must_fail git config --blob=HEAD:config some.value 2>err && + + # just grep for our token as the exact error message is likely to + # change or be internationalized + grep "HEAD:config" err +' + +test_done diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh new file mode 100755 index 0000000000..3e500ed7da --- /dev/null +++ b/t/t1403-show-ref.sh @@ -0,0 +1,167 @@ +#!/bin/sh + +test_description='show-ref' +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + git tag -f -a -m "annotated A" A && + git checkout -b side && + test_commit B && + git tag -f -a -m "annotated B" B && + git checkout master && + test_commit C && + git branch B A^0 +' + +test_expect_success 'show-ref' ' + echo $(git rev-parse refs/tags/A) refs/tags/A >expect && + + git show-ref A >actual && + test_cmp expect actual && + + git show-ref tags/A >actual && + test_cmp expect actual && + + git show-ref refs/tags/A >actual && + test_cmp expect actual && + + >expect && + + test_must_fail git show-ref D >actual + test_cmp expect actual +' + +test_expect_success 'show-ref -q' ' + >expect && + + git show-ref -q A >actual && + test_cmp expect actual && + + git show-ref -q tags/A >actual && + test_cmp expect actual && + + git show-ref -q refs/tags/A >actual && + test_cmp expect actual && + + test_must_fail git show-ref -q D >actual && + test_cmp expect actual +' + +test_expect_success 'show-ref --verify' ' + echo $(git rev-parse refs/tags/A) refs/tags/A >expect && + + git show-ref --verify refs/tags/A >actual && + test_cmp expect actual && + + >expect && + + test_must_fail git show-ref --verify A >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify tags/A >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify D >actual + test_cmp expect actual +' + +test_expect_success 'show-ref --verify -q' ' + >expect && + + git show-ref --verify -q refs/tags/A >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify -q A >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify -q tags/A >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify -q D >actual + test_cmp expect actual +' + +test_expect_success 'show-ref -d' ' + { + echo $(git rev-parse refs/tags/A) refs/tags/A && + echo $(git rev-parse refs/tags/A^0) "refs/tags/A^{}" + echo $(git rev-parse refs/tags/C) refs/tags/C + } >expect && + git show-ref -d A C >actual && + test_cmp expect actual && + + git show-ref -d tags/A tags/C >actual && + test_cmp expect actual && + + git show-ref -d refs/tags/A refs/tags/C >actual && + test_cmp expect actual && + + echo $(git rev-parse refs/heads/master) refs/heads/master >expect && + git show-ref -d master >actual && + test_cmp expect actual && + + git show-ref -d heads/master >actual && + test_cmp expect actual && + + git show-ref -d refs/heads/master >actual && + test_cmp expect actual + + git show-ref -d --verify refs/heads/master >actual && + test_cmp expect actual + + >expect && + + test_must_fail git show-ref -d --verify master >actual && + test_cmp expect actual && + + test_must_fail git show-ref -d --verify heads/master >actual && + test_cmp expect actual + +' + +test_expect_success 'show-ref --heads, --tags, --head, pattern' ' + for branch in B master side + do + echo $(git rev-parse refs/heads/$branch) refs/heads/$branch + done >expect.heads && + git show-ref --heads >actual && + test_cmp expect.heads actual && + + for tag in A B C + do + echo $(git rev-parse refs/tags/$tag) refs/tags/$tag + done >expect.tags && + git show-ref --tags >actual && + test_cmp expect.tags actual && + + cat expect.heads expect.tags >expect && + git show-ref --heads --tags >actual && + test_cmp expect actual && + + { + echo $(git rev-parse HEAD) HEAD && + cat expect.heads expect.tags + } >expect && + git show-ref --heads --tags --head >actual && + test_cmp expect actual && + + { + echo $(git rev-parse HEAD) HEAD && + echo $(git rev-parse refs/heads/B) refs/heads/B + echo $(git rev-parse refs/tags/B) refs/tags/B + } >expect && + git show-ref --head B >actual && + test_cmp expect actual && + + { + echo $(git rev-parse HEAD) HEAD && + echo $(git rev-parse refs/heads/B) refs/heads/B + echo $(git rev-parse refs/tags/B) refs/tags/B + echo $(git rev-parse refs/tags/B^0) "refs/tags/B^{}" + } >expect && + git show-ref --head -d B >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index db228086d3..4a155c8d09 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -77,6 +77,7 @@ test_expect_success 'disambiguate blob' ' test_expect_success 'disambiguate tree' ' commit=$(echo "d7xm" | git commit-tree 000000000) && + # this commit is fffff2e and not ambiguous with the 00000* objects test $(git rev-parse $commit^{tree}) = $(git rev-parse 0000000000cdc) ' @@ -99,10 +100,14 @@ test_expect_success 'disambiguate commit-ish' ' test_expect_success 'disambiguate commit' ' commit=$(echo "hoaxj" | git commit-tree 0000000000cdc -p 000000000) && + # this commit is ffffffd8 and not ambiguous with the 00000* objects test $(git rev-parse $commit^) = $(git rev-parse 0000000000e4f) ' test_expect_success 'log name1..name2 takes only commit-ishes on both ends' ' + # These are underspecified from the prefix-length point of view + # to disambiguate the commit with other objects, but there is only + # one commit that has 00000* prefix at this point. git log 000000000..000000000 && git log ..000000000 && git log 000000000.. && @@ -112,16 +117,19 @@ test_expect_success 'log name1..name2 takes only commit-ishes on both ends' ' ' test_expect_success 'rev-parse name1..name2 takes only commit-ishes on both ends' ' + # Likewise. git rev-parse 000000000..000000000 && git rev-parse ..000000000 && git rev-parse 000000000.. ' test_expect_success 'git log takes only commit-ish' ' + # Likewise. git log 000000000 ' test_expect_success 'git reset takes only commit-ish' ' + # Likewise. git reset 000000000 ' @@ -131,26 +139,30 @@ test_expect_success 'first tag' ' ' test_expect_failure 'two semi-ambiguous commit-ish' ' + # At this point, we have a tag 0000000000f8f that points + # at a commit 0000000000e4f, and a tree and a blob that + # share 0000000000 prefix with these tag and commit. + # # Once the parser becomes ultra-smart, it could notice that - # 110282 before ^{commit} name many different objects, but + # 0000000000 before ^{commit} name many different objects, but # that only two (HEAD and v1.0.0 tag) can be peeled to commit, # and that peeling them down to commit yield the same commit # without ambiguity. - git rev-parse --verify 110282^{commit} && + git rev-parse --verify 0000000000^{commit} && # likewise - git log 000000000..000000000 && - git log ..000000000 && - git log 000000000.. && - git log 000000000...000000000 && - git log ...000000000 && - git log 000000000... + git log 0000000000..0000000000 && + git log ..0000000000 && + git log 0000000000.. && + git log 0000000000...0000000000 && + git log ...0000000000 && + git log 0000000000... ' test_expect_failure 'three semi-ambiguous tree-ish' ' # Likewise for tree-ish. HEAD, v1.0.0 and HEAD^{tree} share # the prefix but peeling them to tree yields the same thing - git rev-parse --verify 000000000^{tree} + git rev-parse --verify 0000000000^{tree} ' test_expect_success 'parse describe name' ' @@ -241,7 +253,7 @@ test_expect_success 'ambiguous commit-ish' ' # Now there are many commits that begin with the # common prefix, none of these should pick one at # random. They all should result in ambiguity errors. - test_must_fail git rev-parse --verify 110282^{commit} && + test_must_fail git rev-parse --verify 00000000^{commit} && # likewise test_must_fail git log 000000000..000000000 && diff --git a/t/t1513-rev-parse-prefix.sh b/t/t1513-rev-parse-prefix.sh new file mode 100755 index 0000000000..87ec3ae714 --- /dev/null +++ b/t/t1513-rev-parse-prefix.sh @@ -0,0 +1,96 @@ +#!/bin/sh + +test_description='Tests for rev-parse --prefix' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir -p sub1/sub2 && + echo top >top && + echo file1 >sub1/file1 && + echo file2 >sub1/sub2/file2 && + git add top sub1/file1 sub1/sub2/file2 && + git commit -m commit +' + +test_expect_success 'empty prefix -- file' ' + git rev-parse --prefix "" -- top sub1/file1 >actual && + cat <<-\EOF >expected && + -- + top + sub1/file1 + EOF + test_cmp expected actual +' + +test_expect_success 'valid prefix -- file' ' + git rev-parse --prefix sub1/ -- file1 sub2/file2 >actual && + cat <<-\EOF >expected && + -- + sub1/file1 + sub1/sub2/file2 + EOF + test_cmp expected actual +' + +test_expect_success 'valid prefix -- ../file' ' + git rev-parse --prefix sub1/ -- ../top sub2/file2 >actual && + cat <<-\EOF >expected && + -- + sub1/../top + sub1/sub2/file2 + EOF + test_cmp expected actual +' + +test_expect_success 'empty prefix HEAD:./path' ' + git rev-parse --prefix "" HEAD:./top >actual && + git rev-parse HEAD:top >expected && + test_cmp expected actual +' + +test_expect_success 'valid prefix HEAD:./path' ' + git rev-parse --prefix sub1/ HEAD:./file1 >actual && + git rev-parse HEAD:sub1/file1 >expected && + test_cmp expected actual +' + +test_expect_success 'valid prefix HEAD:../path' ' + git rev-parse --prefix sub1/ HEAD:../top >actual && + git rev-parse HEAD:top >expected && + test_cmp expected actual +' + +test_expect_success 'prefix ignored with HEAD:top' ' + git rev-parse --prefix sub1/ HEAD:top >actual && + git rev-parse HEAD:top >expected && + test_cmp expected actual +' + +test_expect_success 'disambiguate path with valid prefix' ' + git rev-parse --prefix sub1/ file1 >actual && + cat <<-\EOF >expected && + sub1/file1 + EOF + test_cmp expected actual +' + +test_expect_success 'file and refs with prefix' ' + git rev-parse --prefix sub1/ master file1 >actual && + cat <<-EOF >expected && + $(git rev-parse master) + sub1/file1 + EOF + test_cmp expected actual +' + +test_expect_success 'two-levels deep' ' + git rev-parse --prefix sub1/sub2/ -- file2 >actual && + cat <<-\EOF >expected && + -- + sub1/sub2/file2 + EOF + test_cmp expected actual +' + +test_done diff --git a/t/t2001-checkout-cache-clash.sh b/t/t2001-checkout-cache-clash.sh index 98aa73e823..1fc8e634b7 100755 --- a/t/t2001-checkout-cache-clash.sh +++ b/t/t2001-checkout-cache-clash.sh @@ -59,10 +59,9 @@ test_expect_success \ 'git read-tree -m $tree1 && git checkout-index -f -a' test_debug 'show_files $tree1' -test_expect_success SYMLINKS \ - 'git update-index --add a symlink.' \ - 'ln -s path0 path1 && - git update-index --add path1' +test_expect_success \ + 'add a symlink' \ + 'test_ln_s_add path0 path1' test_expect_success \ 'writing tree out with git write-tree' \ 'tree3=$(git write-tree)' diff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh index 0f4b2896af..f171a5578b 100755 --- a/t/t2004-checkout-cache-temp.sh +++ b/t/t2004-checkout-cache-temp.sh @@ -194,11 +194,10 @@ test_expect_success \ test $(cat ../$s1) = tree1asubdir/path5) )' -test_expect_success SYMLINKS \ +test_expect_success \ 'checkout --temp symlink' ' rm -f path* .merge_* out .git/index && -ln -s b a && -git update-index --add a && +test_ln_s_add b a && t4=$(git write-tree) && rm -f .git/index && git read-tree $t4 && diff --git a/t/t2007-checkout-symlink.sh b/t/t2007-checkout-symlink.sh index e6f59f1914..fc9aad530e 100755 --- a/t/t2007-checkout-symlink.sh +++ b/t/t2007-checkout-symlink.sh @@ -6,7 +6,7 @@ test_description='git checkout to switch between branches with symlink<->dir' . ./test-lib.sh -test_expect_success SYMLINKS setup ' +test_expect_success setup ' mkdir frotz && echo hello >frotz/filfre && @@ -25,25 +25,25 @@ test_expect_success SYMLINKS setup ' git rm --cached frotz/filfre && mv frotz xyzzy && - ln -s xyzzy frotz && - git add xyzzy/filfre frotz && + test_ln_s_add xyzzy frotz && + git add xyzzy/filfre && test_tick && git commit -m "side moves frotz/ to xyzzy/ and adds frotz->xyzzy/" ' -test_expect_success SYMLINKS 'switch from symlink to dir' ' +test_expect_success 'switch from symlink to dir' ' git checkout master ' -test_expect_success SYMLINKS 'Remove temporary directories & switch to master' ' +test_expect_success 'Remove temporary directories & switch to master' ' rm -fr frotz xyzzy nitfol && git checkout -f master ' -test_expect_success SYMLINKS 'switch from dir to symlink' ' +test_expect_success 'switch from dir to symlink' ' git checkout side diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh index b44de9dc62..e7ba8c505f 100755 --- a/t/t2012-checkout-last.sh +++ b/t/t2012-checkout-last.sh @@ -116,4 +116,38 @@ test_expect_success 'master...' ' test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)" ' +test_expect_success '"checkout -" works after a rebase A' ' + git checkout master && + git checkout other && + git rebase master && + git checkout - && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master" +' + +test_expect_success '"checkout -" works after a rebase A B' ' + git branch moodle master~1 && + git checkout master && + git checkout other && + git rebase master moodle && + git checkout - && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master" +' + +test_expect_success '"checkout -" works after a rebase -i A' ' + git checkout master && + git checkout other && + git rebase -i master && + git checkout - && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master" +' + +test_expect_success '"checkout -" works after a rebase -i A B' ' + git branch foodle master~1 && + git checkout master && + git checkout other && + git rebase master foodle && + git checkout - && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master" +' + test_done diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh index 5da63e9fa2..c2ada7de37 100755 --- a/t/t2021-checkout-overwrite.sh +++ b/t/t2021-checkout-overwrite.sh @@ -29,21 +29,25 @@ test_expect_success 'checkout commit with dir must not remove untracked a/b' ' test -f a/b ' -test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' ' +test_expect_success 'create a commit where dir a/b changed to symlink' ' rm -rf a/b && # cleanup if previous test failed git checkout -f -b symlink start && rm -rf a/b && - ln -s foo a/b && git add -A && + test_ln_s_add foo a/b && git commit -m "dir to symlink" ' -test_expect_success SYMLINKS 'checkout commit with dir must not remove untracked a/b' ' +test_expect_success 'checkout commit with dir must not remove untracked a/b' ' git rm --cached a/b && git commit -m "un-track the symlink" && - test_must_fail git checkout start && + test_must_fail git checkout start +' + +test_expect_success SYMLINKS 'the symlink remained' ' + test -h a/b ' diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index b2bd41918e..9bf2bdffd2 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -96,11 +96,10 @@ test_expect_success 'non-limited update in subdir leaves root alone' ' test_cmp expect actual ' -test_expect_success SYMLINKS 'replace a file with a symlink' ' +test_expect_success 'replace a file with a symlink' ' rm foo && - ln -s top foo && - git add -u -- foo + test_ln_s_add top foo ' diff --git a/t/t2202-add-addremove.sh b/t/t2202-add-addremove.sh index 6a8151064c..fc8b59e7f7 100755 --- a/t/t2202-add-addremove.sh +++ b/t/t2202-add-addremove.sh @@ -41,4 +41,14 @@ test_expect_success 'git add --all' ' test_cmp expect actual ' +test_expect_success 'Just "git add" is a no-op' ' + git reset --hard && + echo >will-remove && + >will-not-be-added && + git add && + git diff-index --name-status --cached HEAD >actual && + >expect && + test_cmp expect actual +' + test_done diff --git a/t/t3010-ls-files-killed-modified.sh b/t/t3010-ls-files-killed-modified.sh index 95671c2053..f611d799b6 100755 --- a/t/t3010-ls-files-killed-modified.sh +++ b/t/t3010-ls-files-killed-modified.sh @@ -11,6 +11,8 @@ This test prepares the following in the cache: path1 - a symlink path2/file2 - a file in a directory path3/file3 - a file in a directory + submod1/ - a submodule + submod2/ - another submodule and the following on the filesystem: @@ -21,9 +23,11 @@ and the following on the filesystem: path4 - a file path5 - a symlink path6/file6 - a file in a directory + submod1/ - a submodule (modified from the cache) + submod2/ - a submodule (matches the cache) -git ls-files -k should report that existing filesystem -objects except path4, path5 and path6/file6 to be killed. +git ls-files -k should report that existing filesystem objects +path0/*, path1/*, path2 and path3 to be killed. Also for modification test, the cache and working tree have: @@ -33,75 +37,82 @@ Also for modification test, the cache and working tree have: path10 - a non-empty file, cache dirtied. We should report path0, path1, path2/file2, path3/file3, path7 and path8 -modified without reporting path9 and path10. +modified without reporting path9 and path10. submod1 is also modified. ' . ./test-lib.sh -date >path0 -if test_have_prereq SYMLINKS -then - ln -s xyzzy path1 -else - date > path1 -fi -mkdir path2 path3 -date >path2/file2 -date >path3/file3 -: >path7 -date >path8 -: >path9 -date >path10 -test_expect_success \ - 'git update-index --add to add various paths.' \ - "git update-index --add -- path0 path1 path?/file? path7 path8 path9 path10" - -rm -fr path? ;# leave path10 alone -date >path2 -if test_have_prereq SYMLINKS -then - ln -s frotz path3 - ln -s nitfol path5 -else - date > path3 - date > path5 -fi -mkdir path0 path1 path6 -date >path0/file0 -date >path1/file1 -date >path6/file6 -date >path7 -: >path8 -: >path9 -touch path10 +test_expect_success 'git update-index --add to add various paths.' ' + date >path0 && + test_ln_s_add xyzzy path1 && + mkdir path2 path3 && + date >path2/file2 && + date >path3/file3 && + : >path7 && + date >path8 && + : >path9 && + date >path10 && + git update-index --add -- path0 path?/file? path7 path8 path9 path10 && + for i in 1 2 + do + git init submod$i && + ( + cd submod$i && git commit --allow-empty -m "empty $i" + ) || break + done && + git update-index --add submod[12] + ( + cd submod1 && + git commit --allow-empty -m "empty 1 (updated)" + ) && + rm -fr path? # leave path10 alone +' -test_expect_success \ - 'git ls-files -k to show killed files.' \ - 'git ls-files -k >.output' -cat >.expected <<EOF -path0/file0 -path1/file1 -path2 -path3 -EOF +test_expect_success 'git ls-files -k to show killed files.' ' + date >path2 && + if test_have_prereq SYMLINKS + then + ln -s frotz path3 && + ln -s nitfol path5 + else + date >path3 && + date >path5 + fi && + mkdir path0 path1 path6 && + date >path0/file0 && + date >path1/file1 && + date >path6/file6 && + date >path7 && + : >path8 && + : >path9 && + touch path10 && + git ls-files -k >.output +' -test_expect_success \ - 'validate git ls-files -k output.' \ - 'test_cmp .expected .output' +test_expect_success 'validate git ls-files -k output.' ' + cat >.expected <<-\EOF && + path0/file0 + path1/file1 + path2 + path3 + EOF + test_cmp .expected .output +' -test_expect_success \ - 'git ls-files -m to show modified files.' \ - 'git ls-files -m >.output' -cat >.expected <<EOF -path0 -path1 -path2/file2 -path3/file3 -path7 -path8 -EOF +test_expect_success 'git ls-files -m to show modified files.' ' + git ls-files -m >.output +' -test_expect_success \ - 'validate git ls-files -m output.' \ - 'test_cmp .expected .output' +test_expect_success 'validate git ls-files -m output.' ' + cat >.expected <<-\EOF && + path0 + path1 + path2/file2 + path3/file3 + path7 + path8 + submod1 + EOF + test_cmp .expected .output +' test_done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index a5e3da7e41..2f96100a5f 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -25,10 +25,7 @@ test_expect_success 'setup 1' ' git branch submod && git branch copy && git branch rename && - if test_have_prereq SYMLINKS - then - git branch rename-ln - fi && + git branch rename-ln && echo hello >>a && cp a d/e && @@ -260,16 +257,12 @@ test_expect_success 'setup 8' ' git add e && test_tick && git commit -m "rename a->e" && - if test_have_prereq SYMLINKS - then - git checkout rename-ln && - git mv a e && - ln -s e a && - git add a e && - test_tick && - git commit -m "rename a->e, symlink a->e" && - oln=`printf e | git hash-object --stdin` - fi + git checkout rename-ln && + git mv a e && + test_ln_s_add e a && + test_tick && + git commit -m "rename a->e, symlink a->e" && + oln=`printf e | git hash-object --stdin` ' test_expect_success 'setup 9' ' @@ -569,28 +562,25 @@ test_expect_success 'merge-recursive copy vs. rename' ' test_cmp expected actual ' -if test_have_prereq SYMLINKS -then - test_expect_failure 'merge-recursive rename vs. rename/symlink' ' - - git checkout -f rename && - git merge rename-ln && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && - ( - echo "120000 blob $oln a" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 blob $o0 e" - echo "120000 $oln 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" - echo "100644 $o0 0 d/e" - echo "100644 $o0 0 e" - ) >expected && - test_cmp expected actual - ' -fi +test_expect_failure 'merge-recursive rename vs. rename/symlink' ' + + git checkout -f rename && + git merge rename-ln && + ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( + echo "120000 blob $oln a" + echo "100644 blob $o0 b" + echo "100644 blob $o0 c" + echo "100644 blob $o0 d/e" + echo "100644 blob $o0 e" + echo "120000 $oln 0 a" + echo "100644 $o0 0 b" + echo "100644 $o0 0 c" + echo "100644 $o0 0 d/e" + echo "100644 $o0 0 e" + ) >expected && + test_cmp expected actual +' test_done diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh index 81d90b66c5..eb73c06a4e 100755 --- a/t/t3100-ls-tree-restrict.sh +++ b/t/t3100-ls-tree-restrict.sh @@ -22,20 +22,8 @@ test_expect_success \ 'setup' \ 'mkdir path2 path2/baz && echo Hi >path0 && - if test_have_prereq SYMLINKS - then - ln -s path0 path1 && - ln -s ../path1 path2/bazbo - make_expected () { - cat >expected - } - else - printf path0 > path1 && - printf ../path1 > path2/bazbo - make_expected () { - sed -e "s/120000 /100644 /" >expected - } - fi && + test_ln_s_add path0 path1 && + test_ln_s_add ../path1 path2/bazbo && echo Lo >path2/foo && echo Mi >path2/baz/b && find path? \( -type f -o -type l \) -print | @@ -51,7 +39,7 @@ test_output () { test_expect_success \ 'ls-tree plain' \ 'git ls-tree $tree >current && - make_expected <<\EOF && + cat >expected <<\EOF && 100644 blob X path0 120000 blob X path1 040000 tree X path2 @@ -61,7 +49,7 @@ EOF test_expect_success \ 'ls-tree recursive' \ 'git ls-tree -r $tree >current && - make_expected <<\EOF && + cat >expected <<\EOF && 100644 blob X path0 120000 blob X path1 100644 blob X path2/baz/b @@ -73,7 +61,7 @@ EOF test_expect_success \ 'ls-tree recursive with -t' \ 'git ls-tree -r -t $tree >current && - make_expected <<\EOF && + cat >expected <<\EOF && 100644 blob X path0 120000 blob X path1 040000 tree X path2 @@ -87,7 +75,7 @@ EOF test_expect_success \ 'ls-tree recursive with -d' \ 'git ls-tree -r -d $tree >current && - make_expected <<\EOF && + cat >expected <<\EOF && 040000 tree X path2 040000 tree X path2/baz EOF @@ -96,7 +84,7 @@ EOF test_expect_success \ 'ls-tree filtered with path' \ 'git ls-tree $tree path >current && - make_expected <<\EOF && + cat >expected <<\EOF && EOF test_output' @@ -106,7 +94,7 @@ EOF test_expect_success \ 'ls-tree filtered with path1 path0' \ 'git ls-tree $tree path1 path0 >current && - make_expected <<\EOF && + cat >expected <<\EOF && 100644 blob X path0 120000 blob X path1 EOF @@ -115,7 +103,7 @@ EOF test_expect_success \ 'ls-tree filtered with path0/' \ 'git ls-tree $tree path0/ >current && - make_expected <<\EOF && + cat >expected <<\EOF && EOF test_output' @@ -124,7 +112,7 @@ EOF test_expect_success \ 'ls-tree filtered with path2' \ 'git ls-tree $tree path2 >current && - make_expected <<\EOF && + cat >expected <<\EOF && 040000 tree X path2 EOF test_output' @@ -133,7 +121,7 @@ EOF test_expect_success \ 'ls-tree filtered with path2/' \ 'git ls-tree $tree path2/ >current && - make_expected <<\EOF && + cat >expected <<\EOF && 040000 tree X path2/baz 120000 blob X path2/bazbo 100644 blob X path2/foo @@ -145,7 +133,7 @@ EOF test_expect_success \ 'ls-tree filtered with path2/baz' \ 'git ls-tree $tree path2/baz >current && - make_expected <<\EOF && + cat >expected <<\EOF && 040000 tree X path2/baz EOF test_output' @@ -153,14 +141,14 @@ EOF test_expect_success \ 'ls-tree filtered with path2/bak' \ 'git ls-tree $tree path2/bak >current && - make_expected <<\EOF && + cat >expected <<\EOF && EOF test_output' test_expect_success \ 'ls-tree -t filtered with path2/bak' \ 'git ls-tree -t $tree path2/bak >current && - make_expected <<\EOF && + cat >expected <<\EOF && 040000 tree X path2 EOF test_output' @@ -168,7 +156,7 @@ EOF test_expect_success \ 'ls-tree with one path a prefix of the other' \ 'git ls-tree $tree path2/baz path2/bazbo >current && - make_expected <<\EOF && + cat >expected <<\EOF && 040000 tree X path2/baz 120000 blob X path2/bazbo EOF diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index b58fa1a232..ebf93b0695 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -40,13 +40,6 @@ test_expect_success 'prepare repository with topic branches' ' echo Side >>C && git add C && git commit -m "Add C" && - git checkout -b nonlinear my-topic-branch && - echo Edit >>B && - git add B && - git commit -m "Modify B" && - git merge side && - git checkout -b upstream-merged-nonlinear && - git merge master && git checkout -f my-topic-branch && git tag topic ' @@ -66,26 +59,15 @@ test_expect_success 'rebase against master' ' git rebase master ' -test_expect_success 'rebase against master twice' ' - git rebase master >out && - test_i18ngrep "Current branch my-topic-branch is up to date" out -' - -test_expect_success 'rebase against master twice with --force' ' - git rebase --force-rebase master >out && - test_i18ngrep "Current branch my-topic-branch is up to date, rebase forced" out -' - -test_expect_success 'rebase against master twice from another branch' ' - git checkout my-topic-branch^ && - git rebase master my-topic-branch >out && - test_i18ngrep "Current branch my-topic-branch is up to date" out -' - -test_expect_success 'rebase fast-forward to master' ' - git checkout my-topic-branch^ && - git rebase my-topic-branch >out && - test_i18ngrep "Fast-forwarded HEAD to my-topic-branch" out +test_expect_success 'rebase, with <onto> and <upstream> specified as :/quuxery' ' + test_when_finished "git branch -D torebase" && + git checkout -b torebase my-topic-branch^ && + upstream=$(git rev-parse ":/Add B") && + onto=$(git rev-parse ":/Add A") && + git rebase --onto $onto $upstream && + git reset --hard my-topic-branch^ && + git rebase --onto ":/Add A" ":/Add B" && + git checkout my-topic-branch ' test_expect_success 'the rebase operation should not have destroyed author information' ' @@ -106,31 +88,9 @@ test_expect_success 'rebase from ambiguous branch name' ' git rebase master ' -test_expect_success 'rebase after merge master' ' - git checkout --detach refs/tags/topic && - git branch -D topic && - git reset --hard topic && - git merge master && - git rebase master && - ! (git show | grep "^Merge:") -' - -test_expect_success 'rebase of history with merges is linearized' ' - git checkout nonlinear && - test 4 = $(git rev-list master.. | wc -l) && - git rebase master && - test 3 = $(git rev-list master.. | wc -l) -' - -test_expect_success 'rebase of history with merges after upstream merge is linearized' ' - git checkout upstream-merged-nonlinear && - test 5 = $(git rev-list master.. | wc -l) && - git rebase master && - test 3 = $(git rev-list master.. | wc -l) -' - test_expect_success 'rebase a single mode change' ' git checkout master && + git branch -D topic && echo 1 >X && git add X && test_tick && @@ -185,7 +145,7 @@ test_expect_success 'default to @{upstream} when upstream arg is missing' ' test_expect_success 'rebase -q is quiet' ' git checkout -b quiet topic && git rebase -q master >output.out 2>&1 && - test ! -s output.out + test_must_be_empty output.out ' test_expect_success 'Rebase a commit that sprinkles CRs in' ' diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh deleted file mode 100755 index 58f4823783..0000000000 --- a/t/t3401-rebase-partial.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006 Yann Dirson, based on t3400 by Amos Waterland -# - -test_description='git rebase should detect patches integrated upstream - -This test cherry-picks one local change of two into master branch, and -checks that git rebase succeeds with only the second patch in the -local branch. -' -. ./test-lib.sh - -test_expect_success 'prepare repository with topic branch' ' - test_commit A && - git checkout -b my-topic-branch && - test_commit B && - test_commit C && - git checkout -f master && - test_commit A2 A.t -' - -test_expect_success 'pick top patch from topic branch into master' ' - git cherry-pick C && - git checkout -f my-topic-branch -' - -test_debug ' - git cherry master && - git format-patch -k --stdout --full-index master >/dev/null && - gitk --all & sleep 1 -' - -test_expect_success 'rebase topic branch against new master and check git am did not get halted' ' - git rebase master && - test_path_is_missing .git/rebase-apply -' - -test_expect_success 'rebase --merge topic branch that was partially merged upstream' ' - git reset --hard C && - git rebase --merge master && - test_path_is_missing .git/rebase-merge -' - -test_expect_success 'rebase ignores empty commit' ' - git reset --hard A && - git commit --allow-empty -m empty && - test_commit D && - git rebase C && - test "$(git log --format=%s C..)" = "D" -' - -test_expect_success 'rebase --keep-empty' ' - git reset --hard D && - git rebase --keep-empty C && - test "$(git log --format=%s C..)" = "D -empty" -' - -test_expect_success 'rebase --keep-empty keeps empty even if already in upstream' ' - git reset --hard A && - git commit --allow-empty -m also-empty && - git rebase --keep-empty D && - test "$(git log --format=%s A..)" = "also-empty -D -empty" -' - -test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 79e8d3c596..49ccb38f88 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -477,19 +477,11 @@ test_expect_success 'interrupted squash works as expected (case 2)' ' test $one = $(git rev-parse HEAD~2) ' -test_expect_success 'ignore patch if in upstream' ' - HEAD=$(git rev-parse HEAD) && - git checkout -b has-cherry-picked HEAD^ && +test_expect_success '--continue tries to commit, even for "edit"' ' echo unrelated > file7 && git add file7 && test_tick && git commit -m "unrelated change" && - git cherry-pick $HEAD && - EXPECT_COUNT=1 git rebase -i $HEAD && - test $HEAD = $(git rev-parse HEAD^) -' - -test_expect_success '--continue tries to commit, even for "edit"' ' parent=$(git rev-parse HEAD^) && test_tick && FAKE_LINES="edit 1" git rebase -i HEAD^ && @@ -934,6 +926,21 @@ test_expect_success 'rebase --edit-todo can be used to modify todo' ' test L = $(git cat-file commit HEAD | sed -ne \$p) ' +test_expect_success 'rebase -i produces readable reflog' ' + git reset --hard && + git branch -f branch-reflog-test H && + git rebase -i --onto I F branch-reflog-test && + cat >expect <<-\EOF && + rebase -i (start): checkout I + rebase -i (pick): G + rebase -i (pick): H + rebase -i (finish): returning to refs/heads/branch-reflog-test + EOF + tail -n 4 .git/logs/HEAD | + sed -e "s/.* //" >actual && + test_cmp expect actual +' + test_expect_success 'rebase -i respects core.commentchar' ' git reset --hard && git checkout E^0 && @@ -947,4 +954,26 @@ test_expect_success 'rebase -i respects core.commentchar' ' test B = $(git cat-file commit HEAD^ | sed -ne \$p) ' +test_expect_success 'rebase -i, with <onto> and <upstream> specified as :/quuxery' ' + test_when_finished "git branch -D torebase" && + git checkout -b torebase branch1 && + upstream=$(git rev-parse ":/J") && + onto=$(git rev-parse ":/A") && + git rebase --onto $onto $upstream && + git reset --hard branch1 && + git rebase --onto ":/A" ":/J" && + git checkout branch1 +' + +test_expect_success 'rebase -i with --strategy and -X' ' + git checkout -b conflict-merge-use-theirs conflict-branch && + git reset --hard HEAD^ && + echo five >conflict && + echo Z >file1 && + git commit -a -m "one file conflict" && + EDITOR=true git rebase -i --strategy=recursive -Xours conflict-branch && + test $(git show conflict-branch:conflict) = $(cat conflict) && + test $(cat file1) = Z +' + test_done diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh index e6a9a0d436..0392e36d23 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh @@ -4,27 +4,17 @@ test_description='messages from rebase operation' . ./test-lib.sh -quick_one () { - echo "$1" >"file$1" && - git add "file$1" && - test_tick && - git commit -m "$1" -} +test_expect_success 'setup' ' + test_commit O fileO && + test_commit X fileX && + test_commit A fileA && + test_commit B fileB && + test_commit Y fileY && -test_expect_success setup ' - quick_one O && - git branch topic && - quick_one X && - quick_one A && - quick_one B && - quick_one Y && - - git checkout topic && - quick_one A && - quick_one B && - quick_one Z && + git checkout -b topic O && + git cherry-pick A B && + test_commit Z fileZ && git tag start - ' cat >expect <<\EOF @@ -34,12 +24,32 @@ Committed: 0003 Z EOF test_expect_success 'rebase -m' ' - git rebase -m master >report && sed -n -e "/^Already applied: /p" \ -e "/^Committed: /p" report >actual && test_cmp expect actual +' + +test_expect_success 'rebase against master twice' ' + git rebase master >out && + test_i18ngrep "Current branch topic is up to date" out +' + +test_expect_success 'rebase against master twice with --force' ' + git rebase --force-rebase master >out && + test_i18ngrep "Current branch topic is up to date, rebase forced" out +' + +test_expect_success 'rebase against master twice from another branch' ' + git checkout topic^ && + git rebase master topic >out && + test_i18ngrep "Current branch topic is up to date" out +' +test_expect_success 'rebase fast-forward to master' ' + git checkout topic^ && + git rebase topic >out && + test_i18ngrep "Fast-forwarded HEAD to topic" out ' test_expect_success 'rebase --stat' ' diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh index 6de4e2263f..2e0c36415f 100755 --- a/t/t3409-rebase-preserve-merges.sh +++ b/t/t3409-rebase-preserve-merges.sh @@ -11,14 +11,6 @@ Run "git rebase -p" and check that merges are properly carried along GIT_AUTHOR_EMAIL=bogus_email_address export GIT_AUTHOR_EMAIL -# Clone 1 (trivial merge): -# -# A1--A2 <-- origin/master -# \ \ -# B1--M <-- topic -# \ -# B2 <-- origin/topic -# # Clone 2 (conflicting merge): # # A1--A2--B3 <-- origin/master @@ -36,16 +28,6 @@ export GIT_AUTHOR_EMAIL # \--A3 <-- topic2 # \ # B2 <-- origin/topic -# -# Clone 4 (merge using second parent as base): -# -# A1--A2--B3 <-- origin/master -# \ -# B1--A3--M <-- topic -# \ / -# \--A4 <-- topic2 -# \ -# B2 <-- origin/topic test_expect_success 'setup for merge-preserving rebase' \ 'echo First > A && @@ -58,20 +40,6 @@ test_expect_success 'setup for merge-preserving rebase' \ git checkout -f master && echo Third >> A && git commit -a -m "Modify A2" && - - git clone ./. clone1 && - (cd clone1 && - git checkout -b topic origin/topic && - git merge origin/master - ) && - - git clone ./. clone4 && - ( - cd clone4 && - git checkout -b topic origin/topic && - git merge origin/master - ) && - echo Fifth > B && git add B && git commit -m "Add different B" && @@ -101,16 +69,6 @@ test_expect_success 'setup for merge-preserving rebase' \ git commit -a -m "Modify B2" ' -test_expect_success 'rebase -p fakes interactive rebase' ' - ( - cd clone1 && - git fetch && - git rebase -p origin/topic && - test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) && - test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l) - ) -' - test_expect_success '--continue works after a conflict' ' ( cd clone2 && @@ -138,15 +96,4 @@ test_expect_success 'rebase -p preserves no-ff merges' ' ) ' -test_expect_success 'rebase -p works when base inside second parent' ' - ( - cd clone4 && - git fetch && - git rebase -p HEAD^2 && - test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) && - test 1 = $(git rev-list --all --pretty=oneline | grep "Modify B" | wc -l) && - test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l) - ) -' - test_done diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index a1e86c4097..41370ab998 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -4,6 +4,8 @@ test_description='auto squash' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + test_expect_success setup ' echo 0 >file0 && git add . && @@ -193,4 +195,59 @@ test_expect_success 'use commit --squash' ' test_auto_commit_flags squash 2 ' +test_auto_fixup_fixup () { + git reset --hard base && + echo 1 >file1 && + git add -u && + test_tick && + git commit -m "$1! first" && + echo 2 >file1 && + git add -u && + test_tick && + git commit -m "$1! $2! first" && + git tag "final-$1-$2" && + test_tick && + ( + set_cat_todo_editor && + test_must_fail git rebase --autosquash -i HEAD^^^^ >actual && + cat >expected <<-EOF && + pick $(git rev-parse --short HEAD^^^) first commit + $1 $(git rev-parse --short HEAD^) $1! first + $1 $(git rev-parse --short HEAD) $1! $2! first + pick $(git rev-parse --short HEAD^^) second commit + EOF + test_cmp expected actual + ) && + git rebase --autosquash -i HEAD^^^^ && + git log --oneline >actual && + test_line_count = 3 actual + git diff --exit-code "final-$1-$2" && + test 2 = "$(git cat-file blob HEAD^:file1)" && + if test "$1" = "fixup" + then + test 1 = $(git cat-file commit HEAD^ | grep first | wc -l) + elif test "$1" = "squash" + then + test 3 = $(git cat-file commit HEAD^ | grep first | wc -l) + else + false + fi +} + +test_expect_success 'fixup! fixup!' ' + test_auto_fixup_fixup fixup fixup +' + +test_expect_success 'fixup! squash!' ' + test_auto_fixup_fixup fixup squash +' + +test_expect_success 'squash! squash!' ' + test_auto_fixup_fixup squash squash +' + +test_expect_success 'squash! fixup!' ' + test_auto_fixup_fixup squash fixup +' + test_done diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index 479cbb215f..90eb26493c 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh @@ -141,6 +141,28 @@ testrebase() { ' } +test_expect_success "rebase: fast-forward rebase" ' + test_config rebase.autostash true && + git reset --hard && + git checkout -b behind-feature-branch feature-branch~1 && + test_when_finished git branch -D behind-feature-branch && + echo dirty >>file1 && + git rebase feature-branch && + grep dirty file1 && + git checkout feature-branch +' + +test_expect_success "rebase: noop rebase" ' + test_config rebase.autostash true && + git reset --hard && + git checkout -b same-feature-branch feature-branch && + test_when_finished git branch -D same-feature-branch && + echo dirty >>file1 && + git rebase feature-branch && + grep dirty file1 && + git checkout feature-branch +' + testrebase "" .git/rebase-apply testrebase " --merge" .git/rebase-merge testrebase " --interactive" .git/rebase-merge diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh new file mode 100755 index 0000000000..9c55cba198 --- /dev/null +++ b/t/t3421-rebase-topology-linear.sh @@ -0,0 +1,350 @@ +#!/bin/sh + +test_description='basic rebase topology tests' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + +# a---b---c +# \ +# d---e +test_expect_success 'setup' ' + test_commit a && + test_commit b && + test_commit c && + git checkout b && + test_commit d && + test_commit e +' + +test_run_rebase () { + result=$1 + shift + test_expect_$result "simple rebase $*" " + reset_rebase && + git rebase $* c e && + test_cmp_rev c HEAD~2 && + test_linear_range 'd e' c.. + " +} +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i +test_run_rebase success -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* is no-op if upstream is an ancestor" " + reset_rebase && + git rebase $* b e && + test_cmp_rev e HEAD + " +} +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i +test_run_rebase success -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* -f rewrites even if upstream is an ancestor" " + reset_rebase && + git rebase $* -f b e && + ! test_cmp_rev e HEAD && + test_cmp_rev b HEAD~2 && + test_linear_range 'd e' b.. + " +} +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i +test_run_rebase failure -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* fast-forwards from ancestor of upstream" " + reset_rebase && + git rebase $* e b && + test_cmp_rev e HEAD + " +} +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i +test_run_rebase success -p + +# f +# / +# a---b---c---g---h +# \ +# d---gp--i +# +# gp = cherry-picked g +# h = reverted g +# +# Reverted patches are there for tests to be able to check if a commit +# that introduced the same change as another commit is +# dropped. Without reverted commits, we could get false positives +# because applying the patch succeeds, but simply results in no +# changes. +test_expect_success 'setup of linear history for range selection tests' ' + git checkout c && + test_commit g && + revert h g && + git checkout d && + cherry_pick gp g && + test_commit i && + git checkout b && + test_commit f +' + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* drops patches in upstream" " + reset_rebase && + git rebase $* h i && + test_cmp_rev h HEAD~2 && + test_linear_range 'd i' h.. + " +} +test_run_rebase success '' +test_run_rebase failure -m +test_run_rebase success -i +test_run_rebase success -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* can drop last patch if in upstream" " + reset_rebase && + git rebase $* h gp && + test_cmp_rev h HEAD^ && + test_linear_range 'd' h.. + " +} +test_run_rebase success '' +test_run_rebase failure -m +test_run_rebase success -i +test_run_rebase success -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* --onto drops patches in upstream" " + reset_rebase && + git rebase $* --onto f h i && + test_cmp_rev f HEAD~2 && + test_linear_range 'd i' f.. + " +} +test_run_rebase success '' +test_run_rebase failure -m +test_run_rebase success -i +test_run_rebase success -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* --onto does not drop patches in onto" " + reset_rebase && + git rebase $* --onto h f i && + test_cmp_rev h HEAD~3 && + test_linear_range 'd gp i' h.. + " +} +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i +test_run_rebase success -p + +# a---b---c---j! +# \ +# d---k!--l +# +# ! = empty +test_expect_success 'setup of linear history for empty commit tests' ' + git checkout c && + make_empty j && + git checkout d && + make_empty k && + test_commit l +' + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* drops empty commit" " + reset_rebase && + git rebase $* c l && + test_cmp_rev c HEAD~2 && + test_linear_range 'd l' c.. + " +} +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i +test_run_rebase success -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* --keep-empty" " + reset_rebase && + git rebase $* --keep-empty c l && + test_cmp_rev c HEAD~3 && + test_linear_range 'd k l' c.. + " +} +test_run_rebase success '' +test_run_rebase failure -m +test_run_rebase success -i +test_run_rebase failure -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* --keep-empty keeps empty even if already in upstream" " + reset_rebase && + git rebase $* --keep-empty j l && + test_cmp_rev j HEAD~3 && + test_linear_range 'd k l' j.. + " +} +test_run_rebase success '' +test_run_rebase failure -m +test_run_rebase failure -i +test_run_rebase failure -p + +# m +# / +# a---b---c---g +# +# x---y---bp +# +# bp = cherry-picked b +# m = reverted b +# +# Reverted patches are there for tests to be able to check if a commit +# that introduced the same change as another commit is +# dropped. Without reverted commits, we could get false positives +# because applying the patch succeeds, but simply results in no +# changes. +test_expect_success 'setup of linear history for test involving root' ' + git checkout b && + revert m b && + git checkout --orphan disjoint && + git rm -rf . && + test_commit x && + test_commit y && + cherry_pick bp b +' + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* --onto --root" " + reset_rebase && + git rebase $* --onto c --root y && + test_cmp_rev c HEAD~2 && + test_linear_range 'x y' c.. + " +} +test_run_rebase success '' +test_run_rebase failure -m +test_run_rebase success -i +test_run_rebase success -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* without --onto --root with disjoint history" " + reset_rebase && + git rebase $* c y && + test_cmp_rev c HEAD~2 && + test_linear_range 'x y' c.. + " +} +test_run_rebase success '' +test_run_rebase failure -m +test_run_rebase success -i +test_run_rebase failure -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* --onto --root drops patch in onto" " + reset_rebase && + git rebase $* --onto m --root bp && + test_cmp_rev m HEAD~2 && + test_linear_range 'x y' m.. + " +} +test_run_rebase success '' +test_run_rebase failure -m +test_run_rebase success -i +test_run_rebase success -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* --onto --root with merge-base does not go to root" " + reset_rebase && + git rebase $* --onto m --root g && + test_cmp_rev m HEAD~2 && + test_linear_range 'c g' m.. + " +} + +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i +test_run_rebase failure -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* without --onto --root with disjoint history drops patch in onto" " + reset_rebase && + git rebase $* m bp && + test_cmp_rev m HEAD~2 && + test_linear_range 'x y' m.. + " +} +test_run_rebase success '' +test_run_rebase failure -m +test_run_rebase success -i +test_run_rebase failure -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* --root on linear history is a no-op" " + reset_rebase && + git rebase $* --root c && + test_cmp_rev c HEAD + " +} +test_run_rebase failure '' +test_run_rebase failure -m +test_run_rebase failure -i +test_run_rebase failure -p + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* -f --root on linear history causes re-write" " + reset_rebase && + git rebase $* -f --root c && + ! test_cmp_rev a HEAD~2 && + test_linear_range 'a b c' HEAD + " +} +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i +test_run_rebase success -p + +test_done diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh new file mode 100755 index 0000000000..1d195fbd64 --- /dev/null +++ b/t/t3425-rebase-topology-merges.sh @@ -0,0 +1,258 @@ +#!/bin/sh + +test_description='rebase topology tests with merges' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_revision_subjects () { + expected="$1" + shift + set -- $(git log --format=%s --no-walk=unsorted "$@") + test "$expected" = "$*" +} + +# a---b-----------c +# \ \ +# d-------e \ +# \ \ \ +# n---o---w---v +# \ +# z +test_expect_success 'setup of non-linear-history' ' + test_commit a && + test_commit b && + test_commit c && + git checkout b && + test_commit d && + test_commit e + + git checkout c && + test_commit g && + revert h g && + git checkout d && + cherry_pick gp g && + test_commit i && + git checkout b && + test_commit f + + git checkout d && + test_commit n && + test_commit o && + test_merge w e && + test_merge v c && + git checkout o && + test_commit z +' + +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* after merge from upstream" " + reset_rebase && + git rebase $* e w && + test_cmp_rev e HEAD~2 && + test_linear_range 'n o' e.. + " +} +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i + +test_run_rebase () { + result=$1 + shift + expected=$1 + shift + test_expect_$result "rebase $* of non-linear history is linearized in place" " + reset_rebase && + git rebase $* d w && + test_cmp_rev d HEAD~3 && + test_linear_range "\'"$expected"\'" d.. + " +} +#TODO: make order consistent across all flavors of rebase +test_run_rebase success 'e n o' '' +test_run_rebase success 'e n o' -m +test_run_rebase success 'n o e' -i + +test_run_rebase () { + result=$1 + shift + expected=$1 + shift + test_expect_$result "rebase $* of non-linear history is linearized upstream" " + reset_rebase && + git rebase $* c w && + test_cmp_rev c HEAD~4 && + test_linear_range "\'"$expected"\'" c.. + " +} +#TODO: make order consistent across all flavors of rebase +test_run_rebase success 'd e n o' '' +test_run_rebase success 'd e n o' -m +test_run_rebase success 'd n o e' -i + +test_run_rebase () { + result=$1 + shift + expected=$1 + shift + test_expect_$result "rebase $* of non-linear history with merges after upstream merge is linearized" " + reset_rebase && + git rebase $* c v && + test_cmp_rev c HEAD~4 && + test_linear_range "\'"$expected"\'" c.. + " +} +#TODO: make order consistent across all flavors of rebase +test_run_rebase success 'd e n o' '' +test_run_rebase success 'd e n o' -m +test_run_rebase success 'd n o e' -i + +test_expect_success "rebase -p is no-op in non-linear history" " + reset_rebase && + git rebase -p d w && + test_cmp_rev w HEAD +" + +test_expect_success "rebase -p is no-op when base inside second parent" " + reset_rebase && + git rebase -p e w && + test_cmp_rev w HEAD +" + +test_expect_failure "rebase -p --root on non-linear history is a no-op" " + reset_rebase && + git rebase -p --root w && + test_cmp_rev w HEAD +" + +test_expect_success "rebase -p re-creates merge from side branch" " + reset_rebase && + git rebase -p z w && + test_cmp_rev z HEAD^ && + test_cmp_rev w^2 HEAD^2 +" + +test_expect_success "rebase -p re-creates internal merge" " + reset_rebase && + git rebase -p c w && + test_cmp_rev c HEAD~4 && + test_cmp_rev HEAD^2^ HEAD~3 && + test_revision_subjects 'd n e o w' HEAD~3 HEAD~2 HEAD^2 HEAD^ HEAD +" + +test_expect_success "rebase -p can re-create two branches on onto" " + reset_rebase && + git rebase -p --onto c d w && + test_cmp_rev c HEAD~3 && + test_cmp_rev c HEAD^2^ && + test_revision_subjects 'n e o w' HEAD~2 HEAD^2 HEAD^ HEAD +" + +# f +# / +# a---b---c---g---h +# \ +# d---gp--i +# \ \ +# e-------u +# +# gp = cherry-picked g +# h = reverted g +test_expect_success 'setup of non-linear-history for patch-equivalence tests' ' + git checkout e && + test_merge u i +' + +test_expect_success "rebase -p re-creates history around dropped commit matching upstream" " + reset_rebase && + git rebase -p h u && + test_cmp_rev h HEAD~3 && + test_cmp_rev HEAD^2^ HEAD~2 && + test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD +" + +test_expect_success "rebase -p --onto in merged history drops patches in upstream" " + reset_rebase && + git rebase -p --onto f h u && + test_cmp_rev f HEAD~3 && + test_cmp_rev HEAD^2^ HEAD~2 && + test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD +" + +test_expect_success "rebase -p --onto in merged history does not drop patches in onto" " + reset_rebase && + git rebase -p --onto h f u && + test_cmp_rev h HEAD~3 && + test_cmp_rev HEAD^2~2 HEAD~2 && + test_revision_subjects 'd gp i e u' HEAD~2 HEAD^2^ HEAD^2 HEAD^ HEAD +" + +# a---b---c---g---h +# \ +# d---gp--s +# \ \ / +# \ X +# \ / \ +# e---t +# +# gp = cherry-picked g +# h = reverted g +test_expect_success 'setup of non-linear-history for dropping whole side' ' + git checkout gp && + test_merge s e && + git checkout e && + test_merge t gp +' + +test_expect_failure "rebase -p drops merge commit when entire first-parent side is dropped" " + reset_rebase && + git rebase -p h s && + test_cmp_rev h HEAD~2 && + test_linear_range 'd e' h.. +" + +test_expect_success "rebase -p drops merge commit when entire second-parent side is dropped" " + reset_rebase && + git rebase -p h t && + test_cmp_rev h HEAD~2 && + test_linear_range 'd e' h.. +" + +# a---b---c +# \ +# d---e +# \ \ +# n---r +# \ +# o +# +# r = tree-same with n +test_expect_success 'setup of non-linear-history for empty commits' ' + git checkout n && + git merge --no-commit e && + git reset n . && + git commit -m r && + git reset --hard && + git clean -f && + git tag r +' + +test_expect_success "rebase -p re-creates empty internal merge commit" " + reset_rebase && + git rebase -p c r && + test_cmp_rev c HEAD~3 && + test_cmp_rev HEAD^2^ HEAD~2 && + test_revision_subjects 'd e n r' HEAD~2 HEAD^2 HEAD^ HEAD +" + +test_expect_success "rebase -p re-creates empty merge commit" " + reset_rebase && + git rebase -p o r && + test_cmp_rev e HEAD^2 && + test_cmp_rev o HEAD^ && + test_revision_subjects 'r' HEAD +" + +test_done diff --git a/t/t3509-cherry-pick-merge-df.sh b/t/t3509-cherry-pick-merge-df.sh index df921d1f33..a5b6a5f331 100755 --- a/t/t3509-cherry-pick-merge-df.sh +++ b/t/t3509-cherry-pick-merge-df.sh @@ -10,17 +10,15 @@ test_expect_success 'Initialize repository' ' git commit -m a ' -test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts' ' +test_expect_success 'Setup rename across paths each below D/F conflicts' ' mkdir b && - ln -s ../a b/a && - git add b && + test_ln_s_add ../a b/a && git commit -m b && git checkout -b branch && rm b/a && - mv a b/a && - ln -s b/a a && - git add . && + git mv a b/a && + test_ln_s_add b/a a && git commit -m swap && >f1 && @@ -28,7 +26,7 @@ test_expect_success SYMLINKS 'Setup rename across paths each below D/F conflicts git commit -m f1 ' -test_expect_success SYMLINKS 'Cherry-pick succeeds with rename across D/F conflicts' ' +test_expect_success 'Cherry-pick succeeds with rename across D/F conflicts' ' git reset --hard && git checkout master^0 && git cherry-pick branch diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 0c44e9f5d0..5c87b55645 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -687,4 +687,100 @@ test_expect_failure SYMLINKS 'rm across a symlinked leading path (w/ index)' ' test_path_is_file e/f ' +test_expect_success 'setup for testing rm messages' ' + >bar.txt && + >foo.txt && + git add bar.txt foo.txt +' + +test_expect_success 'rm files with different staged content' ' + cat >expect <<-\EOF && + error: the following files have staged content different from both the + file and the HEAD: + bar.txt + foo.txt + (use -f to force removal) + EOF + echo content1 >foo.txt && + echo content1 >bar.txt && + test_must_fail git rm foo.txt bar.txt 2>actual && + test_i18ncmp expect actual +' + +test_expect_success 'rm files with different staged content without hints' ' + cat >expect <<-\EOF && + error: the following files have staged content different from both the + file and the HEAD: + bar.txt + foo.txt + EOF + echo content2 >foo.txt && + echo content2 >bar.txt && + test_must_fail git -c advice.rmhints=false rm foo.txt bar.txt 2>actual && + test_i18ncmp expect actual +' + +test_expect_success 'rm file with local modification' ' + cat >expect <<-\EOF && + error: the following file has local modifications: + foo.txt + (use --cached to keep the file, or -f to force removal) + EOF + git commit -m "testing rm 3" && + echo content3 >foo.txt && + test_must_fail git rm foo.txt 2>actual && + test_i18ncmp expect actual +' + +test_expect_success 'rm file with local modification without hints' ' + cat >expect <<-\EOF && + error: the following file has local modifications: + bar.txt + EOF + echo content4 >bar.txt && + test_must_fail git -c advice.rmhints=false rm bar.txt 2>actual && + test_i18ncmp expect actual +' + +test_expect_success 'rm file with changes in the index' ' + cat >expect <<-\EOF && + error: the following file has changes staged in the index: + foo.txt + (use --cached to keep the file, or -f to force removal) + EOF + git reset --hard && + echo content5 >foo.txt && + git add foo.txt && + test_must_fail git rm foo.txt 2>actual && + test_i18ncmp expect actual +' + +test_expect_success 'rm file with changes in the index without hints' ' + cat >expect <<-\EOF && + error: the following file has changes staged in the index: + foo.txt + EOF + test_must_fail git -c advice.rmhints=false rm foo.txt 2>actual && + test_i18ncmp expect actual +' + +test_expect_success 'rm files with two different errors' ' + cat >expect <<-\EOF && + error: the following file has staged content different from both the + file and the HEAD: + foo1.txt + (use -f to force removal) + error: the following file has changes staged in the index: + bar1.txt + (use --cached to keep the file, or -f to force removal) + EOF + echo content >foo1.txt && + git add foo1.txt && + echo content6 >foo1.txt && + echo content6 >bar1.txt && + git add bar1.txt && + test_must_fail git rm bar1.txt foo1.txt 2>actual && + test_i18ncmp expect actual +' + test_done diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 874b3a6444..aab86e838b 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -30,10 +30,9 @@ test_expect_success \ *) echo fail; git ls-files --stage xfoo1; (exit 1);; esac' -test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' ' +test_expect_success 'git add: filemode=0 should not get confused by symlink' ' rm -f xfoo1 && - ln -s foo xfoo1 && - git add xfoo1 && + test_ln_s_add foo xfoo1 && case "`git ls-files --stage xfoo1`" in 120000" "*xfoo1) echo pass;; *) echo fail; git ls-files --stage xfoo1; (exit 1);; @@ -51,21 +50,19 @@ test_expect_success \ *) echo fail; git ls-files --stage xfoo2; (exit 1);; esac' -test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' ' +test_expect_success 'git add: filemode=0 should not get confused by symlink' ' rm -f xfoo2 && - ln -s foo xfoo2 && - git update-index --add xfoo2 && + test_ln_s_add foo xfoo2 && case "`git ls-files --stage xfoo2`" in 120000" "*xfoo2) echo pass;; *) echo fail; git ls-files --stage xfoo2; (exit 1);; esac ' -test_expect_success SYMLINKS \ +test_expect_success \ 'git update-index --add: Test that executable bit is not used...' \ 'git config core.filemode 0 && - ln -s xfoo2 xfoo3 && - git update-index --add xfoo3 && + test_ln_s_add xfoo2 xfoo3 && # runs git update-index --add case "`git ls-files --stage xfoo3`" in 120000" "*xfoo3) echo pass;; *) echo fail; git ls-files --stage xfoo3; (exit 1);; diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh index 37ddabba2d..38b00c37b0 100755 --- a/t/t3900-i18n-commit.sh +++ b/t/t3900-i18n-commit.sh @@ -39,6 +39,42 @@ test_expect_failure 'UTF-16 refused because of NULs' ' git commit -a -F "$TEST_DIRECTORY"/t3900/UTF-16.txt ' +test_expect_success 'UTF-8 invalid characters refused' ' + test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + echo "UTF-8 characters" >F && + printf "Commit message\n\nInvalid surrogate:\355\240\200\n" \ + >"$HOME/invalid" && + git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr && + grep "did not conform" "$HOME"/stderr +' + +test_expect_success 'UTF-8 overlong sequences rejected' ' + test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + rm -f "$HOME/stderr" "$HOME/invalid" && + echo "UTF-8 overlong" >F && + printf "\340\202\251ommit message\n\nThis is not a space:\300\240\n" \ + >"$HOME/invalid" && + git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr && + grep "did not conform" "$HOME"/stderr +' + +test_expect_success 'UTF-8 non-characters refused' ' + test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + echo "UTF-8 non-character 1" >F && + printf "Commit message\n\nNon-character:\364\217\277\276\n" \ + >"$HOME/invalid" && + git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr && + grep "did not conform" "$HOME"/stderr +' + +test_expect_success 'UTF-8 non-characters refused' ' + test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + echo "UTF-8 non-character 2." >F && + printf "Commit message\n\nNon-character:\357\267\220\n" \ + >"$HOME/invalid" && + git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr && + grep "did not conform" "$HOME"/stderr +' for H in ISO8859-1 eucJP ISO-2022-JP do diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 5dfbda7491..5d22f17ca2 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -200,17 +200,17 @@ test_expect_success 'apply -q is quiet' ' echo foo > file && git stash && git stash apply -q > output.out 2>&1 && - test ! -s output.out + test_must_be_empty output.out ' test_expect_success 'save -q is quiet' ' git stash save --quiet > output.out 2>&1 && - test ! -s output.out + test_must_be_empty output.out ' test_expect_success 'pop -q is quiet' ' git stash pop -q > output.out 2>&1 && - test ! -s output.out + test_must_be_empty output.out ' test_expect_success 'pop -q --index works and is quiet' ' @@ -219,13 +219,13 @@ test_expect_success 'pop -q --index works and is quiet' ' git stash save --quiet && git stash pop -q --index > output.out 2>&1 && test foo = "$(git show :file)" && - test ! -s output.out + test_must_be_empty output.out ' test_expect_success 'drop -q is quiet' ' git stash && git stash drop -q > output.out 2>&1 && - test ! -s output.out + test_must_be_empty output.out ' test_expect_success 'stash -k' ' @@ -336,41 +336,58 @@ test_expect_success SYMLINKS 'stash file to symlink (full stage)' ' # This test creates a commit with a symlink used for the following tests -test_expect_success SYMLINKS 'stash symlink to file' ' +test_expect_success 'stash symlink to file' ' git reset --hard && - ln -s file filelink && - git add filelink && + test_ln_s_add file filelink && git commit -m "Add symlink" && rm filelink && cp file filelink && - git stash save "symlink to file" && + git stash save "symlink to file" +' + +test_expect_success SYMLINKS 'this must have re-created the symlink' ' test -h filelink && - case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac && + case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac +' + +test_expect_success 'unstash must re-create the file' ' git stash apply && ! test -h filelink && test bar = "$(cat file)" ' -test_expect_success SYMLINKS 'stash symlink to file (stage rm)' ' +test_expect_success 'stash symlink to file (stage rm)' ' git reset --hard && git rm filelink && cp file filelink && - git stash save "symlink to file (stage rm)" && + git stash save "symlink to file (stage rm)" +' + +test_expect_success SYMLINKS 'this must have re-created the symlink' ' test -h filelink && - case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac && + case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac +' + +test_expect_success 'unstash must re-create the file' ' git stash apply && ! test -h filelink && test bar = "$(cat file)" ' -test_expect_success SYMLINKS 'stash symlink to file (full stage)' ' +test_expect_success 'stash symlink to file (full stage)' ' git reset --hard && rm filelink && cp file filelink && git add filelink && - git stash save "symlink to file (full stage)" && + git stash save "symlink to file (full stage)" +' + +test_expect_success SYMLINKS 'this must have re-created the symlink' ' test -h filelink && - case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac && + case "$(ls -l filelink)" in *" filelink -> file") :;; *) false;; esac +' + +test_expect_success 'unstash must re-create the file' ' git stash apply && ! test -h filelink && test bar = "$(cat file)" @@ -637,4 +654,41 @@ test_expect_success 'stash where working directory contains "HEAD" file' ' test_cmp output expect ' +test_expect_success 'store called with invalid commit' ' + test_must_fail git stash store foo +' + +test_expect_success 'store updates stash ref and reflog' ' + git stash clear && + git reset --hard && + echo quux >bazzy && + git add bazzy && + STASH_ID=$(git stash create) && + git reset --hard && + ! grep quux bazzy && + git stash store -m quuxery $STASH_ID && + test $(cat .git/refs/stash) = $STASH_ID && + grep $STASH_ID .git/logs/refs/stash && + git stash pop && + grep quux bazzy +' + +test_expect_success 'stash a change to turn a non-directory to a directory' ' + git reset --hard && + >testfile && + git add testfile && + git commit -m "add testfile as a regular file" && + rm testfile && + mkdir testfile && + >testfile/file && + test_must_fail git stash save "recover regular file" && + test -f testfile/file +' + +test_expect_success 'stash a change to turn a non-directory to a directory (forced)' ' + git stash save --force "recover regular file (forced)" && + ! test -f testfile/file && + test -f testfile +' + test_done diff --git a/t/t4000-diff-format.sh b/t/t4000-diff-format.sh index 6ddd46915d..8de36b7d12 100755 --- a/t/t4000-diff-format.sh +++ b/t/t4000-diff-format.sh @@ -15,17 +15,17 @@ line 3' cat path0 >path1 chmod +x path1 -test_expect_success \ - 'update-index --add two files with and without +x.' \ - 'git update-index --add path0 path1' +test_expect_success 'update-index --add two files with and without +x.' ' + git update-index --add path0 path1 +' mv path0 path0- sed -e 's/line/Line/' <path0- >path0 chmod +x path0 rm -f path1 -test_expect_success \ - 'git diff-files -p after editing work tree.' \ - 'git diff-files -p >current' +test_expect_success 'git diff-files -p after editing work tree.' ' + git diff-files -p >actual +' # that's as far as it comes if [ "$(git config --get core.filemode)" = false ] @@ -55,8 +55,38 @@ deleted file mode 100755 -line 3 EOF -test_expect_success \ - 'validate git diff-files -p output.' \ - 'compare_diff_patch current expected' +test_expect_success 'validate git diff-files -p output.' ' + compare_diff_patch expected actual +' + +test_expect_success 'git diff-files -s after editing work tree' ' + git diff-files -s >actual 2>err && + test_must_be_empty actual && + test_must_be_empty err +' + +test_expect_success 'git diff-files --no-patch as synonym for -s' ' + git diff-files --no-patch >actual 2>err && + test_must_be_empty actual && + test_must_be_empty err +' + +test_expect_success 'git diff-files --no-patch --patch shows the patch' ' + git diff-files --no-patch --patch >actual && + compare_diff_patch expected actual +' + +test_expect_success 'git diff-files --no-patch --patch-with-raw shows the patch and raw data' ' + git diff-files --no-patch --patch-with-raw >actual && + grep -q "^:100644 100755 .* 0000000000000000000000000000000000000000 M path0\$" actual && + tail -n +4 actual >actual-patch && + compare_diff_patch expected actual-patch +' + +test_expect_success 'git diff-files --patch --no-patch does not show the patch' ' + git diff-files --patch --no-patch >actual 2>err && + test_must_be_empty actual && + test_must_be_empty err +' test_done diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh index 73b4a24f5e..27e98a8f9d 100755 --- a/t/t4008-diff-break-rewrite.sh +++ b/t/t4008-diff-break-rewrite.sh @@ -99,11 +99,11 @@ test_expect_success \ 'validate result of -B -M (#4)' \ 'compare_diff_raw expected current' -test_expect_success SYMLINKS \ +test_expect_success \ 'make file0 into something completely different' \ 'rm -f file0 && - ln -s frotz file0 && - git update-index file0 file1' + test_ln_s_add frotz file0 && + git update-index file1' test_expect_success \ 'run diff with -B' \ @@ -114,7 +114,7 @@ cat >expected <<\EOF :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1 EOF -test_expect_success SYMLINKS \ +test_expect_success \ 'validate result of -B (#5)' \ 'compare_diff_raw expected current' @@ -129,7 +129,7 @@ cat >expected <<\EOF :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R file0 file1 EOF -test_expect_success SYMLINKS \ +test_expect_success \ 'validate result of -B -M (#6)' \ 'compare_diff_raw expected current' @@ -144,7 +144,7 @@ cat >expected <<\EOF :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M file1 EOF -test_expect_success SYMLINKS \ +test_expect_success \ 'validate result of -M (#7)' \ 'compare_diff_raw expected current' diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index f0d5041c11..13e7f621ab 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -9,7 +9,7 @@ test_description='Test diff of symlinks. . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh -test_expect_success SYMLINKS 'diff new symlink and file' ' +test_expect_success 'diff new symlink and file' ' cat >expected <<-\EOF && diff --git a/frotz b/frotz new file mode 120000 @@ -27,22 +27,25 @@ test_expect_success SYMLINKS 'diff new symlink and file' ' @@ -0,0 +1 @@ +xyzzy EOF - ln -s xyzzy frotz && - echo xyzzy >nitfol && + + # the empty tree git update-index && tree=$(git write-tree) && - git update-index --add frotz nitfol && + + test_ln_s_add xyzzy frotz && + echo xyzzy >nitfol && + git update-index --add nitfol && GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current && compare_diff_patch expected current ' -test_expect_success SYMLINKS 'diff unchanged symlink and file' ' +test_expect_success 'diff unchanged symlink and file' ' tree=$(git write-tree) && git update-index frotz nitfol && test -z "$(git diff-index --name-only $tree)" ' -test_expect_success SYMLINKS 'diff removed symlink and file' ' +test_expect_success 'diff removed symlink and file' ' cat >expected <<-\EOF && diff --git a/frotz b/frotz deleted file mode 120000 @@ -66,12 +69,18 @@ test_expect_success SYMLINKS 'diff removed symlink and file' ' compare_diff_patch expected current ' -test_expect_success SYMLINKS 'diff identical, but newly created symlink and file' ' +test_expect_success 'diff identical, but newly created symlink and file' ' >expected && rm -f frotz nitfol && echo xyzzy >nitfol && test-chmtime +10 nitfol && - ln -s xyzzy frotz && + if test_have_prereq SYMLINKS + then + ln -s xyzzy frotz + else + printf xyzzy >frotz + # the symlink property propagates from the index + fi && git diff-index -M -p $tree >current && compare_diff_patch expected current && @@ -80,7 +89,7 @@ test_expect_success SYMLINKS 'diff identical, but newly created symlink and file compare_diff_patch expected current ' -test_expect_success SYMLINKS 'diff different symlink and file' ' +test_expect_success 'diff different symlink and file' ' cat >expected <<-\EOF && diff --git a/frotz b/frotz index 7c465af..df1db54 120000 @@ -100,7 +109,13 @@ test_expect_success SYMLINKS 'diff different symlink and file' ' +yxyyz EOF rm -f frotz && - ln -s yxyyz frotz && + if test_have_prereq SYMLINKS + then + ln -s yxyyz frotz + else + printf yxyyz >frotz + # the symlink property propagates from the index + fi && echo yxyyz >nitfol && git diff-index -M -p $tree >current && compare_diff_patch expected current diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 58d418098d..668933bfb2 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -972,6 +972,49 @@ test_expect_success 'empty subject prefix does not have extra space' ' test_cmp expect actual ' +test_expect_success '--from=ident notices bogus ident' ' + test_must_fail git format-patch -1 --stdout --from=foo >patch +' + +test_expect_success '--from=ident replaces author' ' + git format-patch -1 --stdout --from="Me <me@example.com>" >patch && + cat >expect <<-\EOF && + From: Me <me@example.com> + + From: A U Thor <author@example.com> + + EOF + sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head && + test_cmp expect patch.head +' + +test_expect_success '--from uses committer ident' ' + git format-patch -1 --stdout --from >patch && + cat >expect <<-\EOF && + From: C O Mitter <committer@example.com> + + From: A U Thor <author@example.com> + + EOF + sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head && + test_cmp expect patch.head +' + +test_expect_success 'in-body headers trigger content encoding' ' + GIT_AUTHOR_NAME="éxötìc" test_commit exotic && + test_when_finished "git reset --hard HEAD^" && + git format-patch -1 --stdout --from >patch && + cat >expect <<-\EOF && + From: C O Mitter <committer@example.com> + Content-Type: text/plain; charset=UTF-8 + + From: éxötìc <author@example.com> + + EOF + sed -ne "/^From:/p; /^$/p; /^Content-Type/p; /^---$/q" <patch >patch.head && + test_cmp expect patch.head +' + append_signoff() { C=$(git commit-tree HEAD^^{tree} -p HEAD) && diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index cc3db1304e..3fb4b976a2 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -142,6 +142,351 @@ EOF git diff --ignore-space-at-eol > out test_expect_success 'another test, with --ignore-space-at-eol' 'test_cmp expect out' +test_expect_success 'ignore-blank-lines: only new lines' ' + test_seq 5 >x && + git update-index x && + test_seq 5 | sed "/3/i \\ +" >x && + git diff --ignore-blank-lines >out && + >expect && + test_cmp out expect +' + +test_expect_success 'ignore-blank-lines: only new lines with space' ' + test_seq 5 >x && + git update-index x && + test_seq 5 | sed "/3/i \ " >x && + git diff -w --ignore-blank-lines >out && + >expect && + test_cmp out expect +' + +test_expect_success 'ignore-blank-lines: after change' ' + cat <<-\EOF >x && + 1 + 2 + + 3 + 4 + 5 + + 6 + 7 + EOF + git update-index x && + cat <<-\EOF >x && + change + + 1 + 2 + 3 + 4 + 5 + 6 + + 7 + EOF + git diff --inter-hunk-context=100 --ignore-blank-lines >out.tmp && + cat <<-\EOF >expected && + diff --git a/x b/x + --- a/x + +++ b/x + @@ -1,6 +1,7 @@ + +change + + + 1 + 2 + - + 3 + 4 + 5 + EOF + compare_diff_patch expected out.tmp +' + +test_expect_success 'ignore-blank-lines: before change' ' + cat <<-\EOF >x && + 1 + 2 + + 3 + 4 + 5 + 6 + 7 + EOF + git update-index x && + cat <<-\EOF >x && + + 1 + 2 + 3 + 4 + 5 + + 6 + 7 + change + EOF + git diff --inter-hunk-context=100 --ignore-blank-lines >out.tmp && + cat <<-\EOF >expected && + diff --git a/x b/x + --- a/x + +++ b/x + @@ -4,5 +4,7 @@ + 3 + 4 + 5 + + + 6 + 7 + +change + EOF + compare_diff_patch expected out.tmp +' + +test_expect_success 'ignore-blank-lines: between changes' ' + cat <<-\EOF >x && + 1 + 2 + 3 + 4 + 5 + + + 6 + 7 + 8 + 9 + 10 + EOF + git update-index x && + cat <<-\EOF >x && + change + 1 + 2 + + 3 + 4 + 5 + 6 + 7 + 8 + + 9 + 10 + change + EOF + git diff --ignore-blank-lines >out.tmp && + cat <<-\EOF >expected && + diff --git a/x b/x + --- a/x + +++ b/x + @@ -1,5 +1,7 @@ + +change + 1 + 2 + + + 3 + 4 + 5 + @@ -8,5 +8,7 @@ + 6 + 7 + 8 + + + 9 + 10 + +change + EOF + compare_diff_patch expected out.tmp +' + +test_expect_success 'ignore-blank-lines: between changes (with interhunkctx)' ' + test_seq 10 >x && + git update-index x && + cat <<-\EOF >x && + change + 1 + 2 + + 3 + 4 + 5 + + 6 + 7 + 8 + 9 + + 10 + change + EOF + git diff --inter-hunk-context=2 --ignore-blank-lines >out.tmp && + cat <<-\EOF >expected && + diff --git a/x b/x + --- a/x + +++ b/x + @@ -1,10 +1,15 @@ + +change + 1 + 2 + + + 3 + 4 + 5 + + + 6 + 7 + 8 + 9 + + + 10 + +change + EOF + compare_diff_patch expected out.tmp +' + +test_expect_success 'ignore-blank-lines: scattered spaces' ' + test_seq 10 >x && + git update-index x && + cat <<-\EOF >x && + change + 1 + 2 + 3 + + 4 + + 5 + + 6 + + + 7 + + 8 + 9 + 10 + change + EOF + git diff --inter-hunk-context=4 --ignore-blank-lines >out.tmp && + cat <<-\EOF >expected && + diff --git a/x b/x + --- a/x + +++ b/x + @@ -1,3 +1,4 @@ + +change + 1 + 2 + 3 + @@ -8,3 +15,4 @@ + 8 + 9 + 10 + +change + EOF + compare_diff_patch expected out.tmp +' + +test_expect_success 'ignore-blank-lines: spaces coalesce' ' + test_seq 6 >x && + git update-index x && + cat <<-\EOF >x && + change + 1 + 2 + 3 + + 4 + + 5 + + 6 + change + EOF + git diff --inter-hunk-context=4 --ignore-blank-lines >out.tmp && + cat <<-\EOF >expected && + diff --git a/x b/x + --- a/x + +++ b/x + @@ -1,6 +1,11 @@ + +change + 1 + 2 + 3 + + + 4 + + + 5 + + + 6 + +change + EOF + compare_diff_patch expected out.tmp +' + +test_expect_success 'ignore-blank-lines: mix changes and blank lines' ' + test_seq 16 >x && + git update-index x && + cat <<-\EOF >x && + change + 1 + 2 + + 3 + 4 + 5 + change + 6 + 7 + 8 + + 9 + 10 + 11 + change + 12 + 13 + 14 + + 15 + 16 + change + EOF + git diff --ignore-blank-lines >out.tmp && + cat <<-\EOF >expected && + diff --git a/x b/x + --- a/x + +++ b/x + @@ -1,8 +1,11 @@ + +change + 1 + 2 + + + 3 + 4 + 5 + +change + 6 + 7 + 8 + @@ -9,8 +13,11 @@ + 9 + 10 + 11 + +change + 12 + 13 + 14 + + + 15 + 16 + +change + EOF + compare_diff_patch expected out.tmp +' + test_expect_success 'check mixed spaces and tabs in indent' ' # This is indented with SP HT SP. diff --git a/t/t4023-diff-rename-typechange.sh b/t/t4023-diff-rename-typechange.sh index 5d20acf436..55d549fcf4 100755 --- a/t/t4023-diff-rename-typechange.sh +++ b/t/t4023-diff-rename-typechange.sh @@ -4,44 +4,44 @@ test_description='typechange rename detection' . ./test-lib.sh -test_expect_success SYMLINKS setup ' +test_expect_success setup ' rm -f foo bar && cat "$TEST_DIRECTORY"/../COPYING >foo && - ln -s linklink bar && - git add foo bar && + test_ln_s_add linklink bar && + git add foo && git commit -a -m Initial && git tag one && - rm -f foo bar && + git rm -f foo bar && cat "$TEST_DIRECTORY"/../COPYING >bar && - ln -s linklink foo && - git add foo bar && + test_ln_s_add linklink foo && + git add bar && git commit -a -m Second && git tag two && - rm -f foo bar && + git rm -f foo bar && cat "$TEST_DIRECTORY"/../COPYING >foo && git add foo && git commit -a -m Third && git tag three && mv foo bar && - ln -s linklink foo && - git add foo bar && + test_ln_s_add linklink foo && + git add bar && git commit -a -m Fourth && git tag four && # This is purely for sanity check - rm -f foo bar && + git rm -f foo bar && cat "$TEST_DIRECTORY"/../COPYING >foo && cat "$TEST_DIRECTORY"/../Makefile >bar && git add foo bar && git commit -a -m Fifth && git tag five && - rm -f foo bar && + git rm -f foo bar && cat "$TEST_DIRECTORY"/../Makefile >foo && cat "$TEST_DIRECTORY"/../COPYING >bar && git add foo bar && @@ -50,7 +50,7 @@ test_expect_success SYMLINKS setup ' ' -test_expect_success SYMLINKS 'cross renames to be detected for regular files' ' +test_expect_success 'cross renames to be detected for regular files' ' git diff-tree five six -r --name-status -B -M | sort >actual && { @@ -61,7 +61,7 @@ test_expect_success SYMLINKS 'cross renames to be detected for regular files' ' ' -test_expect_success SYMLINKS 'cross renames to be detected for typechange' ' +test_expect_success 'cross renames to be detected for typechange' ' git diff-tree one two -r --name-status -B -M | sort >actual && { @@ -72,7 +72,7 @@ test_expect_success SYMLINKS 'cross renames to be detected for typechange' ' ' -test_expect_success SYMLINKS 'moves and renames' ' +test_expect_success 'moves and renames' ' git diff-tree three four -r --name-status -B -M | sort >actual && { diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh index 53ec330ce8..f75f46f92d 100755 --- a/t/t4030-diff-textconv.sh +++ b/t/t4030-diff-textconv.sh @@ -139,12 +139,10 @@ index 0000000..67be421 +frotz \ No newline at end of file EOF -# make a symlink the hard way that works on symlink-challenged file systems + test_expect_success 'textconv does not act on symlinks' ' - printf frotz > file && - git add file && - git ls-files -s | sed -e s/100644/120000/ | - git update-index --index-info && + rm -f file && + test_ln_s_add frotz file && git commit -m typechange && git show >diff && find_diff <diff >actual && diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 32d4a60425..1751c83307 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -1,6 +1,7 @@ #!/bin/sh # # Copyright (c) 2009 Jens Lehmann, based on t7401 by Ping Yin +# Copyright (c) 2011 Alexey Shumkin (+ non-UTF-8 commit encoding tests) # test_description='Support for verbose submodule differences in git diff @@ -10,6 +11,9 @@ This test tries to verify the sanity of the --submodule option of git diff. . ./test-lib.sh +# String "added" in German (translated with Google Translate), encoded in UTF-8, +# used in sample commit log messages in add_file() function below. +added=$(printf "hinzugef\303\274gt") add_file () { ( cd "$1" && @@ -19,7 +23,8 @@ add_file () { echo "$name" >"$name" && git add "$name" && test_tick && - git commit -m "Add $name" || exit + msg_added_iso88591=$(echo "Add $name ($added $name)" | iconv -f utf-8 -t iso8859-1) && + git -c 'i18n.commitEncoding=iso8859-1' commit -m "$msg_added_iso88591" done >/dev/null && git rev-parse --short --verify HEAD ) @@ -93,7 +98,7 @@ test_expect_success 'modified submodule(forward)' ' git diff-index -p --submodule=log HEAD >actual && cat >expected <<-EOF && Submodule sm1 $head1..$head2: - > Add foo3 + > Add foo3 ($added foo3) EOF test_cmp expected actual ' @@ -102,7 +107,7 @@ test_expect_success 'modified submodule(forward)' ' git diff --submodule=log >actual && cat >expected <<-EOF && Submodule sm1 $head1..$head2: - > Add foo3 + > Add foo3 ($added foo3) EOF test_cmp expected actual ' @@ -111,7 +116,7 @@ test_expect_success 'modified submodule(forward) --submodule' ' git diff --submodule >actual && cat >expected <<-EOF && Submodule sm1 $head1..$head2: - > Add foo3 + > Add foo3 ($added foo3) EOF test_cmp expected actual ' @@ -142,8 +147,8 @@ test_expect_success 'modified submodule(backward)' ' git diff-index -p --submodule=log HEAD >actual && cat >expected <<-EOF && Submodule sm1 $head2..$head3 (rewind): - < Add foo3 - < Add foo2 + < Add foo3 ($added foo3) + < Add foo2 ($added foo2) EOF test_cmp expected actual ' @@ -153,10 +158,10 @@ test_expect_success 'modified submodule(backward and forward)' ' git diff-index -p --submodule=log HEAD >actual && cat >expected <<-EOF && Submodule sm1 $head2...$head4: - > Add foo5 - > Add foo4 - < Add foo3 - < Add foo2 + > Add foo5 ($added foo5) + > Add foo4 ($added foo4) + < Add foo3 ($added foo3) + < Add foo2 ($added foo2) EOF test_cmp expected actual ' diff --git a/t/t4111-apply-subdir.sh b/t/t4111-apply-subdir.sh index 7c398432ba..1618a6dbc7 100755 --- a/t/t4111-apply-subdir.sh +++ b/t/t4111-apply-subdir.sh @@ -86,6 +86,20 @@ test_expect_success 'apply --index from subdir of toplevel' ' test_cmp expected sub/dir/file ' +test_expect_success 'apply half-broken patch from subdir of toplevel' ' + ( + cd sub/dir && + test_must_fail git apply <<-EOF + --- sub/dir/file + +++ sub/dir/file + @@ -1,0 +1,0 @@ + --- file_in_root + +++ file_in_root + @@ -1,0 +1,0 @@ + EOF + ) +' + test_expect_success 'apply from .git dir' ' cp postimage expected && cp preimage .git/file && diff --git a/t/t4114-apply-typechange.sh b/t/t4114-apply-typechange.sh index f12826fb09..ebadbc347f 100755 --- a/t/t4114-apply-typechange.sh +++ b/t/t4114-apply-typechange.sh @@ -9,20 +9,19 @@ test_description='git apply should not get confused with type changes. . ./test-lib.sh -test_expect_success SYMLINKS 'setup repository and commits' ' +test_expect_success 'setup repository and commits' ' echo "hello world" > foo && echo "hi planet" > bar && git update-index --add foo bar && git commit -m initial && git branch initial && rm -f foo && - ln -s bar foo && - git update-index foo && + test_ln_s_add bar foo && git commit -m "foo symlinked to bar" && git branch foo-symlinked-to-bar && - rm -f foo && + git rm -f foo && echo "how far is the sun?" > foo && - git update-index foo && + git update-index --add foo && git commit -m "foo back to file" && git branch foo-back-to-file && printf "\0" > foo && @@ -42,7 +41,7 @@ test_expect_success SYMLINKS 'setup repository and commits' ' git branch foo-baz-renamed-from-foo ' -test_expect_success SYMLINKS 'file renamed from foo to foo/baz' ' +test_expect_success 'file renamed from foo to foo/baz' ' git checkout -f initial && git diff-tree -M -p HEAD foo-baz-renamed-from-foo > patch && git apply --index < patch @@ -50,7 +49,7 @@ test_expect_success SYMLINKS 'file renamed from foo to foo/baz' ' test_debug 'cat patch' -test_expect_success SYMLINKS 'file renamed from foo/baz to foo' ' +test_expect_success 'file renamed from foo/baz to foo' ' git checkout -f foo-baz-renamed-from-foo && git diff-tree -M -p HEAD initial > patch && git apply --index < patch @@ -58,7 +57,7 @@ test_expect_success SYMLINKS 'file renamed from foo/baz to foo' ' test_debug 'cat patch' -test_expect_success SYMLINKS 'directory becomes file' ' +test_expect_success 'directory becomes file' ' git checkout -f foo-becomes-a-directory && git diff-tree -p HEAD initial > patch && git apply --index < patch @@ -66,7 +65,7 @@ test_expect_success SYMLINKS 'directory becomes file' ' test_debug 'cat patch' -test_expect_success SYMLINKS 'file becomes directory' ' +test_expect_success 'file becomes directory' ' git checkout -f initial && git diff-tree -p HEAD foo-becomes-a-directory > patch && git apply --index < patch @@ -74,7 +73,7 @@ test_expect_success SYMLINKS 'file becomes directory' ' test_debug 'cat patch' -test_expect_success SYMLINKS 'file becomes symlink' ' +test_expect_success 'file becomes symlink' ' git checkout -f initial && git diff-tree -p HEAD foo-symlinked-to-bar > patch && git apply --index < patch @@ -82,21 +81,21 @@ test_expect_success SYMLINKS 'file becomes symlink' ' test_debug 'cat patch' -test_expect_success SYMLINKS 'symlink becomes file' ' +test_expect_success 'symlink becomes file' ' git checkout -f foo-symlinked-to-bar && git diff-tree -p HEAD foo-back-to-file > patch && git apply --index < patch ' test_debug 'cat patch' -test_expect_success SYMLINKS 'binary file becomes symlink' ' +test_expect_success 'binary file becomes symlink' ' git checkout -f foo-becomes-binary && git diff-tree -p --binary HEAD foo-symlinked-to-bar > patch && git apply --index < patch ' test_debug 'cat patch' -test_expect_success SYMLINKS 'symlink becomes binary file' ' +test_expect_success 'symlink becomes binary file' ' git checkout -f foo-symlinked-to-bar && git diff-tree -p --binary HEAD foo-becomes-binary > patch && git apply --index < patch @@ -104,7 +103,7 @@ test_expect_success SYMLINKS 'symlink becomes binary file' ' test_debug 'cat patch' -test_expect_success SYMLINKS 'symlink becomes directory' ' +test_expect_success 'symlink becomes directory' ' git checkout -f foo-symlinked-to-bar && git diff-tree -p HEAD foo-becomes-a-directory > patch && git apply --index < patch @@ -112,7 +111,7 @@ test_expect_success SYMLINKS 'symlink becomes directory' ' test_debug 'cat patch' -test_expect_success SYMLINKS 'directory becomes symlink' ' +test_expect_success 'directory becomes symlink' ' git checkout -f foo-becomes-a-directory && git diff-tree -p HEAD foo-symlinked-to-bar > patch && git apply --index < patch diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh index 7674dd2ec9..872fcda6cb 100755 --- a/t/t4115-apply-symlink.sh +++ b/t/t4115-apply-symlink.sh @@ -9,18 +9,16 @@ test_description='git apply symlinks and partial files . ./test-lib.sh -test_expect_success SYMLINKS setup ' +test_expect_success setup ' - ln -s path1/path2/path3/path4/path5 link1 && - git add link? && + test_ln_s_add path1/path2/path3/path4/path5 link1 && git commit -m initial && git branch side && rm -f link? && - ln -s htap6 link1 && - git update-index link? && + test_ln_s_add htap6 link1 && git commit -m second && git diff-tree -p HEAD^ HEAD >patch && @@ -37,7 +35,7 @@ test_expect_success SYMLINKS 'apply symlink patch' ' ' -test_expect_success SYMLINKS 'apply --index symlink patch' ' +test_expect_success 'apply --index symlink patch' ' git checkout -f side && git apply --index patch && diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh index 39407376ba..70b3a06e1d 100755 --- a/t/t4122-apply-symlink-inside.sh +++ b/t/t4122-apply-symlink-inside.sh @@ -10,11 +10,11 @@ lecho () { done } -test_expect_success SYMLINKS setup ' +test_expect_success setup ' mkdir -p arch/i386/boot arch/x86_64 && lecho 1 2 3 4 5 >arch/i386/boot/Makefile && - ln -s ../i386/boot arch/x86_64/boot && + test_ln_s_add ../i386/boot arch/x86_64/boot && git add . && test_tick && git commit -m initial && @@ -31,7 +31,7 @@ test_expect_success SYMLINKS setup ' ' -test_expect_success SYMLINKS apply ' +test_expect_success apply ' git checkout test && git diff --exit-code test && @@ -40,7 +40,7 @@ test_expect_success SYMLINKS apply ' ' -test_expect_success SYMLINKS 'check result' ' +test_expect_success 'check result' ' git diff --exit-code master && git diff --exit-code --cached master && diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 12f6b027ac..5edb79a058 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -147,7 +147,7 @@ test_expect_success 'am applies patch correctly' ' git checkout first && test_tick && git am <patch1 && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git diff --exit-code second && test "$(git rev-parse second)" = "$(git rev-parse HEAD)" && test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" @@ -158,7 +158,7 @@ test_expect_success 'am applies patch e-mail not in a mbox' ' git reset --hard && git checkout first && git am patch1.eml && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git diff --exit-code second && test "$(git rev-parse second)" = "$(git rev-parse HEAD)" && test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" @@ -169,7 +169,7 @@ test_expect_success 'am applies patch e-mail not in a mbox with CRLF' ' git reset --hard && git checkout first && git am patch1-crlf.eml && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git diff --exit-code second && test "$(git rev-parse second)" = "$(git rev-parse HEAD)" && test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" @@ -180,7 +180,7 @@ test_expect_success 'am applies patch e-mail with preceding whitespace' ' git reset --hard && git checkout first && git am patch1-ws.eml && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git diff --exit-code second && test "$(git rev-parse second)" = "$(git rev-parse HEAD)" && test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" @@ -206,7 +206,7 @@ test_expect_success 'am changes committer and keeps author' ' git reset --hard && git checkout first && git am patch2 && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" && git diff --exit-code master..HEAD && git diff --exit-code master^..HEAD^ && @@ -258,7 +258,7 @@ test_expect_success 'am --keep really keeps the subject' ' git reset --hard && git checkout HEAD^ && git am --keep patch4 && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git cat-file commit HEAD >actual && grep "Re: Re: Re: \[PATCH 1/5 v2\] \[foo\] third" actual ' @@ -268,7 +268,7 @@ test_expect_success 'am --keep-non-patch really keeps the non-patch part' ' git reset --hard && git checkout HEAD^ && git am --keep-non-patch patch4 && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git cat-file commit HEAD >actual && grep "^\[foo\] third" actual ' @@ -283,7 +283,7 @@ test_expect_success 'am -3 falls back to 3-way merge' ' test_tick && git commit -m "copied stuff" && git am -3 lorem-move.patch && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git diff --exit-code lorem ' @@ -297,7 +297,7 @@ test_expect_success 'am -3 -p0 can read --no-prefix patch' ' test_tick && git commit -m "copied stuff" && git am -3 -p0 lorem-zero.patch && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git diff --exit-code lorem ' @@ -307,7 +307,7 @@ test_expect_success 'am can rename a file' ' git reset --hard && git checkout lorem^0 && git am rename.patch && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git update-index --refresh && git diff --exit-code rename ' @@ -318,7 +318,7 @@ test_expect_success 'am -3 can rename a file' ' git reset --hard && git checkout lorem^0 && git am -3 rename.patch && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git update-index --refresh && git diff --exit-code rename ' @@ -329,7 +329,7 @@ test_expect_success 'am -3 can rename a file after falling back to 3-way merge' git reset --hard && git checkout lorem^0 && git am -3 rename-add.patch && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git update-index --refresh && git diff --exit-code rename ' @@ -358,11 +358,17 @@ test_expect_success 'am pauses on conflict' ' test_expect_success 'am --skip works' ' echo goodbye >expected && git am --skip && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git diff --exit-code lorem2^^ -- file && test_cmp expected another ' +test_expect_success 'am --abort removes a stray directory' ' + mkdir .git/rebase-apply && + git am --abort && + test_path_is_missing .git/rebase-apply +' + test_expect_success 'am --resolved works' ' echo goodbye >expected && rm -fr .git/rebase-apply && @@ -373,7 +379,7 @@ test_expect_success 'am --resolved works' ' echo resolved >>file && git add file && git am --resolved && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && test_cmp expected another ' @@ -382,7 +388,7 @@ test_expect_success 'am takes patches from a Pine mailbox' ' git reset --hard && git checkout first && cat pine patch1 | git am && - ! test -d .git/rebase-apply && + test_path_is_missing .git/rebase-apply && git diff --exit-code master^..HEAD ' @@ -391,7 +397,7 @@ test_expect_success 'am fails on mail without patch' ' git reset --hard && test_must_fail git am <failmail && git am --abort && - ! test -d .git/rebase-apply + test_path_is_missing .git/rebase-apply ' test_expect_success 'am fails on empty patch' ' @@ -400,7 +406,7 @@ test_expect_success 'am fails on empty patch' ' echo "---" >>failmail && test_must_fail git am <failmail && git am --skip && - ! test -d .git/rebase-apply + test_path_is_missing .git/rebase-apply ' test_expect_success 'am works from stdin in subdirectory' ' diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh index 842b7549ec..baa4685dcc 100755 --- a/t/t4203-mailmap.sh +++ b/t/t4203-mailmap.sh @@ -13,6 +13,11 @@ fuzz_blame () { } test_expect_success setup ' + cat >contacts <<-\EOF && + A U Thor <author@example.com> + nick1 <bugs@company.xx> + EOF + echo one >one && git add one && test_tick && @@ -23,6 +28,44 @@ test_expect_success setup ' git commit --author "nick1 <bugs@company.xx>" -m second ' +test_expect_success 'check-mailmap no arguments' ' + test_must_fail git check-mailmap +' + +test_expect_success 'check-mailmap arguments' ' + cat >expect <<-\EOF && + A U Thor <author@example.com> + nick1 <bugs@company.xx> + EOF + git check-mailmap \ + "A U Thor <author@example.com>" \ + "nick1 <bugs@company.xx>" >actual && + test_cmp expect actual +' + +test_expect_success 'check-mailmap --stdin' ' + cat >expect <<-\EOF && + A U Thor <author@example.com> + nick1 <bugs@company.xx> + EOF + git check-mailmap --stdin <contacts >actual && + test_cmp expect actual +' + +test_expect_success 'check-mailmap --stdin arguments' ' + cat >expect <<-\EOF && + Internal Guy <bugs@company.xy> + EOF + cat <contacts >>expect && + git check-mailmap --stdin "Internal Guy <bugs@company.xy>" \ + <contacts >actual && + test_cmp expect actual +' + +test_expect_success 'check-mailmap bogus contact' ' + test_must_fail git check-mailmap bogus +' + cat >expect <<\EOF A U Thor (1): initial @@ -247,6 +290,24 @@ test_expect_success 'cleanup after mailmap.blob tests' ' rm -f .mailmap ' +test_expect_success 'single-character name' ' + echo " 1 A <author@example.com>" >expect && + echo " 1 nick1 <bugs@company.xx>" >>expect && + echo "A <author@example.com>" >.mailmap && + test_when_finished "rm .mailmap" && + git shortlog -es HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'preserve canonical email case' ' + echo " 1 A U Thor <AUTHOR@example.com>" >expect && + echo " 1 nick1 <bugs@company.xx>" >>expect && + echo "<AUTHOR@example.com> <author@example.com>" >.mailmap && + test_when_finished "rm .mailmap" && + git shortlog -es HEAD >actual && + test_cmp expect actual +' + # Extended mailmap configurations should give us the following output for shortlog cat >expect <<\EOF A U Thor <author@example.com> (1): diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 26fbfde4a3..fb00041139 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -1,20 +1,38 @@ #!/bin/sh # # Copyright (c) 2010, Will Palmer +# Copyright (c) 2011, Alexey Shumkin (+ non-UTF-8 commit encoding tests) # test_description='Test pretty formats' . ./test-lib.sh +sample_utf8_part=$(printf "f\303\244ng") + +commit_msg () { + # String "initial. initial" partly in German + # (translated with Google Translate), + # encoded in UTF-8, used as a commit log message below. + msg="initial. an${sample_utf8_part}lich\n" + if test -n "$1" + then + printf "$msg" | iconv -f utf-8 -t "$1" + else + printf "$msg" + fi +} + test_expect_success 'set up basic repos' ' >foo && >bar && git add foo && test_tick && - git commit -m initial && + git config i18n.commitEncoding iso8859-1 && + git commit -m "$(commit_msg iso8859-1)" && git add bar && test_tick && - git commit -m "add bar" + git commit -m "add bar" && + git config --unset i18n.commitEncoding ' test_expect_success 'alias builtin format' ' @@ -38,6 +56,20 @@ test_expect_success 'alias user-defined format' ' test_cmp expected actual ' +test_expect_success 'alias user-defined tformat with %s (iso8859-1 encoding)' ' + git config i18n.logOutputEncoding iso8859-1 && + git log --oneline >expected-s && + git log --pretty="tformat:%h %s" >actual-s && + git config --unset i18n.logOutputEncoding && + test_cmp expected-s actual-s +' + +test_expect_success 'alias user-defined tformat with %s (utf-8 encoding)' ' + git log --oneline >expected-s && + git log --pretty="tformat:%h %s" >actual-s && + test_cmp expected-s actual-s +' + test_expect_success 'alias user-defined tformat' ' git log --pretty="tformat:%h" >expected && git config pretty.test-alias "tformat:%h" && @@ -72,13 +104,13 @@ test_expect_success 'alias loop' ' ' test_expect_success 'NUL separation' ' - printf "add bar\0initial" >expected && + printf "add bar\0$(commit_msg)" >expected && git log -z --pretty="format:%s" >actual && test_cmp expected actual ' test_expect_success 'NUL termination' ' - printf "add bar\0initial\0" >expected && + printf "add bar\0$(commit_msg)\0" >expected && git log -z --pretty="tformat:%s" >actual && test_cmp expected actual ' @@ -86,7 +118,7 @@ test_expect_success 'NUL termination' ' test_expect_success 'NUL separation with --stat' ' stat0_part=$(git diff --stat HEAD^ HEAD) && stat1_part=$(git diff-tree --no-commit-id --stat --root HEAD^) && - printf "add bar\n$stat0_part\n\0initial\n$stat1_part\n" >expected && + printf "add bar\n$stat0_part\n\0$(commit_msg)\n$stat1_part\n" >expected && git log -z --stat --pretty="format:%s" >actual && test_i18ncmp expected actual ' @@ -94,25 +126,29 @@ test_expect_success 'NUL separation with --stat' ' test_expect_failure 'NUL termination with --stat' ' stat0_part=$(git diff --stat HEAD^ HEAD) && stat1_part=$(git diff-tree --no-commit-id --stat --root HEAD^) && - printf "add bar\n$stat0_part\n\0initial\n$stat1_part\n\0" >expected && + printf "add bar\n$stat0_part\n\0$(commit_msg)\n$stat1_part\n0" >expected && git log -z --stat --pretty="tformat:%s" >actual && test_i18ncmp expected actual ' test_expect_success 'setup more commits' ' test_commit "message one" one one message-one && - test_commit "message two" two two message-two + test_commit "message two" two two message-two && + head1=$(git rev-parse --verify --short HEAD~0) && + head2=$(git rev-parse --verify --short HEAD~1) && + head3=$(git rev-parse --verify --short HEAD~2) && + head4=$(git rev-parse --verify --short HEAD~3) ' test_expect_success 'left alignment formatting' ' git log --pretty="format:%<(40)%s" >actual && # complete the incomplete line at the end echo >>actual && - qz_to_tab_space <<\EOF >expected && + qz_to_tab_space <<EOF >expected && message two Z message one Z add bar Z -initial Z +$(commit_msg) Z EOF test_cmp expected actual ' @@ -121,11 +157,11 @@ test_expect_success 'left alignment formatting at the nth column' ' git log --pretty="format:%h %<|(40)%s" >actual && # complete the incomplete line at the end echo >>actual && - qz_to_tab_space <<\EOF >expected && -fa33ab1 message two Z -7cd6c63 message one Z -1711bf9 add bar Z -af20c06 initial Z + qz_to_tab_space <<EOF >expected && +$head1 message two Z +$head2 message one Z +$head3 add bar Z +$head4 $(commit_msg) Z EOF test_cmp expected actual ' @@ -134,11 +170,11 @@ test_expect_success 'left alignment formatting with no padding' ' git log --pretty="format:%<(1)%s" >actual && # complete the incomplete line at the end echo >>actual && - cat <<\EOF >expected && + cat <<EOF >expected && message two message one add bar -initial +$(commit_msg) EOF test_cmp expected actual ' @@ -147,11 +183,11 @@ test_expect_success 'left alignment formatting with trunc' ' git log --pretty="format:%<(10,trunc)%s" >actual && # complete the incomplete line at the end echo >>actual && - qz_to_tab_space <<\EOF >expected && + qz_to_tab_space <<EOF >expected && message .. message .. add bar Z -initial Z +initial... EOF test_cmp expected actual ' @@ -160,11 +196,11 @@ test_expect_success 'left alignment formatting with ltrunc' ' git log --pretty="format:%<(10,ltrunc)%s" >actual && # complete the incomplete line at the end echo >>actual && - qz_to_tab_space <<\EOF >expected && + qz_to_tab_space <<EOF >expected && ..sage two ..sage one add bar Z -initial Z +..${sample_utf8_part}lich EOF test_cmp expected actual ' @@ -173,11 +209,11 @@ test_expect_success 'left alignment formatting with mtrunc' ' git log --pretty="format:%<(10,mtrunc)%s" >actual && # complete the incomplete line at the end echo >>actual && - qz_to_tab_space <<\EOF >expected && + qz_to_tab_space <<EOF >expected && mess.. two mess.. one add bar Z -initial Z +init..lich EOF test_cmp expected actual ' @@ -186,11 +222,11 @@ test_expect_success 'right alignment formatting' ' git log --pretty="format:%>(40)%s" >actual && # complete the incomplete line at the end echo >>actual && - qz_to_tab_space <<\EOF >expected && + qz_to_tab_space <<EOF >expected && Z message two Z message one Z add bar -Z initial +Z $(commit_msg) EOF test_cmp expected actual ' @@ -199,11 +235,11 @@ test_expect_success 'right alignment formatting at the nth column' ' git log --pretty="format:%h %>|(40)%s" >actual && # complete the incomplete line at the end echo >>actual && - qz_to_tab_space <<\EOF >expected && -fa33ab1 message two -7cd6c63 message one -1711bf9 add bar -af20c06 initial + qz_to_tab_space <<EOF >expected && +$head1 message two +$head2 message one +$head3 add bar +$head4 $(commit_msg) EOF test_cmp expected actual ' @@ -212,11 +248,11 @@ test_expect_success 'right alignment formatting with no padding' ' git log --pretty="format:%>(1)%s" >actual && # complete the incomplete line at the end echo >>actual && - cat <<\EOF >expected && + cat <<EOF >expected && message two message one add bar -initial +$(commit_msg) EOF test_cmp expected actual ' @@ -225,11 +261,11 @@ test_expect_success 'center alignment formatting' ' git log --pretty="format:%><(40)%s" >actual && # complete the incomplete line at the end echo >>actual && - qz_to_tab_space <<\EOF >expected && + qz_to_tab_space <<EOF >expected && Z message two Z Z message one Z Z add bar Z -Z initial Z +Z $(commit_msg) Z EOF test_cmp expected actual ' @@ -238,11 +274,11 @@ test_expect_success 'center alignment formatting at the nth column' ' git log --pretty="format:%h %><|(40)%s" >actual && # complete the incomplete line at the end echo >>actual && - qz_to_tab_space <<\EOF >expected && -fa33ab1 message two Z -7cd6c63 message one Z -1711bf9 add bar Z -af20c06 initial Z + qz_to_tab_space <<EOF >expected && +$head1 message two Z +$head2 message one Z +$head3 add bar Z +$head4 $(commit_msg) Z EOF test_cmp expected actual ' @@ -251,11 +287,11 @@ test_expect_success 'center alignment formatting with no padding' ' git log --pretty="format:%><(1)%s" >actual && # complete the incomplete line at the end echo >>actual && - cat <<\EOF >expected && + cat <<EOF >expected && message two message one add bar -initial +$(commit_msg) EOF test_cmp expected actual ' @@ -265,11 +301,11 @@ test_expect_success 'left/right alignment formatting with stealing' ' git log --pretty="format:%<(10,trunc)%s%>>(10,ltrunc)% an" >actual && # complete the incomplete line at the end echo >>actual && - cat <<\EOF >expected && + cat <<EOF >expected && short long long long message .. A U Thor add bar A U Thor -initial A U Thor +initial... A U Thor EOF test_cmp expected actual ' diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh index 7776f93e3d..7665d6785c 100755 --- a/t/t4211-line-log.sh +++ b/t/t4211-line-log.sh @@ -64,4 +64,17 @@ test_bad_opts "-L 1,1000:b.c" "has only.*lines" test_bad_opts "-L :b.c" "argument.*not of the form" test_bad_opts "-L :foo:b.c" "no match" +# There is a separate bug when an empty -L range is the first -L encountered, +# thus to demonstrate this particular bug, the empty -L range must follow a +# non-empty -L range. +test_expect_success '-L {empty-range} (any -L)' ' + n=$(expr $(wc -l <b.c) + 1) && + git log -L1,1:b.c -L$n:b.c +' + +test_expect_success '-L {empty-range} (first -L)' ' + n=$(expr $(wc -l <b.c) + 1) && + git log -L$n:b.c +' + test_done diff --git a/t/t4211/expect.multiple-superset b/t/t4211/expect.multiple-superset index a1f5bc49c8..d930b6eec4 100644 --- a/t/t4211/expect.multiple-superset +++ b/t/t4211/expect.multiple-superset @@ -1,3 +1,100 @@ +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -4,19 +4,21 @@ + long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* + * This is only an example! + */ + + int main () + { + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} ++ ++/* incomplete lines are bad! */ + +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -4,19 +4,19 @@ + long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* + * This is only an example! + */ + + int main () + { + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:41 2013 +0100 + + touch comment + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -3,19 +3,19 @@ + long f(long x) + { + int s = 0; + while (x) { + x >>= 1; + s++; + } + return s; + } + + /* +- * A comment. ++ * This is only an example! + */ + + int main () + { + printf("%ld\n", f(15)); + return 0; + } + commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 Author: Thomas Rast <trast@student.ethz.ch> Date: Thu Feb 28 10:45:16 2013 +0100 @@ -7,7 +104,7 @@ Date: Thu Feb 28 10:45:16 2013 +0100 diff --git a/a.c b/a.c --- a/a.c +++ b/a.c -@@ -3,9 +3,9 @@ +@@ -3,19 +3,19 @@ -int f(int x) +long f(long x) { @@ -18,6 +115,17 @@ diff --git a/a.c b/a.c } return s; } + + /* + * A comment. + */ + + int main () + { +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } commit f04fb20f2c77850996cba739709acc6faecc58f7 Author: Thomas Rast <trast@student.ethz.ch> @@ -28,7 +136,7 @@ Date: Thu Feb 28 10:44:55 2013 +0100 diff --git a/a.c b/a.c --- a/a.c +++ b/a.c -@@ -3,8 +3,9 @@ +@@ -3,18 +3,19 @@ int f(int x) { int s = 0; @@ -38,6 +146,16 @@ diff --git a/a.c b/a.c } + return s; } + + /* + * A comment. + */ + + int main () + { + printf("%d\n", f(15)); + return 0; + } commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a Author: Thomas Rast <trast@student.ethz.ch> @@ -48,7 +166,7 @@ Date: Thu Feb 28 10:44:48 2013 +0100 diff --git a/a.c b/a.c --- /dev/null +++ b/a.c -@@ -0,0 +3,8 @@ +@@ -0,0 +3,18 @@ +int f(int x) +{ + int s = 0; @@ -57,3 +175,13 @@ diff --git a/a.c b/a.c + s++; + } +} ++ ++/* ++ * A comment. ++ */ ++ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh index 432f98c357..1afa0d5c44 100755 --- a/t/t5150-request-pull.sh +++ b/t/t5150-request-pull.sh @@ -80,12 +80,12 @@ test_expect_success 'setup: two scripts for reading pull requests' ' cat <<-EOT >fuzz.sed #!/bin/sed -nf + s/$downstream_url_for_sed/URL/g s/$_x40/OBJECT_NAME/g s/A U Thor/AUTHOR/g s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g s/ [^ ].*/ SUBJECT/g s/ [^ ].* (DATE)/ SUBJECT (DATE)/g - s/$downstream_url_for_sed/URL/g s/for-upstream/BRANCH/g s/mnemonic.txt/FILENAME/g s/^version [0-9]/VERSION/ diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh index 5b1250f0d2..35926debe3 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -51,7 +51,7 @@ do_corrupt_object() { ofs=`git show-index < ${pack}.idx | grep $1 | cut -f1 -d" "` && ofs=$(($ofs + $2)) && chmod +w ${pack}.pack && - dd of=${pack}.pack count=1 bs=1 conv=notrunc seek=$ofs && + dd of=${pack}.pack bs=1 conv=notrunc seek=$ofs && test_must_fail git verify-pack ${pack}.pack } @@ -276,6 +276,33 @@ test_expect_success \ git cat-file blob $blob_3 > /dev/null' test_expect_success \ + 'corruption of delta base reference pointing to wrong object' \ + 'create_new_pack --delta-base-offset && + git prune-packed && + printf "\220\033" | do_corrupt_object $blob_3 2 && + git cat-file blob $blob_1 >/dev/null && + git cat-file blob $blob_2 >/dev/null && + test_must_fail git cat-file blob $blob_3 >/dev/null' + +test_expect_success \ + '... but having a loose copy allows for full recovery' \ + 'mv ${pack}.idx tmp && + git hash-object -t blob -w file_3 && + mv tmp ${pack}.idx && + git cat-file blob $blob_1 > /dev/null && + git cat-file blob $blob_2 > /dev/null && + git cat-file blob $blob_3 > /dev/null' + +test_expect_success \ + '... and then a repack "clears" the corruption' \ + 'do_repack --delta-base-offset --no-reuse-delta && + git prune-packed && + git verify-pack ${pack}.pack && + git cat-file blob $blob_1 > /dev/null && + 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 && diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index dd10ff053c..8f6e3922dc 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -42,107 +42,104 @@ check_tracking_branch () { } test_expect_success setup ' - setup_repository one && setup_repository two && ( - cd two && git branch another + cd two && + git branch another ) && git clone one test - ' test_expect_success C_LOCALE_OUTPUT 'remote information for the origin' ' -( - cd test && - tokens_match origin "$(git remote)" && - check_remote_track origin master side && - check_tracking_branch origin HEAD master side -) + ( + cd test && + tokens_match origin "$(git remote)" && + check_remote_track origin master side && + check_tracking_branch origin HEAD master side + ) ' test_expect_success 'add another remote' ' -( - cd test && - git remote add -f second ../two && - tokens_match "origin second" "$(git remote)" && - check_tracking_branch second master side another && - git for-each-ref "--format=%(refname)" refs/remotes | - sed -e "/^refs\/remotes\/origin\//d" \ - -e "/^refs\/remotes\/second\//d" >actual && - >expect && - test_cmp expect actual -) + ( + cd test && + git remote add -f second ../two && + tokens_match "origin second" "$(git remote)" && + check_tracking_branch second master side another && + git for-each-ref "--format=%(refname)" refs/remotes | + sed -e "/^refs\/remotes\/origin\//d" \ + -e "/^refs\/remotes\/second\//d" >actual && + >expect && + test_cmp expect actual + ) ' -test_expect_success C_LOCALE_OUTPUT 'check remote tracking' ' -( - cd test && - check_remote_track origin master side && - check_remote_track second master side another -) +test_expect_success C_LOCALE_OUTPUT 'check remote-tracking' ' + ( + cd test && + check_remote_track origin master side && + check_remote_track second master side another + ) ' test_expect_success 'remote forces tracking branches' ' -( - cd test && - case `git config remote.second.fetch` in - +*) true ;; - *) false ;; - esac -) + ( + cd test && + case `git config remote.second.fetch` in + +*) true ;; + *) false ;; + esac + ) ' test_expect_success 'remove remote' ' -( - cd test && - git symbolic-ref refs/remotes/second/HEAD refs/remotes/second/master && - git remote rm second -) + ( + cd test && + git symbolic-ref refs/remotes/second/HEAD refs/remotes/second/master && + git remote rm second + ) ' test_expect_success C_LOCALE_OUTPUT 'remove remote' ' -( - cd test && - tokens_match origin "$(git remote)" && - check_remote_track origin master side && - git for-each-ref "--format=%(refname)" refs/remotes | - sed -e "/^refs\/remotes\/origin\//d" >actual && - >expect && - test_cmp expect actual -) + ( + cd test && + tokens_match origin "$(git remote)" && + check_remote_track origin master side && + git for-each-ref "--format=%(refname)" refs/remotes | + sed -e "/^refs\/remotes\/origin\//d" >actual && + >expect && + test_cmp expect actual + ) ' test_expect_success 'remove remote protects local branches' ' -( - cd test && - { cat >expect1 <<EOF -Note: A branch outside the refs/remotes/ hierarchy was not removed; -to delete it, use: - git branch -d master -EOF - } && - { cat >expect2 <<EOF -Note: Some branches outside the refs/remotes/ hierarchy were not removed; -to delete them, use: - git branch -d foobranch - git branch -d master -EOF - } && - git tag footag && - git config --add remote.oops.fetch "+refs/*:refs/*" && - git remote remove oops 2>actual1 && - git branch foobranch && - git config --add remote.oops.fetch "+refs/*:refs/*" && - git remote rm oops 2>actual2 && - git branch -d foobranch && - git tag -d footag && - test_i18ncmp expect1 actual1 && - test_i18ncmp expect2 actual2 -) -' - -cat > test/expect << EOF + ( + cd test && + cat >expect1 <<-\EOF && + Note: A branch outside the refs/remotes/ hierarchy was not removed; + to delete it, use: + git branch -d master + EOF + cat >expect2 <<-\EOF && + Note: Some branches outside the refs/remotes/ hierarchy were not removed; + to delete them, use: + git branch -d foobranch + git branch -d master + EOF + git tag footag && + git config --add remote.oops.fetch "+refs/*:refs/*" && + git remote remove oops 2>actual1 && + git branch foobranch && + git config --add remote.oops.fetch "+refs/*:refs/*" && + git remote rm oops 2>actual2 && + git branch -d foobranch && + git tag -d footag && + test_i18ncmp expect1 actual1 && + test_i18ncmp expect2 actual2 + ) +' + +cat >test/expect <<EOF * remote origin Fetch URL: $(pwd)/one Push URL: $(pwd)/one @@ -172,36 +169,40 @@ cat > test/expect << EOF EOF test_expect_success 'show' ' - (cd test && - git config --add remote.origin.fetch refs/heads/master:refs/heads/upstream && - git fetch && - git checkout -b ahead origin/master && - echo 1 >> file && - test_tick && - git commit -m update file && - git checkout master && - git branch --track octopus origin/master && - git branch --track rebase origin/master && - git branch -d -r origin/master && - git config --add remote.two.url ../two && - git config --add remote.two.pushurl ../three && - git config branch.rebase.rebase true && - git config branch.octopus.merge "topic-a topic-b topic-c" && - (cd ../one && - echo 1 > file && - test_tick && - git commit -m update file) && - git config --add remote.origin.push : && - git config --add remote.origin.push refs/heads/master:refs/heads/upstream && - git config --add remote.origin.push +refs/tags/lastbackup && - git config --add remote.two.push +refs/heads/ahead:refs/heads/master && - git config --add remote.two.push refs/heads/master:refs/heads/another && - git remote show origin two > output && - git branch -d rebase octopus && - test_i18ncmp expect output) -' - -cat > test/expect << EOF + ( + cd test && + git config --add remote.origin.fetch refs/heads/master:refs/heads/upstream && + git fetch && + git checkout -b ahead origin/master && + echo 1 >>file && + test_tick && + git commit -m update file && + git checkout master && + git branch --track octopus origin/master && + git branch --track rebase origin/master && + git branch -d -r origin/master && + git config --add remote.two.url ../two && + git config --add remote.two.pushurl ../three && + git config branch.rebase.rebase true && + git config branch.octopus.merge "topic-a topic-b topic-c" && + ( + cd ../one && + echo 1 >file && + test_tick && + git commit -m update file + ) && + git config --add remote.origin.push : && + git config --add remote.origin.push refs/heads/master:refs/heads/upstream && + git config --add remote.origin.push +refs/tags/lastbackup && + git config --add remote.two.push +refs/heads/ahead:refs/heads/master && + git config --add remote.two.push refs/heads/master:refs/heads/another && + git remote show origin two >output && + git branch -d rebase octopus && + test_i18ncmp expect output + ) +' + +cat >test/expect <<EOF * remote origin Fetch URL: $(pwd)/one Push URL: $(pwd)/one @@ -219,152 +220,187 @@ cat > test/expect << EOF EOF test_expect_success 'show -n' ' - (mv one one.unreachable && - cd test && - git remote show -n origin > output && - mv ../one.unreachable ../one && - test_i18ncmp expect output) + mv one one.unreachable && + ( + cd test && + git remote show -n origin >output && + mv ../one.unreachable ../one && + test_i18ncmp expect output + ) ' test_expect_success 'prune' ' - (cd one && - git branch -m side side2) && - (cd test && - git fetch origin && - git remote prune origin && - git rev-parse refs/remotes/origin/side2 && - test_must_fail git rev-parse refs/remotes/origin/side) + ( + cd one && + git branch -m side side2 + ) && + ( + cd test && + git fetch origin && + git remote prune origin && + git rev-parse refs/remotes/origin/side2 && + test_must_fail git rev-parse refs/remotes/origin/side + ) ' test_expect_success 'set-head --delete' ' - (cd test && - git symbolic-ref refs/remotes/origin/HEAD && - git remote set-head --delete origin && - test_must_fail git symbolic-ref refs/remotes/origin/HEAD) + ( + cd test && + git symbolic-ref refs/remotes/origin/HEAD && + git remote set-head --delete origin && + test_must_fail git symbolic-ref refs/remotes/origin/HEAD + ) ' test_expect_success 'set-head --auto' ' - (cd test && - git remote set-head --auto origin && - echo refs/remotes/origin/master >expect && - git symbolic-ref refs/remotes/origin/HEAD >output && - test_cmp expect output + ( + cd test && + git remote set-head --auto origin && + echo refs/remotes/origin/master >expect && + git symbolic-ref refs/remotes/origin/HEAD >output && + test_cmp expect output ) ' -cat >test/expect <<EOF +cat >test/expect <<\EOF error: Multiple remote HEAD branches. Please choose one explicitly with: git remote set-head two another git remote set-head two master EOF test_expect_success 'set-head --auto fails w/multiple HEADs' ' - (cd test && - test_must_fail git remote set-head --auto two >output 2>&1 && - test_i18ncmp expect output) + ( + cd test && + test_must_fail git remote set-head --auto two >output 2>&1 && + test_i18ncmp expect output + ) ' -cat >test/expect <<EOF +cat >test/expect <<\EOF refs/remotes/origin/side2 EOF test_expect_success 'set-head explicit' ' - (cd test && - git remote set-head origin side2 && - git symbolic-ref refs/remotes/origin/HEAD >output && - git remote set-head origin master && - test_cmp expect output) + ( + cd test && + git remote set-head origin side2 && + git symbolic-ref refs/remotes/origin/HEAD >output && + git remote set-head origin master && + test_cmp expect output + ) ' -cat > test/expect << EOF +cat >test/expect <<EOF Pruning origin URL: $(pwd)/one * [would prune] origin/side2 EOF test_expect_success 'prune --dry-run' ' - (cd one && - git branch -m side2 side) && - (cd test && - git remote prune --dry-run origin > output && - git rev-parse refs/remotes/origin/side2 && - test_must_fail git rev-parse refs/remotes/origin/side && - (cd ../one && - git branch -m side side2) && - test_i18ncmp expect output) + ( + cd one && + git branch -m side2 side) && + ( + cd test && + git remote prune --dry-run origin >output && + git rev-parse refs/remotes/origin/side2 && + test_must_fail git rev-parse refs/remotes/origin/side && + ( + cd ../one && + git branch -m side side2) && + test_i18ncmp expect output + ) ' test_expect_success 'add --mirror && prune' ' - (mkdir mirror && - cd mirror && - git init --bare && - git remote add --mirror -f origin ../one) && - (cd one && - git branch -m side2 side) && - (cd mirror && - git rev-parse --verify refs/heads/side2 && - test_must_fail git rev-parse --verify refs/heads/side && - git fetch origin && - git remote prune origin && - test_must_fail git rev-parse --verify refs/heads/side2 && - git rev-parse --verify refs/heads/side) + mkdir mirror && + ( + cd mirror && + git init --bare && + git remote add --mirror -f origin ../one + ) && + ( + cd one && + git branch -m side2 side + ) && + ( + cd mirror && + git rev-parse --verify refs/heads/side2 && + test_must_fail git rev-parse --verify refs/heads/side && + git fetch origin && + git remote prune origin && + test_must_fail git rev-parse --verify refs/heads/side2 && + git rev-parse --verify refs/heads/side + ) ' test_expect_success 'add --mirror=fetch' ' mkdir mirror-fetch && git init mirror-fetch/parent && - (cd mirror-fetch/parent && - test_commit one) && + ( + cd mirror-fetch/parent && + test_commit one + ) && git init --bare mirror-fetch/child && - (cd mirror-fetch/child && - git remote add --mirror=fetch -f parent ../parent) + ( + cd mirror-fetch/child && + git remote add --mirror=fetch -f parent ../parent + ) ' test_expect_success 'fetch mirrors act as mirrors during fetch' ' - (cd mirror-fetch/parent && - git branch new && - git branch -m master renamed + ( + cd mirror-fetch/parent && + git branch new && + git branch -m master renamed ) && - (cd mirror-fetch/child && - git fetch parent && - git rev-parse --verify refs/heads/new && - git rev-parse --verify refs/heads/renamed + ( + cd mirror-fetch/child && + git fetch parent && + git rev-parse --verify refs/heads/new && + git rev-parse --verify refs/heads/renamed ) ' test_expect_success 'fetch mirrors can prune' ' - (cd mirror-fetch/child && - git remote prune parent && - test_must_fail git rev-parse --verify refs/heads/master + ( + cd mirror-fetch/child && + git remote prune parent && + test_must_fail git rev-parse --verify refs/heads/master ) ' test_expect_success 'fetch mirrors do not act as mirrors during push' ' - (cd mirror-fetch/parent && - git checkout HEAD^0 + ( + cd mirror-fetch/parent && + git checkout HEAD^0 ) && - (cd mirror-fetch/child && - git branch -m renamed renamed2 && - git push parent : + ( + cd mirror-fetch/child && + git branch -m renamed renamed2 && + git push parent : ) && - (cd mirror-fetch/parent && - git rev-parse --verify renamed && - test_must_fail git rev-parse --verify refs/heads/renamed2 + ( + cd mirror-fetch/parent && + git rev-parse --verify renamed && + test_must_fail git rev-parse --verify refs/heads/renamed2 ) ' test_expect_success 'add fetch mirror with specific branches' ' git init --bare mirror-fetch/track && - (cd mirror-fetch/track && - git remote add --mirror=fetch -t heads/new parent ../parent + ( + cd mirror-fetch/track && + git remote add --mirror=fetch -t heads/new parent ../parent ) ' test_expect_success 'fetch mirror respects specific branches' ' - (cd mirror-fetch/track && - git fetch parent && - git rev-parse --verify refs/heads/new && - test_must_fail git rev-parse --verify refs/heads/renamed + ( + cd mirror-fetch/track && + git fetch parent && + git rev-parse --verify refs/heads/new && + test_must_fail git rev-parse --verify refs/heads/renamed ) ' @@ -372,60 +408,72 @@ test_expect_success 'add --mirror=push' ' mkdir mirror-push && git init --bare mirror-push/public && git init mirror-push/private && - (cd mirror-push/private && - test_commit one && - git remote add --mirror=push public ../public + ( + cd mirror-push/private && + test_commit one && + git remote add --mirror=push public ../public ) ' test_expect_success 'push mirrors act as mirrors during push' ' - (cd mirror-push/private && - git branch new && - git branch -m master renamed && - git push public + ( + cd mirror-push/private && + git branch new && + git branch -m master renamed && + git push public ) && - (cd mirror-push/private && - git rev-parse --verify refs/heads/new && - git rev-parse --verify refs/heads/renamed && - test_must_fail git rev-parse --verify refs/heads/master + ( + cd mirror-push/private && + git rev-parse --verify refs/heads/new && + git rev-parse --verify refs/heads/renamed && + test_must_fail git rev-parse --verify refs/heads/master ) ' test_expect_success 'push mirrors do not act as mirrors during fetch' ' - (cd mirror-push/public && - git branch -m renamed renamed2 && - git symbolic-ref HEAD refs/heads/renamed2 + ( + cd mirror-push/public && + git branch -m renamed renamed2 && + git symbolic-ref HEAD refs/heads/renamed2 ) && - (cd mirror-push/private && - git fetch public && - git rev-parse --verify refs/heads/renamed && - test_must_fail git rev-parse --verify refs/heads/renamed2 + ( + cd mirror-push/private && + git fetch public && + git rev-parse --verify refs/heads/renamed && + test_must_fail git rev-parse --verify refs/heads/renamed2 ) ' test_expect_success 'push mirrors do not allow you to specify refs' ' git init mirror-push/track && - (cd mirror-push/track && - test_must_fail git remote add --mirror=push -t new public ../public + ( + cd mirror-push/track && + test_must_fail git remote add --mirror=push -t new public ../public ) ' test_expect_success 'add alt && prune' ' - (mkdir alttst && - cd alttst && - git init && - git remote add -f origin ../one && - git config remote.alt.url ../one && - git config remote.alt.fetch "+refs/heads/*:refs/remotes/origin/*") && - (cd one && - git branch -m side side2) && - (cd alttst && - git rev-parse --verify refs/remotes/origin/side && - test_must_fail git rev-parse --verify refs/remotes/origin/side2 && - git fetch alt && - git remote prune alt && - test_must_fail git rev-parse --verify refs/remotes/origin/side && - git rev-parse --verify refs/remotes/origin/side2) + mkdir alttst && + ( + cd alttst && + git init && + git remote add -f origin ../one && + git config remote.alt.url ../one && + git config remote.alt.fetch "+refs/heads/*:refs/remotes/origin/*" + ) && + ( + cd one && + git branch -m side side2 + ) && + ( + cd alttst && + git rev-parse --verify refs/remotes/origin/side && + test_must_fail git rev-parse --verify refs/remotes/origin/side2 && + git fetch alt && + git remote prune alt && + test_must_fail git rev-parse --verify refs/remotes/origin/side && + git rev-parse --verify refs/remotes/origin/side2 + ) ' cat >test/expect <<\EOF @@ -433,20 +481,24 @@ some-tag EOF test_expect_success 'add with reachable tags (default)' ' - (cd one && - >foobar && - git add foobar && - git commit -m "Foobar" && - git tag -a -m "Foobar tag" foobar-tag && - git reset --hard HEAD~1 && - git tag -a -m "Some tag" some-tag) && - (mkdir add-tags && - cd add-tags && - git init && - git remote add -f origin ../one && - git tag -l some-tag >../test/output && - git tag -l foobar-tag >>../test/output && - test_must_fail git config remote.origin.tagopt) && + ( + cd one && + >foobar && + git add foobar && + git commit -m "Foobar" && + git tag -a -m "Foobar tag" foobar-tag && + git reset --hard HEAD~1 && + git tag -a -m "Some tag" some-tag + ) && + mkdir add-tags && + ( + cd add-tags && + git init && + git remote add -f origin ../one && + git tag -l some-tag >../test/output && + git tag -l foobar-tag >>../test/output && + test_must_fail git config remote.origin.tagopt + ) && test_cmp test/expect test/output ' @@ -457,14 +509,16 @@ foobar-tag EOF test_expect_success 'add --tags' ' - (rm -rf add-tags && - mkdir add-tags && - cd add-tags && - git init && - git remote add -f --tags origin ../one && - git tag -l some-tag >../test/output && - git tag -l foobar-tag >>../test/output && - git config remote.origin.tagopt >>../test/output) && + rm -rf add-tags && + ( + mkdir add-tags && + cd add-tags && + git init && + git remote add -f --tags origin ../one && + git tag -l some-tag >../test/output && + git tag -l foobar-tag >>../test/output && + git config remote.origin.tagopt >>../test/output + ) && test_cmp test/expect test/output ' @@ -473,25 +527,31 @@ cat >test/expect <<\EOF EOF test_expect_success 'add --no-tags' ' - (rm -rf add-tags && - mkdir add-no-tags && - cd add-no-tags && - git init && - git remote add -f --no-tags origin ../one && - git tag -l some-tag >../test/output && - git tag -l foobar-tag >../test/output && - git config remote.origin.tagopt >>../test/output) && - (cd one && - git tag -d some-tag foobar-tag) && + rm -rf add-tags && + ( + mkdir add-no-tags && + cd add-no-tags && + git init && + git remote add -f --no-tags origin ../one && + git tag -l some-tag >../test/output && + git tag -l foobar-tag >../test/output && + git config remote.origin.tagopt >>../test/output + ) && + ( + cd one && + git tag -d some-tag foobar-tag + ) && test_cmp test/expect test/output ' test_expect_success 'reject --no-no-tags' ' - (cd add-no-tags && - test_must_fail git remote add -f --no-no-tags neworigin ../one) + ( + cd add-no-tags && + test_must_fail git remote add -f --no-no-tags neworigin ../one + ) ' -cat > one/expect << EOF +cat >one/expect <<\EOF apis/master apis/side drosophila/another @@ -500,17 +560,17 @@ cat > one/expect << EOF EOF test_expect_success 'update' ' - - (cd one && - git remote add drosophila ../two && - git remote add apis ../mirror && - git remote update && - git branch -r > output && - test_cmp expect output) - + ( + cd one && + git remote add drosophila ../two && + git remote add apis ../mirror && + git remote update && + git branch -r >output && + test_cmp expect output + ) ' -cat > one/expect << EOF +cat >one/expect <<\EOF drosophila/another drosophila/master drosophila/side @@ -521,34 +581,40 @@ cat > one/expect << EOF EOF test_expect_success 'update with arguments' ' - - (cd one && - for b in $(git branch -r) - do + ( + cd one && + for b in $(git branch -r) + do git branch -r -d $b || break - done && - git remote add manduca ../mirror && - git remote add megaloprepus ../mirror && - git config remotes.phobaeticus "drosophila megaloprepus" && - git config remotes.titanus manduca && - git remote update phobaeticus titanus && - git branch -r > output && - test_cmp expect output) - + done && + git remote add manduca ../mirror && + git remote add megaloprepus ../mirror && + git config remotes.phobaeticus "drosophila megaloprepus" && + git config remotes.titanus manduca && + git remote update phobaeticus titanus && + git branch -r >output && + test_cmp expect output + ) ' 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) + ( + 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 +cat >one/expect <<-\EOF apis/master apis/side manduca/master @@ -558,176 +624,204 @@ cat > one/expect << EOF EOF test_expect_success 'update default' ' - - (cd one && - for b in $(git branch -r) - do + ( + cd one && + for b in $(git branch -r) + do git branch -r -d $b || break - done && - git config remote.drosophila.skipDefaultUpdate true && - git remote update default && - git branch -r > output && - test_cmp expect output) - + done && + git config remote.drosophila.skipDefaultUpdate true && + git remote update default && + git branch -r >output && + test_cmp expect output + ) ' -cat > one/expect << EOF +cat >one/expect <<\EOF drosophila/another drosophila/master drosophila/side EOF test_expect_success 'update default (overridden, with funny whitespace)' ' - - (cd one && - for b in $(git branch -r) - do + ( + cd one && + for b in $(git branch -r) + do git branch -r -d $b || break - done && - git config remotes.default "$(printf "\t drosophila \n")" && - git remote update default && - git branch -r > output && - test_cmp expect output) - + done && + git config remotes.default "$(printf "\t drosophila \n")" && + git remote update default && + git branch -r >output && + test_cmp expect output + ) ' test_expect_success 'update (with remotes.default defined)' ' - - (cd one && - for b in $(git branch -r) - do + ( + cd one && + for b in $(git branch -r) + do git branch -r -d $b || break - done && - git config remotes.default "drosophila" && - git remote update && - git branch -r > output && - test_cmp expect output) - + done && + git config remotes.default "drosophila" && + git remote update && + git branch -r >output && + test_cmp expect output + ) ' test_expect_success '"remote show" does not show symbolic refs' ' - git clone one three && - (cd three && - git remote show origin > output && - ! grep "^ *HEAD$" < output && - ! grep -i stale < output) - + ( + cd three && + git remote show origin >output && + ! grep "^ *HEAD$" < output && + ! grep -i stale < output + ) ' test_expect_success 'reject adding remote with an invalid name' ' - test_must_fail git remote add some:url desired-name - ' # The first three test if the tracking branches are properly renamed, # the last two ones check if the config is updated. test_expect_success 'rename a remote' ' - git clone one four && - (cd four && - git remote rename origin upstream && - rmdir .git/refs/remotes/origin && - test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" && - test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" && - test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" && - test "$(git config branch.master.remote)" = "upstream") - + ( + cd four && + git remote rename origin upstream && + rmdir .git/refs/remotes/origin && + test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" && + test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" && + test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" && + test "$(git config branch.master.remote)" = "upstream" + ) ' test_expect_success 'rename does not update a non-default fetch refspec' ' - git clone one four.one && - (cd four.one && - git config remote.origin.fetch +refs/heads/*:refs/heads/origin/* && - git remote rename origin upstream && - test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/heads/origin/*" && - git rev-parse -q origin/master) - + ( + cd four.one && + git config remote.origin.fetch +refs/heads/*:refs/heads/origin/* && + git remote rename origin upstream && + test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/heads/origin/*" && + git rev-parse -q origin/master + ) ' test_expect_success 'rename a remote with name part of fetch spec' ' - git clone one four.two && - (cd four.two && - git remote rename origin remote && - git remote rename remote upstream && - test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*") - + ( + cd four.two && + git remote rename origin remote && + git remote rename remote upstream && + test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" + ) ' test_expect_success 'rename a remote with name prefix of other remote' ' - git clone one four.three && - (cd four.three && - git remote add o git://example.com/repo.git && - git remote rename o upstream && - test "$(git rev-parse origin/master)" = "$(git rev-parse master)") - + ( + cd four.three && + git remote add o git://example.com/repo.git && + git remote rename o upstream && + test "$(git rev-parse origin/master)" = "$(git rev-parse master)" + ) ' -cat > remotes_origin << EOF +cat >remotes_origin <<EOF URL: $(pwd)/one Push: refs/heads/master:refs/heads/upstream +Push: refs/heads/next:refs/heads/upstream2 Pull: refs/heads/master:refs/heads/origin +Pull: refs/heads/next:refs/heads/origin2 EOF test_expect_success 'migrate a remote from named file in $GIT_DIR/remotes' ' git clone one five && origin_url=$(pwd)/one && - (cd five && - git remote remove origin && - mkdir -p .git/remotes && - cat ../remotes_origin > .git/remotes/origin && - git remote rename origin origin && - ! test -f .git/remotes/origin && - test "$(git config remote.origin.url)" = "$origin_url" && - test "$(git config remote.origin.push)" = "refs/heads/master:refs/heads/upstream" && - test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin") + ( + cd five && + git remote remove origin && + mkdir -p .git/remotes && + cat ../remotes_origin >.git/remotes/origin && + git remote rename origin origin && + test_path_is_missing .git/remotes/origin && + test "$(git config remote.origin.url)" = "$origin_url" && + cat >push_expected <<-\EOF && + refs/heads/master:refs/heads/upstream + refs/heads/next:refs/heads/upstream2 + EOF + cat >fetch_expected <<-\EOF && + refs/heads/master:refs/heads/origin + refs/heads/next:refs/heads/origin2 + EOF + git config --get-all remote.origin.push >push_actual && + git config --get-all remote.origin.fetch >fetch_actual && + test_cmp push_expected push_actual && + test_cmp fetch_expected fetch_actual + ) ' test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' ' git clone one six && origin_url=$(pwd)/one && - (cd six && - git remote rm origin && - echo "$origin_url" > .git/branches/origin && - git remote rename origin origin && - ! test -f .git/branches/origin && - test "$(git config remote.origin.url)" = "$origin_url" && - test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin") + ( + cd six && + git remote rm origin && + echo "$origin_url" >.git/branches/origin && + git remote rename origin origin && + test_path_is_missing .git/branches/origin && + test "$(git config remote.origin.url)" = "$origin_url" && + test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin" && + test "$(git config remote.origin.push)" = "HEAD:refs/heads/master" + ) ' -test_expect_success 'remote prune to cause a dangling symref' ' +test_expect_success 'migrate a remote from named file in $GIT_DIR/branches (2)' ' git clone one seven && ( + cd seven && + git remote rm origin && + echo "quux#foom" > .git/branches/origin && + git remote rename origin origin && + test_path_is_missing .git/branches/origin && + test "$(git config remote.origin.url)" = "quux" && + test "$(git config remote.origin.fetch)" = "refs/heads/foom:refs/heads/origin" + test "$(git config remote.origin.push)" = "HEAD:refs/heads/foom" + ) +' + +test_expect_success 'remote prune to cause a dangling symref' ' + git clone one eight && + ( cd one && git checkout side2 && git branch -D master ) && ( - cd seven && + cd eight && git remote prune origin ) >err 2>&1 && test_i18ngrep "has become dangling" err && : And the dangling symref will not cause other annoying errors && ( - cd seven && + cd eight && git branch -a ) 2>err && ! grep "points nowhere" err && ( - cd seven && + cd eight && test_must_fail git branch nomore origin ) 2>err && grep "dangling symref" err ' test_expect_success 'show empty remote' ' - test_create_repo empty && git clone empty empty-clone && ( diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 6af6c63350..ed4d9c8318 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -57,6 +57,35 @@ test_expect_success 'pulling into void does not overwrite untracked files' ' ) ' +test_expect_success 'pulling into void does not overwrite staged files' ' + git init cloned-staged-colliding && + ( + cd cloned-staged-colliding && + echo "alternate content" >file && + git add file && + test_must_fail git pull .. master && + echo "alternate content" >expect && + test_cmp expect file && + git cat-file blob :file >file.index && + test_cmp expect file.index + ) +' + + +test_expect_success 'pulling into void does not remove new staged files' ' + git init cloned-staged-new && + ( + cd cloned-staged-new && + echo "new tracked file" >newfile && + git add newfile && + git pull .. master && + echo "new tracked file" >expect && + test_cmp expect newfile && + git cat-file blob :newfile >newfile.index && + test_cmp expect newfile.index + ) +' + test_expect_success 'test . as a remote' ' git branch copy master && diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh index aa31abe32b..453aba53f4 100755 --- a/t/t5521-pull-options.sh +++ b/t/t5521-pull-options.sh @@ -15,19 +15,19 @@ test_expect_success 'git pull -q' ' mkdir clonedq && (cd clonedq && git init && git pull -q "../parent" >out 2>err && - test ! -s err && - test ! -s out) + test_must_be_empty err && + test_must_be_empty out) ' test_expect_success 'git pull -q --rebase' ' mkdir clonedqrb && (cd clonedqrb && git init && git pull -q --rebase "../parent" >out 2>err && - test ! -s err && - test ! -s out && + test_must_be_empty err && + test_must_be_empty out && git pull -q --rebase "../parent" >out 2>err && - test ! -s err && - test ! -s out) + test_must_be_empty err && + test_must_be_empty out) ' test_expect_success 'git pull' ' @@ -35,7 +35,7 @@ test_expect_success 'git pull' ' (cd cloned && git init && git pull "../parent" >out 2>err && test -s err && - test ! -s out) + test_must_be_empty out) ' test_expect_success 'git pull --rebase' ' @@ -43,7 +43,7 @@ test_expect_success 'git pull --rebase' ' (cd clonedrb && git init && git pull --rebase "../parent" >out 2>err && test -s err && - test ! -s out) + test_must_be_empty out) ' test_expect_success 'git pull -v' ' @@ -51,7 +51,7 @@ test_expect_success 'git pull -v' ' (cd clonedv && git init && git pull -v "../parent" >out 2>err && test -s err && - test ! -s out) + test_must_be_empty out) ' test_expect_success 'git pull -v --rebase' ' @@ -59,22 +59,22 @@ test_expect_success 'git pull -v --rebase' ' (cd clonedvrb && git init && git pull -v --rebase "../parent" >out 2>err && test -s err && - test ! -s out) + test_must_be_empty out) ' test_expect_success 'git pull -v -q' ' mkdir clonedvq && (cd clonedvq && git init && git pull -v -q "../parent" >out 2>err && - test ! -s out && - test ! -s err) + test_must_be_empty out && + test_must_be_empty err) ' test_expect_success 'git pull -q -v' ' mkdir clonedqv && (cd clonedqv && git init && git pull -q -v "../parent" >out 2>err && - test ! -s out && + test_must_be_empty out && test -s err) ' diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh index 4736da8f36..6a5ac3add4 100755 --- a/t/t5528-push-default.sh +++ b/t/t5528-push-default.sh @@ -15,17 +15,19 @@ test_expect_success 'setup bare remotes' ' # $1 = local revision # $2 = remote revision (tested to be equal to the local one) +# $3 = [optional] repo to check for actual output (repo1 by default) check_pushed_commit () { git log -1 --format='%h %s' "$1" >expect && - git --git-dir=repo1 log -1 --format='%h %s' "$2" >actual && + git --git-dir="${3:-repo1}" log -1 --format='%h %s' "$2" >actual && test_cmp expect actual } # $1 = push.default value # $2 = expected target branch for the push +# $3 = [optional] repo to check for actual output (repo1 by default) test_push_success () { git -c push.default="$1" push && - check_pushed_commit HEAD "$2" + check_pushed_commit HEAD "$2" "$3" } # $1 = push.default value @@ -37,6 +39,26 @@ test_push_failure () { test_cmp expect actual } +# $1 = success or failure +# $2 = push.default value +# $3 = branch to check for actual output (master or foo) +# $4 = [optional] switch to triangular workflow +test_pushdefault_workflow () { + workflow=central + pushdefault=parent1 + if test -n "${4-}"; then + workflow=triangular + pushdefault=parent2 + fi + test_expect_success "push.default = $2 $1 in $workflow workflows" " + test_config branch.master.remote parent1 && + test_config branch.master.merge refs/heads/foo && + test_config remote.pushdefault $pushdefault && + test_commit commit-for-$2${4+-triangular} && + test_push_$1 $2 $3 ${4+repo2} + " +} + test_expect_success '"upstream" pushes to configured upstream' ' git checkout master && test_config branch.master.remote parent1 && @@ -48,7 +70,6 @@ test_expect_success '"upstream" pushes to configured upstream' ' test_expect_success '"upstream" does not push on unconfigured remote' ' git checkout master && test_unconfig branch.master.remote && - test_config push.default upstream && test_commit three && test_push_failure upstream ' @@ -57,7 +78,6 @@ test_expect_success '"upstream" does not push on unconfigured branch' ' git checkout master && test_config branch.master.remote parent1 && test_unconfig branch.master.merge && - test_config push.default upstream test_commit four && test_push_failure upstream ' @@ -115,4 +135,41 @@ test_expect_success 'push to existing branch, upstream configured with different test_cmp expect-other-name actual-other-name ' +# We are on 'master', which integrates with 'foo' from parent1 +# remote (set in test_pushdefault_workflow helper). Push to +# parent1 in centralized, and push to parent2 in triangular workflow. +# The parent1 repository has 'master' and 'foo' branches, while +# the parent2 repository has only 'master' branch. +# +# test_pushdefault_workflow() arguments: +# $1 = success or failure +# $2 = push.default value +# $3 = branch to check for actual output (master or foo) +# $4 = [optional] switch to triangular workflow + +# update parent1's master (which is not our upstream) +test_pushdefault_workflow success current master + +# update parent1's foo (which is our upstream) +test_pushdefault_workflow success upstream foo + +# upsream is foo which is not the name of the current branch +test_pushdefault_workflow failure simple master + +# master and foo are updated +test_pushdefault_workflow success matching master + +# master is updated +test_pushdefault_workflow success current master triangular + +# upstream mode cannot be used in triangular +test_pushdefault_workflow failure upstream foo triangular + +# in triangular, 'simple' works as 'current' and update the branch +# with the same name. +test_pushdefault_workflow success simple master triangular + +# master is updated (parent2 does not have foo) +test_pushdefault_workflow success matching master triangular + test_done diff --git a/t/t5702-clone-options.sh b/t/t5702-clone-options.sh index 02cb024723..85cadfad6d 100755 --- a/t/t5702-clone-options.sh +++ b/t/t5702-clone-options.sh @@ -22,7 +22,7 @@ test_expect_success 'clone -o' ' test_expect_success 'redirected clone' ' git clone "file://$(pwd)/parent" clone-redirected >out 2>err && - test ! -s err + test_must_be_empty err ' test_expect_success 'redirected clone -v' ' diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh index 8956c21617..5a6e49d18d 100755 --- a/t/t5710-info-alternate.sh +++ b/t/t5710-info-alternate.sh @@ -58,7 +58,13 @@ test_expect_success 'creating too deep nesting' \ git clone -l -s D E && git clone -l -s E F && git clone -l -s F G && -test_must_fail git clone --bare -l -s G H' +git clone --bare -l -s G H' + +test_expect_success 'invalidity of deepest repository' \ +'cd H && { + test_valid_repo + test $? -ne 0 +}' cd "$base_dir" diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh index 4899af3f7a..8c4c5396a8 100755 --- a/t/t5801-remote-helpers.sh +++ b/t/t5801-remote-helpers.sh @@ -210,9 +210,7 @@ test_expect_success 'proper failure checks for pushing' ' (GIT_REMOTE_TESTGIT_FAILURE=1 && export GIT_REMOTE_TESTGIT_FAILURE && cd local && - test_must_fail git push --all 2> error && - cat error && - grep -q "Reading from helper .git-remote-testgit. failed" error + test_must_fail git push --all ) ' diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh index fb07536a0f..43ad772484 100755 --- a/t/t6002-rev-list-bisect.sh +++ b/t/t6002-rev-list-bisect.sh @@ -39,25 +39,25 @@ test_bisection_diff() date >path0 git update-index --add path0 save_tag tree git write-tree -on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree -on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root -on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0 -on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1 -on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2 -on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0 -on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0 -on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1 -on_committer_date "1971-08-16 00:00:08" save_tag b2 unique_commit b2 tree -p b1 -on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2 -on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2 -on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2 -on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1 -on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2 -on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3 -on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3 -on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4 -on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3 -on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4 +on_committer_date "00:00" hide_error save_tag root unique_commit root tree +on_committer_date "00:01" save_tag l0 unique_commit l0 tree -p root +on_committer_date "00:02" save_tag l1 unique_commit l1 tree -p l0 +on_committer_date "00:03" save_tag l2 unique_commit l2 tree -p l1 +on_committer_date "00:04" save_tag a0 unique_commit a0 tree -p l2 +on_committer_date "00:05" save_tag a1 unique_commit a1 tree -p a0 +on_committer_date "00:06" save_tag b1 unique_commit b1 tree -p a0 +on_committer_date "00:07" save_tag c1 unique_commit c1 tree -p b1 +on_committer_date "00:08" save_tag b2 unique_commit b2 tree -p b1 +on_committer_date "00:09" save_tag b3 unique_commit b2 tree -p b2 +on_committer_date "00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2 +on_committer_date "00:11" save_tag c3 unique_commit c3 tree -p c2 +on_committer_date "00:12" save_tag a2 unique_commit a2 tree -p a1 +on_committer_date "00:13" save_tag a3 unique_commit a3 tree -p a2 +on_committer_date "00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3 +on_committer_date "00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3 +on_committer_date "00:16" save_tag l3 unique_commit l3 tree -p a4 +on_committer_date "00:17" save_tag l4 unique_commit l4 tree -p l3 +on_committer_date "00:18" save_tag l5 unique_commit l5 tree -p l4 git update-ref HEAD $(tag l5) @@ -90,29 +90,29 @@ git update-ref HEAD $(tag l5) # F -on_committer_date "1971-08-16 00:00:00" hide_error save_tag F unique_commit F tree -on_committer_date "1971-08-16 00:00:01" save_tag e8 unique_commit e8 tree -p F -on_committer_date "1971-08-16 00:00:02" save_tag e7 unique_commit e7 tree -p e8 -on_committer_date "1971-08-16 00:00:03" save_tag e6 unique_commit e6 tree -p e7 -on_committer_date "1971-08-16 00:00:04" save_tag e5 unique_commit e5 tree -p e6 -on_committer_date "1971-08-16 00:00:05" save_tag f4 unique_commit f4 tree -p F -on_committer_date "1971-08-16 00:00:06" save_tag f3 unique_commit f3 tree -p f4 -on_committer_date "1971-08-16 00:00:07" save_tag f2 unique_commit f2 tree -p f3 -on_committer_date "1971-08-16 00:00:08" save_tag f1 unique_commit f1 tree -p f2 -on_committer_date "1971-08-16 00:00:09" save_tag e4 unique_commit e4 tree -p e5 -on_committer_date "1971-08-16 00:00:10" save_tag e3 unique_commit e3 tree -p e4 -on_committer_date "1971-08-16 00:00:11" save_tag e2 unique_commit e2 tree -p e3 -on_committer_date "1971-08-16 00:00:12" save_tag e1 unique_commit e1 tree -p e2 -on_committer_date "1971-08-16 00:00:13" save_tag E unique_commit E tree -p e1 -p f1 - -on_committer_date "1971-08-16 00:00:00" hide_error save_tag U unique_commit U tree -on_committer_date "1971-08-16 00:00:01" save_tag u0 unique_commit u0 tree -p U -on_committer_date "1971-08-16 00:00:01" save_tag u1 unique_commit u1 tree -p u0 -on_committer_date "1971-08-16 00:00:02" save_tag u2 unique_commit u2 tree -p u0 -on_committer_date "1971-08-16 00:00:03" save_tag u3 unique_commit u3 tree -p u0 -on_committer_date "1971-08-16 00:00:04" save_tag u4 unique_commit u4 tree -p u0 -on_committer_date "1971-08-16 00:00:05" save_tag u5 unique_commit u5 tree -p u0 -on_committer_date "1971-08-16 00:00:06" save_tag V unique_commit V tree -p u1 -p u2 -p u3 -p u4 -p u5 +on_committer_date "00:00" hide_error save_tag F unique_commit F tree +on_committer_date "00:01" save_tag e8 unique_commit e8 tree -p F +on_committer_date "00:02" save_tag e7 unique_commit e7 tree -p e8 +on_committer_date "00:03" save_tag e6 unique_commit e6 tree -p e7 +on_committer_date "00:04" save_tag e5 unique_commit e5 tree -p e6 +on_committer_date "00:05" save_tag f4 unique_commit f4 tree -p F +on_committer_date "00:06" save_tag f3 unique_commit f3 tree -p f4 +on_committer_date "00:07" save_tag f2 unique_commit f2 tree -p f3 +on_committer_date "00:08" save_tag f1 unique_commit f1 tree -p f2 +on_committer_date "00:09" save_tag e4 unique_commit e4 tree -p e5 +on_committer_date "00:10" save_tag e3 unique_commit e3 tree -p e4 +on_committer_date "00:11" save_tag e2 unique_commit e2 tree -p e3 +on_committer_date "00:12" save_tag e1 unique_commit e1 tree -p e2 +on_committer_date "00:13" save_tag E unique_commit E tree -p e1 -p f1 + +on_committer_date "00:00" hide_error save_tag U unique_commit U tree +on_committer_date "00:01" save_tag u0 unique_commit u0 tree -p U +on_committer_date "00:01" save_tag u1 unique_commit u1 tree -p u0 +on_committer_date "00:02" save_tag u2 unique_commit u2 tree -p u0 +on_committer_date "00:03" save_tag u3 unique_commit u3 tree -p u0 +on_committer_date "00:04" save_tag u4 unique_commit u4 tree -p u0 +on_committer_date "00:05" save_tag u5 unique_commit u5 tree -p u0 +on_committer_date "00:06" save_tag V unique_commit V tree -p u1 -p u2 -p u3 -p u4 -p u5 test_sequence() { diff --git a/t/t6003-rev-list-topo-order.sh b/t/t6003-rev-list-topo-order.sh index e4c52b0214..24d1836f41 100755 --- a/t/t6003-rev-list-topo-order.sh +++ b/t/t6003-rev-list-topo-order.sh @@ -16,39 +16,34 @@ list_duplicates() date >path0 git update-index --add path0 save_tag tree git write-tree -on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree -on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root -on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0 -on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1 -on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2 -on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0 -on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0 -on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1 -on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1 -on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b3 tree -p b2 -on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2 -on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2 -on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1 -on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2 -on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3 -on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3 -on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4 -on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3 -on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4 -on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3 -on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4 -on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree -on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root -on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0 -on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1 -on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5 +on_dates "00:00" "00:00" hide_error save_tag root unique_commit root tree +on_dates "00:01" "00:01" save_tag l0 unique_commit l0 tree -p root +on_dates "00:02" "00:02" save_tag l1 unique_commit l1 tree -p l0 +on_dates "00:03" "00:03" save_tag l2 unique_commit l2 tree -p l1 +on_dates "00:04" "00:04" save_tag a0 unique_commit a0 tree -p l2 +on_dates "00:05" "00:05" save_tag a1 unique_commit a1 tree -p a0 +on_dates "00:06" "00:06" save_tag b1 unique_commit b1 tree -p a0 +on_dates "00:07" "00:07" save_tag c1 unique_commit c1 tree -p b1 +on_dates "00:08" "00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1 +on_dates "00:09" "00:09" save_tag b3 unique_commit b3 tree -p b2 +on_dates "00:10" "00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2 +on_dates "00:11" "00:11" save_tag c3 unique_commit c3 tree -p c2 +on_dates "00:12" "00:00" save_tag a2 unique_commit a2 tree -p a1 +on_dates "00:13" "00:01" save_tag a3 unique_commit a3 tree -p a2 +on_dates "00:14" "00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3 +on_dates "00:15" "00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3 +on_dates "00:16" "00:16" save_tag l3 unique_commit l3 tree -p a4 +on_dates "00:17" "00:17" save_tag l4 unique_commit l4 tree -p l3 +on_dates "00:18" "00:18" save_tag l5 unique_commit l5 tree -p l4 +on_dates "00:19" "00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3 +on_dates "00:20" "00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4 +on_dates "00:21" "00:21" hide_error save_tag alt_root unique_commit alt_root tree +on_dates "00:22" "00:22" save_tag r0 unique_commit r0 tree -p alt_root +on_dates "00:23" "00:23" save_tag r1 unique_commit r1 tree -p r0 +on_dates "00:24" "00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1 +on_dates "00:25" "00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5 -# -# note: as of 20/6, it isn't possible to create duplicate parents, so this -# can't be tested. -# -#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3 hide_error save_tag e1 as_author e@example.com unique_commit e1 tree save_tag e2 as_author e@example.com unique_commit e2 tree -p e1 save_tag f1 as_author f@example.com unique_commit f1 tree -p e1 @@ -105,6 +100,50 @@ l0 root EOF +test_output_expect_success 'simple date order' 'git rev-list --date-order HEAD' <<EOF +l5 +l4 +l3 +a4 +b4 +a3 +a2 +c3 +c2 +b3 +b2 +c1 +b1 +a1 +a0 +l2 +l1 +l0 +root +EOF + +test_output_expect_success 'simple author-date order' 'git rev-list --author-date-order HEAD' <<EOF +l5 +l4 +l3 +a4 +b4 +c3 +c2 +b3 +b2 +c1 +b1 +a3 +a2 +a1 +a0 +l2 +l1 +l0 +root +EOF + test_output_expect_success 'two diamonds topo order (g6)' 'git rev-list --topo-order g4' <<EOF g4 h2 diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 0393c9fd0b..98744038ec 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -1,20 +1,45 @@ #!/bin/sh +# Copyright (c) 2009 Jens Lehmann +# Copyright (c) 2011 Alexey Shumkin (+ non-UTF-8 commit encoding tests) + test_description='git rev-list --pretty=format test' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh test_tick +# String "added" in German +# (translated with Google Translate), +# encoded in UTF-8, used as a commit log message below. +added=$(printf "added (hinzugef\303\274gt) foo") +added_iso88591=$(echo "$added" | iconv -f utf-8 -t iso8859-1) +# same but "changed" +changed=$(printf "changed (ge\303\244ndert) foo") +changed_iso88591=$(echo "$changed" | iconv -f utf-8 -t iso8859-1) + test_expect_success 'setup' ' -touch foo && git add foo && git commit -m "added foo" && - echo changed >foo && git commit -a -m "changed foo" + : >foo && + git add foo && + git config i18n.commitEncoding iso8859-1 && + git commit -m "$added_iso88591" && + head1=$(git rev-parse --verify HEAD) && + head1_short=$(git rev-parse --verify --short $head1) && + tree1=$(git rev-parse --verify HEAD:) && + tree1_short=$(git rev-parse --verify --short $tree1) && + echo "$changed" > foo && + git commit -a -m "$changed_iso88591" && + head2=$(git rev-parse --verify HEAD) && + head2_short=$(git rev-parse --verify --short $head2) && + tree2=$(git rev-parse --verify HEAD:) && + tree2_short=$(git rev-parse --verify --short $tree2) + git config --unset i18n.commitEncoding ' -# usage: test_format name format_string <expected_output +# usage: test_format name format_string [failure] <expected_output test_format () { cat >expect.$1 - test_expect_success "format $1" " + test_expect_${3:-success} "format $1" " git rev-list --pretty=format:'$2' master >output.$1 && test_cmp expect.$1 output.$1 " @@ -32,49 +57,49 @@ has_no_color () { test_cmp expect "$1" } -test_format percent %%h <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d +test_format percent %%h <<EOF +commit $head2 %h -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +commit $head1 %h EOF -test_format hash %H%n%h <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d -131a310eb913d107dd3c09a65d1651175898735d -131a310 -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -86c75cf +test_format hash %H%n%h <<EOF +commit $head2 +$head2 +$head2_short +commit $head1 +$head1 +$head1_short EOF -test_format tree %T%n%t <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d -fe722612f26da5064c32ca3843aa154bdb0b08a0 -fe72261 -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -4d5fcadc293a348e88f777dc0920f11e7d71441c -4d5fcad +test_format tree %T%n%t <<EOF +commit $head2 +$tree2 +$tree2_short +commit $head1 +$tree1 +$tree1_short EOF -test_format parents %P%n%p <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d -86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -86c75cf -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +test_format parents %P%n%p <<EOF +commit $head2 +$head1 +$head1_short +commit $head1 EOF # we don't test relative here -test_format author %an%n%ae%n%ad%n%aD%n%at <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d +test_format author %an%n%ae%n%ad%n%aD%n%at <<EOF +commit $head2 A U Thor author@example.com Thu Apr 7 15:13:13 2005 -0700 Thu, 7 Apr 2005 15:13:13 -0700 1112911993 -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +commit $head1 A U Thor author@example.com Thu Apr 7 15:13:13 2005 -0700 @@ -82,14 +107,14 @@ Thu, 7 Apr 2005 15:13:13 -0700 1112911993 EOF -test_format committer %cn%n%ce%n%cd%n%cD%n%ct <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d +test_format committer %cn%n%ce%n%cd%n%cD%n%ct <<EOF +commit $head2 C O Mitter committer@example.com Thu Apr 7 15:13:13 2005 -0700 Thu, 7 Apr 2005 15:13:13 -0700 1112911993 -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +commit $head1 C O Mitter committer@example.com Thu Apr 7 15:13:13 2005 -0700 @@ -97,43 +122,45 @@ Thu, 7 Apr 2005 15:13:13 -0700 1112911993 EOF -test_format encoding %e <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +test_format encoding %e <<EOF +commit $head2 +iso8859-1 +commit $head1 +iso8859-1 EOF -test_format subject %s <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d -changed foo -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -added foo +test_format subject %s <<EOF +commit $head2 +$changed +commit $head1 +$added EOF -test_format body %b <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +test_format body %b <<EOF +commit $head2 +commit $head1 EOF -test_format raw-body %B <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d -changed foo +test_format raw-body %B <<EOF +commit $head2 +$changed -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -added foo +commit $head1 +$added EOF -test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d +test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<EOF +commit $head2 [31mfoo[32mbar[34mbaz[mxyzzy -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +commit $head1 [31mfoo[32mbar[34mbaz[mxyzzy EOF -test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<'EOF' -commit 131a310eb913d107dd3c09a65d1651175898735d +test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<EOF +commit $head2 [1;31;43mfoo[m -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +commit $head1 [1;31;43mfoo[m EOF @@ -179,46 +206,71 @@ test_expect_success '%C(auto) respects --color=auto (stdout not tty)' ' ) ' -cat >commit-msg <<'EOF' +iconv -f utf-8 -t iso8859-1 > commit-msg <<EOF Test printing of complex bodies This commit message is much longer than the others, and it will be encoded in iso8859-1. We should therefore -include an iso8859 character: ¡bueno! +include an iso8859 character: ¡bueno! EOF + test_expect_success 'setup complex body' ' -git config i18n.commitencoding iso8859-1 && - echo change2 >foo && git commit -a -F commit-msg + git config i18n.commitencoding iso8859-1 && + echo change2 >foo && git commit -a -F commit-msg && + head3=$(git rev-parse --verify HEAD) && + head3_short=$(git rev-parse --short $head3) ' -test_format complex-encoding %e <<'EOF' -commit 1ed88da4a5b5ed8c449114ac131efc62178734c3 +test_format complex-encoding %e <<EOF +commit $head3 +iso8859-1 +commit $head2 +iso8859-1 +commit $head1 iso8859-1 -commit 131a310eb913d107dd3c09a65d1651175898735d -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 EOF -test_format complex-subject %s <<'EOF' -commit 1ed88da4a5b5ed8c449114ac131efc62178734c3 +test_format complex-subject %s <<EOF +commit $head3 Test printing of complex bodies -commit 131a310eb913d107dd3c09a65d1651175898735d -changed foo -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 -added foo +commit $head2 +$changed_iso88591 +commit $head1 +$added_iso88591 EOF -test_format complex-body %b <<'EOF' -commit 1ed88da4a5b5ed8c449114ac131efc62178734c3 -This commit message is much longer than the others, -and it will be encoded in iso8859-1. We should therefore -include an iso8859 character: ¡bueno! +test_expect_success 'prepare expected messages (for test %b)' ' + cat <<-EOF >expected.utf-8 && + commit $head3 + This commit message is much longer than the others, + and it will be encoded in iso8859-1. We should therefore + include an iso8859 character: ¡bueno! + + commit $head2 + commit $head1 + EOF + iconv -f utf-8 -t iso8859-1 expected.utf-8 >expected.iso8859-1 +' + +test_format complex-body %b <expected.iso8859-1 -commit 131a310eb913d107dd3c09a65d1651175898735d -commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +# Git uses i18n.commitEncoding if no i18n.logOutputEncoding set +# so unset i18n.commitEncoding to test encoding conversion +git config --unset i18n.commitEncoding + +test_format complex-subject-commitencoding-unset %s <<EOF +commit $head3 +Test printing of complex bodies +commit $head2 +$changed +commit $head1 +$added EOF +test_format complex-body-commitencoding-unset %b <expected.utf-8 + test_expect_success '%x00 shows NUL' ' - echo >expect commit 1ed88da4a5b5ed8c449114ac131efc62178734c3 && + echo >expect commit $head3 && echo >>expect fooQbar && git rev-list -1 --format=foo%x00bar HEAD >actual.nul && nul_to_q <actual.nul >actual && @@ -265,12 +317,12 @@ test_expect_success 'add LF before non-empty (2)' ' test_expect_success 'add SP before non-empty (1)' ' git show -s --pretty=format:"%s% bThanks" HEAD^^ >actual && - test $(wc -w <actual) = 2 + test $(wc -w <actual) = 3 ' test_expect_success 'add SP before non-empty (2)' ' git show -s --pretty=format:"%s% sThanks" HEAD^^ >actual && - test $(wc -w <actual) = 4 + test $(wc -w <actual) = 6 ' test_expect_success '--abbrev' ' diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh index 331b9b07d4..d15b313d4b 100755 --- a/t/t6021-merge-criss-cross.sh +++ b/t/t6021-merge-criss-cross.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Fredrik Kuivinen # -# See http://marc.theaimsgroup.com/?l=git&m=111463358500362&w=2 for a +# See http://marc.info/?l=git&m=111463358500362&w=2 for a # nice description of what this is about. diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh index 2599ae50eb..9324ea4416 100755 --- a/t/t6035-merge-dir-to-symlink.sh +++ b/t/t6035-merge-dir-to-symlink.sh @@ -3,7 +3,7 @@ test_description='merging when a directory was replaced with a symlink' . ./test-lib.sh -test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' ' +test_expect_success 'create a commit where dir a/b changed to symlink' ' mkdir -p a/b/c a/b-2/c && > a/b/c/d && > a/b-2/c/d && @@ -12,12 +12,12 @@ test_expect_success SYMLINKS 'create a commit where dir a/b changed to symlink' git commit -m base && git tag start && rm -rf a/b && - ln -s b-2 a/b && git add -A && + test_ln_s_add b-2 a/b && git commit -m "dir to symlink" ' -test_expect_success SYMLINKS 'checkout does not clobber untracked symlink' ' +test_expect_success 'checkout does not clobber untracked symlink' ' git checkout HEAD^0 && git reset --hard master && git rm --cached a/b && @@ -25,7 +25,7 @@ test_expect_success SYMLINKS 'checkout does not clobber untracked symlink' ' test_must_fail git checkout start^0 ' -test_expect_success SYMLINKS 'a/b-2/c/d is kept when clobbering symlink b' ' +test_expect_success 'a/b-2/c/d is kept when clobbering symlink b' ' git checkout HEAD^0 && git reset --hard master && git rm --cached a/b && @@ -34,14 +34,14 @@ test_expect_success SYMLINKS 'a/b-2/c/d is kept when clobbering symlink b' ' test -f a/b-2/c/d ' -test_expect_success SYMLINKS 'checkout should not have deleted a/b-2/c/d' ' +test_expect_success 'checkout should not have deleted a/b-2/c/d' ' git checkout HEAD^0 && git reset --hard master && git checkout start^0 && test -f a/b-2/c/d ' -test_expect_success SYMLINKS 'setup for merge test' ' +test_expect_success 'setup for merge test' ' git reset --hard && test -f a/b-2/c/d && echo x > a/x && @@ -50,39 +50,51 @@ test_expect_success SYMLINKS 'setup for merge test' ' git tag baseline ' -test_expect_success SYMLINKS 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' ' +test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' ' git reset --hard && git checkout baseline^0 && git merge -s resolve master && - test -h a/b && test -f a/b-2/c/d ' -test_expect_success SYMLINKS 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' ' +test_expect_success SYMLINKS 'a/b was resolved as symlink' ' + test -h a/b +' + +test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' ' git reset --hard && git checkout baseline^0 && git merge -s recursive master && - test -h a/b && test -f a/b-2/c/d ' -test_expect_success SYMLINKS 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' ' +test_expect_success SYMLINKS 'a/b was resolved as symlink' ' + test -h a/b +' + +test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' ' git reset --hard && git checkout master^0 && git merge -s resolve baseline^0 && - test -h a/b && test -f a/b-2/c/d ' -test_expect_success SYMLINKS 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' ' +test_expect_success SYMLINKS 'a/b was resolved as symlink' ' + test -h a/b +' + +test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' ' git reset --hard && git checkout master^0 && git merge -s recursive baseline^0 && - test -h a/b && test -f a/b-2/c/d ' -test_expect_failure SYMLINKS 'do not lose untracked in merge (resolve)' ' +test_expect_success SYMLINKS 'a/b was resolved as symlink' ' + test -h a/b +' + +test_expect_failure 'do not lose untracked in merge (resolve)' ' git reset --hard && git checkout baseline^0 && >a/b/c/e && @@ -91,7 +103,7 @@ test_expect_failure SYMLINKS 'do not lose untracked in merge (resolve)' ' test -f a/b-2/c/d ' -test_expect_success SYMLINKS 'do not lose untracked in merge (recursive)' ' +test_expect_success 'do not lose untracked in merge (recursive)' ' git reset --hard && git checkout baseline^0 && >a/b/c/e && @@ -100,52 +112,61 @@ test_expect_success SYMLINKS 'do not lose untracked in merge (recursive)' ' test -f a/b-2/c/d ' -test_expect_success SYMLINKS 'do not lose modifications in merge (resolve)' ' +test_expect_success 'do not lose modifications in merge (resolve)' ' git reset --hard && git checkout baseline^0 && echo more content >>a/b/c/d && test_must_fail git merge -s resolve master ' -test_expect_success SYMLINKS 'do not lose modifications in merge (recursive)' ' +test_expect_success 'do not lose modifications in merge (recursive)' ' git reset --hard && git checkout baseline^0 && echo more content >>a/b/c/d && test_must_fail git merge -s recursive master ' -test_expect_success SYMLINKS 'setup a merge where dir a/b-2 changed to symlink' ' +test_expect_success 'setup a merge where dir a/b-2 changed to symlink' ' git reset --hard && git checkout start^0 && rm -rf a/b-2 && - ln -s b a/b-2 && git add -A && + test_ln_s_add b a/b-2 && git commit -m "dir a/b-2 to symlink" && git tag test2 ' -test_expect_success SYMLINKS 'merge should not have D/F conflicts (resolve)' ' +test_expect_success 'merge should not have D/F conflicts (resolve)' ' git reset --hard && git checkout baseline^0 && git merge -s resolve test2 && - test -h a/b-2 && test -f a/b/c/d ' -test_expect_success SYMLINKS 'merge should not have D/F conflicts (recursive)' ' +test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' ' + test -h a/b-2 +' + +test_expect_success 'merge should not have D/F conflicts (recursive)' ' git reset --hard && git checkout baseline^0 && git merge -s recursive test2 && - test -h a/b-2 && test -f a/b/c/d ' -test_expect_success SYMLINKS 'merge should not have F/D conflicts (recursive)' ' +test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' ' + test -h a/b-2 +' + +test_expect_success 'merge should not have F/D conflicts (recursive)' ' git reset --hard && git checkout -b foo test2 && git merge -s recursive baseline^0 && - test -h a/b-2 && test -f a/b/c/d ' +test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' ' + test -h a/b-2 +' + test_done diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index a25729f2a7..c0e5b2a627 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -174,4 +174,28 @@ check_describe "test2-lightweight-*" --tags --match="test2-*" check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^ +test_expect_success 'name-rev with exact tags' ' + echo A >expect && + tag_object=$(git rev-parse refs/tags/A) && + git name-rev --tags --name-only $tag_object >actual && + test_cmp expect actual && + + echo "A^0" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + git name-rev --tags --name-only $tagged_commit >actual && + test_cmp expect actual +' + +test_expect_success 'describe --contains with the exact tags' ' + echo "A^0" >expect && + tag_object=$(git rev-parse refs/tags/A) && + git describe --contains $tag_object >actual && + test_cmp expect actual && + + echo "A^0" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + git describe --contains $tagged_commit >actual && + test_cmp expect actual +' + test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index a845b154e4..101816e718 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -218,13 +218,13 @@ test_expect_success 'git mv should not change sha1 of moved cache entry' ' rm -f dirty dirty2 -test_expect_success SYMLINKS 'git mv should overwrite symlink to a file' ' +test_expect_success 'git mv should overwrite symlink to a file' ' rm -fr .git && git init && echo 1 >moved && - ln -s moved symlink && - git add moved symlink && + test_ln_s_add moved symlink && + git add moved && test_must_fail git mv moved symlink && git mv -f moved symlink && ! test -e moved && @@ -237,22 +237,26 @@ test_expect_success SYMLINKS 'git mv should overwrite symlink to a file' ' rm -f moved symlink -test_expect_success SYMLINKS 'git mv should overwrite file with a symlink' ' +test_expect_success 'git mv should overwrite file with a symlink' ' rm -fr .git && git init && echo 1 >moved && - ln -s moved symlink && - git add moved symlink && + test_ln_s_add moved symlink && + git add moved && test_must_fail git mv symlink moved && git mv -f symlink moved && ! test -e symlink && - test -h moved && git update-index --refresh && git diff-files --quiet ' +test_expect_success SYMLINKS 'check moved symlink' ' + + test -h moved +' + rm -f moved symlink test_done diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh index 8f3b54d826..88d60c1ce2 100755 --- a/t/t7011-skip-worktree-reading.sh +++ b/t/t7011-skip-worktree-reading.sh @@ -91,12 +91,12 @@ test_expect_success 'update-index --remove' ' test_cmp expected 1 ' -test_expect_success 'ls-files --delete' ' +test_expect_success 'ls-files --deleted' ' setup_absent && test -z "$(git ls-files -d)" ' -test_expect_success 'ls-files --delete' ' +test_expect_success 'ls-files --deleted' ' setup_dirty && test -z "$(git ls-files -d)" ' diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index df82ec9dda..8d4b50d1b5 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -9,6 +9,19 @@ Documented tests for git reset' . ./test-lib.sh +commit_msg () { + # String "modify 2nd file (changed)" partly in German + # (translated with Google Translate), + # encoded in UTF-8, used as a commit log message below. + msg="modify 2nd file (ge\303\244ndert)\n" + if test -n "$1" + then + printf "$msg" | iconv -f utf-8 -t "$1" + else + printf "$msg" + fi +} + test_expect_success 'creating initial files and commits' ' test_tick && echo "1st file" >first && @@ -28,7 +41,7 @@ test_expect_success 'creating initial files and commits' ' echo "1st line 2nd file" >secondfile && echo "2nd line 2nd file" >>secondfile && - git commit -a -m "modify 2nd file" && + git -c "i18n.commitEncoding=iso8859-1" commit -a -m "$(commit_msg iso8859-1)" && head5=$(git rev-parse --verify HEAD) ' # git log --pretty=oneline # to see those SHA1 involved @@ -44,6 +57,20 @@ check_changes () { done | test_cmp .cat_expect - } +test_expect_success 'reset --hard message' ' + hex=$(git log -1 --format="%h") && + git reset --hard > .actual && + echo HEAD is now at $hex $(commit_msg) > .expected && + test_cmp .expected .actual +' + +test_expect_success 'reset --hard message (iso8859-1 logoutputencoding)' ' + hex=$(git log -1 --format="%h") && + git -c "i18n.logOutputEncoding=iso8859-1" reset --hard > .actual && + echo HEAD is now at $hex $(commit_msg iso8859-1) > .expected && + test_cmp .expected .actual +' + >.diff_expect >.cached_expect cat >.cat_expect <<EOF @@ -192,7 +219,8 @@ test_expect_success \ 'changing files and redo the last commit should succeed' ' echo "3rd line 2nd file" >>secondfile && git commit -a -C ORIG_HEAD && - check_changes 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d && + head4=$(git rev-parse --verify HEAD) && + check_changes $head4 && test "$(git rev-parse ORIG_HEAD)" = \ $head5 ' @@ -211,7 +239,7 @@ test_expect_success \ git reset --hard HEAD~2 && check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e && test "$(git rev-parse ORIG_HEAD)" = \ - 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d + $head4 ' >.diff_expect @@ -303,7 +331,7 @@ test_expect_success 'redoing the last two commits should succeed' ' echo "1st line 2nd file" >secondfile && echo "2nd line 2nd file" >>secondfile && - git commit -a -m "modify 2nd file" && + git -c "i18n.commitEncoding=iso8859-1" commit -a -m "$(commit_msg iso8859-1)" && check_changes $head5 ' @@ -326,10 +354,11 @@ test_expect_success '--hard reset to HEAD should clear a failed merge' ' git checkout branch2 && echo "3rd line in branch2" >>secondfile && git commit -a -m "change in branch2" && + head3=$(git rev-parse --verify HEAD) && test_must_fail git pull . branch1 && git reset --hard && - check_changes 77abb337073fb4369a7ad69ff6f5ec0e4d6b54bb + check_changes $head3 ' >.diff_expect @@ -457,7 +486,7 @@ test_expect_success 'disambiguation (1)' ' test_must_fail git diff --quiet -- secondfile && test -z "$(git diff --cached --name-only)" && test -f secondfile && - test ! -s secondfile + test_must_be_empty secondfile ' diff --git a/t/t7301-clean-interactive.sh b/t/t7301-clean-interactive.sh new file mode 100755 index 0000000000..4e6055d06a --- /dev/null +++ b/t/t7301-clean-interactive.sh @@ -0,0 +1,439 @@ +#!/bin/sh + +test_description='git clean -i basic tests' + +. ./test-lib.sh + +test_expect_success 'setup' ' + + mkdir -p src && + touch src/part1.c Makefile && + echo build >.gitignore && + echo \*.o >>.gitignore && + git add . && + git commit -m setup && + touch src/part2.c README && + git add . + +' + +test_expect_success 'git clean -i (clean)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo c | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -i (quit)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo q | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -i (Ctrl+D)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + echo "\04" | git clean -i && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter all)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "*"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter patterns)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "part3.* *.out"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (filter patterns 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo f; echo "* !*.out"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - all)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo "*"; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - none)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 3; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 2 3; echo 5; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - number 3)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 3,4 5; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - range)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 1,3-4; echo 2; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test ! -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (select - range 2)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo 4- 1; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test ! -f src/part4.c && + test ! -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (inverse select)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo s; echo "*"; echo -5- 1 -2; echo; echo c) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (ask)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id (ask - Ctrl+D)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (echo a; echo Y; echo no; echo yes; echo "\04") | \ + git clean -id && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (filter)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo f; echo "docs"; echo "*.h"; echo ; echo c) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (select by name)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo s; echo "../docs/"; echo "../src/part3.c"; \ + echo "../src/part4.c"; echo; echo c) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test -f a.out && + test ! -f docs/manual.txt && + test ! -f src/part3.c && + test -f src/part3.h && + test ! -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_expect_success 'git clean -id with prefix and path (ask)' ' + + mkdir -p build docs && + touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ + docs/manual.txt obj.o build/lib.so && + (cd build/ && \ + (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \ + git clean -id ..) && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f docs/manual.txt && + test -f src/part3.c && + test ! -f src/part3.h && + test -f src/part4.c && + test -f src/part4.h && + test -f obj.o && + test -f build/lib.so + +' + +test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index ff265353a3..5ee97b003a 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -78,7 +78,7 @@ test_expect_success 'submodule add' ' ( cd addtest && git submodule add -q "$submodurl" submod >actual && - test ! -s actual && + test_must_be_empty actual && echo "gitdir: ../.git/modules/submod" >expect && test_cmp expect submod/.git && ( @@ -212,6 +212,32 @@ test_expect_success 'submodule add with ./, /.. and // in path' ' test_cmp empty untracked ' +test_expect_success 'submodule add in subdirectory' ' + echo "refs/heads/master" >expect && + >empty && + + mkdir addtest/sub && + ( + cd addtest/sub && + git submodule add "$submodurl" ../realsubmod3 && + git submodule init + ) && + + rm -f heads head untracked && + inspect addtest/realsubmod3 ../.. && + test_cmp expect heads && + test_cmp expect head && + test_cmp empty untracked +' + +test_expect_success 'submodule add in subdirectory with relative path should fail' ' + ( + cd addtest/sub && + test_must_fail git submodule add ../../ submod3 2>../../output.err + ) && + test_i18ngrep toplevel output.err +' + test_expect_success 'setup - add an example entry to .gitmodules' ' GIT_CONFIG=.gitmodules \ git config submodule.example.url git://example.com/init.git @@ -308,7 +334,7 @@ test_expect_success 'update should work when path is an empty dir' ' mkdir init && git submodule update -q >update.out && - test ! -s update.out && + test_must_be_empty update.out && inspect init && test_cmp expect head-sha1 @@ -319,6 +345,26 @@ test_expect_success 'status should be "up-to-date" after update' ' grep "^ $rev1" list ' +test_expect_success 'status "up-to-date" from subdirectory' ' + mkdir -p sub && + ( + cd sub && + git submodule status >../list + ) && + grep "^ $rev1" list && + grep "\\.\\./init" list +' + +test_expect_success 'status "up-to-date" from subdirectory with path' ' + mkdir -p sub && + ( + cd sub && + git submodule status ../init >../list + ) && + grep "^ $rev1" list && + grep "\\.\\./init" list +' + test_expect_success 'status should be "modified" after submodule commit' ' ( cd init && @@ -399,6 +445,25 @@ test_expect_success 'update --init' ' git rev-parse --resolve-git-dir init/.git ' +test_expect_success 'update --init from subdirectory' ' + mv init init2 && + git config -f .gitmodules submodule.example.url "$(pwd)/init2" && + git config --remove-section submodule.example && + test_must_fail git config submodule.example.url && + + mkdir -p sub && + ( + cd sub && + git submodule update ../init >update.out && + cat update.out && + test_i18ngrep "not initialized" update.out && + test_must_fail git rev-parse --resolve-git-dir ../init/.git && + + git submodule update --init ../init + ) && + git rev-parse --resolve-git-dir init/.git +' + test_expect_success 'do not add files from a submodule' ' git reset --hard && @@ -696,7 +761,7 @@ test_expect_success 'submodule add --name allows to replace a submodule with ano rm -rf repo && git rm repo && git submodule add -q --name repo_new "$submodurl/bare.git" repo >actual && - test ! -s actual && + test_must_be_empty actual && echo "gitdir: ../.git/modules/submod" >expect && test_cmp expect submod/.git && ( @@ -772,6 +837,21 @@ test_expect_success 'submodule deinit should remove the whole submodule section rmdir init ' +test_expect_success 'submodule deinit from subdirectory' ' + git submodule update --init && + git config submodule.example.foo bar && + mkdir -p sub && + ( + cd sub && + git submodule deinit ../init >../output + ) && + grep "\\.\\./init" output && + test -z "$(git config --get-regexp "submodule\.example\.")" && + test -n "$(git config --get-regexp "submodule\.example2\.")" && + test -f example2/.git && + rmdir init +' + test_expect_success 'submodule deinit . deinits all initialized submodules' ' git submodule update --init && git config submodule.example.foo bar && @@ -868,4 +948,35 @@ test_expect_success 'submodule deinit fails when submodule has a .git directory test -n "$(git config --get-regexp "submodule\.example\.")" ' +test_expect_success 'submodule with UTF-8 name' ' + svname=$(printf "\303\245 \303\244\303\266") && + mkdir "$svname" && + ( + cd "$svname" && + git init && + >sub && + git add sub && + git commit -m "init sub" + ) && + test_config core.precomposeunicode true && + git submodule add ./"$svname" && + git submodule >&2 && + test -n "$(git submodule | grep "$svname")" +' + +test_expect_success 'submodule add clone shallow submodule' ' + mkdir super && + pwd=$(pwd) + ( + cd super && + git init && + git submodule add --depth=1 file://"$pwd"/example2 submodule && + ( + cd submodule && + test 1 = $(git log --oneline | wc -l) + ) + ) +' + + test_done diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 30b429e7dc..ac2434c0db 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -45,6 +45,42 @@ EOF test_cmp expected actual " +test_expect_success 'added submodule (subdirectory)' " + mkdir sub && + ( + cd sub && + git submodule summary >../actual + ) && + cat >expected <<-EOF && +* ../sm1 0000000...$head1 (2): + > Add foo2 + +EOF + test_cmp expected actual +" + +test_expect_success 'added submodule (subdirectory only)' " + ( + cd sub && + git submodule summary . >../actual + ) && + >expected && + test_cmp expected actual +" + +test_expect_success 'added submodule (subdirectory with explicit path)' " + ( + cd sub && + git submodule summary ../sm1 >../actual + ) && + cat >expected <<-EOF && +* ../sm1 0000000...$head1 (2): + > Add foo2 + +EOF + test_cmp expected actual +" + commit_file sm1 && head2=$(add_file sm1 foo3) @@ -76,8 +112,8 @@ head3=$( ) test_expect_success 'modified submodule(backward)' " - git submodule summary >actual && - cat >expected <<-EOF && + git submodule summary >actual && + cat >expected <<-EOF && * sm1 $head2...$head3 (2): < Add foo3 < Add foo2 @@ -89,8 +125,8 @@ 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 submodule summary >actual && - cat >expected <<-EOF && + git submodule summary >actual && + cat >expected <<-EOF && * sm1 $head2...$head4 (4): > Add foo5 > Add foo4 @@ -102,15 +138,15 @@ EOF " test_expect_success '--summary-limit' " - git submodule summary -n 3 >actual && - cat >expected <<-EOF && + git submodule summary -n 3 >actual && + cat >expected <<-EOF && * sm1 $head2...$head4 (4): > Add foo5 > Add foo4 < Add foo3 EOF - test_cmp expected actual + test_cmp expected actual " commit_file sm1 && @@ -122,8 +158,8 @@ rm -f sm1 && mv sm1-bak sm1 test_expect_success 'typechanged submodule(submodule->blob), --cached' " - git submodule summary --cached >actual && - cat >expected <<-EOF && + git submodule summary --cached >actual && + cat >expected <<-EOF && * sm1 $head4(submodule)->$head5(blob) (3): < Add foo5 @@ -132,59 +168,59 @@ EOF " test_expect_success 'typechanged submodule(submodule->blob), --files' " - git submodule summary --files >actual && - cat >expected <<-EOF && + git submodule summary --files >actual && + cat >expected <<-EOF && * sm1 $head5(blob)->$head4(submodule) (3): > Add foo5 EOF - test_i18ncmp actual expected + test_i18ncmp actual expected " rm -rf sm1 && git checkout-index sm1 test_expect_success 'typechanged submodule(submodule->blob)' " - git submodule summary >actual && - cat >expected <<-EOF && + git submodule summary >actual && + cat >expected <<-EOF && * sm1 $head4(submodule)->$head5(blob): EOF - test_i18ncmp actual expected + test_i18ncmp actual expected " rm -f sm1 && test_create_repo sm1 && head6=$(add_file sm1 foo6 foo7) test_expect_success 'nonexistent commit' " - git submodule summary >actual && - cat >expected <<-EOF && + git submodule summary >actual && + cat >expected <<-EOF && * sm1 $head4...$head6: Warn: sm1 doesn't contain commit $head4_full EOF - test_i18ncmp actual expected + test_i18ncmp actual expected " commit_file test_expect_success 'typechanged submodule(blob->submodule)' " - git submodule summary >actual && - cat >expected <<-EOF && + git submodule summary >actual && + cat >expected <<-EOF && * sm1 $head5(blob)->$head6(submodule) (2): > Add foo7 EOF - test_i18ncmp expected actual + test_i18ncmp expected actual " commit_file sm1 && rm -rf sm1 test_expect_success 'deleted submodule' " - git submodule summary >actual && - cat >expected <<-EOF && + git submodule summary >actual && + cat >expected <<-EOF && * sm1 $head6...0000000: EOF - test_cmp expected actual + test_cmp expected actual " test_create_repo sm2 && @@ -192,43 +228,43 @@ head7=$(add_file sm2 foo8 foo9) && git add sm2 test_expect_success 'multiple submodules' " - git submodule summary >actual && - cat >expected <<-EOF && + git submodule summary >actual && + cat >expected <<-EOF && * sm1 $head6...0000000: * sm2 0000000...$head7 (2): > Add foo9 EOF - test_cmp expected actual + test_cmp expected actual " test_expect_success 'path filter' " - git submodule summary sm2 >actual && - cat >expected <<-EOF && + git submodule summary sm2 >actual && + cat >expected <<-EOF && * sm2 0000000...$head7 (2): > Add foo9 EOF - test_cmp expected actual + test_cmp expected actual " commit_file sm2 test_expect_success 'given commit' " - git submodule summary HEAD^ >actual && - cat >expected <<-EOF && + git submodule summary HEAD^ >actual && + cat >expected <<-EOF && * sm1 $head6...0000000: * sm2 0000000...$head7 (2): > Add foo9 EOF - test_cmp expected actual + test_cmp expected actual " test_expect_success '--for-status' " - git submodule summary --for-status HEAD^ >actual && - test_i18ncmp actual - <<EOF + git submodule summary --for-status HEAD^ >actual && + test_i18ncmp actual - <<EOF # Submodule changes to be committed: # # * sm1 $head6...0000000: @@ -240,14 +276,14 @@ EOF " test_expect_success 'fail when using --files together with --cached' " - test_must_fail git submodule summary --files --cached + test_must_fail git submodule summary --files --cached " test_expect_success 'should not fail in an empty repo' " - git init xyzzy && - cd xyzzy && - git submodule summary >output 2>&1 && - test_cmp output /dev/null + git init xyzzy && + cd xyzzy && + git submodule summary >output 2>&1 && + test_cmp output /dev/null " test_done diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index 94e26c47ea..79bc135bf6 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -11,216 +11,338 @@ These tests exercise the "git submodule sync" subcommand. . ./test-lib.sh test_expect_success setup ' - echo file > file && + echo file >file && git add file && test_tick && git commit -m upstream && git clone . super && git clone super submodule && - (cd submodule && - git submodule add ../submodule sub-submodule && - test_tick && - git commit -m "sub-submodule" + ( + cd submodule && + git submodule add ../submodule sub-submodule && + test_tick && + git commit -m "sub-submodule" ) && - (cd super && - git submodule add ../submodule submodule && - test_tick && - git commit -m "submodule" + ( + cd super && + git submodule add ../submodule submodule && + test_tick && + git commit -m "submodule" ) && git clone super super-clone && - (cd super-clone && git submodule update --init --recursive) && + ( + cd super-clone && + git submodule update --init --recursive + ) && git clone super empty-clone && - (cd empty-clone && git submodule init) && + ( + cd empty-clone && + git submodule init + ) && git clone super top-only-clone && git clone super relative-clone && - (cd relative-clone && git submodule update --init --recursive) && + ( + cd relative-clone && + git submodule update --init --recursive + ) && git clone super recursive-clone && - (cd recursive-clone && git submodule update --init --recursive) + ( + cd recursive-clone && + git submodule update --init --recursive + ) ' test_expect_success 'change submodule' ' - (cd submodule && - echo second line >> file && - test_tick && - git commit -a -m "change submodule" + ( + cd submodule && + echo second line >>file && + test_tick && + git commit -a -m "change submodule" ) ' +reset_submodule_urls () { + local root + root=$(pwd) && + ( + cd super-clone/submodule && + git config remote.origin.url "$root/submodule" + ) && + ( + cd super-clone/submodule/sub-submodule && + git config remote.origin.url "$root/submodule" + ) +} + test_expect_success 'change submodule url' ' - (cd super && - cd submodule && - git checkout master && - git pull + ( + cd super && + cd submodule && + git checkout master && + git pull ) && mv submodule moved-submodule && - (cd moved-submodule && - git config -f .gitmodules submodule.sub-submodule.url ../moved-submodule && - test_tick && - git commit -a -m moved-sub-submodule + ( + cd moved-submodule && + git config -f .gitmodules submodule.sub-submodule.url ../moved-submodule && + test_tick && + git commit -a -m moved-sub-submodule ) && - (cd super && - git config -f .gitmodules submodule.submodule.url ../moved-submodule && - test_tick && - git commit -a -m moved-submodule + ( + cd super && + git config -f .gitmodules submodule.submodule.url ../moved-submodule && + test_tick && + git commit -a -m moved-submodule ) ' test_expect_success '"git submodule sync" should update submodule URLs' ' - (cd super-clone && - git pull --no-recurse-submodules && - git submodule sync + ( + cd super-clone && + git pull --no-recurse-submodules && + git submodule sync ) && - test -d "$(cd super-clone/submodule && - git config remote.origin.url + test -d "$( + cd super-clone/submodule && + git config remote.origin.url )" && - test ! -d "$(cd super-clone/submodule/sub-submodule && - git config remote.origin.url + test ! -d "$( + cd super-clone/submodule/sub-submodule && + git config remote.origin.url )" && - (cd super-clone/submodule && - git checkout master && - git pull + ( + cd super-clone/submodule && + git checkout master && + git pull ) && - (cd super-clone && - test -d "$(git config submodule.submodule.url)" + ( + cd super-clone && + test -d "$(git config submodule.submodule.url)" ) ' test_expect_success '"git submodule sync --recursive" should update all submodule URLs' ' - (cd super-clone && - (cd submodule && - git pull --no-recurse-submodules - ) && - git submodule sync --recursive + ( + cd super-clone && + ( + cd submodule && + git pull --no-recurse-submodules + ) && + git submodule sync --recursive + ) && + test -d "$( + cd super-clone/submodule && + git config remote.origin.url + )" && + test -d "$( + cd super-clone/submodule/sub-submodule && + git config remote.origin.url + )" && + ( + cd super-clone/submodule/sub-submodule && + git checkout master && + git pull + ) +' + +test_expect_success 'reset submodule URLs' ' + reset_submodule_urls super-clone +' + +test_expect_success '"git submodule sync" should update submodule URLs - subdirectory' ' + ( + cd super-clone && + git pull --no-recurse-submodules && + mkdir -p sub && + cd sub && + git submodule sync >../../output + ) && + grep "\\.\\./submodule" output && + test -d "$( + cd super-clone/submodule && + git config remote.origin.url + )" && + test ! -d "$( + cd super-clone/submodule/sub-submodule && + git config remote.origin.url + )" && + ( + cd super-clone/submodule && + git checkout master && + git pull + ) && + ( + cd super-clone && + test -d "$(git config submodule.submodule.url)" + ) +' + +test_expect_success '"git submodule sync --recursive" should update all submodule URLs - subdirectory' ' + ( + cd super-clone && + ( + cd submodule && + git pull --no-recurse-submodules + ) && + mkdir -p sub && + cd sub && + git submodule sync --recursive >../../output ) && - test -d "$(cd super-clone/submodule && - git config remote.origin.url + grep "\\.\\./submodule/sub-submodule" output && + test -d "$( + cd super-clone/submodule && + git config remote.origin.url )" && - test -d "$(cd super-clone/submodule/sub-submodule && - git config remote.origin.url + test -d "$( + cd super-clone/submodule/sub-submodule && + git config remote.origin.url )" && - (cd super-clone/submodule/sub-submodule && - git checkout master && - git pull + ( + cd super-clone/submodule/sub-submodule && + git checkout master && + git pull ) ' test_expect_success '"git submodule sync" should update known submodule URLs' ' - (cd empty-clone && - git pull && - git submodule sync && - test -d "$(git config submodule.submodule.url)" + ( + cd empty-clone && + git pull && + git submodule sync && + test -d "$(git config submodule.submodule.url)" ) ' test_expect_success '"git submodule sync" should not vivify uninteresting submodule' ' - (cd top-only-clone && - git pull && - git submodule sync && - test -z "$(git config submodule.submodule.url)" && - git submodule sync submodule && - test -z "$(git config submodule.submodule.url)" + ( + cd top-only-clone && + git pull && + git submodule sync && + test -z "$(git config submodule.submodule.url)" && + git submodule sync submodule && + test -z "$(git config submodule.submodule.url)" ) ' test_expect_success '"git submodule sync" handles origin URL of the form foo' ' - (cd relative-clone && - git remote set-url origin foo && - git submodule sync && - (cd submodule && - #actual fails with: "cannot strip off url foo - test "$(git config remote.origin.url)" = "../submodule" - ) + ( + cd relative-clone && + git remote set-url origin foo && + git submodule sync && + ( + cd submodule && + #actual fails with: "cannot strip off url foo + test "$(git config remote.origin.url)" = "../submodule" + ) ) ' test_expect_success '"git submodule sync" handles origin URL of the form foo/bar' ' - (cd relative-clone && - git remote set-url origin foo/bar && - git submodule sync && - (cd submodule && - #actual foo/submodule - test "$(git config remote.origin.url)" = "../foo/submodule" - ) - (cd submodule/sub-submodule && - test "$(git config remote.origin.url)" != "../../foo/submodule" - ) + ( + cd relative-clone && + git remote set-url origin foo/bar && + git submodule sync && + ( + cd submodule && + #actual foo/submodule + test "$(git config remote.origin.url)" = "../foo/submodule" + ) && + ( + cd submodule/sub-submodule && + test "$(git config remote.origin.url)" != "../../foo/submodule" + ) ) ' test_expect_success '"git submodule sync --recursive" propagates changes in origin' ' - (cd recursive-clone && - git remote set-url origin foo/bar && - git submodule sync --recursive && - (cd submodule && - #actual foo/submodule - test "$(git config remote.origin.url)" = "../foo/submodule" - ) - (cd submodule/sub-submodule && - test "$(git config remote.origin.url)" = "../../foo/submodule" - ) + ( + cd recursive-clone && + git remote set-url origin foo/bar && + git submodule sync --recursive && + ( + cd submodule && + #actual foo/submodule + test "$(git config remote.origin.url)" = "../foo/submodule" + ) && + ( + cd submodule/sub-submodule && + test "$(git config remote.origin.url)" = "../../foo/submodule" + ) ) ' test_expect_success '"git submodule sync" handles origin URL of the form ./foo' ' - (cd relative-clone && - git remote set-url origin ./foo && - git submodule sync && - (cd submodule && - #actual ./submodule - test "$(git config remote.origin.url)" = "../submodule" - ) + ( + cd relative-clone && + git remote set-url origin ./foo && + git submodule sync && + ( + cd submodule && + #actual ./submodule + test "$(git config remote.origin.url)" = "../submodule" + ) ) ' test_expect_success '"git submodule sync" handles origin URL of the form ./foo/bar' ' - (cd relative-clone && - git remote set-url origin ./foo/bar && - git submodule sync && - (cd submodule && - #actual ./foo/submodule - test "$(git config remote.origin.url)" = "../foo/submodule" - ) + ( + cd relative-clone && + git remote set-url origin ./foo/bar && + git submodule sync && + ( + cd submodule && + #actual ./foo/submodule + test "$(git config remote.origin.url)" = "../foo/submodule" + ) ) ' test_expect_success '"git submodule sync" handles origin URL of the form ../foo' ' - (cd relative-clone && - git remote set-url origin ../foo && - git submodule sync && - (cd submodule && - #actual ../submodule - test "$(git config remote.origin.url)" = "../../submodule" - ) + ( + cd relative-clone && + git remote set-url origin ../foo && + git submodule sync && + ( + cd submodule && + #actual ../submodule + test "$(git config remote.origin.url)" = "../../submodule" + ) ) ' test_expect_success '"git submodule sync" handles origin URL of the form ../foo/bar' ' - (cd relative-clone && - git remote set-url origin ../foo/bar && - git submodule sync && - (cd submodule && - #actual ../foo/submodule - test "$(git config remote.origin.url)" = "../../foo/submodule" - ) + ( + cd relative-clone && + git remote set-url origin ../foo/bar && + git submodule sync && + ( + cd submodule && + #actual ../foo/submodule + test "$(git config remote.origin.url)" = "../../foo/submodule" + ) ) ' test_expect_success '"git submodule sync" handles origin URL of the form ../foo/bar with deeply nested submodule' ' - (cd relative-clone && - git remote set-url origin ../foo/bar && - mkdir -p a/b/c && - ( cd a/b/c && - git init && - :> .gitignore && - git add .gitignore && - test_tick && - git commit -m "initial commit" ) && - git submodule add ../bar/a/b/c ./a/b/c && - git submodule sync && - (cd a/b/c && - #actual ../foo/bar/a/b/c - test "$(git config remote.origin.url)" = "../../../../foo/bar/a/b/c" - ) + ( + cd relative-clone && + git remote set-url origin ../foo/bar && + mkdir -p a/b/c && + ( + cd a/b/c && + git init && + >.gitignore && + git add .gitignore && + test_tick && + git commit -m "initial commit" + ) && + git submodule add ../bar/a/b/c ./a/b/c && + git submodule sync && + ( + cd a/b/c && + #actual ../foo/bar/a/b/c + test "$(git config remote.origin.url)" = "../../../../foo/bar/a/b/c" + ) ) ' diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index a4ffea0dbe..b192f936bc 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -80,6 +80,21 @@ test_expect_success 'submodule update detaching the HEAD ' ' ) ' +test_expect_success 'submodule update from subdirectory' ' + (cd super/submodule && + git reset --hard HEAD~1 + ) && + mkdir super/sub && + (cd super/sub && + (cd ../submodule && + compare_head + ) && + git submodule update ../submodule && + cd ../submodule && + ! compare_head + ) +' + apos="'"; test_expect_success 'submodule update does not fetch already present commits' ' (cd submodule && @@ -279,6 +294,35 @@ test_expect_success 'submodule update - checkout in .git/config' ' ) ' +test_expect_success 'submodule update - command in .git/config' ' + (cd super && + git config submodule.submodule.update "!git checkout" + ) && + (cd super/submodule && + git reset --hard HEAD^ + ) && + (cd super && + (cd submodule && + compare_head + ) && + git submodule update submodule && + cd submodule && + ! compare_head + ) +' + +test_expect_success 'submodule update - command in .git/config catches failure' ' + (cd super && + git config submodule.submodule.update "!false" + ) && + (cd super/submodule && + git reset --hard HEAD^ + ) && + (cd super && + test_must_fail git submodule update submodule + ) +' + test_expect_success 'submodule init picks up rebase' ' (cd super && git config -f .gitmodules submodule.rebasing.update rebase && @@ -685,14 +729,24 @@ test_expect_success 'submodule update properly revives a moved submodule' ' test_expect_success SYMLINKS 'submodule update can handle symbolic links in pwd' ' mkdir -p linked/dir && ln -s linked/dir linkto && - ( - cd linkto && - git clone "$TRASH_DIRECTORY"/super_update_r2 super && - ( - cd super && - git submodule update --init --recursive - ) + (cd linkto && + git clone "$TRASH_DIRECTORY"/super_update_r2 super && + (cd super && + git submodule update --init --recursive + ) ) ' +test_expect_success 'submodule update clone shallow submodule' ' + git clone cloned super3 && + pwd=$(pwd) + (cd super3 && + sed -e "s#url = ../#url = file://$pwd/#" <.gitmodules >.gitmodules.tmp && + mv -f .gitmodules.tmp .gitmodules && + git submodule update --init --depth=3 + (cd submodule && + test 1 = $(git log --oneline | wc -l) + ) + ) +' test_done diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index 107b4b7c45..91d4fd1fac 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -80,6 +80,22 @@ test_expect_success 'test basic "submodule foreach" usage' ' test_i18ncmp expect actual ' +cat >expect <<EOF +Entering '../sub1' +$pwd/clone-foo1-../sub1-$sub1sha1 +Entering '../sub3' +$pwd/clone-foo3-../sub3-$sub3sha1 +EOF + +test_expect_success 'test "submodule foreach" from subdirectory' ' + mkdir clone/sub && + ( + cd clone/sub && + git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$sha1" >../../actual + ) && + test_i18ncmp expect actual +' + test_expect_success 'setup nested submodules' ' git clone submodule nested1 && git clone submodule nested2 && diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 436b7b606e..bdc1f29503 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -13,9 +13,9 @@ commit_msg_is () { expect=commit_msg_is.expect actual=commit_msg_is.actual - printf "%s" "$(git log --pretty=format:%s%b -1)" >$expect && - printf "%s" "$1" >$actual && - test_i18ncmp $expect $actual + printf "%s" "$(git log --pretty=format:%s%b -1)" >"$actual" && + printf "%s" "$1" >"$expect" && + test_i18ncmp "$expect" "$actual" } # A sanity check to see if commit is working at all. diff --git a/t/t7508-status.sh b/t/t7508-status.sh index e2ffdacc26..ac3d0fe445 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -1335,4 +1335,66 @@ test_expect_failure '.git/config ignore=all suppresses submodule summary' ' git config -f .gitmodules --remove-section submodule.subname ' +test_expect_success 'setup of test environment' ' + git config status.showUntrackedFiles no && + git status -s >expected_short && + git status --no-short >expected_noshort +' + +test_expect_success '"status.short=true" same as "-s"' ' + git -c status.short=true status >actual && + test_cmp expected_short actual +' + +test_expect_success '"status.short=true" weaker than "--no-short"' ' + git -c status.short=true status --no-short >actual && + test_cmp expected_noshort actual +' + +test_expect_success '"status.short=false" same as "--no-short"' ' + git -c status.short=false status >actual && + test_cmp expected_noshort actual +' + +test_expect_success '"status.short=false" weaker than "-s"' ' + git -c status.short=false status -s >actual && + test_cmp expected_short actual +' + +test_expect_success '"status.branch=true" same as "-b"' ' + git status -sb >expected_branch && + git -c status.branch=true status -s >actual && + test_cmp expected_branch actual +' + +test_expect_success '"status.branch=true" different from "--no-branch"' ' + git status -s --no-branch >expected_nobranch && + git -c status.branch=true status -s >actual && + test_must_fail test_cmp expected_nobranch actual +' + +test_expect_success '"status.branch=true" weaker than "--no-branch"' ' + git -c status.branch=true status -s --no-branch >actual && + test_cmp expected_nobranch actual +' + +test_expect_success '"status.branch=true" weaker than "--porcelain"' ' + git -c status.branch=true status --porcelain >actual && + test_cmp expected_nobranch actual +' + +test_expect_success '"status.branch=false" same as "--no-branch"' ' + git -c status.branch=false status -s >actual && + test_cmp expected_nobranch actual +' + +test_expect_success '"status.branch=false" weaker than "-b"' ' + git -c status.branch=false status -sb >actual && + test_cmp expected_branch actual +' + +test_expect_success 'Restore default test environment' ' + git config --unset status.showUntrackedFiles +' + test_done diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index bf08d4e098..31a798fda2 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -77,7 +77,7 @@ test_expect_success 'status when rebase in progress before resolving conflicts' ONTO=$(git rev-parse --short HEAD^^) && test_must_fail git rebase HEAD^ --onto HEAD^^ && cat >expected <<-EOF && - # HEAD detached at $ONTO + # rebase in progress; onto $ONTO # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''. # (fix conflicts and then run "git rebase --continue") # (use "git rebase --skip" to skip this patch) @@ -104,7 +104,7 @@ test_expect_success 'status when rebase in progress before rebase --continue' ' echo three >main.txt && git add main.txt && cat >expected <<-EOF && - # HEAD detached at $ONTO + # rebase in progress; onto $ONTO # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''. # (all conflicts fixed: run "git rebase --continue") # @@ -136,7 +136,7 @@ test_expect_success 'status during rebase -i when conflicts unresolved' ' ONTO=$(git rev-parse --short rebase_i_conflicts) && test_must_fail git rebase -i rebase_i_conflicts && cat >expected <<-EOF && - # HEAD detached at $ONTO + # rebase in progress; onto $ONTO # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''. # (fix conflicts and then run "git rebase --continue") # (use "git rebase --skip" to skip this patch) @@ -162,7 +162,7 @@ test_expect_success 'status during rebase -i after resolving conflicts' ' test_must_fail git rebase -i rebase_i_conflicts && git add main.txt && cat >expected <<-EOF && - # HEAD detached at $ONTO + # rebase in progress; onto $ONTO # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''. # (all conflicts fixed: run "git rebase --continue") # @@ -188,10 +188,9 @@ test_expect_success 'status when rebasing -i in edit mode' ' export FAKE_LINES && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD~2) && - TGT=$(git rev-parse --short two_rebase_i) && git rebase -i HEAD~2 && cat >expected <<-EOF && - # HEAD detached from $TGT + # rebase in progress; onto $ONTO # You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -216,9 +215,8 @@ test_expect_success 'status when splitting a commit' ' ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git reset HEAD^ && - TGT=$(git rev-parse --short HEAD) && cat >expected <<-EOF && - # HEAD detached at $TGT + # rebase in progress; onto $ONTO # You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -246,11 +244,10 @@ test_expect_success 'status after editing the last commit with --amend during a export FAKE_LINES && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD~3) && - TGT=$(git rev-parse --short three_amend) && git rebase -i HEAD~3 && git commit --amend -m "foo" && cat >expected <<-EOF && - # HEAD detached from $TGT + # rebase in progress; onto $ONTO # You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -280,7 +277,7 @@ test_expect_success 'status: (continue first edit) second edit' ' git rebase -i HEAD~3 && git rebase --continue && cat >expected <<-EOF && - # HEAD detached from $ONTO + # rebase in progress; onto $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -302,7 +299,7 @@ test_expect_success 'status: (continue first edit) second edit and split' ' git rebase --continue && git reset HEAD^ && cat >expected <<-EOF && - # HEAD detached from $ONTO + # rebase in progress; onto $ONTO # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -329,7 +326,7 @@ test_expect_success 'status: (continue first edit) second edit and amend' ' git rebase --continue && git commit --amend -m "foo" && cat >expected <<-EOF && - # HEAD detached from $ONTO + # rebase in progress; onto $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -351,7 +348,7 @@ test_expect_success 'status: (amend first edit) second edit' ' git commit --amend -m "a" && git rebase --continue && cat >expected <<-EOF && - # HEAD detached from $ONTO + # rebase in progress; onto $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -374,7 +371,7 @@ test_expect_success 'status: (amend first edit) second edit and split' ' git rebase --continue && git reset HEAD^ && cat >expected <<-EOF && - # HEAD detached from $ONTO + # rebase in progress; onto $ONTO # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -402,7 +399,7 @@ test_expect_success 'status: (amend first edit) second edit and amend' ' git rebase --continue && git commit --amend -m "d" && cat >expected <<-EOF && - # HEAD detached from $ONTO + # rebase in progress; onto $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -426,7 +423,7 @@ test_expect_success 'status: (split first edit) second edit' ' git commit -m "e" && git rebase --continue && cat >expected <<-EOF && - # HEAD detached from $ONTO + # rebase in progress; onto $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -451,7 +448,7 @@ test_expect_success 'status: (split first edit) second edit and split' ' git rebase --continue && git reset HEAD^ && cat >expected <<-EOF && - # HEAD detached from $ONTO + # rebase in progress; onto $ONTO # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -481,7 +478,7 @@ test_expect_success 'status: (split first edit) second edit and amend' ' git rebase --continue && git commit --amend -m "h" && cat >expected <<-EOF && - # HEAD detached from $ONTO + # rebase in progress; onto $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -510,7 +507,7 @@ test_expect_success 'status in an am session: file already exists' ' cat >expected <<-\EOF && # On branch am_already_exists # You are in the middle of an am session. - # (fix conflicts and then run "git am --resolved") + # (fix conflicts and then run "git am --continue") # (use "git am --skip" to skip this patch) # (use "git am --abort" to restore the original branch) # @@ -532,7 +529,7 @@ test_expect_success 'status in an am session: file does not exist' ' cat >expected <<-\EOF && # On branch am_not_exists # You are in the middle of an am session. - # (fix conflicts and then run "git am --resolved") + # (fix conflicts and then run "git am --continue") # (use "git am --skip" to skip this patch) # (use "git am --abort" to restore the original branch) # @@ -601,7 +598,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' ' ONTO=$(git rev-parse --short HEAD^^) && test_must_fail git rebase HEAD^ --onto HEAD^^ && cat >expected <<-EOF && - # HEAD detached at $ONTO + # rebase in progress; onto $ONTO # You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''. # # Unmerged paths: @@ -632,7 +629,8 @@ test_expect_success 'status when cherry-picking before resolving conflicts' ' cat >expected <<-\EOF && # On branch cherry_branch # You are currently cherry-picking. - # (fix conflicts and run "git commit") + # (fix conflicts and run "git cherry-pick --continue") + # (use "git cherry-pick --abort" to cancel the cherry-pick operation) # # Unmerged paths: # (use "git add <file>..." to mark resolution) @@ -655,7 +653,8 @@ test_expect_success 'status when cherry-picking after resolving conflicts' ' cat >expected <<-\EOF && # On branch cherry_branch # You are currently cherry-picking. - # (all conflicts fixed: run "git commit") + # (all conflicts fixed: run "git cherry-pick --continue") + # (use "git cherry-pick --abort" to cancel the cherry-pick operation) # # Changes to be committed: # @@ -667,7 +666,7 @@ test_expect_success 'status when cherry-picking after resolving conflicts' ' test_i18ncmp expected actual ' -test_expect_success 'status showing detached from a tag' ' +test_expect_success 'status showing detached at and from a tag' ' test_commit atag tagging && git checkout atag && cat >expected <<-\EOF @@ -675,6 +674,14 @@ test_expect_success 'status showing detached from a tag' ' nothing to commit (use -u to show untracked files) EOF git status --untracked-files=no >actual && + test_i18ncmp expected actual && + + git reset --hard HEAD^ && + cat >expected <<-\EOF + # HEAD detached from atag + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && test_i18ncmp expected actual ' diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 2f70433568..10aa028017 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -316,7 +316,7 @@ test_expect_success 'merge c1 with c2 (squash)' ' test_debug 'git log --graph --decorate --oneline --all' -test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' ' +test_expect_success 'unsuccessful merge of c1 with c2 (squash, ff-only)' ' git reset --hard c1 && test_must_fail git merge --squash --ff-only c2 ' @@ -497,9 +497,15 @@ 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 'option --ff-only overwrites --no-ff' ' + git merge --no-ff --ff-only c1 && + test_must_fail git merge --no-ff --ff-only c2 +' + +test_expect_success 'option --no-ff overrides merge.ff=only config' ' + git reset --hard c0 && + test_config merge.ff only && + git merge --no-ff c1 ' test_expect_success 'merge c0 with c1 (ff overrides no-ff)' ' diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index 6547eb8f54..758a623cdb 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -141,11 +141,10 @@ test_expect_success SYMLINKS 'will not overwrite untracked symlink in leading pa test_path_is_missing .git/MERGE_HEAD ' -test_expect_success SYMLINKS 'will not be confused by symlink in leading path' ' +test_expect_success 'will not be confused by symlink in leading path' ' git reset --hard c0 && rm -rf sub && - ln -s sub2 sub && - git add sub && + test_ln_s_add sub2 sub && git commit -m ln && git checkout sub ' diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh index 41962f04a7..72176e42c1 100755 --- a/t/t8001-annotate.sh +++ b/t/t8001-annotate.sh @@ -6,9 +6,9 @@ test_description='git annotate' PROG='git annotate' . "$TEST_DIRECTORY"/annotate-tests.sh -test_expect_success 'Annotating an old revision works' ' - git annotate file master >result && - awk "{ print \$3; }" <result >authors && +test_expect_success 'annotate old revision' ' + git annotate file master >actual && + awk "{ print \$3; }" <actual >authors && test 2 = $(grep A <authors | wc -l) && test 2 = $(grep B <authors | wc -l) ' diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index e2896cffc1..5cdf3f178e 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -7,8 +7,16 @@ PROG='git blame -c' . "$TEST_DIRECTORY"/annotate-tests.sh PROG='git blame -c -e' -test_expect_success 'Blame --show-email works' ' - check_count "<A@test.git>" 1 "<B@test.git>" 1 "<B1@test.git>" 1 "<B2@test.git>" 1 "<author@example.com>" 1 "<C@test.git>" 1 "<D@test.git>" 1 "<E at test dot git>" 1 +test_expect_success 'blame --show-email' ' + check_count \ + "<A@test.git>" 1 \ + "<B@test.git>" 1 \ + "<B1@test.git>" 1 \ + "<B2@test.git>" 1 \ + "<author@example.com>" 1 \ + "<C@test.git>" 1 \ + "<D@test.git>" 1 \ + "<E at test dot git>" 1 ' test_done diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh index bf6caa4dc3..7683515155 100755 --- a/t/t8006-blame-textconv.sh +++ b/t/t8006-blame-textconv.sh @@ -18,17 +18,13 @@ test_expect_success 'setup ' ' echo "bin: test number 0" >zero.bin && echo "bin: test 1" >one.bin && echo "bin: test number 2" >two.bin && - if test_have_prereq SYMLINKS; then - ln -s one.bin symlink.bin - fi && + test_ln_s_add one.bin symlink.bin && git add . && GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" && echo "bin: test 1 version 2" >one.bin && echo "bin: test number 2 version 2" >>two.bin && - if test_have_prereq SYMLINKS; then - rm symlink.bin && - ln -s two.bin symlink.bin - fi && + rm -f symlink.bin && + test_ln_s_add two.bin symlink.bin && GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00" ' @@ -135,7 +131,7 @@ test_expect_success SYMLINKS 'blame --textconv (on symlink)' ' # cp two.bin three.bin and make small tweak # (this will direct blame -C -C three.bin to consider two.bin and symlink.bin) -test_expect_success SYMLINKS 'make another new commit' ' +test_expect_success 'make another new commit' ' cat >three.bin <<\EOF && bin: test number 2 bin: test number 2 version 2 @@ -146,7 +142,7 @@ EOF GIT_AUTHOR_NAME=Number4 git commit -a -m Fourth --date="2010-01-01 23:00:00" ' -test_expect_success SYMLINKS 'blame on last commit (-C -C, symlink)' ' +test_expect_success 'blame on last commit (-C -C, symlink)' ' git blame -C -C three.bin >blame && find_blame <blame >result && cat >expected <<\EOF && diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh index 78a0085e64..b95e102891 100755 --- a/t/t8007-cat-file-textconv.sh +++ b/t/t8007-cat-file-textconv.sh @@ -12,9 +12,7 @@ chmod +x helper test_expect_success 'setup ' ' echo "bin: test" >one.bin && - if test_have_prereq SYMLINKS; then - ln -s one.bin symlink.bin - fi && + test_ln_s_add one.bin symlink.bin && git add . && GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" && echo "bin: test version 2" >one.bin && @@ -72,14 +70,14 @@ test_expect_success 'cat-file --textconv on previous commit' ' test_cmp expected result ' -test_expect_success SYMLINKS 'cat-file without --textconv (symlink)' ' +test_expect_success 'cat-file without --textconv (symlink)' ' git cat-file blob :symlink.bin >result && printf "%s" "one.bin" >expected test_cmp expected result ' -test_expect_success SYMLINKS 'cat-file --textconv on index (symlink)' ' +test_expect_success 'cat-file --textconv on index (symlink)' ' ! git cat-file --textconv :symlink.bin 2>result && cat >expected <<\EOF && fatal: git cat-file --textconv: unable to run textconv on :symlink.bin @@ -87,7 +85,7 @@ EOF test_cmp expected result ' -test_expect_success SYMLINKS 'cat-file --textconv on HEAD (symlink)' ' +test_expect_success 'cat-file --textconv on HEAD (symlink)' ' ! git cat-file --textconv HEAD:symlink.bin 2>result && cat >expected <<EOF && fatal: git cat-file --textconv: unable to run textconv on HEAD:symlink.bin diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 5d2dbe98d5..2813aa9a61 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -171,6 +171,81 @@ Result: OK EOF " +test_suppress_self () { + test_commit $3 && + test_when_finished "git reset --hard HEAD^" && + + write_script cccmd-sed <<-EOF && + sed -n -e s/^cccmd--//p "\$1" + EOF + + git commit --amend --author="$1 <$2>" -F - && + clean_fake_sendmail && + git format-patch --stdout -1 >"suppress-self-$3.patch" && + + git send-email --from="$1 <$2>" \ + --to=nobody@example.com \ + --cc-cmd=./cccmd-sed \ + --suppress-cc=self \ + --smtp-server="$(pwd)/fake.sendmail" \ + suppress-self-$3.patch && + + mv msgtxt1 msgtxt1-$3 && + sed -e '/^$/q' msgtxt1-$3 >"msghdr1-$3" && + >"expected-no-cc-$3" && + + (grep '^Cc:' msghdr1-$3 >"actual-no-cc-$3"; + test_cmp expected-no-cc-$3 actual-no-cc-$3) +} + +test_suppress_self_unquoted () { + test_suppress_self "$1" "$2" "unquoted-$3" <<-EOF + test suppress-cc.self unquoted-$3 with name $1 email $2 + + unquoted-$3 + + cccmd--$1 <$2> + + Cc: $1 <$2> + Signed-off-by: $1 <$2> + EOF +} + +test_suppress_self_quoted () { + test_suppress_self "$1" "$2" "quoted-$3" <<-EOF + test suppress-cc.self quoted-$3 with name $1 email $2 + + quoted-$3 + + cccmd--"$1" <$2> + + Cc: $1 <$2> + Cc: "$1" <$2> + Signed-off-by: $1 <$2> + Signed-off-by: "$1" <$2> + EOF +} + +test_expect_success $PREREQ 'self name is suppressed' " + test_suppress_self_unquoted 'A U Thor' 'author@example.com' \ + 'self_name_suppressed' +" + +test_expect_success $PREREQ 'self name with dot is suppressed' " + test_suppress_self_quoted 'A U. Thor' 'author@example.com' \ + 'self_name_dot_suppressed' +" + +test_expect_success $PREREQ 'non-ascii self name is suppressed' " + test_suppress_self_quoted 'Füñný Nâmé' 'odd_?=mail@example.com' \ + 'non_ascii_self_suppressed' +" + +test_expect_success $PREREQ 'sanitized self name is suppressed' " + test_suppress_self_unquoted '\"A U. Thor\"' 'author@example.com' \ + 'self_name_sanitized_suppressed' +" + test_expect_success $PREREQ 'Show all headers' ' git send-email \ --dry-run \ @@ -881,6 +956,20 @@ test_expect_success $PREREQ 'utf8 author is correctly passed on' ' grep "^From: Füñný Nâmé <odd_?=mail@example.com>" msgtxt1 ' +test_expect_success $PREREQ 'utf8 sender is not duplicated' ' + clean_fake_sendmail && + test_commit weird_sender && + test_when_finished "git reset --hard HEAD^" && + git commit --amend --author "Füñný Nâmé <odd_?=mail@example.com>" && + git format-patch --stdout -1 >funny_name.patch && + git send-email --from="Füñný Nâmé <odd_?=mail@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + funny_name.patch && + grep "^From: " msgtxt1 >msgfrom && + test_line_count = 1 msgfrom +' + test_expect_success $PREREQ 'sendemail.composeencoding works' ' clean_fake_sendmail && git config sendemail.composeencoding iso-8859-1 && diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 2471bc6777..34c2d8f49a 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -396,7 +396,7 @@ test_expect_success 'tree_tag-obj' 'git fast-export tree_tag-obj' test_expect_success 'tag-obj_tag' 'git fast-export tag-obj_tag' test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj' -test_expect_success SYMLINKS 'directory becomes symlink' ' +test_expect_success 'directory becomes symlink' ' git init dirtosymlink && git init result && ( @@ -408,8 +408,7 @@ test_expect_success SYMLINKS 'directory becomes symlink' ' git add foo/world bar/world && git commit -q -mone && git rm -r foo && - ln -s bar foo && - git add foo && + test_ln_s_add bar foo && git commit -q -mtwo ) && ( diff --git a/t/t9402-git-cvsserver-refs.sh b/t/t9402-git-cvsserver-refs.sh index 735a018ecc..db69af2cff 100755 --- a/t/t9402-git-cvsserver-refs.sh +++ b/t/t9402-git-cvsserver-refs.sh @@ -330,7 +330,7 @@ test_expect_success 'validate result of edits [cvswork2]' ' test_expect_success 'validate basic diffs saved during above cvswork2 edits' ' test $(grep Index: cvsEdit1.diff | wc -l) = 1 && - test ! -s cvsEdit2-empty.diff && + test_must_be_empty cvsEdit2-empty.diff && test $(grep Index: cvsEdit2-N.diff | wc -l) = 1 && test $(grep Index: cvsEdit3.diff | wc -l) = 3 && rm -rf diffSandbox && @@ -456,20 +456,20 @@ test_expect_success 'cvs up -r $(git rev-parse v1)' ' test_expect_success 'cvs diff -r v1 -u' ' ( cd cvswork && cvs -f diff -r v1 -u ) >cvsDiff.out 2>cvs.log && - test ! -s cvsDiff.out && - test ! -s cvs.log + test_must_be_empty cvsDiff.out && + test_must_be_empty cvs.log ' test_expect_success 'cvs diff -N -r v2 -u' ' ( cd cvswork && ! cvs -f diff -N -r v2 -u ) >cvsDiff.out 2>cvs.log && - test ! -s cvs.log && + test_must_be_empty cvs.log && test -s cvsDiff.out && check_diff cvsDiff.out v2 v1 >check_diff.out 2>&1 ' test_expect_success 'cvs diff -N -r v2 -r v1.2' ' ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u ) >cvsDiff.out 2>cvs.log && - test ! -s cvs.log && + test_must_be_empty cvs.log && test -s cvsDiff.out && check_diff cvsDiff.out v2 v1.2 >check_diff.out 2>&1 ' @@ -488,7 +488,7 @@ test_expect_success 'apply early [cvswork3] diff to b3' ' test_expect_success 'check [cvswork3] diff' ' ( cd cvswork3 && ! cvs -f diff -N -u ) >"$WORKDIR/cvsDiff.out" 2>cvs.log && - test ! -s cvs.log && + test_must_be_empty cvs.log && test -s cvsDiff.out && test $(grep Index: cvsDiff.out | wc -l) = 3 && test_cmp cvsDiff.out cvswork3edit.diff && diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 6783c14c1a..6fca19353d 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -156,10 +156,10 @@ test_expect_success \ git commit -a -m "File renamed." && gitweb_run "p=.git;a=commitdiff"' -test_expect_success SYMLINKS \ +test_expect_success \ 'commitdiff(0): file to symlink' \ 'rm renamed_file && - ln -s file renamed_file && + test_ln_s_add file renamed_file && git commit -a -m "File to symlink." && gitweb_run "p=.git;a=commitdiff"' @@ -212,15 +212,14 @@ test_expect_success \ # ---------------------------------------------------------------------- # commitdiff testing (taken from t4114-apply-typechange.sh) -test_expect_success SYMLINKS 'setup typechange commits' ' +test_expect_success 'setup typechange commits' ' echo "hello world" > foo && echo "hi planet" > bar && git update-index --add foo bar && git commit -m initial && git branch initial && rm -f foo && - ln -s bar foo && - git update-index foo && + test_ln_s_add bar foo && git commit -m "foo symlinked to bar" && git branch foo-symlinked-to-bar && rm -f foo && @@ -361,11 +360,7 @@ test_expect_success \ echo "Changed" >> 04-rename-to && test_chmod +x 05-mode-change && rm -f 06-file-or-symlink && - if test_have_prereq SYMLINKS; then - ln -s 01-change 06-file-or-symlink - else - printf %s 01-change > 06-file-or-symlink - fi && + test_ln_s_add 01-change 06-file-or-symlink && echo "Changed and have mode changed" > 07-change-mode-change && test_chmod +x 07-change-mode-change && git commit -a -m "Large commit" && diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh index 9730821c30..2bf142d09c 100755 --- a/t/t9801-git-p4-branch.sh +++ b/t/t9801-git-p4-branch.sh @@ -469,9 +469,11 @@ test_expect_success 'use-client-spec detect-branches skips branches setup' ' View: //depot/usecs/b1/... //depot/usecs/b3/... EOF - echo b3/b3-file3 >b3/b3-file3 && - p4 add b3/b3-file3 && - p4 submit -d "b3/b3-file3" + echo b3/b3-file3_1 >b3/b3-file3_1 && + echo b3/b3-file3_2 >b3/b3-file3_2 && + p4 add b3/b3-file3_1 && + p4 add b3/b3-file3_2 && + p4 submit -d "b3/b3-file3_1 b3/b3-file3_2" ) ' @@ -487,6 +489,21 @@ test_expect_success 'use-client-spec detect-branches skips branches' ' ) ' +test_expect_success 'use-client-spec detect-branches skips files in branches' ' + client_view "//depot/usecs/... //client/..." \ + "-//depot/usecs/b3/b3-file3_1 //client/b3/b3-file3_1" && + test_when_finished cleanup_git && + test_create_repo "$git" && + ( + cd "$git" && + git p4 sync --detect-branches --use-client-spec //depot/usecs@all && + git checkout -b master p4/usecs/b3 && + test_path_is_file b1-file1 && + test_path_is_file b3-file3_2 && + test_path_is_missing b3-file3_1 + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh index eeefa67904..b0d1d94bf4 100755 --- a/t/t9802-git-p4-filetype.sh +++ b/t/t9802-git-p4-filetype.sh @@ -95,7 +95,7 @@ test_expect_success 'gitattributes setting eol=lf produces lf newlines' ' git init && echo "* eol=lf" >.gitattributes && git p4 sync //depot@all && - git checkout master && + git checkout -b master p4/master && test_cmp "$cli"/f-unix-orig f-unix && test_cmp "$cli"/f-win-as-lf f-win ) @@ -109,7 +109,7 @@ test_expect_success 'gitattributes setting eol=crlf produces crlf newlines' ' git init && echo "* eol=crlf" >.gitattributes && git p4 sync //depot@all && - git checkout master && + git checkout -b master p4/master && test_cmp "$cli"/f-unix-as-crlf f-unix && test_cmp "$cli"/f-win-orig f-win ) diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 81a1657efb..272a071e85 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -69,7 +69,7 @@ run_completion () local -a COMPREPLY _words local _cword _words=( $1 ) - test "${1: -1}" == ' ' && _words+=('') + test "${1: -1}" = ' ' && _words+=('') (( _cword = ${#_words[@]} - 1 )) __git_wrap__git_main && print_comp } @@ -122,6 +122,140 @@ test_gitcomp_nl () invalid_variable_name='${foo.bar}' +actual="$TRASH_DIRECTORY/actual" + +test_expect_success 'setup for __gitdir tests' ' + mkdir -p subdir/subsubdir && + git init otherrepo +' + +test_expect_success '__gitdir - from command line (through $__git_dir)' ' + echo "$TRASH_DIRECTORY/otherrepo/.git" >expected && + ( + __git_dir="$TRASH_DIRECTORY/otherrepo/.git" && + __gitdir >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - repo as argument' ' + echo "otherrepo/.git" >expected && + __gitdir "otherrepo" >"$actual" && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - remote as argument' ' + echo "remote" >expected && + __gitdir "remote" >"$actual" && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - .git directory in cwd' ' + echo ".git" >expected && + __gitdir >"$actual" && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - .git directory in parent' ' + echo "$(pwd -P)/.git" >expected && + ( + cd subdir/subsubdir && + __gitdir >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - cwd is a .git directory' ' + echo "." >expected && + ( + cd .git && + __gitdir >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - parent is a .git directory' ' + echo "$(pwd -P)/.git" >expected && + ( + cd .git/refs/heads && + __gitdir >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - $GIT_DIR set while .git directory in cwd' ' + echo "$TRASH_DIRECTORY/otherrepo/.git" >expected && + ( + GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && + export GIT_DIR && + __gitdir >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - $GIT_DIR set while .git directory in parent' ' + echo "$TRASH_DIRECTORY/otherrepo/.git" >expected && + ( + GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && + export GIT_DIR && + cd subdir && + __gitdir >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - non-existing $GIT_DIR' ' + ( + GIT_DIR="$TRASH_DIRECTORY/non-existing" && + export GIT_DIR && + test_must_fail __gitdir + ) +' + +test_expect_success '__gitdir - gitfile in cwd' ' + echo "$(pwd -P)/otherrepo/.git" >expected && + echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" >subdir/.git && + test_when_finished "rm -f subdir/.git" && + ( + cd subdir && + __gitdir >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - gitfile in parent' ' + echo "$(pwd -P)/otherrepo/.git" >expected && + echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" >subdir/.git && + test_when_finished "rm -f subdir/.git" && + ( + cd subdir/subsubdir && + __gitdir >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success SYMLINKS '__gitdir - resulting path avoids symlinks' ' + echo "$(pwd -P)/otherrepo/.git" >expected && + mkdir otherrepo/dir && + test_when_finished "rm -rf otherrepo/dir" && + ln -s otherrepo/dir link && + test_when_finished "rm -f link" && + ( + cd link && + __gitdir >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - not a git repository' ' + ( + cd subdir/subsubdir && + GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" && + export GIT_CEILING_DIRECTORIES && + test_must_fail __gitdir + ) +' + test_expect_success '__gitcomp - trailing space - options' ' test_gitcomp "--re" "--dry-run --reuse-message= --reedit-message= --reset-author" <<-EOF @@ -231,6 +365,7 @@ test_expect_success 'double dash "git" itself' ' --exec-path Z --exec-path= --html-path Z + --man-path Z --info-path Z --work-tree= --namespace= diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index 15521cc4f9..3c3e4e8c38 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -10,528 +10,558 @@ test_description='test git-specific bash prompt functions' . "$GIT_BUILD_DIR/contrib/completion/git-prompt.sh" actual="$TRASH_DIRECTORY/actual" +c_red='\\[\\e[31m\\]' +c_green='\\[\\e[32m\\]' +c_lblue='\\[\\e[1;34m\\]' +c_clear='\\[\\e[0m\\]' test_expect_success 'setup for prompt tests' ' - mkdir -p subdir/subsubdir && git init otherrepo && - echo 1 > file && + echo 1 >file && git add file && test_tick && git commit -m initial && git tag -a -m msg1 t1 && git checkout -b b1 && - echo 2 > file && + echo 2 >file && git commit -m "second b1" file && - echo 3 > file && + echo 3 >file && git commit -m "third b1" file && git tag -a -m msg2 t2 && git checkout -b b2 master && - echo 0 > file && + echo 0 >file && git commit -m "second b2" file && - echo 00 > file && + echo 00 >file && git commit -m "another b2" file && - echo 000 > file && + echo 000 >file && git commit -m "yet another b2" file && git checkout master ' -test_expect_success 'gitdir - from command line (through $__git_dir)' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && - ( - __git_dir="$TRASH_DIRECTORY/otherrepo/.git" && - __gitdir > "$actual" - ) && - test_cmp expected "$actual" -' - -test_expect_success 'gitdir - repo as argument' ' - echo "otherrepo/.git" > expected && - __gitdir "otherrepo" > "$actual" && - test_cmp expected "$actual" -' - -test_expect_success 'gitdir - remote as argument' ' - echo "remote" > expected && - __gitdir "remote" > "$actual" && - test_cmp expected "$actual" -' - -test_expect_success 'gitdir - .git directory in cwd' ' - echo ".git" > expected && - __gitdir > "$actual" && - test_cmp expected "$actual" -' - -test_expect_success 'gitdir - .git directory in parent' ' - echo "$(pwd -P)/.git" > expected && - ( - cd subdir/subsubdir && - __gitdir > "$actual" - ) && - test_cmp expected "$actual" -' - -test_expect_success 'gitdir - cwd is a .git directory' ' - echo "." > expected && - ( - cd .git && - __gitdir > "$actual" - ) && - test_cmp expected "$actual" -' - -test_expect_success 'gitdir - parent is a .git directory' ' - echo "$(pwd -P)/.git" > expected && - ( - cd .git/refs/heads && - __gitdir > "$actual" - ) && - test_cmp expected "$actual" -' - -test_expect_success 'gitdir - $GIT_DIR set while .git directory in cwd' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && - ( - GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && - export GIT_DIR && - __gitdir > "$actual" - ) && - test_cmp expected "$actual" -' - -test_expect_success 'gitdir - $GIT_DIR set while .git directory in parent' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && - ( - GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && - export GIT_DIR && - cd subdir && - __gitdir > "$actual" - ) && - test_cmp expected "$actual" -' - -test_expect_success 'gitdir - non-existing $GIT_DIR' ' - ( - GIT_DIR="$TRASH_DIRECTORY/non-existing" && - export GIT_DIR && - test_must_fail __gitdir - ) -' - -test_expect_success 'gitdir - gitfile in cwd' ' - echo "$(pwd -P)/otherrepo/.git" > expected && - echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git && - test_when_finished "rm -f subdir/.git" && - ( - cd subdir && - __gitdir > "$actual" - ) && - test_cmp expected "$actual" -' - -test_expect_success 'gitdir - gitfile in parent' ' - echo "$(pwd -P)/otherrepo/.git" > expected && - echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git && - test_when_finished "rm -f subdir/.git" && - ( - cd subdir/subsubdir && - __gitdir > "$actual" - ) && +test_expect_success 'prompt - branch name' ' + printf " (master)" >expected && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' -test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' ' - echo "$(pwd -P)/otherrepo/.git" > expected && - mkdir otherrepo/dir && - test_when_finished "rm -rf otherrepo/dir" && - ln -s otherrepo/dir link && - test_when_finished "rm -f link" && - ( - cd link && - __gitdir > "$actual" - ) && +test_expect_success SYMLINKS 'prompt - branch name - symlink symref' ' + printf " (master)" >expected && + test_when_finished "git checkout master" && + test_config core.preferSymlinkRefs true && + git checkout master && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' -test_expect_success 'gitdir - not a git repository' ' - ( - cd subdir/subsubdir && - GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" && - export GIT_CEILING_DIRECTORIES && - test_must_fail __gitdir - ) -' - -test_expect_success 'prompt - branch name' ' - printf " (master)" > expected && - __git_ps1 > "$actual" && +test_expect_success 'prompt - unborn branch' ' + printf " (unborn)" >expected && + git checkout --orphan unborn && + test_when_finished "git checkout master" && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' test_expect_success 'prompt - detached head' ' - printf " ((%s...))" $(git log -1 --format="%h" b1^) > expected && + printf " ((%s...))" $(git log -1 --format="%h" --abbrev=13 b1^) >expected && + test_config core.abbrev 13 && git checkout b1^ && test_when_finished "git checkout master" && - __git_ps1 > "$actual" && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' test_expect_success 'prompt - describe detached head - contains' ' - printf " ((t2~1))" > expected && + printf " ((t2~1))" >expected && git checkout b1^ && test_when_finished "git checkout master" && ( GIT_PS1_DESCRIBE_STYLE=contains && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - describe detached head - branch' ' - printf " ((b1~1))" > expected && + printf " ((b1~1))" >expected && git checkout b1^ && test_when_finished "git checkout master" && ( GIT_PS1_DESCRIBE_STYLE=branch && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - describe detached head - describe' ' - printf " ((t1-1-g%s))" $(git log -1 --format="%h" b1^) > expected && + printf " ((t1-1-g%s))" $(git log -1 --format="%h" b1^) >expected && git checkout b1^ && test_when_finished "git checkout master" && ( GIT_PS1_DESCRIBE_STYLE=describe && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - describe detached head - default' ' - printf " ((t2))" > expected && + printf " ((t2))" >expected && git checkout --detach b1 && test_when_finished "git checkout master" && - __git_ps1 > "$actual" && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' test_expect_success 'prompt - inside .git directory' ' - printf " (GIT_DIR!)" > expected && + printf " (GIT_DIR!)" >expected && ( cd .git && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - deep inside .git directory' ' - printf " (GIT_DIR!)" > expected && + printf " (GIT_DIR!)" >expected && ( cd .git/refs/heads && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - inside bare repository' ' - printf " (BARE:master)" > expected && + printf " (BARE:master)" >expected && git init --bare bare.git && test_when_finished "rm -rf bare.git" && ( cd bare.git && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - interactive rebase' ' - printf " (b1|REBASE-i 2/3)" > expected - echo "#!$SHELL_PATH" >fake_editor.sh && - cat >>fake_editor.sh <<\EOF && -echo "exec echo" > "$1" -echo "edit $(git log -1 --format="%h")" >> "$1" -echo "exec echo" >> "$1" -EOF + printf " (b1|REBASE-i 2/3)" >expected + write_script fake_editor.sh <<-\EOF && + echo "exec echo" >"$1" + echo "edit $(git log -1 --format="%h")" >>"$1" + echo "exec echo" >>"$1" + EOF test_when_finished "rm -f fake_editor.sh" && - chmod a+x fake_editor.sh && test_set_editor "$TRASH_DIRECTORY/fake_editor.sh" && git checkout b1 && test_when_finished "git checkout master" && git rebase -i HEAD^ && test_when_finished "git rebase --abort" - __git_ps1 > "$actual" && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' test_expect_success 'prompt - rebase merge' ' - printf " (b2|REBASE-m 1/3)" > expected && + printf " (b2|REBASE-m 1/3)" >expected && git checkout b2 && test_when_finished "git checkout master" && test_must_fail git rebase --merge b1 b2 && test_when_finished "git rebase --abort" && - __git_ps1 > "$actual" && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' test_expect_success 'prompt - rebase' ' - printf " (b2|REBASE 1/3)" > expected && + printf " (b2|REBASE 1/3)" >expected && git checkout b2 && test_when_finished "git checkout master" && test_must_fail git rebase b1 b2 && test_when_finished "git rebase --abort" && - __git_ps1 > "$actual" && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' test_expect_success 'prompt - merge' ' - printf " (b1|MERGING)" > expected && + printf " (b1|MERGING)" >expected && git checkout b1 && test_when_finished "git checkout master" && test_must_fail git merge b2 && test_when_finished "git reset --hard" && - __git_ps1 > "$actual" && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' test_expect_success 'prompt - cherry-pick' ' - printf " (master|CHERRY-PICKING)" > expected && + printf " (master|CHERRY-PICKING)" >expected && test_must_fail git cherry-pick b1 && test_when_finished "git reset --hard" && - __git_ps1 > "$actual" && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' test_expect_success 'prompt - bisect' ' - printf " (master|BISECTING)" > expected && + printf " (master|BISECTING)" >expected && git bisect start && test_when_finished "git bisect reset" && - __git_ps1 > "$actual" && + __git_ps1 >"$actual" && test_cmp expected "$actual" ' test_expect_success 'prompt - dirty status indicator - clean' ' - printf " (master)" > expected && + printf " (master)" >expected && ( GIT_PS1_SHOWDIRTYSTATE=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - dirty status indicator - dirty worktree' ' - printf " (master *)" > expected && - echo "dirty" > file && + printf " (master *)" >expected && + echo "dirty" >file && test_when_finished "git reset --hard" && ( GIT_PS1_SHOWDIRTYSTATE=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - dirty status indicator - dirty index' ' - printf " (master +)" > expected && - echo "dirty" > file && + printf " (master +)" >expected && + echo "dirty" >file && test_when_finished "git reset --hard" && git add -u && ( GIT_PS1_SHOWDIRTYSTATE=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - dirty status indicator - dirty index and worktree' ' - printf " (master *+)" > expected && - echo "dirty index" > file && + printf " (master *+)" >expected && + echo "dirty index" >file && test_when_finished "git reset --hard" && git add -u && - echo "dirty worktree" > file && + echo "dirty worktree" >file && ( GIT_PS1_SHOWDIRTYSTATE=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - dirty status indicator - before root commit' ' - printf " (master #)" > expected && + printf " (master #)" >expected && ( GIT_PS1_SHOWDIRTYSTATE=y && cd otherrepo && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - dirty status indicator - shell variable unset with config disabled' ' - printf " (master)" > expected && - echo "dirty" > file && + printf " (master)" >expected && + echo "dirty" >file && test_when_finished "git reset --hard" && test_config bash.showDirtyState false && ( sane_unset GIT_PS1_SHOWDIRTYSTATE && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - dirty status indicator - shell variable unset with config enabled' ' - printf " (master)" > expected && - echo "dirty" > file && + printf " (master)" >expected && + echo "dirty" >file && test_when_finished "git reset --hard" && test_config bash.showDirtyState true && ( sane_unset GIT_PS1_SHOWDIRTYSTATE && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - dirty status indicator - shell variable set with config disabled' ' - printf " (master)" > expected && - echo "dirty" > file && + printf " (master)" >expected && + echo "dirty" >file && test_when_finished "git reset --hard" && test_config bash.showDirtyState false && ( GIT_PS1_SHOWDIRTYSTATE=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - dirty status indicator - shell variable set with config enabled' ' - printf " (master *)" > expected && - echo "dirty" > file && + printf " (master *)" >expected && + echo "dirty" >file && test_when_finished "git reset --hard" && test_config bash.showDirtyState true && ( GIT_PS1_SHOWDIRTYSTATE=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - dirty status indicator - not shown inside .git directory' ' - printf " (GIT_DIR!)" > expected && - echo "dirty" > file && + printf " (GIT_DIR!)" >expected && + echo "dirty" >file && test_when_finished "git reset --hard" && ( GIT_PS1_SHOWDIRTYSTATE=y && cd .git && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - stash status indicator - no stash' ' - printf " (master)" > expected && + printf " (master)" >expected && ( GIT_PS1_SHOWSTASHSTATE=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - stash status indicator - stash' ' - printf " (master $)" > expected && + printf " (master $)" >expected && echo 2 >file && git stash && test_when_finished "git stash drop" && + git pack-refs --all && ( GIT_PS1_SHOWSTASHSTATE=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - stash status indicator - not shown inside .git directory' ' - printf " (GIT_DIR!)" > expected && + printf " (GIT_DIR!)" >expected && echo 2 >file && git stash && test_when_finished "git stash drop" && ( GIT_PS1_SHOWSTASHSTATE=y && cd .git && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - untracked files status indicator - no untracked files' ' - printf " (master)" > expected && + printf " (master)" >expected && ( GIT_PS1_SHOWUNTRACKEDFILES=y && cd otherrepo && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - untracked files status indicator - untracked files' ' - printf " (master %%)" > expected && + printf " (master %%)" >expected && ( GIT_PS1_SHOWUNTRACKEDFILES=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - untracked files status indicator - shell variable unset with config disabled' ' - printf " (master)" > expected && + printf " (master)" >expected && test_config bash.showUntrackedFiles false && ( sane_unset GIT_PS1_SHOWUNTRACKEDFILES && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - untracked files status indicator - shell variable unset with config enabled' ' - printf " (master)" > expected && + printf " (master)" >expected && test_config bash.showUntrackedFiles true && ( sane_unset GIT_PS1_SHOWUNTRACKEDFILES && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - untracked files status indicator - shell variable set with config disabled' ' - printf " (master)" > expected && + printf " (master)" >expected && test_config bash.showUntrackedFiles false && ( GIT_PS1_SHOWUNTRACKEDFILES=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - untracked files status indicator - shell variable set with config enabled' ' - printf " (master %%)" > expected && + printf " (master %%)" >expected && test_config bash.showUntrackedFiles true && ( GIT_PS1_SHOWUNTRACKEDFILES=y && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - untracked files status indicator - not shown inside .git directory' ' - printf " (GIT_DIR!)" > expected && + printf " (GIT_DIR!)" >expected && ( GIT_PS1_SHOWUNTRACKEDFILES=y && cd .git && - __git_ps1 > "$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' test_expect_success 'prompt - format string starting with dash' ' - printf -- "-master" > expected && - __git_ps1 "-%s" > "$actual" && + printf -- "-master" >expected && + __git_ps1 "-%s" >"$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - pc mode' ' + printf "BEFORE: (master):AFTER" >expected && + printf "" >expected_output && + ( + __git_ps1 "BEFORE:" ":AFTER" >"$actual" && + test_cmp expected_output "$actual" && + printf "%s" "$PS1" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - bash color pc mode - branch name' ' + printf "BEFORE: (${c_green}master${c_clear}):AFTER" >expected && + ( + GIT_PS1_SHOWCOLORHINTS=y && + __git_ps1 "BEFORE:" ":AFTER" >"$actual" + printf "%s" "$PS1" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - bash color pc mode - detached head' ' + printf "BEFORE: (${c_red}(%s...)${c_clear}):AFTER" $(git log -1 --format="%h" b1^) >expected && + git checkout b1^ && + test_when_finished "git checkout master" && + ( + GIT_PS1_SHOWCOLORHINTS=y && + __git_ps1 "BEFORE:" ":AFTER" && + printf "%s" "$PS1" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirty worktree' ' + printf "BEFORE: (${c_green}master${c_clear} ${c_red}*${c_clear}):AFTER" >expected && + echo "dirty" >file && + test_when_finished "git reset --hard" && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + GIT_PS1_SHOWCOLORHINTS=y && + __git_ps1 "BEFORE:" ":AFTER" && + printf "%s" "$PS1" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirty index' ' + printf "BEFORE: (${c_green}master${c_clear} ${c_green}+${c_clear}):AFTER" >expected && + echo "dirty" >file && + test_when_finished "git reset --hard" && + git add -u && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + GIT_PS1_SHOWCOLORHINTS=y && + __git_ps1 "BEFORE:" ":AFTER" && + printf "%s" "$PS1" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirty index and worktree' ' + printf "BEFORE: (${c_green}master${c_clear} ${c_red}*${c_green}+${c_clear}):AFTER" >expected && + echo "dirty index" >file && + test_when_finished "git reset --hard" && + git add -u && + echo "dirty worktree" >file && + ( + GIT_PS1_SHOWCOLORHINTS=y && + GIT_PS1_SHOWDIRTYSTATE=y && + __git_ps1 "BEFORE:" ":AFTER" && + printf "%s" "$PS1" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - bash color pc mode - dirty status indicator - before root commit' ' + printf "BEFORE: (${c_green}master${c_clear} ${c_green}#${c_clear}):AFTER" >expected && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + GIT_PS1_SHOWCOLORHINTS=y && + cd otherrepo && + __git_ps1 "BEFORE:" ":AFTER" && + printf "%s" "$PS1" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - bash color pc mode - inside .git directory' ' + printf "BEFORE: (${c_green}GIT_DIR!${c_clear}):AFTER" >expected && + echo "dirty" >file && + test_when_finished "git reset --hard" && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + GIT_PS1_SHOWCOLORHINTS=y && + cd .git && + __git_ps1 "BEFORE:" ":AFTER" && + printf "%s" "$PS1" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - bash color pc mode - stash status indicator' ' + printf "BEFORE: (${c_green}master${c_clear} ${c_lblue}\$${c_clear}):AFTER" >expected && + echo 2 >file && + git stash && + test_when_finished "git stash drop" && + ( + GIT_PS1_SHOWSTASHSTATE=y && + GIT_PS1_SHOWCOLORHINTS=y && + __git_ps1 "BEFORE:" ":AFTER" && + printf "%s" "$PS1" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - bash color pc mode - untracked files status indicator' ' + printf "BEFORE: (${c_green}master${c_clear} ${c_red}%%${c_clear}):AFTER" >expected && + ( + GIT_PS1_SHOWUNTRACKEDFILES=y && + GIT_PS1_SHOWCOLORHINTS=y && + __git_ps1 "BEFORE:" ":AFTER" && + printf "%s" "$PS1" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - zsh color pc mode' ' + printf "BEFORE: (%%F{green}master%%f):AFTER" >expected && + ( + ZSH_VERSION=5.0.0 && + GIT_PS1_SHOWCOLORHINTS=y && + __git_ps1 "BEFORE:" ":AFTER" >"$actual" + printf "%s" "$PS1" >"$actual" + ) && test_cmp expected "$actual" ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 52510094ad..a7e9aacbb2 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -343,6 +343,7 @@ test_declared_prereq () { } test_expect_failure () { + test_start_ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test-expect-failure" @@ -357,10 +358,11 @@ test_expect_failure () { test_known_broken_failure_ "$1" fi fi - echo >&3 "" + test_finish_ } test_expect_success () { + test_start_ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test-expect-success" @@ -375,7 +377,7 @@ test_expect_success () { test_failure_ "$@" fi fi - echo >&3 "" + test_finish_ } # test_external runs external test scripts that provide continuous @@ -609,6 +611,18 @@ test_cmp() { $GIT_TEST_CMP "$@" } +# Check if the file expected to be empty is indeed empty, and barfs +# otherwise. + +test_must_be_empty () { + if test -s "$1" + then + echo "'$1' is not empty, it contains:" + cat "$1" + return 1 + fi +} + # Tests that its two parameters refer to the same revision test_cmp_rev () { git rev-parse --verify "$1" >expect.rev && @@ -679,3 +693,20 @@ test_create_repo () { mv .git/hooks .git/hooks-disabled ) || exit } + +# This function helps on symlink challenged file systems when it is not +# important that the file system entry is a symbolic link. +# Use test_ln_s_add instead of "ln -s x y && git add y" to add a +# symbolic link entry y to the index. + +test_ln_s_add () { + if test_have_prereq SYMLINKS + then + ln -s "$1" "$2" && + git update-index --add "$2" + else + printf '%s' "$1" >"$2" && + ln_s_obj=$(git hash-object -w "$2") && + git update-index --add --cacheinfo 120000 $ln_s_obj "$2" + fi +} diff --git a/t/test-lib.sh b/t/test-lib.sh index eff3a653d1..b490283182 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -92,6 +92,7 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' print join("\n", @vars); ') unset XDG_CONFIG_HOME +unset GITPERLLIB GIT_AUTHOR_EMAIL=author@example.com GIT_AUTHOR_NAME='A U Thor' GIT_COMMITTER_EMAIL=committer@example.com @@ -184,6 +185,9 @@ do help=t; shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; + --verbose-only=*) + verbose_only=$(expr "z$1" : 'z[^=]*=\(.*\)') + shift ;; -q|--q|--qu|--qui|--quie|--quiet) # Ignore --quiet under a TAP::Harness. Saying how many tests # passed without the ok/not ok details is always an error. @@ -198,17 +202,39 @@ do --valgrind=*) valgrind=$(expr "z$1" : 'z[^=]*=\(.*\)') shift ;; + --valgrind-only=*) + valgrind_only=$(expr "z$1" : 'z[^=]*=\(.*\)') + shift ;; + --valgrind-parallel=*) + valgrind_parallel=$(expr "z$1" : 'z[^=]*=\(.*\)') + shift ;; + --valgrind-only-stride=*) + valgrind_only_stride=$(expr "z$1" : 'z[^=]*=\(.*\)') + shift ;; + --valgrind-only-offset=*) + valgrind_only_offset=$(expr "z$1" : 'z[^=]*=\(.*\)') + shift ;; --tee) shift ;; # was handled already --root=*) root=$(expr "z$1" : 'z[^=]*=\(.*\)') shift ;; + --statusprefix=*) + statusprefix=$(expr "z$1" : 'z[^=]*=\(.*\)') + shift ;; *) echo "error: unknown test option '$1'" >&2; exit 1 ;; esac done -test -n "$valgrind" && verbose=t +if test -n "$valgrind_only" || test -n "$valgrind_only_stride" +then + test -z "$valgrind" && valgrind=memcheck + test -z "$verbose" && verbose_only="$valgrind_only" +elif test -n "$valgrind" +then + verbose=t +fi if test -n "$color" then @@ -303,12 +329,12 @@ trap 'die' EXIT test_ok_ () { test_success=$(($test_success + 1)) - say_color "" "ok $test_count - $@" + say_color "" "${statusprefix}ok $test_count - $@" } test_failure_ () { test_failure=$(($test_failure + 1)) - say_color error "not ok $test_count - $1" + say_color error "${statusprefix}not ok $test_count - $1" shift echo "$@" | sed -e 's/^/# /' test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; } @@ -316,18 +342,83 @@ test_failure_ () { test_known_broken_ok_ () { test_fixed=$(($test_fixed+1)) - say_color error "ok $test_count - $@ # TODO known breakage vanished" + say_color error "${statusprefix}ok $test_count - $@ # TODO known breakage vanished" } test_known_broken_failure_ () { test_broken=$(($test_broken+1)) - say_color warn "not ok $test_count - $@ # TODO known breakage" + say_color warn "${statusprefix}not ok $test_count - $@ # TODO known breakage" } test_debug () { test "$debug" = "" || eval "$1" } +match_pattern_list () { + arg="$1" + shift + test -z "$*" && return 1 + for pattern_ + do + case "$arg" in + $pattern_) + return 0 + esac + done + return 1 +} + +maybe_teardown_verbose () { + test -z "$verbose_only" && return + exec 4>/dev/null 3>/dev/null + verbose= +} + +last_verbose=t +maybe_setup_verbose () { + test -z "$verbose_only" && return + if match_pattern_list $test_count $verbose_only || + { test -n "$valgrind_only_stride" && + expr $test_count "%" $valgrind_only_stride - $valgrind_only_offset = 0 >/dev/null; } + then + exec 4>&2 3>&1 + # Emit a delimiting blank line when going from + # non-verbose to verbose. Within verbose mode the + # delimiter is printed by test_expect_*. The choice + # of the initial $last_verbose is such that before + # test 1, we do not print it. + test -z "$last_verbose" && echo >&3 "" + verbose=t + else + exec 4>/dev/null 3>/dev/null + verbose= + fi + last_verbose=$verbose +} + +maybe_teardown_valgrind () { + test -z "$GIT_VALGRIND" && return + GIT_VALGRIND_ENABLED= +} + +maybe_setup_valgrind () { + test -z "$GIT_VALGRIND" && return + if test -z "$valgrind_only" && test -z "$valgrind_only_stride" + then + GIT_VALGRIND_ENABLED=t + return + fi + GIT_VALGRIND_ENABLED= + if match_pattern_list $test_count $valgrind_only + then + GIT_VALGRIND_ENABLED=t + elif test -n "$valgrind_only_stride" && + expr $test_count "%" $valgrind_only_stride - $valgrind_only_offset = 0 >/dev/null + then + GIT_VALGRIND_ENABLED=t + fi +} + test_eval_ () { # This is a separate function because some tests use # "return" to end a test_expect_success block early. @@ -337,8 +428,10 @@ test_eval_ () { test_run_ () { test_cleanup=: expecting_failure=$2 + setup_malloc_check test_eval_ "$1" eval_ret=$? + teardown_malloc_check if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure" then @@ -353,17 +446,24 @@ test_run_ () { return "$eval_ret" } -test_skip () { +test_start_ () { test_count=$(($test_count+1)) + maybe_setup_verbose + maybe_setup_valgrind +} + +test_finish_ () { + echo >&3 "" + maybe_teardown_valgrind + maybe_teardown_verbose +} + +test_skip () { to_skip= - for skp in $GIT_SKIP_TESTS - do - case $this_test.$test_count in - $skp) - to_skip=t - break - esac - done + if match_pattern_list $this_test.$test_count $GIT_SKIP_TESTS + then + to_skip=t + fi if test -z "$to_skip" && test -n "$test_prereq" && ! test_have_prereq "$test_prereq" then @@ -377,8 +477,8 @@ test_skip () { of_prereq=" of $test_prereq" fi - say_color skip >&3 "skipping test: $@" - say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})" + say_color skip >&3 "${statusprefix}skipping test: $@" + say_color skip "${statusprefix}ok $test_count # skip $1 (missing $missing_prereq${of_prereq})" : true ;; *) @@ -395,6 +495,8 @@ test_at_end_hook_ () { test_done () { GIT_EXIT_OK=t + # Note: t0000 relies on $HARNESS_ACTIVE disabling the .counts + # output file if test -z "$HARNESS_ACTIVE" then test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results" @@ -414,11 +516,11 @@ test_done () { if test "$test_fixed" != 0 then - say_color error "# $test_fixed known breakage(s) vanished; please update test(s)" + say_color error "${statusprefix}# $test_fixed known breakage(s) vanished; please update test(s)" fi if test "$test_broken" != 0 then - say_color warn "# still have $test_broken known breakage(s)" + say_color warn "${statusprefix}# still have $test_broken known breakage(s)" fi if test "$test_broken" != 0 || test "$test_fixed" != 0 then @@ -441,9 +543,9 @@ test_done () { then if test $test_remaining -gt 0 then - say_color pass "# passed all $msg" + say_color pass "${statusprefix}# passed all $msg" fi - say "1..$test_count$skip_all" + say "${statusprefix}1..$test_count$skip_all" fi test -d "$remove_trash" && @@ -457,8 +559,8 @@ test_done () { *) if test $test_external_has_tap -eq 0 then - say_color error "# failed $test_failure among $msg" - say "1..$test_count" + say_color error "${statusprefix}# failed $test_failure among $msg" + say "${statusprefix}1..$test_count" fi exit 1 ;; @@ -466,6 +568,9 @@ test_done () { esac } + +# Set up a directory that we can put in PATH which redirects all git +# calls to 'valgrind git ...'. if test -n "$valgrind" then make_symlink () { @@ -513,31 +618,43 @@ then make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit } - # override all git executables in TEST_DIRECTORY/.. - GIT_VALGRIND=$TEST_DIRECTORY/valgrind - mkdir -p "$GIT_VALGRIND"/bin - for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-* - do - make_valgrind_symlink $file - done - # special-case the mergetools loadables - make_symlink "$GIT_BUILD_DIR"/mergetools "$GIT_VALGRIND/bin/mergetools" - OLDIFS=$IFS - IFS=: - for path in $PATH - do - ls "$path"/git-* 2> /dev/null | - while read file + # In the case of --valgrind-parallel, we only need to do the + # wrapping once, in the main script. The worker children all + # have $valgrind_only_stride set, so we can skip based on that. + if test -z "$valgrind_only_stride" + then + # override all git executables in TEST_DIRECTORY/.. + GIT_VALGRIND=$TEST_DIRECTORY/valgrind + mkdir -p "$GIT_VALGRIND"/bin + for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-* do - make_valgrind_symlink "$file" + make_valgrind_symlink $file done - done - IFS=$OLDIFS + # special-case the mergetools loadables + make_symlink "$GIT_BUILD_DIR"/mergetools "$GIT_VALGRIND/bin/mergetools" + OLDIFS=$IFS + IFS=: + for path in $PATH + do + ls "$path"/git-* 2> /dev/null | + while read file + do + make_valgrind_symlink "$file" + done + done + IFS=$OLDIFS + fi PATH=$GIT_VALGRIND/bin:$PATH GIT_EXEC_PATH=$GIT_VALGRIND/bin export GIT_VALGRIND GIT_VALGRIND_MODE="$valgrind" export GIT_VALGRIND_MODE + GIT_VALGRIND_ENABLED=t + if test -n "$valgrind_only" || test -n "$valgrind_only_stride" + then + GIT_VALGRIND_ENABLED= + fi + export GIT_VALGRIND_ENABLED elif test -n "$GIT_TEST_INSTALLED" then GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || @@ -622,21 +739,53 @@ then else mkdir -p "$TRASH_DIRECTORY" fi + +# Gross hack to spawn N sub-instances of the tests in parallel, and +# summarize the results. Note that if this is enabled, the script +# terminates at the end of this 'if' block. +if test -n "$valgrind_parallel" +then + for i in $(test_seq 1 $valgrind_parallel) + do + root="$TRASH_DIRECTORY/vgparallel-$i" + mkdir "$root" + TEST_OUTPUT_DIRECTORY="$root" \ + ${SHELL_PATH} "$0" \ + --root="$root" --statusprefix="[$i] " \ + --valgrind="$valgrind" \ + --valgrind-only-stride="$valgrind_parallel" \ + --valgrind-only-offset="$i" & + pids="$pids $!" + done + trap "kill $pids" INT TERM HUP + wait $pids + trap - INT TERM HUP + for i in $(test_seq 1 $valgrind_parallel) + do + root="$TRASH_DIRECTORY/vgparallel-$i" + eval "$(cat "$root/test-results/$(basename "$0" .sh)"-*.counts | + sed 's/^\([a-z][a-z]*\) \([0-9][0-9]*\)/inner_\1=\2/')" + test_count=$(expr $test_count + $inner_total) + test_success=$(expr $test_success + $inner_success) + test_fixed=$(expr $test_fixed + $inner_fixed) + test_broken=$(expr $test_broken + $inner_broken) + test_failure=$(expr $test_failure + $inner_failed) + done + test_done +fi + # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). cd -P "$TRASH_DIRECTORY" || exit 1 this_test=${0##*/} this_test=${this_test%%-*} -for skp in $GIT_SKIP_TESTS -do - case "$this_test" in - $skp) - say_color info >&3 "skipping test $this_test altogether" - skip_all="skip all tests in $this_test" - test_done - esac -done +if match_pattern_list "$this_test" $GIT_SKIP_TESTS +then + say_color info >&3 "skipping test $this_test altogether" + skip_all="skip all tests in $this_test" + test_done +fi # Provide an implementation of the 'yes' utility yes () { @@ -737,7 +886,14 @@ test_i18ngrep () { test_lazy_prereq PIPE ' # test whether the filesystem supports FIFOs - rm -f testfifo && mkfifo testfifo + case $(uname -s) in + CYGWIN*) + false + ;; + *) + rm -f testfifo && mkfifo testfifo + ;; + esac ' test_lazy_prereq SYMLINKS ' diff --git a/t/valgrind/valgrind.sh b/t/valgrind/valgrind.sh index 6b87c91b60..42153036dc 100755 --- a/t/valgrind/valgrind.sh +++ b/t/valgrind/valgrind.sh @@ -4,6 +4,9 @@ base=$(basename "$0") TOOL_OPTIONS='--leak-check=no' +test -z "$GIT_VALGRIND_ENABLED" && +exec "$GIT_VALGRIND"/../../"$base" "$@" + case "$GIT_VALGRIND_MODE" in memcheck-fast) ;; diff --git a/test-chmtime.c b/test-chmtime.c index 923bf327f8..0df748784b 100644 --- a/test-chmtime.c +++ b/test-chmtime.c @@ -84,7 +84,7 @@ int main(int argc, char *argv[]) if (stat(argv[i], &sb) < 0) { fprintf(stderr, "Failed to stat %s: %s\n", argv[i], strerror(errno)); - return -1; + return 1; } #ifdef GIT_WINDOWS_NATIVE @@ -92,7 +92,7 @@ int main(int argc, char *argv[]) chmod(argv[i], sb.st_mode | S_IWUSR)) { fprintf(stderr, "Could not make user-writable %s: %s", argv[i], strerror(errno)); - return -1; + return 1; } #endif @@ -107,7 +107,7 @@ int main(int argc, char *argv[]) if (utb.modtime != sb.st_mtime && utime(argv[i], &utb) < 0) { fprintf(stderr, "Failed to modify time on %s: %s\n", argv[i], strerror(errno)); - return -1; + return 1; } } @@ -115,5 +115,5 @@ int main(int argc, char *argv[]) usage: fprintf(stderr, "usage: %s %s\n", argv[0], usage_str); - return -1; + return 1; } diff --git a/test-dump-cache-tree.c b/test-dump-cache-tree.c index a6ffdf39d5..47eab9765f 100644 --- a/test-dump-cache-tree.c +++ b/test-dump-cache-tree.c @@ -59,6 +59,8 @@ int main(int ac, char **av) struct cache_tree *another = cache_tree(); if (read_cache() < 0) die("unable to read index file"); - cache_tree_update(another, active_cache, active_nr, WRITE_TREE_DRY_RUN); + cache_tree_update(another, + (const struct cache_entry * const *)active_cache, + active_nr, WRITE_TREE_DRY_RUN); return dump_cache_tree(active_cache_tree, another, ""); } diff --git a/test-path-utils.c b/test-path-utils.c index 0092cbf354..bb975e4d3e 100644 --- a/test-path-utils.c +++ b/test-path-utils.c @@ -28,6 +28,19 @@ static int normalize_ceiling_entry(struct string_list_item *item, void *unused) return 1; } +static void normalize_argv_string(const char **var, const char *input) +{ + if (!strcmp(input, "<null>")) + *var = NULL; + else if (!strcmp(input, "<empty>")) + *var = ""; + else + *var = input; + + if (*var && (**var == '<' || **var == '(')) + die("Bad value: %s\n", input); +} + int main(int argc, char **argv) { if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { @@ -103,6 +116,25 @@ int main(int argc, char **argv) return 0; } + if (argc == 3 && !strcmp(argv[1], "mingw_path")) { + puts(argv[2]); + return 0; + } + + if (argc == 4 && !strcmp(argv[1], "relative_path")) { + struct strbuf sb = STRBUF_INIT; + const char *in, *prefix, *rel; + normalize_argv_string(&in, argv[2]); + normalize_argv_string(&prefix, argv[3]); + rel = relative_path(in, prefix, &sb); + if (!rel) + puts("(null)"); + else + puts(strlen(rel) > 0 ? rel : "(empty)"); + strbuf_release(&sb); + return 0; + } + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); return 1; diff --git a/test-prio-queue.c b/test-prio-queue.c new file mode 100644 index 0000000000..7be72f0086 --- /dev/null +++ b/test-prio-queue.c @@ -0,0 +1,39 @@ +#include "cache.h" +#include "prio-queue.h" + +static int intcmp(const void *va, const void *vb, void *data) +{ + const int *a = va, *b = vb; + return *a - *b; +} + +static void show(int *v) +{ + if (!v) + printf("NULL\n"); + else + printf("%d\n", *v); + free(v); +} + +int main(int argc, char **argv) +{ + struct prio_queue pq = { intcmp }; + + while (*++argv) { + if (!strcmp(*argv, "get")) + show(prio_queue_get(&pq)); + else if (!strcmp(*argv, "dump")) { + int *v; + while ((v = prio_queue_get(&pq))) + show(v); + } + else { + int *v = malloc(sizeof(*v)); + *v = atoi(*argv); + prio_queue_put(&pq, v); + } + } + + return 0; +} diff --git a/test-read-cache.c b/test-read-cache.c new file mode 100644 index 0000000000..b25bcf139b --- /dev/null +++ b/test-read-cache.c @@ -0,0 +1,13 @@ +#include "cache.h" + +int main (int argc, char **argv) +{ + int i, cnt = 1; + if (argc == 2) + cnt = strtol(argv[1], NULL, 0); + for (i = 0; i < cnt; i++) { + read_cache(); + discard_cache(); + } + return 0; +} @@ -75,6 +75,7 @@ static void trace_vprintf(const char *key, const char *fmt, va_list ap) strbuf_release(&buf); } +__attribute__((format (printf, 2, 3))) static void trace_printf_key(const char *key, const char *fmt, ...) { va_list ap; diff --git a/transport-helper.c b/transport-helper.c index 06c08a1786..45a35df66e 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -56,7 +56,7 @@ static int recvline_fh(FILE *helper, struct strbuf *buffer, const char *name) if (strbuf_getline(buffer, helper, '\n') == EOF) { if (debug) fprintf(stderr, "Debug: Remote helper quit.\n"); - die("Reading from helper 'git-remote-%s' failed", name); + exit(128); } if (debug) @@ -982,6 +982,7 @@ int transport_helper_init(struct transport *transport, const char *name) #define PBUFFERSIZE 8192 /* Print bidirectional transfer loop debug message. */ +__attribute__((format (printf, 1, 2))) static void transfer_debug(const char *fmt, ...) { va_list args; @@ -1067,7 +1068,7 @@ static int udt_do_read(struct unidirectional_transfer *t) return -1; } else if (bytes == 0) { transfer_debug("%s EOF (with %i bytes in buffer)", - t->src_name, t->bufuse); + t->src_name, (int)t->bufuse); t->state = SSTATE_FLUSHING; } else if (bytes > 0) { t->bufuse += bytes; diff --git a/transport.c b/transport.c index 359a671c8c..e15db9808c 100644 --- a/transport.c +++ b/transport.c @@ -1232,7 +1232,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs) * then local and remote refs are likely to still be equal. * Just feed them all to the fetch method in that case. * This condition shouldn't be met in a non-deepening fetch - * (see builtin-fetch.c:quickfetch()). + * (see builtin/fetch.c:quickfetch()). */ heads = xmalloc(nr_refs * sizeof(*heads)); for (rm = refs; rm; rm = rm->next) diff --git a/transport.h b/transport.h index 4edebc5355..ea70ea7e4a 100644 --- a/transport.h +++ b/transport.h @@ -170,7 +170,7 @@ int transport_connect(struct transport *transport, const char *name, int transport_helper_init(struct transport *transport, const char *name); int bidirectional_transfer_loop(int input, int output); -/* common methods used by transport.c and builtin-send-pack.c */ +/* common methods used by transport.c and builtin/send-pack.c */ void transport_verify_remote_names(int nr_heads, const char **heads); void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose); diff --git a/tree-walk.h b/tree-walk.h index 2bf0db9814..ae04b6417d 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -46,7 +46,7 @@ struct traverse_info { int pathlen; struct pathspec *pathspec; - unsigned long conflicts; + unsigned long df_conflicts; traverse_callback_t fn; void *data; int show_all_errors; @@ -159,7 +159,7 @@ int read_tree(struct tree *tree, int stage, struct pathspec *match) * sort at the end. */ for (i = 0; !fn && i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (ce_stage(ce) == stage) fn = read_one_entry; } diff --git a/unpack-trees.c b/unpack-trees.c index 57b40743a1..bf01717015 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -179,7 +179,7 @@ static void display_error_msgs(struct unpack_trees_options *o) * Unlink the last component and schedule the leading directories for * removal, such that empty directories get removed. */ -static void unlink_entry(struct cache_entry *ce) +static void unlink_entry(const struct cache_entry *ce) { if (!check_leading_path(ce->name, ce_namelen(ce))) return; @@ -199,7 +199,7 @@ static int check_updates(struct unpack_trees_options *o) if (o->update && o->verbose_update) { for (total = cnt = 0; cnt < index->cache_nr; cnt++) { - struct cache_entry *ce = index->cache[cnt]; + const struct cache_entry *ce = index->cache[cnt]; if (ce->ce_flags & (CE_UPDATE | CE_WT_REMOVE)) total++; } @@ -212,7 +212,7 @@ static int check_updates(struct unpack_trees_options *o) if (o->update) git_attr_set_direction(GIT_ATTR_CHECKOUT, &o->result); for (i = 0; i < index->cache_nr; i++) { - struct cache_entry *ce = index->cache[i]; + const struct cache_entry *ce = index->cache[i]; if (ce->ce_flags & CE_WT_REMOVE) { display_progress(progress, ++cnt); @@ -376,7 +376,7 @@ static struct cache_entry *next_cache_entry(struct unpack_trees_options *o) return NULL; } -static void add_same_unmerged(struct cache_entry *ce, +static void add_same_unmerged(const struct cache_entry *ce, struct unpack_trees_options *o) { struct index_state *index = o->src_index; @@ -464,7 +464,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, newinfo.pathspec = info->pathspec; newinfo.name = *p; newinfo.pathlen += tree_entry_len(p) + 1; - newinfo.conflicts |= df_conflicts; + newinfo.df_conflicts |= df_conflicts; for (i = 0; i < n; i++, dirmask >>= 1) { const unsigned char *sha1 = NULL; @@ -565,17 +565,12 @@ static int unpack_nondirectories(int n, unsigned long mask, { int i; struct unpack_trees_options *o = info->data; - unsigned long conflicts; + unsigned long conflicts = info->df_conflicts | dirmask; /* Do we have *only* directories? Nothing to do */ if (mask == dirmask && !src[0]) return 0; - conflicts = info->conflicts; - if (o->merge) - conflicts >>= 1; - conflicts |= dirmask; - /* * Ok, we've filled in up to any potential index entry in src[0], * now do the rest. @@ -655,7 +650,7 @@ static int find_cache_pos(struct traverse_info *info, int p_len = tree_entry_len(p); for (pos = o->cache_bottom; pos < index->cache_nr; pos++) { - struct cache_entry *ce = index->cache[pos]; + const struct cache_entry *ce = index->cache[pos]; const char *ce_name, *ce_slash; int cmp, ce_len; @@ -807,13 +802,6 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str /* Now handle any directories.. */ if (dirmask) { - unsigned long conflicts = mask & ~dirmask; - if (o->merge) { - conflicts <<= 1; - if (src[0]) - conflicts |= 1; - } - /* special case: "diff-index --cached" looking at a tree */ if (o->diff_index_cached && n == 1 && dirmask == 1 && S_ISDIR(names->mode)) { @@ -832,7 +820,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str } } - if (traverse_trees_recursive(n, dirmask, conflicts, + if (traverse_trees_recursive(n, dirmask, mask & ~dirmask, names, info) < 0) return -1; return mask; @@ -1365,7 +1353,7 @@ static int verify_clean_subdirectory(const struct cache_entry *ce, */ static int icase_exists(struct unpack_trees_options *o, const char *name, int len, struct stat *st) { - struct cache_entry *src; + const struct cache_entry *src; src = index_name_exists(o->src_index, name, len, 1); return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); @@ -1376,7 +1364,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype, enum unpack_trees_error_types error_type, struct unpack_trees_options *o) { - struct cache_entry *result; + const struct cache_entry *result; /* * It may be that the 'lstat()' succeeded even though @@ -10,6 +10,7 @@ int utf8_strwidth(const char *string); int is_utf8(const char *text); int is_encoding_utf8(const char *name); int same_encoding(const char *, const char *); +__attribute__((format (printf, 2, 3))) int utf8_fprintf(FILE *, const char *, ...); void strbuf_add_wrapped_text(struct strbuf *buf, diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh index 53a8dd0a3f..701d2339b9 100644 --- a/wrap-for-bin.sh +++ b/wrap-for-bin.sh @@ -14,7 +14,7 @@ else GIT_TEMPLATE_DIR='@@BUILD_DIR@@/templates/blt' export GIT_TEMPLATE_DIR fi -GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib' +GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'"${GITPERLLIB:+:$GITPERLLIB}" GIT_TEXTDOMAINDIR='@@BUILD_DIR@@/po/build/locale' PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH" export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR @@ -322,7 +322,7 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode) template[5] = letters[v % num_letters]; v /= num_letters; fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode); - if (fd > 0) + if (fd >= 0) return fd; /* * Fatal error (EPERM, ENOSPC etc). diff --git a/wt-status.c b/wt-status.c index bf84a86ee3..cb24f1fa9b 100644 --- a/wt-status.c +++ b/wt-status.c @@ -127,6 +127,7 @@ void wt_status_prepare(struct wt_status *s) s->change.strdup_strings = 1; s->untracked.strdup_strings = 1; s->ignored.strdup_strings = 1; + s->show_branch = -1; /* unspecified */ } static void wt_status_print_unmerged_header(struct wt_status *s) @@ -243,7 +244,7 @@ static void wt_status_print_unmerged_data(struct wt_status *s, struct strbuf onebuf = STRBUF_INIT; const char *one, *how = _("bug"); - one = quote_path(it->string, -1, &onebuf, s->prefix); + one = quote_path(it->string, s->prefix, &onebuf); status_printf(s, color(WT_STATUS_HEADER, s), "\t"); switch (d->stagemask) { case 1: how = _("both deleted:"); break; @@ -297,8 +298,8 @@ static void wt_status_print_change_data(struct wt_status *s, change_type); } - one = quote_path(one_name, -1, &onebuf, s->prefix); - two = quote_path(two_name, -1, &twobuf, s->prefix); + one = quote_path(one_name, s->prefix, &onebuf); + two = quote_path(two_name, s->prefix, &twobuf); status_printf(s, color(WT_STATUS_HEADER, s), "\t"); switch (status) { @@ -371,7 +372,7 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, static int unmerged_mask(const char *path) { int pos, mask; - struct cache_entry *ce; + const struct cache_entry *ce; pos = cache_name_pos(path, strlen(path)); if (0 <= pos) @@ -475,7 +476,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s) for (i = 0; i < active_nr; i++) { struct string_list_item *it; struct wt_status_change_data *d; - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (!ce_path_match(ce, &pathspec)) continue; @@ -706,8 +707,7 @@ static void wt_status_print_other(struct wt_status *s, struct string_list_item *it; const char *path; it = &(l->items[i]); - path = quote_path(it->string, strlen(it->string), - &buf, s->prefix); + path = quote_path(it->string, s->prefix, &buf); if (column_active(s->colopts)) { string_list_append(&output, path); continue; @@ -826,7 +826,7 @@ static void show_am_in_progress(struct wt_status *s, if (advice_status_hints) { if (!state->am_empty_patch) status_printf_ln(s, color, - _(" (fix conflicts and then run \"git am --resolved\")")); + _(" (fix conflicts and then run \"git am --continue\")")); status_printf_ln(s, color, _(" (use \"git am --skip\" to skip this patch)")); status_printf_ln(s, color, @@ -955,10 +955,12 @@ static void show_cherry_pick_in_progress(struct wt_status *s, if (advice_status_hints) { if (has_unmerged(s)) status_printf_ln(s, color, - _(" (fix conflicts and run \"git commit\")")); + _(" (fix conflicts and run \"git cherry-pick --continue\")")); else status_printf_ln(s, color, - _(" (all conflicts fixed: run \"git commit\")")); + _(" (all conflicts fixed: run \"git cherry-pick --continue\")")); + status_printf_ln(s, color, + _(" (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)")); } wt_status_print_trailer(s); } @@ -1035,7 +1037,6 @@ got_nothing: } struct grab_1st_switch_cbdata { - int found; struct strbuf buf; unsigned char nsha1[20]; }; @@ -1059,7 +1060,6 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1, for (end = target; *end && *end != '\n'; end++) ; strbuf_add(&cb->buf, target, end - target); - cb->found = 1; return 1; } @@ -1176,7 +1176,10 @@ void wt_status_print(struct wt_status *s) branch_name += 11; else if (!strcmp(branch_name, "HEAD")) { branch_status_color = color(WT_STATUS_NOBRANCH, s); - if (state.detached_from) { + if (state.rebase_in_progress || state.rebase_interactive_in_progress) { + on_what = _("rebase in progress; onto "); + branch_name = state.onto; + } else if (state.detached_from) { unsigned char sha1[20]; branch_name = state.detached_from; if (!get_sha1("HEAD", sha1) && @@ -1289,7 +1292,7 @@ static void wt_shortstatus_unmerged(struct string_list_item *it, } else { struct strbuf onebuf = STRBUF_INIT; const char *one; - one = quote_path(it->string, -1, &onebuf, s->prefix); + one = quote_path(it->string, s->prefix, &onebuf); printf(" %s\n", one); strbuf_release(&onebuf); } @@ -1317,7 +1320,7 @@ static void wt_shortstatus_status(struct string_list_item *it, struct strbuf onebuf = STRBUF_INIT; const char *one; if (d->head_path) { - one = quote_path(d->head_path, -1, &onebuf, s->prefix); + one = quote_path(d->head_path, s->prefix, &onebuf); if (*one != '"' && strchr(one, ' ') != NULL) { putchar('"'); strbuf_addch(&onebuf, '"'); @@ -1326,7 +1329,7 @@ static void wt_shortstatus_status(struct string_list_item *it, printf("%s -> ", one); strbuf_release(&onebuf); } - one = quote_path(it->string, -1, &onebuf, s->prefix); + one = quote_path(it->string, s->prefix, &onebuf); if (*one != '"' && strchr(one, ' ') != NULL) { putchar('"'); strbuf_addch(&onebuf, '"'); @@ -1345,7 +1348,7 @@ static void wt_shortstatus_other(struct string_list_item *it, } else { struct strbuf onebuf = STRBUF_INIT; const char *one; - one = quote_path(it->string, -1, &onebuf, s->prefix); + one = quote_path(it->string, s->prefix, &onebuf); color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign); printf(" %s\n", one); strbuf_release(&onebuf); diff --git a/wt-status.h b/wt-status.h index 4121bc208d..fb7152e187 100644 --- a/wt-status.h +++ b/wt-status.h @@ -96,9 +96,9 @@ void wt_status_get_state(struct wt_status_state *state, int get_detached_from); void wt_shortstatus_print(struct wt_status *s); void wt_porcelain_print(struct wt_status *s); -void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...) - ; -void status_printf(struct wt_status *s, const char *color, const char *fmt, ...) - ; +__attribute__((format (printf, 3, 4))) +void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...); +__attribute__((format (printf, 3, 4))) +void status_printf(struct wt_status *s, const char *color, const char *fmt, ...); #endif /* STATUS_H */ diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 219a3bbca6..c0339919cc 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -39,6 +39,8 @@ extern "C" { #define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF) #define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK) +#define XDF_IGNORE_BLANK_LINES (1 << 7) + #define XDL_EMIT_FUNCNAMES (1 << 0) #define XDL_EMIT_COMMON (1 << 1) #define XDL_EMIT_FUNCCONTEXT (1 << 2) diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index b2eb6db2c8..2358a2d632 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -394,6 +394,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, xch->i2 = i2; xch->chg1 = chg1; xch->chg2 = chg2; + xch->ignore = 0; return xch; } @@ -544,7 +545,9 @@ static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, xdchange_t *xch, *xche; for (xch = xscr; xch; xch = xche->next) { - xche = xdl_get_hunk(xch, xecfg); + xche = xdl_get_hunk(&xch, xecfg); + if (!xch) + break; if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1, xch->i2, xche->i2 + xche->chg2 - xch->i2, ecb->priv) < 0) @@ -553,6 +556,27 @@ static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, return 0; } +static void xdl_mark_ignorable(xdchange_t *xscr, xdfenv_t *xe, long flags) +{ + xdchange_t *xch; + + for (xch = xscr; xch; xch = xch->next) { + int ignore = 1; + xrecord_t **rec; + long i; + + rec = &xe->xdf1.recs[xch->i1]; + for (i = 0; i < xch->chg1 && ignore; i++) + ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags); + + rec = &xe->xdf2.recs[xch->i2]; + for (i = 0; i < xch->chg2 && ignore; i++) + ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags); + + xch->ignore = ignore; + } +} + int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb) { xdchange_t *xscr; @@ -571,6 +595,9 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, return -1; } if (xscr) { + if (xpp->flags & XDF_IGNORE_BLANK_LINES) + xdl_mark_ignorable(xscr, &xe, xpp->flags); + if (ef(&xe, xscr, ecb, xecfg) < 0) { xdl_free_script(xscr); diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h index 7a92ea9c4d..8b81206c9a 100644 --- a/xdiff/xdiffi.h +++ b/xdiff/xdiffi.h @@ -41,6 +41,7 @@ typedef struct s_xdchange { struct s_xdchange *next; long i1, i2; long chg1, chg2; + int ignore; } xdchange_t; diff --git a/xdiff/xemit.c b/xdiff/xemit.c index d11dbf9f13..4d8645867e 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -56,16 +56,51 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t * /* * Starting at the passed change atom, find the latest change atom to be included * inside the differential hunk according to the specified configuration. + * Also advance xscr if the first changes must be discarded. */ -xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) { - xdchange_t *xch, *xchp; +xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg) +{ + xdchange_t *xch, *xchp, *lxch; long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; + long max_ignorable = xecfg->ctxlen; + unsigned long ignored = 0; /* number of ignored blank lines */ + + /* remove ignorable changes that are too far before other changes */ + for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) { + xch = xchp->next; + + if (xch == NULL || + xch->i1 - (xchp->i1 + xchp->chg1) >= max_ignorable) + *xscr = xch; + } + + if (*xscr == NULL) + return NULL; + + lxch = *xscr; - for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next) - if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common) + for (xchp = *xscr, xch = xchp->next; xch; xchp = xch, xch = xch->next) { + long distance = xch->i1 - (xchp->i1 + xchp->chg1); + if (distance > max_common) break; - return xchp; + if (distance < max_ignorable && (!xch->ignore || lxch == xchp)) { + lxch = xch; + ignored = 0; + } else if (distance < max_ignorable && xch->ignore) { + ignored += xch->chg2; + } else if (lxch != xchp && + xch->i1 + ignored - (lxch->i1 + lxch->chg1) > max_common) { + break; + } else if (!xch->ignore) { + lxch = xch; + ignored = 0; + } else { + ignored += xch->chg2; + } + } + + return lxch; } @@ -139,7 +174,9 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, return xdl_emit_common(xe, xscr, ecb, xecfg); for (xch = xscr; xch; xch = xche->next) { - xche = xdl_get_hunk(xch, xecfg); + xche = xdl_get_hunk(&xch, xecfg); + if (!xch) + break; s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0); s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0); diff --git a/xdiff/xemit.h b/xdiff/xemit.h index c2e2e83027..d29710770c 100644 --- a/xdiff/xemit.h +++ b/xdiff/xemit.h @@ -27,7 +27,7 @@ typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, xdemitconf_t const *xecfg); -xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg); +xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg); int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, xdemitconf_t const *xecfg); diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 9504eaecb8..62cb23dfd3 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -143,6 +143,19 @@ long xdl_guess_lines(mmfile_t *mf, long sample) { return nl + 1; } +int xdl_blankline(const char *line, long size, long flags) +{ + long i; + + if (!(flags & XDF_WHITESPACE_FLAGS)) + return (size <= 1); + + for (i = 0; i < size && XDL_ISSPACE(line[i]); i++) + ; + + return (i == size); +} + int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) { int i1, i2; diff --git a/xdiff/xutils.h b/xdiff/xutils.h index ad1428ed69..4646ce5752 100644 --- a/xdiff/xutils.h +++ b/xdiff/xutils.h @@ -32,6 +32,7 @@ int xdl_cha_init(chastore_t *cha, long isize, long icount); void xdl_cha_free(chastore_t *cha); void *xdl_cha_alloc(chastore_t *cha); long xdl_guess_lines(mmfile_t *mf, long sample); +int xdl_blankline(const char *line, long size, long flags); int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); unsigned long xdl_hash_record(char const **data, char const *top, long flags); unsigned int xdl_hashbits(unsigned int size); |