/git-cat-file
/git-check-attr
/git-check-ignore
+/git-check-mailmap
/git-check-ref-format
/git-checkout
/git-checkout-index
/git-remote-ftps
/git-remote-fd
/git-remote-ext
+/git-remote-testgit
/git-remote-testpy
/git-remote-testsvn
/git-repack
/test-mktemp
/test-parse-options
/test-path-utils
+/test-prio-queue
+/test-read-cache
/test-regex
/test-revision-walking
/test-run-command
/cscope*
*.obj
*.lib
+*.res
*.sln
*.suo
*.ncb
# 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>
+Cord Seele <cowose@gmail.com> <cowose@googlemail.com>
+Christian Stimming <stimming@tuhh.de> <chs@ckiste.goetheallee>
+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>
+John 'Warthog9' Hawley <warthog9@kernel.org> <warthog9@eaglescrag.net>
+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>
+Štěpán Němec <stepnem@gmail.com> <stepan.nemec@gmail.com>
+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>
Writing Documentation:
- Most (if not all) of the documentation pages are written in AsciiDoc
- and processed into HTML output and manpages.
+ Most (if not all) of the documentation pages are written in the
+ AsciiDoc format in *.txt files (e.g. Documentation/git.txt), and
+ processed into HTML and manpages (e.g. git.html and git.1 in the
+ same directory).
Every user-visible change should be reflected in the documentation.
The same general rule as for code applies -- imitate the existing
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
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
not exclude them and tried to apply funny patches only to fail.
* "git blame" started missing quite a few changes from the origin
- since we stopped using the diff minimalization by default in v1.7.2
+ since we stopped using the diff minimization by default in v1.7.2
era.
* When PATH contains an unreadable directory, alias expansion code
--------------------
* The single-key mode of "git add -p" was easily fooled into thinking
- that it was told to add everthing ('a') when up-arrow was pressed by
+ that it was told to add everything ('a') when up-arrow was pressed by
mistake.
* Setting a git command that uses custom configuration via "-c var=val"
* The configuration file parser used for sizes (e.g. bigFileThreshold)
did not correctly interpret 'g' suffix.
- * The replacement implemention for snprintf used on platforms with
+ * The replacement implementation for snprintf used on platforms with
native snprintf that is broken did not use va_copy correctly.
* LF-to-CRLF streaming filter replaced all LF with CRLF, which might
- be techinically correct but not friendly to people who are trying
+ be technically correct but not friendly to people who are trying
to recover from earlier mistakes of using CRLF in the repository
data in the first place. It now refrains from doing so for LF that
follows a CR.
* Updates to bash completion scripts.
* The build procedure has been taught to take advantage of computed
- dependency automatically when the complier supports it.
+ dependency automatically when the compiler supports it.
* The date parser now accepts timezone designators that lack minutes
part and also has a colon between "hh:mm".
* Variants of "git cherry-pick" and "git revert" that take multiple
commits learned to "--continue" and "--abort".
- * "git daemon" gives more human readble error messages to clients
+ * "git daemon" gives more human readable error messages to clients
using ERR packets when appropriate.
* Errors at the network layer is logged by "git daemon".
common prefix and suffix between the two filenames overlapped.
* "git submodule update", when recursed into sub-submodules, did not
- acccumulate the prefix paths.
+ accumulate the prefix paths.
* "git am $maildir/" applied messages in an unexpected order; sort
filenames read from the maildir/ in a way that is more likely to
conflicts have been applied.
* "git bundle" did not like a bundle created using a commit without
- any message as its one of the prerequistes.
+ any message as its one of the prerequisites.
--- /dev/null
+Git v1.8.2.3 Release Notes
+==========================
+
+Fixes since v1.8.2.2
+--------------------
+
+ * "rev-list --stdin" and friends kept bogus pointers into the input
+ buffer around as human readable object names. This was not a
+ huge problem but was exposed by a new change that uses these
+ names in error output.
+
+ * When "git difftool" drove "kdiff3", it mistakenly passed --auto
+ option that was meant while resolving merge conflicts.
+
+ * "git remote add" command did not diagnose extra command line
+ arguments as an error and silently ignored them.
+
+Also contains a handful of trivial code clean-ups, documentation
+updates, updates to the test suite, etc.
--- /dev/null
+Git v1.8.3.1 Release Notes
+========================
+
+Fixes since v1.8.3
+------------------
+
+ * When $HOME is misconfigured to point at an unreadable directory, we
+ used to complain and die. The check has been loosened.
+
+ * Handling of negative exclude pattern for directories "!dir" was
+ broken in the update to v1.8.3.
+
+Also contains a handful of trivial code clean-ups, documentation
+updates, updates to the test suite, etc.
--- /dev/null
+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.
--- /dev/null
+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.
--- /dev/null
+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.
Foreign interface
- * remote-hg and remote-bzr helpers (in contrib/) have been updated.
+ * remote-hg and remote-bzr helpers (in contrib/ since v1.8.2) have
+ been updated; especially, the latter has been done in an
+ accelerated schedule (read: we may not have merged to this release
+ if we were following the usual "cook sufficiently in next before
+ unleashing it to the world" workflow) in order to help Emacs folks,
+ whose primary SCM seems to be stagnating.
UI, Workflows & Features
+ * A handful of updates applied to gitk, including an addition of
+ "revert" action, showing dates in tags in a nicer way, making
+ colors configurable, and support for -G'pickaxe' search.
+
* The prompt string generator (in contrib/completion/) learned to
show how many changes there are in total and how many have been
replayed during a "git rebase" session.
of erroneous inputs was suboptimal and has been improved.
* When the interactive access to git-shell is not enabled, it issues
- a message meant to help the system administrator to enable it.
- An explicit way to help the end users who connect to the service by
- issuing custom messages to refuse such an access has been added.
+ a message meant to help the system administrator to enable it. An
+ explicit way has been added to issue custom messages to refuse an
+ access over the network to help the end users who connect to the
+ service expecting an interactive shell.
* In addition to the case where the user edits the log message with
the "e)dit" option of "am -i", replace the "Applying: this patch"
* "git status" suggests users to look into using --untracked=no option
when it takes too long.
- * "git status" shows a bit more information during a
- rebase/bisect session.
+ * "git status" shows a bit more information during a rebase/bisect
+ session.
* "git fetch" learned to fetch a commit at the tip of an unadvertised
ref by specifying a raw object name from the command line when the
* Various subcommands of "git remote" simply ignored extraneous
command line arguments instead of diagnosing them as errors.
- (merge b17dd3f tr/remote-tighten-commandline-parsing later to maint).
* When receive-pack detects an error in the pack header it received in
order to decide which of unpack-objects or index-pack to run, it
buffer around as human readable object names. This was not a huge
problem but was exposed by a new change that uses these names in
error output.
- (merge 70d26c6 tr/copy-revisions-from-stdin later to maint).
* Smart-capable HTTP servers were not restricted via the
GIT_NAMESPACE mechanism when talking with commit-walking clients,
* Fix a 1.8.1.x regression that stopped matching "dir" (without a
trailing slash) to a directory "dir".
- (merge efa5f82 jc/directory-attrs-regression-fix later to maint-1.8.1).
* "git apply --whitespace=fix" was not prepared to see a line getting
longer after fixing whitespaces (e.g. tab-in-indent aka Python).
- (merge 329b26e jc/apply-ws-fix-tab-in-indent later to maint-1.8.1).
* The prompt string generator (in contrib/completion/) did not notice
when we are in a middle of a "git revert" session.
--- /dev/null
+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, subsystems and ports.
+
+ * Cygwin port has been updated for more recent Cygwin 1.7.
+
+ * "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
+ ahead.
+
+ * The credential helper to talk to keychain on OS X (in contrib/) has
+ been updated to kick in not just when talking http/https but also
+ imap(s) and smtp.
+
+ * Remote transport helper has been updated to report errors and
+ maintain ref hierarchy used to keep track of its own state better.
+
+ * With "export" remote-helper protocol, (1) a push that tries to
+ update a remote ref whose name is different from the pushing side
+ does not work yet, and (2) the helper may not know how to do
+ --dry-run; these problematic cases are disabled for now.
+
+ * git-remote-hg/bzr (in contrib/) updates.
+
+ * 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
+
+ * Sample "post-receive-email" hook script got an enhanced replacement
+ "multimail" (in contrib/).
+
+ * Also in contrib/ is a new "contacts" script that runs "git blame"
+ to find out the people who may be interested in a set of changes.
+
+ * "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
+ is unlikely to change, but we should warn about the ambiguity just
+ like we warn when more than one refs/ hierarchies share the same
+ name.
+
+ * "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). 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 @".
+
+ * "check-ignore" (new feature since 1.8.2) has been updated to work
+ more like "check-attr" over bidi-pipes.
+
+ * "git describe" learned "--first-parent" option to limit its closest
+ tagged commit search to the first-parent chain.
+
+ * "git merge foo" that might have meant "git merge origin/foo" is
+ diagnosed with a more informative error message.
+
+ * "git log -L<line>,<range>:<filename>" has been added. This may
+ still have leaks and rough edges, though.
+
+ * We used the approxidate() parser for "--expire=<timestamp>" options
+ of various commands, but it is better to treat --expire=all and
+ --expire=now a bit more specially than using the current timestamp.
+ "git gc" and "git reflog" have been updated with a new parsing
+ function for expiry dates.
+
+ * Updates to completion (both bash and zsh) helpers.
+
+ * The behaviour of the "--chain-reply-to" option of "git send-email"
+ have changed at 1.7.0, and we added a warning/advice message to
+ help users adjust to the new behaviour back then, but we kept it
+ around for too long. The message has finally been removed.
+
+ * "git fetch origin master" unlike "git fetch origin" or "git fetch"
+ did not update "refs/remotes/origin/master"; this was an early
+ design decision to keep the update of remote tracking branches
+ predictable, but in practice it turns out that people find it more
+ convenient to opportunistically update them whenever we have a
+ chance, and we have been updating them when we run "git push" which
+ already breaks the original "predictability" anyway.
+
+ * The configuration variable core.checkstat was advertised in the
+ documentation but the code expected core.statinfo instead.
+ For now, we accept both core.checkstat and core.statinfo, but the
+ latter will be removed in the longer term.
+
+
+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.
+
+ * The codepath to read from marks files in fast-import/export did not
+ have to accept anything but 40-hex representation of the object
+ name. Further, fast-export did not need full in-core object
+ representation to have parsed wen reading from them. These
+ codepaths have been optimized by taking advantage of these access
+ patterns.
+
+ * Object lookup logic, when the object hashtable starts to become
+ crowded, has been optimized.
+
+ * When TEST_OUTPUT_DIRECTORY setting is used, it was handled somewhat
+ inconsistently between the test framework and t/Makefile, and logic
+ to summarize the results looked at a wrong place.
+
+ * "git clone" uses a lighter-weight implementation when making sure
+ that the history behind refs are complete.
+
+ * Many warnings from sparse source checker in compat/ area has been
+ squelched.
+
+ * The code to reading and updating packed-refs file has been updated,
+ correcting corner case bugs.
+
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v1.8.3
+------------------
+
+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.
+
+ * 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", we
+ did not say which branch and worse said "branch ''".
+
+ * "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.
+
+ * "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.
+
+ * 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".
+
+ * Handling of negative exclude pattern for directories "!dir" was
+ broken in the update to v1.8.3.
+
+ * zsh prompt script that borrowed from bash prompt script did not
+ work due to slight differences in array variable notation between
+ these two shells.
+
+ * 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.
+
+ * "git push --[no-]verify" was not documented.
+
+ * Stop installing the git-remote-testpy script that is only used for
+ testing.
+
+ * "git commit --allow-empty-message -m ''" should not start an
+ editor.
+
+ * "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 diff -c -p" was not showing a deleted line from a hunk when
+ another hunk immediately begins where the earlier one ends.
+
+ * "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.
+
+ * 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.
+
+ * "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".
+
+ * When $HOME is misconfigured to point at an unreadable directory, we
+ used to complain and die. Loosen the check.
+
+ * "git subtree" (in contrib/) had one codepath with loose error
+ checks to lose data at the remote side.
+
+ * "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 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.
Include additional statistics at the end of blame output.
-L <start>,<end>::
- Annotate only the given line range. <start> and <end> can take
- one of these forms:
-
- - number
-+
-If <start> or <end> is a number, it specifies an
-absolute line number (lines count from 1).
+-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:
-- /regex/
-+
-This form will use the first line matching the given
-POSIX regex. If <end> is a regex, it will search
-starting at the line given by <start>.
-+
-
-- +offset or -offset
-+
-This is only valid for <end> and will specify a number
-of lines before or after the line given by <start>.
-+
+include::line-range-format.txt[]
-l::
Show long rev (Default: off).
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::
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
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
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.
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
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
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
{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
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>]::
that matches other criteria, nothing is selected.
-S<string>::
- Look for differences that introduce or remove an instance of
- <string>. Note that this is different than the string simply
- appearing in diff output; see the 'pickaxe' entry in
- linkgit:gitdiffcore[7] for more details.
+ Look for differences that change the number of occurrences of
+ the specified string (i.e. addition/deletion) in a file.
+ Intended for the scripter's use.
++
+It is useful when you're looking for an exact block of code (like a
+struct), and want to know the history of that block since it first
+came into being: use the feature iteratively to feed the interesting
+block in the preimage back into `-S`, and keep going until you get the
+very first version of the block.
-G<regex>::
- Look for differences whose added or removed line matches
- the given <regex>.
+ Look for differences whose patch text contains added/removed
+ lines that match <regex>.
++
+To illustrate the difference between `-S<regex> --pickaxe-regex` and
+`-G<regex>`, consider a commit with the following diff in the same
+file:
++
+----
++ return !regexec(regexp, two->ptr, 1, ®match, 0);
+...
+- hit = !regexec(regexp, mf2.ptr, 1, ®match, 0);
+----
++
+While `git log -G"regexec\(regexp"` will show this commit, `git log
+-S"regexec\(regexp" --pickaxe-regex` will not (because the number of
+occurrences of that string did not change).
++
+See the 'pickaxe' entry in linkgit:gitdiffcore[7] for more
+information.
--pickaxe-all::
When `-S` or `-G` finds a change, show all the changes in that
in <string>.
--pickaxe-regex::
- Make the <string> not a plain string but an extended POSIX
- regex to match.
+ Treat the <string> given to `-S` as an extended POSIX regular
+ expression to match.
endif::git-format-patch[]
-O<orderfile>::
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.
--ignore-submodules[=<when>]::
Ignore changes to submodules in the diff generation. <when> can be
- either "none", "untracked", "dirty" or "all", which is the default
+ either "none", "untracked", "dirty" or "all", which is the default.
Using "none" will consider the submodule modified when it either contains
untracked or modified files or its HEAD differs from the commit recorded
in the superproject and can be used to override any settings of the
ifndef::git-pull[]
-t::
--tags::
- This is a short-hand for giving "refs/tags/*:refs/tags/*"
+ This is a short-hand for giving `refs/tags/*:refs/tags/*`
refspec from the command line, to ask all tags to be fetched
and stored locally. Because this acts as an explicit
refspec, the default refspecs (configured with the
SYNOPSIS
--------
[verse]
-'git am' [--signoff] [--keep] [--keep-cr | --no-keep-cr] [--utf8 | --no-utf8]
+'git am' [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8]
[--3way] [--interactive] [--committer-date-is-author-date]
[--ignore-date] [--ignore-space-change | --ignore-whitespace]
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
[--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
- [--scissors | --no-scissors]
+ [--[no-]scissors]
[(<mbox> | <Maildir>)...]
'git am' (--continue | --skip | --abort)
--keep-non-patch::
Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
---keep-cr::
---no-keep-cr::
+--[no-]keep-cr::
With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1])
with the same option, to prevent it from stripping CR at the end of
lines. `am.keepcr` configuration variable can be used to specify the
--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'.
. 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,
--------
[verse]
'git archive' [--format=<fmt>] [--list] [--prefix=<prefix>/] [<extra>]
- [-o | --output=<file>] [--worktree-attributes]
+ [-o <file> | --output=<file>] [--worktree-attributes]
[--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish>
[<path>...]
;-)
_____________
-Acknowledgements
+Acknowledgments
----------------
Many thanks to Junio Hamano for his help in reviewing this paper, for
SYNOPSIS
--------
[verse]
-'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m]
- [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--abbrev=<n>]
- [<rev> | --contents <file> | --reverse <rev>] [--] <file>
+'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
+ [-L n,m | -L :fn] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
+ [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>
DESCRIPTION
-----------
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
------
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
'set';; when the attribute is defined as true.
<value>;; when a value has been assigned to the attribute.
+Buffering happens as documented under the `GIT_FLUSH` option in
+linkgit:git[1]. The caller is responsible for avoiding deadlocks
+caused by overfilling an input buffer or reading from an empty output
+buffer.
+
EXAMPLES
--------
below). If `--stdin` is also given, input paths are separated
with a NUL character instead of a linefeed character.
+-n, --non-matching::
+ Show given paths which don't match any pattern. This only
+ makes sense when `--verbose` is enabled, otherwise it would
+ not be possible to distinguish between paths which match a
+ pattern and those which don't.
+
OUTPUT
------
<source> <NULL> <linenum> <NULL> <pattern> <NULL> <pathname> <NULL>
+If `-n` or `--non-matching` are specified, non-matching pathnames will
+also be output, in which case all fields in each output record except
+for <pathname> will be empty. This can be useful when running
+non-interactively, so that files can be incrementally streamed to
+STDIN of a long-running check-ignore process, and for each of these
+files, STDOUT will indicate whether that file matched a pattern or
+not. (Without this option, it would be impossible to tell whether the
+absence of output for a given file meant that it didn't match any
+pattern, or that the output hadn't been generated yet.)
+
+Buffering happens as documented under the `GIT_FLUSH` option in
+linkgit:git[1]. The caller is responsible for avoiding deadlocks
+caused by overfilling an input buffer or reading from an empty output
+buffer.
EXIT STATUS
-----------
--------
linkgit:gitignore[5]
linkgit:gitconfig[5]
-linkgit:git-ls-files[5]
+linkgit:git-ls-files[1]
GIT
---
--- /dev/null
+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
. They cannot contain a sequence `@{`.
+. They cannot be the single character `@`.
+
. They cannot contain a `\`.
These rules make it easy for shell script based tools to parse
OPTIONS
-------
---allow-onelevel::
---no-allow-onelevel::
+--[no-]allow-onelevel::
Controls whether one-level refnames are accepted (i.e.,
refnames that do not contain multiple `/`-separated
components). The default is `--no-allow-onelevel`.
"--track" in linkgit:git-branch[1] for details.
+
If no '-b' option is given, the name of the new branch will be
-derived from the remote-tracking branch. If "remotes/" or "refs/remotes/"
-is prefixed it is stripped away, and then the part up to the
-next slash (which would be the nickname of the remote) is removed.
+derived from the remote-tracking branch, by looking at the local part of
+the refspec configured for the corresponding remote, and then stripping
+the initial part up to the "*".
This would tell us to use "hack" as the local branch when branching
off of "origin/hack" (or "remotes/origin/hack", or even
"refs/remotes/origin/hack"). If the given name has no slash, or the above
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
-----------
-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::
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]
[-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
[--separate-git-dir <git dir>]
[--depth <depth>] [--[no-]single-branch]
- [--recursive|--recurse-submodules] [--] <repository>
+ [--recursive | --recurse-submodules] [--] <repository>
[<directory>]
DESCRIPTION
with a long history, and would want to send in fixes
as patches.
---single-branch::
+--[no-]single-branch::
Clone only the history leading to the tip of a single branch,
either specified by the `--branch` option or the primary
branch remote's `HEAD` points at. When creating a shallow
* 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
------------
* 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
------------
------------
-* 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
[--dry-run] [(-c | -C | --fixup | --squash) <commit>]
[-F <file> | -m <msg>] [--reset-author] [--allow-empty]
[--allow-empty-message] [--no-verify] [-e] [--author=<author>]
- [--date=<date>] [--cleanup=<mode>] [--status | --no-status]
+ [--date=<date>] [--cleanup=<mode>] [--[no-]status]
[-i | -o] [-S[<keyid>]] [--] [<file>...]
DESCRIPTION
--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
names are not.
--global::
- For writing options: write to global ~/.gitconfig file rather than
- the repository .git/config, write to $XDG_CONFIG_HOME/git/config file
- if this file exists and the ~/.gitconfig file doesn't.
+ For writing options: write to global `~/.gitconfig` file
+ rather than the repository `.git/config`, write to
+ `$XDG_CONFIG_HOME/git/config` file if this file exists and the
+ `~/.gitconfig` file doesn't.
+
-For reading options: read only from global ~/.gitconfig and from
-$XDG_CONFIG_HOME/git/config rather than from all available files.
+For reading options: read only from global `~/.gitconfig` and from
+`$XDG_CONFIG_HOME/git/config` rather than from all available files.
+
See also <<FILES>>.
--system::
- For writing options: write to system-wide $(prefix)/etc/gitconfig
- rather than the repository .git/config.
+ For writing options: write to system-wide
+ `$(prefix)/etc/gitconfig` rather than the repository
+ `.git/config`.
+
-For reading options: read only from system-wide $(prefix)/etc/gitconfig
+For reading options: read only from system-wide `$(prefix)/etc/gitconfig`
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.
Opens an editor to modify the specified config file; either
'--system', '--global', or repository (default).
---includes::
---no-includes::
+--[no-]includes::
Respect `include.*` directives in config files when looking up
values. Defaults to on.
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
- or empty, $HOME/.config/git/config will be used. Any single-valued
+ or empty, `$HOME/.config/git/config` will be used. Any single-valued
variable set in this file will be overwritten by whatever is in
- ~/.gitconfig. It is a good idea not to create this file if
+ `~/.gitconfig`. It is a good idea not to create this file if
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
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*.
[--reuseaddr] [--detach] [--pid-file=<file>]
[--enable=<service>] [--disable=<service>]
[--allow-override=<service>] [--forbid-override=<service>]
- [--access-hook=<path>]
- [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>] [--user=<user> [--group=<group>]]
+ [--access-hook=<path>] [--[no-]informative-errors]
+ [--inetd |
+ [--listen=<host_or_ipaddr>] [--port=<n>]
+ [--user=<user> [--group=<group>]]]
[<directory>...]
DESCRIPTION
repository configuration. By default, all the services
are overridable.
---informative-errors::
---no-informative-errors::
+--[no-]informative-errors::
When informative errors are turned on, git-daemon will report
more verbose errors to the client, differentiating conditions
like "no such repository" from "repository not exported". This
--always::
Show uniquely abbreviated commit object as fallback.
+--first-parent::
+ Follow only the first parent commit upon seeing a merge commit.
+ This is useful when you wish to not match tags on branches merged
+ in the history of the target commit.
+
EXAMPLES
--------
If an exact match was not found, 'git describe' will walk back
through the commit history to locate an ancestor commit which
has been tagged. The ancestor's tag will be output along with an
-abbreviation of the input committish's SHA-1.
+abbreviation of the input committish's SHA-1. If '--first-parent' was
+specified then the walk will only consider the first parent of each
+commit.
If multiple tags were found during the walk then the tag which
has the fewest commits different from the input committish will be
NAME
----
-git-diff-index - Compares content and mode of blobs between the index and repository
+git-diff-index - Compare a tree to the working tree or index
SYNOPSIS
DESCRIPTION
-----------
-Compares the content and mode of the blobs found via a tree
-object with the content of the current index and, optionally
-ignoring the stat state of the file on disk. When paths are
-specified, compares only those named paths. Otherwise all
-entries in the index are compared.
+Compares the content and mode of the blobs found in a tree object
+with the corresponding tracked files in the working tree, or with the
+corresponding paths in the index. When <path> arguments are present,
+compares only paths matching those patterns. Otherwise all tracked
+files are compared.
OPTIONS
-------
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>...]::
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
"<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
--tool-help::
Print a list of diff tools that may be used with `--tool`.
---symlinks::
---no-symlinks::
+--[no-]symlinks::
'git difftool''s default behavior is create symlinks to the
working tree when run in `--dir-diff` mode and the right-hand
side of the comparison yields the same content as the file in
different from the commit's first parent).
[<git-rev-list-args>...]::
- A list of arguments, acceptable to 'git rev-parse' and
- 'git rev-list', that specifies the specific objects and references
- to export. For example, `master~10..master` causes the
- current master reference to be exported along with all objects
- added since its 10th ancestor commit.
+ A list of arguments, acceptable to 'git rev-parse' and
+ 'git rev-list', that specifies the specific objects and references
+ to export. For example, `master~10..master` causes the
+ current master reference to be exported along with all objects
+ added since its 10th ancestor commit.
EXAMPLES
--------
-----------
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
--------
[verse]
'git fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag]
- [--upload-pack=<git-upload-pack>]
- [--depth=<n>] [--no-progress]
- [-v] [<host>:]<directory> [<refs>...]
+ [--upload-pack=<git-upload-pack>]
+ [--depth=<n>] [--no-progress]
+ [-v] [<host>:]<directory> [<refs>...]
DESCRIPTION
-----------
Do not list one-line descriptions from the actual commits being
merged.
---summary::
---no-summary::
+--[no-]summary::
Synonyms to --log and --no-log; these are deprecated and will be
removed in the future.
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.
Print out objects that exist but that aren't reachable from any
of the reference nodes.
---dangling::
---no-dangling::
+--[no-]dangling::
Print objects that exist but that are never 'directly' used (default).
`--no-dangling` can be used to omit this information from the output.
a blob, the contents are written into the file, rather than
its object name.
---progress::
---no-progress::
+--[no-]progress::
Progress status is reported on the standard error stream by
default when it is attached to a terminal, unless
--no-progress or --verbose is specified. --progress forces
--prune=<date>::
Prune loose objects older than date (default is 2 weeks ago,
- overridable by the config variable `gc.pruneExpire`). This
- option is on by default.
+ overridable by the config variable `gc.pruneExpire`).
+ --prune=all prunes loose objects regardless of their age.
+ --prune is on by default.
--no-prune::
Do not prune any loose objects.
[-W | --function-context]
[-f <file>] [-e] <pattern>
[--and|--or|--not|(|)|-e <pattern>...]
- [ [--exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
+ [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
[--] [<pathspec>...]
DESCRIPTION
--strict::
Die, if the pack contains broken objects or links.
+--check-self-contained-and-connected::
+ Die if the pack contains broken links. For internal use only.
+
--threads=<n>::
Specifies the number of threads to spawn when resolving
deltas. This requires that index-pack be compiled with
Note that only message is considered, if also a diff is shown
its size is not included.
+-L <start>,<end>:<file>, -L :<regex>:<file>::
+
+ Trace the evolution of the line range given by "<start>,<end>"
+ (or the funcname regex <regex>) within the <file>. You may
+ not give any pathspec limiters. This is currently limited to
+ a walk starting from a single revision, i.e., you may only
+ give zero or one positive revision arguments.
+ You can specify this option more than once.
++
+<start> and <end> can take one of these forms:
+
+include::line-range-format.txt[]
+
<revision range>::
Show only commits in the specified revision range. When no
<revision range> is specified, it defaults to `HEAD` (i.e. the
include::pretty-formats.txt[]
-Common diff options
+COMMON DIFF OPTIONS
-------------------
:git-log: 1
include::diff-generate-patch.txt[]
-Examples
+EXAMPLES
--------
`git log --no-merges`::
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.
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`::
+
+ Shows how the function `main()` in the file 'main.c' evolved
+ over time.
+
`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]
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
$ 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
SYNOPSIS
--------
[verse]
-'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch>
+'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch>
DESCRIPTION
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.
used to give a good default for automated 'git merge'
invocations.
---rerere-autoupdate::
---no-rerere-autoupdate::
+--[no-]rerere-autoupdate::
Allow the rerere mechanism to update the index with the
result of auto-conflict resolution if possible.
SYNOPSIS
--------
[verse]
-'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>...]
+'git mergetool' [--tool=<tool>] [-y | --[no-]prompt] [<file>...]
DESCRIPTION
-----------
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
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:
`$GIT_DIR/refs` directory hierarchy.
A recommended practice to deal with a repository with too many
-refs is to pack its refs with `--all --prune` once, and
-occasionally run `git pack-refs --prune`. Tags are by
+refs is to pack its refs with `--all` once, and
+occasionally run `git pack-refs`. Tags are by
definition stationary and are not expected to change. Branch
heads will be packed with the initial `pack-refs --all`, but
only the currently active branch heads will become unpacked,
`.git/objects/info/alternates`:
------------
-$ git prune $(cd ../another && $(git rev-parse --all))
+$ git prune $(cd ../another && git rev-parse --all)
------------
Notes
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
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
- [<repository> [<refspec>...]]
+ [--no-verify] [<repository> [<refspec>...]]
DESCRIPTION
-----------
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
linkgit:git-pull[1] and other commands. For more information,
see 'branch.<name>.merge' in linkgit:git-config[1].
---thin::
---no-thin::
+--[no-]thin::
These options are passed to linkgit:git-send-pack[1]. A thin transfer
significantly reduces the amount of sent data when the sender and
receiver share many of the same objects in common. The default is
be pushed. If on-demand was not able to push all necessary
revisions it will also be aborted and exit with non-zero status.
+--[no-]verify::
+ Toggle the pre-push hook (see linkgit:githooks[5]). The
+ default is \--verify, giving the hook a chance to prevent the
+ push. With \--no-verify, the hook is bypassed completely.
+
include::urls-remotes.txt[]
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.
+
--expire=<time>::
Entries older than this time are pruned. Without the
option it is taken from configuration `gc.reflogExpire`,
- which in turn defaults to 90 days.
+ which in turn defaults to 90 days. --expire=all prunes
+ entries regardless of their age; --expire=never turns off
+ pruning of reachable entries (but see --expire-unreachable).
--expire-unreachable=<time>::
Entries older than this time and not reachable from
the current tip of the branch are pruned. Without the
option it is taken from configuration
`gc.reflogExpireUnreachable`, which in turn defaults to
- 30 days.
+ 30 days. --expire-unreachable=all prunes unreachable
+ entries regardless of their age; --expire-unreachable=never
+ turns off early pruning of unreachable entries (but see
+ --expire).
--all::
Instead of listing <refs> explicitly, prune all refs.
--------
[verse]
'git remote' [-v | --verbose]
-'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>
+'git remote add' [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=<fetch|push>] <name> <url>
'git remote rename' <old> <new>
'git remote remove' <name>
'git remote set-head' <name> (-a | -d | <branch>)
$ 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
...
------------
--------
[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
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
+
If you want to make sure that the output actually names an object in
your object database and/or can be used as a specific type of object
-you require, you can add "^{type}" peeling operator to the parmeter.
+you require, you can add "^{type}" peeling operator to the parameter.
For example, `git rev-parse "$VAR^{commit}"` will make sure `$VAR`
names an existing object that is a commit-ish (i.e. a commit, or an
annotated tag that points at a commit). To make sure that `$VAR`
SYNOPSIS
--------
[verse]
-'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
+'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] <commit>...
'git revert' --continue
'git revert' --quit
'git revert' --abort
--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'),
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.
--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::
include::pretty-formats.txt[]
+COMMON DIFF OPTIONS
+-------------------
+
+:git-log: 1
+include::diff-options.txt[]
+
+include::diff-generate-patch.txt[]
+
+
EXAMPLES
--------
'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
-----------
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
+
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>]::
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
----------
--------
[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>...]
to exist in the superproject. If <path> is not given, the
"humanish" part of the source repository is used ("repo" for
"/path/to/repo.git" and "foo" for "host.xz:foo/.git").
+The <path> is also used as the submodule's logical name in its
+configuration entries unless `--name` is used to specify a logical name.
+
<repository> is the URL of the new submodule's origin repository.
This may be either an absolute URL, or (if it begins with ./
too (and can also report changes to a submodule's work tree).
init::
- Initialize the submodules, i.e. register each submodule name
- and url found in .gitmodules into .git/config.
+ Initialize the submodules recorded in the index (which were
+ added and committed elsewhere) by copying submodule
+ names and urls from .gitmodules to .git/config.
+ Optional <path> arguments limit which submodules will be initialized.
It will also copy the value of `submodule.$name.update` into
.git/config.
The key used in .git/config is `submodule.$name.url`.
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
--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
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.
When passed to 'init' or 'clone' this regular expression will
be preserved as a config key. See 'fetch' for a description
of '--ignore-paths'.
+--include-paths=<regex>;;
+ When passed to 'init' or 'clone' this regular expression will
+ be preserved as a config key. See 'fetch' for a description
+ of '--include-paths'.
--no-minimize-url;;
When tracking multiple directories (using --stdlayout,
--branches, or --tags options), git svn will attempt to connect
------------------------------------------------------------------------
--
+--include-paths=<regex>;;
+ This allows one to specify a Perl regular expression that will
+ cause the inclusion of only matching paths from checkout from SVN.
+ The '--include-paths' option should match for every 'fetch'
+ (including automatic fetches due to 'clone', 'dcommit',
+ 'rebase', etc) on a given repository. '--ignore-paths' takes
+ precedence over '--include-paths'.
+
--log-window-size=<n>;;
Fetch <n> log entries per request when scanning Subversion history.
The default is 100. For very large Subversion repositories, larger
Create a tag by using the tags_subdir instead of the branches_subdir
specified during git svn init.
--d;;
---destination;;
+-d<path>;;
+--destination=<path>;;
+
If more than one --branches (or --tags) option was given to the 'init'
or 'clone' command, you must provide the location of the branch (or
- tag) you wish to create in the SVN repository. The value of this
- option must match one of the paths specified by a --branches (or
- --tags) option. You can see these paths with the commands
+ tag) you wish to create in the SVN repository. <path> specifies which
+ path to use to create the branch or tag and should match the pattern
+ on the left-hand side of one of the configured branches or tags
+ refspecs. You can see these refspecs with the commands
+
git config --get-all svn-remote.<name>.branches
git config --get-all svn-remote.<name>.tags
git config --get-all svn-remote.<name>.commiturl
+
+--parents;;
+ Create parent folders. This parameter is equivalent to the parameter
+ --parents on svn cp commands and is useful for non-standard repository
+ layouts.
+
'tag'::
Create a tag in the SVN repository. This is a shorthand for
'branch -t'.
tags = tags/{1.0,2.0}/src:refs/remotes/tags/*
------------------------------------------------------------------------
+Multiple fetch, branches, and tags keys are supported:
+
+------------------------------------------------------------------------
+[svn-remote "messy-repo"]
+ url = http://server.org/svn
+ fetch = trunk/project-a:refs/remotes/project-a/trunk
+ fetch = branches/demos/june-project-a-demo:refs/remotes/project-a/demos/june-demo
+ branches = branches/server/*:refs/remotes/project-a/branches/*
+ branches = branches/demos/2011/*:refs/remotes/project-a/2011-demos/*
+ tags = tags/server/*:refs/remotes/project-a/tags/*
+------------------------------------------------------------------------
+
+Creating a branch in such a configuration requires disambiguating which
+location to use using the -d or --destination flag:
+
+------------------------------------------------------------------------
+$ git svn branch -d branches/server release-2-3-0
+------------------------------------------------------------------------
+
Note that git-svn keeps track of the highest revision in which a branch
or tag has appeared. If the subset of branches or tags is changed after
fetching, then .git/svn/.metadata must be manually edited to remove (or
GnuPG key for signing. The configuration variable `gpg.program`
is used to specify custom GnuPG binary.
+Tag objects (created with `-a`, `s`, or `-u`) are called "annotated"
+tags; they contain a creation date, the tagger name and e-mail, a
+tagging message, and an optional GnuPG signature. Whereas a
+"lightweight" tag is simply a name for an object (usually a commit
+object).
+
+Annotated tags are meant for release while lightweight tags are meant
+for private or temporary object labels. For this reason, some git
+commands for naming objects (like `git describe`) will ignore
+lightweight tags by default.
+
OPTIONS
-------
[--refresh] [-q] [--unmerged] [--ignore-missing]
[(--cacheinfo <mode> <object> <file>)...]
[--chmod=(+|-)x]
- [--assume-unchanged | --no-assume-unchanged]
- [--skip-worktree | --no-skip-worktree]
+ [--[no-]assume-unchanged]
+ [--[no-]skip-worktree]
[--ignore-submodules]
[--really-refresh] [--unresolve] [--again | -g]
[--info-only] [--index-info]
--chmod=(+|-)x::
Set the execute permissions on the updated files.
---assume-unchanged::
---no-assume-unchanged::
+--[no-]assume-unchanged::
When these flags are specified, the object names recorded
for the paths are not updated. Instead, these options
set and unset the "assume unchanged" bit for the
Like '--refresh', but checks stat information unconditionally,
without regard to the "assume unchanged" setting.
---skip-worktree::
---no-skip-worktree::
+--[no-]skip-worktree::
When one of these flags is specified, the object name recorded
for the paths are not updated. Instead, these options
set and unset the "skip-worktree" bit for the paths. See
* 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.
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.8.2.2/git.html[documentation for release 1.8.2.2]
+* link:v1.8.3.4/git.html[documentation for release 1.8.3.4]
* release notes for
- link:RelNotes/1.8.2.2.txt[1.8.2.2].
- link:RelNotes/1.8.2.1.txt[1.8.2.1].
+ 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].
+
+* link:v1.8.2.3/git.html[documentation for release 1.8.2.3]
+
+* release notes for
+ link:RelNotes/1.8.2.3.txt[1.8.2.3],
+ link:RelNotes/1.8.2.2.txt[1.8.2.2],
+ link:RelNotes/1.8.2.1.txt[1.8.2.1],
link:RelNotes/1.8.2.txt[1.8.2].
* link:v1.8.1.6/git.html[documentation for release 1.8.1.6]
The '--git-dir' command-line option also sets this value.
'GIT_WORK_TREE'::
- Set the path to the working tree. The value will not be
- used in combination with repositories found automatically in
- a .git directory (i.e. $GIT_DIR is not set).
+ Set the path to the root of the working tree.
This can also be controlled by the '--work-tree' command line
option and the core.worktree configuration variable.
'GIT_FLUSH'::
If this environment variable is set to "1", then commands such
as 'git blame' (in incremental mode), 'git rev-list', 'git log',
- and 'git whatchanged' will force a flush of the output stream
- after each commit-oriented record have been flushed. If this
+ 'git check-attr', 'git check-ignore', and 'git whatchanged' will
+ force a flush of the output stream after each record have been
+ flushed. If this
variable is set to "0", the output of these commands will be done
using completely buffered I/O. If this environment variable is
not set, Git will choose buffered or record-oriented flushing
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,
diffcore-pickaxe: For Detecting Addition/Deletion of Specified String
---------------------------------------------------------------------
-This transformation is used to find filepairs that represent
-changes that touch a specified string, and is controlled by the
--S option and the `--pickaxe-all` option to the 'git diff-*'
-commands.
-
-When diffcore-pickaxe is in use, it checks if there are
-filepairs whose "result" side and whose "origin" side have
-different number of specified string. Such a filepair represents
-"the string appeared in this changeset". It also checks for the
-opposite case that loses the specified string.
-
-When `--pickaxe-all` is not in effect, diffcore-pickaxe leaves
-only such filepairs that touch the specified string in its
-output. When `--pickaxe-all` is used, diffcore-pickaxe leaves all
-filepairs intact if there is such a filepair, or makes the
-output empty otherwise. The latter behaviour is designed to
-make reviewing of the changes in the context of the whole
+This transformation limits the set of filepairs to those that change
+specified strings between the preimage and the postimage in a certain
+way. -S<block of text> and -G<regular expression> options are used to
+specify different ways these strings are sought.
+
+"-S<block of text>" detects filepairs whose preimage and postimage
+have different number of occurrences of the specified block of text.
+By definition, it will not detect in-file moves. Also, when a
+changeset moves a file wholesale without affecting the interesting
+string, diffcore-rename kicks in as usual, and `-S` omits the filepair
+(since the number of occurrences of that string didn't change in that
+rename-detected filepair). When used with `--pickaxe-regex`, treat
+the <block of text> as an extended POSIX regular expression to match,
+instead of a literal string.
+
+"-G<regular expression>" (mnemonic: grep) detects filepairs whose
+textual diff has an added or a deleted line that matches the given
+regular expression. This means that it will detect in-file (or what
+rename-detection considers the same file) moves, which is noise. The
+implementation runs diff twice and greps, and this can be quite
+expensive.
+
+When `-S` or `-G` are used without `--pickaxe-all`, only filepairs
+that match their respective criterion are kept in the output. When
+`--pickaxe-all` is used, if even one filepair matches their respective
+criterion in a changeset, the entire changeset is kept. This behavior
+is designed to make reviewing changes in the context of the whole
changeset easier.
-
diffcore-order: For Sorting the Output Based on Filenames
---------------------------------------------------------
carried out.
'refspec' <refspec>::
- This modifies the 'import' capability, allowing the produced
- fast-import stream to modify refs in a private namespace
- instead of writing to refs/heads or refs/remotes directly.
+ For remote helpers that implement 'import' or 'export', this capability
+ allows the refs to be constrained to a private namespace, instead of
+ writing to refs/heads or refs/remotes directly.
It is recommended that all importers providing the 'import'
- capability use this.
+ capability use this. It's mandatory for 'export'.
+
A helper advertising the capability
`refspec refs/heads/*:refs/svn/origin/branches/*`
This capability can be advertised multiple times. The first
applicable refspec takes precedence. The left-hand of refspecs
advertised with this capability must cover all refs reported by
-the list command. If a helper does not need a specific 'refspec'
-capability then it should advertise `refspec *:*`.
+the list command. If no 'refspec' capability is advertised,
+there is an implied `refspec *:*`.
'bidi-import'::
This modifies the 'import' capability.
and maintained by shallow clone mechanism. See `--depth`
option to linkgit:git-clone[1] and linkgit:git-fetch[1].
+modules::
+ Contains the git-repositories of the submodules.
+
SEE ALSO
--------
linkgit:git-init[1],
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::
The above configuration expects your public repositories to live under
'/pub/git' and will serve them as `http://git.domain.org/dir-under-pub-git`,
-both as cloneable Git URL and as browseable gitweb interface. If you then
+both as clonable Git URL and as browseable gitweb interface. If you then
start your linkgit:git-daemon[1] with `--base-path=/pub/git --export-all`
then you can even use the `git://` URL with exactly the same path.
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.
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`.
<<def_ref,ref>> and local ref.
[[def_remote_tracking_branch]]remote-tracking branch::
- A regular Git <<def_branch,branch>> that is used to follow changes from
- another <<def_repository,repository>>. A remote-tracking
- branch should not contain direct modifications or have local commits
- made to it. A remote-tracking branch can usually be
- identified as the right-hand-side <<def_ref,ref>> in a Pull:
- <<def_refspec,refspec>>.
+ A <<def_ref,ref>> that is used to follow changes from another
+ <<def_repository,repository>>. It typically looks like
+ 'refs/remotes/foo/bar' (indicating that it tracks a branch named
+ 'bar' in a remote named 'foo'), and matches the right-hand-side of
+ a configured fetch <<def_refspec,refspec>>. A remote-tracking
+ branch should not contain direct modifications or have local
+ commits made to it.
[[def_repository]]repository::
A collection of <<def_ref,refs>> together with an
================================
One of the changes I pulled into the 'master' branch turns out to
-break building Git with GCC 2.95. While they were well intentioned
+break building Git with GCC 2.95. While they were well-intentioned
portability fixes, keeping things working with gcc-2.95 was also
important. Here is what I did to revert the change in the 'master'
branch and to adjust the 'pu' branch, using core Git tools and
--- /dev/null
+- number
++
+If <start> or <end> is a number, it specifies an
+absolute line number (lines count from 1).
++
+
+- /regex/
++
+This form will use the first line matching the given
+POSIX regex. If <end> is a regex, it will search
+starting at the line given by <start>.
++
+
+- +offset or -offset
++
+This is only valid for <end> and will specify a number
+of lines before or after the line given by <start>.
++
+
+- :regex
++
+If the option's argument is of the form :regex, it denotes the range
+from the first funcname line that matches <regex>, up to the next
+funcname line.
++
inspect and further tweak the merge result before committing.
--edit::
+-e::
--no-edit::
Invoke an editor before committing successful mechanical merge to
further edit the auto-generated merge message, so that the user
can explain and justify the merge. The `--no-edit` option can be
used to accept the auto-generated message (this is generally
- discouraged). The `--edit` option is still useful if you are
+ discouraged). The `--edit` (or `-e`) option is still useful if you are
giving a draft message with the `-m` option from the command line
and want to edit it in the editor.
+
+
* `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`;
it requests fetching everything up to the given tag.
-* A parameter <ref> without a colon is equivalent to
- <ref>: when pulling/fetching, so it merges <ref> into the current
- branch without storing the remote branch anywhere locally
+ifndef::git-pull[]
+* A parameter <ref> without a colon fetches that ref into FETCH_HEAD,
+endif::git-pull[]
+ifdef::git-pull[]
+* A parameter <ref> without a colon merges <ref> into the current
+ branch,
+endif::git-pull[]
+ and updates the remote-tracking branches (if any).
--boundary::
- Output uninteresting commits at the boundary, which are usually
- not shown.
+ Output excluded boundary commits. Boundary commits are
+ prefixed with `-`.
--
illustrate the differences between simplification settings. We assume
that you are filtering for a file `foo` in this commit graph:
-----------------------------------------------------------------------
- .-A---M---N---O---P
- / / / / /
- I B C D E
- \ / / / /
- `-------------'
+ .-A---M---N---O---P---Q
+ / / / / / /
+ I B C D E Y
+ \ / / / / /
+ `-------------' X
-----------------------------------------------------------------------
-The horizontal line of history A---P is taken to be the first parent of
+The horizontal line of history A---Q is taken to be the first parent of
each merge. The commits are:
* `I` is the initial commit, in which `foo` exists with contents
`N` and `D` to "foobarbaz"; i.e., it is not TREESAME to any parent.
* `E` changes `quux` to "xyzzy", and its merge `P` combines the
- strings to "quux xyzzy". Despite appearing interesting, `P` is
- TREESAME to all parents.
+ strings to "quux xyzzy". `P` is TREESAME to `O`, but not to `E`.
+
+* `X` is an independent root commit that added a new file `side`, and `Y`
+ modified it. `Y` is TREESAME to `X`. Its merge `Q` added `side` to `P`, and
+ `Q` is TREESAME to `P`, but not to `Y`.
'rev-list' walks backwards through history, including or excluding
commits based on whether '\--full-history' and/or parent rewriting
the example, we get
+
-----------------------------------------------------------------------
- I A B N D O
+ I A B N D O P Q
-----------------------------------------------------------------------
+
-`P` and `M` were excluded because they are TREESAME to a parent. `E`,
+`M` was excluded because it is TREESAME to both parents. `E`,
`C` and `B` were all walked, but only `B` was !TREESAME, so the others
do not appear.
+
themselves. This results in
+
-----------------------------------------------------------------------
- .-A---M---N---O---P
+ .-A---M---N---O---P---Q
/ / / / /
I B / D /
\ / / / /
Compare to '\--full-history' without rewriting above. Note that `E`
was pruned away because it is TREESAME, but the parent list of P was
rewritten to contain `E`'s parent `I`. The same happened for `C` and
-`N`. Note also that `P` was included despite being TREESAME.
+`N`, and `X`, `Y` and `Q`.
In addition to the above settings, you can change whether TREESAME
affects inclusion:
* Set `C'` to `C`.
+
* Replace each parent `P` of `C'` with its simplification `P'`. In
- the process, drop parents that are ancestors of other parents, and
- remove duplicates.
+ the process, drop parents that are ancestors of other parents or that are
+ root commits TREESAME to an empty tree, and remove duplicates, but take care
+ to never drop all parents that we are TREESAME to.
+
* If after this parent rewriting, `C'` is a root or merge commit (has
zero or >1 parents), a boundary commit, or !TREESAME, it remains.
`---------'
-----------------------------------------------------------------------
+
-Note the major differences in `N` and `P` over '--full-history':
+Note the major differences in `N`, `P` and `Q` over '--full-history':
+
--
* `N`'s parent list had `I` removed, because it is an ancestor of the
+
* `P`'s parent list similarly had `I` removed. `P` was then
removed completely, because it had one parent and is TREESAME.
++
+* `Q`'s parent list had `Y` simplified to `X`. `X` was then removed, because it
+ was a TREESAME root. `Q` was then removed completely, because it had one
+ parent and is TREESAME.
--
Finally, there is a fifth simplification mode available:
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
-t::
Show the tree objects in the diff output. This implies '-r'.
-
--s::
- Suppress diff output.
endif::git-rev-list[]
While the ref name encoding is unspecified, UTF-8 is preferred as
some output processing may assume ref names in UTF-8.
+'@'::
+ '@' alone is a shortcut for 'HEAD'.
+
'<refname>@\{<date>\}', e.g. 'master@\{yesterday\}', 'HEAD@\{5 minutes ago\}'::
A ref followed by the suffix '@' with a date specification
enclosed in a brace
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:
* Boolean long options can be 'negated' (or 'unset') by prepending
`no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely,
options that begin with `no-` can be 'negated' by removing it.
+ Other long options can be unset (e.g., set string to NULL, set
+ integer to 0) by prepending `no-`.
* Options and non-option arguments can clearly be separated using the `--`
option, e.g. `-a -b --option -- --this-is-a-file` indicates that
Introduce an option with date argument, see `approxidate()`.
The timestamp is put into `int_var`.
+`OPT_EXPIRY_DATE(short, long, &int_var, description)`::
+ Introduce an option with expiry date argument, see `parse_expiry_date()`.
+ The timestamp is put into `int_var`.
+
`OPT_CALLBACK(short, long, &var, arg_str, description, func_ptr)`::
Introduce an option with argument.
The argument will be fed into the function given by `func_ptr`
--------
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.
A conflict is represented in the index as a set of higher stage entries.
When a conflict is resolved (e.g. with "git add path"), these higher
- stage entries will be removed and a stage-0 entry with proper resoluton
+ stage entries will be removed and a stage-0 entry with proper resolution
is added.
When these higher stage entries are removed, they are saved in the
The client MUST write all obj-ids which it only has shallow copies
of (meaning that it does not have the parents of a commit) as
'shallow' lines so that the server is aware of the limitations of
-the client's history. Clients MUST NOT mention an obj-id which
-it does not know exists on the server.
+the client's history.
The client now sends the maximum commit history depth it wants for
this transaction, which is the number of commits it wants from the
and server advertised. As a consequence of these rules, server MUST
NOT advertise capabilities it does not understand.
-The 'report-status' and 'delete-refs' capabilities are sent and
+The 'report-status', 'delete-refs', and 'quiet' capabilities are sent and
recognized by the receive-pack (push to server) process.
-The 'ofs-delta' capability is sent and recognized by both upload-pack
-and receive-pack protocols.
+The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized
+by both upload-pack and receive-pack protocols. The 'agent' capability
+may optionally be sent in both protocols.
All other capabilities are only recognized by the upload-pack (fetch
from server) process.
its base by position in pack rather than by an obj-id. That is, they can
send/read OBJ_OFS_DELTA (aka type 6) in a packfile.
+agent
+-----
+
+The server may optionally send a capability of the form `agent=X` to
+notify the client that the server is running version `X`. The client may
+optionally return its own agent string by responding with an `agent=Y`
+capability (but it MUST NOT do so if the server did not mention the
+agent capability). The `X` and `Y` strings may contain any printable
+ASCII characters except space (i.e., the byte range 32 < x < 127), and
+are typically of the form "package/version" (e.g., "git/1.8.3.1"). The
+agent strings are purely informative for statistics and debugging
+purposes, and MUST NOT be used to programatically assume the presence
+or absence of particular features.
+
shallow
-------
report-status
-------------
-The upload-pack process can receive a 'report-status' capability,
+The receive-pack process can receive a 'report-status' capability,
which tells it that the client wants a report of what happened after
a packfile upload and reference update. If the pushing client requests
this capability, after unpacking and updating references the server
value of a reference update. It is not sent back by the client, it
simply informs the client that it can be sent zero-id values
to delete references.
+
+quiet
+-----
+
+If the receive-pack server advertises the 'quiet' capability, it is
+capable of silencing human-readable progress output which otherwise may
+be shown when processing the received pack. A send-pack client should
+respond with the 'quiet' capability to suppress server-side progress
+reporting if the local progress reporting is also being suppressed
+(e.g., via `push -q`, or if stderr does not go to a tty).
+
+allow-tip-sha1-in-want
+----------------------
+
+If the upload-pack server advertises this capability, fetch-pack may
+send "want" lines with SHA-1s that exist at the server but are not
+advertised by upload-pack.
on-disk timestamps, resulting in meaningless changes when an
inode is evicted from the inode cache. See commit 8ce13b0
of git://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git
-([PATCH] Sync in core time granuality with filesystems,
+([PATCH] Sync in core time granularity with filesystems,
2005-01-04).
Racy Git
$ 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
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/
- {startsb}user@{endsb}host.xz:path/to/repo.git/
+This syntax is only recognized if there are no slashes before the
+first colon. This helps differentiate a local path that contains a
+colon. For example the local path `foo:bar` could be specified as an
+absolute path or `./foo:bar` to avoid being misinterpreted as an ssh
+url.
+
The ssh and git protocols additionally support ~username expansion:
- ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/~{startsb}user{endsb}/path/to/repo.git/
syntaxes may be used:
- /path/to/repo.git/
-- file:///path/to/repo.git/
+- \file:///path/to/repo.git/
ifndef::git-clone[]
These two syntaxes are mostly equivalent, except when cloning, when
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
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
-------------------------------------------------
$ 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/*
...
-------------------------------------------------
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
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
-------------------------------------------------
-------------------------------------------------
$ 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
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`.
- 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.
------------------------------------------------------------------
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
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
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.8.3-rc1
+DEF_VER=v1.8.4-rc0
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) ;;
# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
# doesn't support GNU extensions like --check and --statistics
#
+# Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than
+# INT_MAX bytes at once (e.g. MacOS X).
+#
# Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
# it specifies.
#
# 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
# specify your own (or DarwinPort's) include directories and
# library directories by defining CFLAGS and LDFLAGS appropriately.
#
+# Define NO_APPLE_COMMON_CRYPTO if you are building on Darwin/Mac OS X
+# and do not want to use Apple's CommonCrypto library. This allows you
+# to provide your own OpenSSL library, for example from MacPorts.
+#
# Define BLK_SHA1 environment variable to make use of the bundled
# optimized C SHA1 routine.
#
#
# Define NO_REGEX if you have no or inferior regex support in your C library.
#
-# Define CYGWIN_V15_WIN32API if you are using Cygwin v1.7.x but are not
-# using the current w32api packages. The recommended approach, however,
-# is to update your installation if compilation errors occur.
-#
# Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the
# user.
#
SCRIPT_SH += git-pull.sh
SCRIPT_SH += git-quiltimport.sh
SCRIPT_SH += git-rebase.sh
+SCRIPT_SH += git-remote-testgit.sh
SCRIPT_SH += git-repack.sh
SCRIPT_SH += git-request-pull.sh
SCRIPT_SH += git-stash.sh
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
SCRIPT_SH_GEN = $(patsubst %.sh,%,$(SCRIPT_SH))
SCRIPT_PERL_GEN = $(patsubst %.perl,%,$(SCRIPT_PERL))
SCRIPT_PYTHON_GEN = $(patsubst %.py,%,$(SCRIPT_PYTHON))
+SCRIPT_SH_INS = $(filter-out $(NO_INSTALL),$(SCRIPT_SH_GEN))
+SCRIPT_PERL_INS = $(filter-out $(NO_INSTALL),$(SCRIPT_PERL_GEN))
+SCRIPT_PYTHON_INS = $(filter-out $(NO_INSTALL),$(SCRIPT_PYTHON_GEN))
+
# Individual rules to allow e.g.
# "make -C ../.. SCRIPT_PERL=contrib/foo/bar.perl build-perl-script"
# from subdirectories like contrib/*/
build-python-script: $(SCRIPT_PYTHON_GEN)
.PHONY: install-perl-script install-sh-script install-python-script
-install-sh-script: $(SCRIPT_SH_GEN)
- $(INSTALL) $(SCRIPT_SH_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
-install-perl-script: $(SCRIPT_PERL_GEN)
- $(INSTALL) $(SCRIPT_PERL_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
-install-python-script: $(SCRIPT_PYTHON_GEN)
- $(INSTALL) $(SCRIPT_PYTHON_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+install-sh-script: $(SCRIPT_SH_INS)
+ $(INSTALL) $^ '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+install-perl-script: $(SCRIPT_PERL_INS)
+ $(INSTALL) $^ '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+install-python-script: $(SCRIPT_PYTHON_INS)
+ $(INSTALL) $^ '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
.PHONY: clean-perl-script clean-sh-script clean-python-script
clean-sh-script:
clean-python-script:
$(RM) $(SCRIPT_PYTHON_GEN)
-SCRIPTS = $(SCRIPT_SH_GEN) \
- $(SCRIPT_PERL_GEN) \
- $(SCRIPT_PYTHON_GEN) \
+SCRIPTS = $(SCRIPT_SH_INS) \
+ $(SCRIPT_PERL_INS) \
+ $(SCRIPT_PYTHON_INS) \
git-instaweb
ETAGS_TARGET = TAGS
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
LIB_H += http.h
LIB_H += kwset.h
LIB_H += levenshtein.h
+LIB_H += line-log.h
+LIB_H += line-range.h
LIB_H += list-objects.h
LIB_H += ll-merge.h
LIB_H += log-tree.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-refs.h
LIB_H += pack-revindex.h
LIB_H += pack.h
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
LIB_OBJS += ident.o
LIB_OBJS += kwset.o
LIB_OBJS += levenshtein.o
+LIB_OBJS += line-log.o
+LIB_OBJS += line-range.o
LIB_OBJS += list-objects.o
LIB_OBJS += ll-merge.o
LIB_OBJS += lockfile.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-refs.o
LIB_OBJS += pack-revindex.o
LIB_OBJS += pack-write.o
LIB_OBJS += pager.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
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
BASIC_LDFLAGS += -L/opt/local/lib
endif
endif
+ ifndef NO_APPLE_COMMON_CRYPTO
+ APPLE_COMMON_CRYPTO = YesPlease
+ COMPAT_CFLAGS += -DAPPLE_COMMON_CRYPTO
+ endif
+ NO_REGEX = YesPlease
PTHREAD_LIBS =
endif
COMPAT_OBJS += compat/fnmatch/fnmatch.o
endif
endif
-ifdef USE_WILDMATCH
+ifndef NO_WILDMATCH
COMPAT_CFLAGS += -DUSE_WILDMATCH
endif
ifdef NO_SETENV
SHA1_HEADER = "ppc/sha1.h"
LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
LIB_H += ppc/sha1.h
+else
+ifdef APPLE_COMMON_CRYPTO
+ COMPAT_CFLAGS += -DCOMMON_DIGEST_FOR_OPENSSL
+ SHA1_HEADER = <CommonCrypto/CommonDigest.h>
else
SHA1_HEADER = <openssl/sha.h>
EXTLIBS += $(LIB_4_CRYPTO)
endif
endif
+endif
+
ifdef NO_PERL_MAKEMAKER
export NO_PERL_MAKEMAKER
endif
COMPAT_CFLAGS += -Icompat/regex
COMPAT_OBJS += compat/regex/regex.o
endif
-ifdef CYGWIN_V15_WIN32API
- COMPAT_CFLAGS += -DCYGWIN_V15_WIN32API
-endif
ifdef USE_NED_ALLOCATOR
COMPAT_CFLAGS += -Icompat/nedmalloc
MSGFMT += --check --statistics
endif
+ifdef NEEDS_CLIPPED_WRITE
+ BASIC_CFLAGS += -DNEEDS_CLIPPED_WRITE
+ COMPAT_OBJS += compat/clipped-write.o
+endif
+
ifneq (,$(XDL_FAST_HASH))
BASIC_CFLAGS += -DXDL_FAST_HASH
endif
QUIET_MSGFMT = @echo ' ' MSGFMT $@;
QUIET_GCOV = @echo ' ' GCOV $@;
QUIET_SP = @echo ' ' SP $<;
+ QUIET_RC = @echo ' ' RC $@;
QUIET_SUBDIR0 = +@subdir=
QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
$(MAKE) $(PRINT_DIR) -C $$subdir
shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell
strip: $(PROGRAMS) git$X
- $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
+ $(STRIP) $(STRIP_OPTS) $^
### Target-specific flags and dependencies
$(BUILT_INS): git$X
$(QUIET_BUILT_IN)$(RM) $@ && \
- ln git$X $@ 2>/dev/null || \
- ln -s git$X $@ 2>/dev/null || \
- cp git$X $@
+ ln $< $@ 2>/dev/null || \
+ ln -s $< $@ 2>/dev/null || \
+ cp $< $@
common-cmds.h: ./generate-cmdlist.sh command-list.txt
$(QUIET_GEN)$(cmd_munge_script) && \
mv $@+ $@
+git.res: git.rc GIT-VERSION-FILE
+ $(QUIET_RC)$(RC) \
+ $(join -DMAJOR= -DMINOR= -DPATCH=, $(wordlist 1,3,$(subst -, ,$(subst ., ,$(GIT_VERSION))))) \
+ -DGIT_VERSION="\\\"$(GIT_VERSION)\\\"" $< -o $@
+
ifndef NO_PERL
$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
-e ' x' \
-e '}' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
- $@.perl >$@+ && \
+ $< >$@+ && \
chmod +x $@+ && \
mv $@+ $@
endif # NO_PERL
ifndef NO_PYTHON
-$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
-$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
+$(SCRIPT_PYTHON_GEN): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
+$(SCRIPT_PYTHON_GEN): % : %.py
$(QUIET_GEN)$(RM) $@ $@+ && \
INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
--no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
-e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \
-e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
- $@.py >$@+ && \
+ $< >$@+ && \
chmod +x $@+ && \
mv $@+ $@
else # NO_PYTHON
-$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh
+$(SCRIPT_PYTHON_GEN): % : unimplemented.sh
$(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \
ifdef USE_NED_ALLOCATOR
compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
-DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR
+compat/nedmalloc/nedmalloc.sp: SPARSE_FLAGS += -Wno-non-pointer-null
endif
git-%$X: %.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
@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
@echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
+ifdef TEST_OUTPUT_DIRECTORY
+ @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@
+endif
ifdef GIT_TEST_OPTS
@echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@
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
exit 1; \
fi
-remove-dashes:
- ./fixup-builtins $(BUILT_INS) $(PROGRAMS) $(SCRIPTS)
-
### Installation rules
ifneq ($(filter /%,$(firstword $(template_dir))),)
$(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
$(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
-clean: profile-clean
- $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \
+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*
### Test suite coverage testing
#
-.PHONY: coverage coverage-clean coverage-build coverage-report
+.PHONY: coverage coverage-clean coverage-compile coverage-test coverage-report
+.PHONY: coverage-clean-results
coverage:
- $(MAKE) coverage-build
- $(MAKE) coverage-report
+ $(MAKE) coverage-test
+ $(MAKE) coverage-untested-functions
object_dirs := $(sort $(dir $(OBJECTS)))
-coverage-clean:
+coverage-clean-results:
$(RM) $(addsuffix *.gcov,$(object_dirs))
$(RM) $(addsuffix *.gcda,$(object_dirs))
- $(RM) $(addsuffix *.gcno,$(object_dirs))
$(RM) coverage-untested-functions
$(RM) -r cover_db/
$(RM) -r cover_db_html/
+coverage-clean: coverage-clean-results
+ $(RM) $(addsuffix *.gcno,$(object_dirs))
+
COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs
COVERAGE_LDFLAGS = $(CFLAGS) -O0 -lgcov
GCOVFLAGS = --preserve-paths --branch-probabilities --all-blocks
-coverage-build: coverage-clean
+coverage-compile:
$(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all
+
+coverage-test: coverage-clean-results coverage-compile
$(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" \
- -j1 test
+ DEFAULT_TEST_TARGET=test -j1 test
coverage-report:
$(QUIET_GCOV)for dir in $(object_dirs); do \
-Documentation/RelNotes/1.8.3.txt
\ No newline at end of file
+Documentation/RelNotes/1.8.4.txt
\ No newline at end of file
const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
{
static char path[PATH_MAX];
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
if (!pfx_len || is_absolute_path(arg))
return arg;
memcpy(path, pfx, pfx_len);
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;
{ "implicitidentity", &advice_implicit_identity },
{ "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 }
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);
size = 0;
compressed_size = 0;
buffer = NULL;
- size = 0;
} else if (S_ISREG(mode) || S_ISLNK(mode)) {
enum object_type type = sha1_object_info(sha1, &size);
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 *);
static struct sha1_array good_revs;
static struct sha1_array skipped_revs;
-static const unsigned char *current_bad_sha1;
+static unsigned char *current_bad_sha1;
static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
int flags, void *cb_data)
{
if (!strcmp(refname, "bad")) {
- current_bad_sha1 = sha1;
+ current_bad_sha1 = xmalloc(20);
+ hashcpy(current_bad_sha1, sha1);
} else if (!prefixcmp(refname, "good-")) {
sha1_array_append(&good_revs, sha1);
} else if (!prefixcmp(refname, "skip-")) {
return 1;
}
+static int check_tracking_branch(struct remote *remote, void *cb_data)
+{
+ char *tracking_branch = cb_data;
+ struct refspec query;
+ memset(&query, 0, sizeof(struct refspec));
+ query.dst = tracking_branch;
+ return !(remote_find_tracking(remote, &query) ||
+ prefixcmp(query.src, "refs/heads/"));
+}
+
+static int validate_remote_tracking_branch(char *ref)
+{
+ return !for_each_remote(check_tracking_branch, ref);
+}
+
static const char upstream_not_branch[] =
N_("Cannot setup tracking information; starting point '%s' is not a branch.");
static const char upstream_missing[] =
case 1:
/* Unique completion -- good, only if it is a real branch */
if (prefixcmp(real_ref, "refs/heads/") &&
- prefixcmp(real_ref, "refs/remotes/")) {
+ validate_remote_tracking_branch(real_ref)) {
if (explicit_tracking)
die(_(upstream_not_branch), start_name);
else
#include "strbuf.h"
#include "cache.h"
#include "commit.h"
-#include "notes.h"
#define DEFAULT_MERGE_LOG_LEN 20
extern const char git_usage_string[];
extern const char git_more_info_string[];
+#define PRUNE_PACKED_DRY_RUN 01
+#define PRUNE_PACKED_VERBOSE 02
+
extern void prune_packed_objects(int);
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);
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);
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)
/*
* 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);
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 == '"') {
patch->old_name = name;
} else {
patch->old_name = name;
- patch->new_name = xstrdup(name);
+ patch->new_name = null_strdup(name);
}
}
if (!name)
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;
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))
#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)
* 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;
}
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;
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;
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) {
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)
#include "parse-options.h"
#include "utf8.h"
#include "userdiff.h"
+#include "line-range.h"
static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
dst->score = 0;
}
-static const char *nth_line(struct scoreboard *sb, int lno)
+static const char *nth_line(struct scoreboard *sb, long lno)
{
return sb->final_buf + sb->lineno[lno];
}
+static const char *nth_line_cb(void *data, long lno)
+{
+ return nth_line((struct scoreboard *)data, lno);
+}
+
/*
* It is known that lines between tlno to same came from parent, and e
* has an overlap with that range. it also is known that parent's
return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
}
-/*
- * Parsing of (comma separated) one item in the -L option
- */
-static const char *parse_loc(const char *spec,
- struct scoreboard *sb, long lno,
- long begin, long *ret)
-{
- char *term;
- const char *line;
- long num;
- int reg_error;
- regex_t regexp;
- regmatch_t match[1];
-
- /* Allow "-L <something>,+20" to mean starting at <something>
- * for 20 lines, or "-L <something>,-5" for 5 lines ending at
- * <something>.
- */
- if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
- num = strtol(spec + 1, &term, 10);
- if (term != spec + 1) {
- if (spec[0] == '-')
- num = 0 - num;
- if (0 < num)
- *ret = begin + num - 2;
- else if (!num)
- *ret = begin;
- else
- *ret = begin + num;
- return term;
- }
- return spec;
- }
- num = strtol(spec, &term, 10);
- if (term != spec) {
- *ret = num;
- return term;
- }
- if (spec[0] != '/')
- return spec;
-
- /* it could be a regexp of form /.../ */
- for (term = (char *) spec + 1; *term && *term != '/'; term++) {
- if (*term == '\\')
- term++;
- }
- if (*term != '/')
- return spec;
-
- /* try [spec+1 .. term-1] as regexp */
- *term = 0;
- begin--; /* input is in human terms */
- line = nth_line(sb, begin);
-
- if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) &&
- !(reg_error = regexec(®exp, line, 1, match, 0))) {
- const char *cp = line + match[0].rm_so;
- const char *nline;
-
- while (begin++ < lno) {
- nline = nth_line(sb, begin);
- if (line <= cp && cp < nline)
- break;
- line = nline;
- }
- *ret = begin;
- regfree(®exp);
- *term++ = '/';
- return term;
- }
- else {
- char errbuf[1024];
- regerror(reg_error, ®exp, errbuf, 1024);
- die("-L parameter '%s': %s", spec + 1, errbuf);
- }
-}
-
/*
* Parsing of -L option
*/
long lno,
long *bottom, long *top)
{
- const char *term;
-
- term = parse_loc(bottomtop, sb, lno, 1, bottom);
- if (*term == ',') {
- term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
- if (*term)
- usage(blame_usage);
- }
- if (*term)
+ if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
usage(blame_usage);
}
bottom = top = 0;
if (bottomtop)
prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
- if (bottom && top && top < bottom) {
- long tmp;
- tmp = top; top = bottom; bottom = tmp;
- }
if (bottom < 1)
bottom = 1;
if (top < 1)
#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];
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)
+ data->info.typep = &data->type;
+ else
+ 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) {
+ if (sha1_object_info_extended(data->sha1, &data->info) < 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;
+
+ /*
+ * We are going to call get_sha1 on a potentially very large number of
+ * objects. In most large cases, these will be actual object sha1s. The
+ * cost to double-check that each one is not also a ref (just so we can
+ * warn) ends up dwarfing the actual cost of the object lookups
+ * themselves. We can work around it by just turning off the warning.
+ */
+ warn_on_object_refname_ambiguity = 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;
}
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")),
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()
};
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);
}
#include "pathspec.h"
#include "parse-options.h"
-static int quiet, verbose, stdin_paths;
+static int quiet, verbose, stdin_paths, show_non_matching;
static const char * const check_ignore_usage[] = {
"git check-ignore [options] pathname...",
"git check-ignore [options] --stdin < <list-of-paths>",
N_("read file names from stdin")),
OPT_BOOLEAN('z', NULL, &null_term_line,
N_("input paths are terminated by a null character")),
+ OPT_BOOLEAN('n', "non-matching", &show_non_matching,
+ N_("show non-matching input paths")),
OPT_END()
};
static void output_exclude(const char *path, struct exclude *exclude)
{
- char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : "";
- char *slash = exclude->flags & EXC_FLAG_MUSTBEDIR ? "/" : "";
+ char *bang = (exclude && exclude->flags & EXC_FLAG_NEGATIVE) ? "!" : "";
+ char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : "";
if (!null_term_line) {
if (!verbose) {
write_name_quoted(path, stdout, '\n');
} else {
- quote_c_style(exclude->el->src, NULL, stdout, 0);
- printf(":%d:%s%s%s\t",
- exclude->srcpos,
- bang, exclude->pattern, slash);
+ if (exclude) {
+ quote_c_style(exclude->el->src, NULL, stdout, 0);
+ printf(":%d:%s%s%s\t",
+ exclude->srcpos,
+ bang, exclude->pattern, slash);
+ }
+ else {
+ printf("::\t");
+ }
quote_c_style(path, NULL, stdout, 0);
fputc('\n', stdout);
}
if (!verbose) {
printf("%s%c", path, '\0');
} else {
- printf("%s%c%d%c%s%s%s%c%s%c",
- exclude->el->src, '\0',
- exclude->srcpos, '\0',
- bang, exclude->pattern, slash, '\0',
- path, '\0');
+ if (exclude)
+ printf("%s%c%d%c%s%s%s%c%s%c",
+ exclude->el->src, '\0',
+ exclude->srcpos, '\0',
+ bang, exclude->pattern, slash, '\0',
+ path, '\0');
+ else
+ printf("%c%c%c%s%c", '\0', '\0', '\0', path, '\0');
}
}
}
-static int check_ignore(const char *prefix, const char **pathspec)
+static int check_ignore(struct dir_struct *dir,
+ const char *prefix, const char **pathspec)
{
- struct dir_struct dir;
const char *path, *full_path;
char *seen;
int num_ignored = 0, dtype = DT_UNKNOWN, i;
struct exclude *exclude;
- /* read_cache() is only necessary so we can watch out for submodules. */
- if (read_cache() < 0)
- die(_("index file corrupt"));
-
- memset(&dir, 0, sizeof(dir));
- setup_standard_excludes(&dir);
-
if (!pathspec || !*pathspec) {
if (!quiet)
fprintf(stderr, "no pathspec given.\n");
? strlen(prefix) : 0, path);
full_path = check_path_for_gitlink(full_path);
die_if_path_beyond_symlink(full_path, prefix);
+ exclude = NULL;
if (!seen[i]) {
- exclude = last_exclude_matching(&dir, full_path, &dtype);
- if (exclude) {
- if (!quiet)
- output_exclude(path, exclude);
- num_ignored++;
- }
+ exclude = last_exclude_matching(dir, full_path, &dtype);
}
+ if (!quiet && (exclude || show_non_matching))
+ output_exclude(path, exclude);
+ if (exclude)
+ num_ignored++;
}
free(seen);
- clear_directory(&dir);
return num_ignored;
}
-static int check_ignore_stdin_paths(const char *prefix)
+static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
{
struct strbuf buf, nbuf;
- char **pathspec = NULL;
- size_t nr = 0, alloc = 0;
+ char *pathspec[2] = { NULL, NULL };
int line_termination = null_term_line ? 0 : '\n';
- int num_ignored;
+ int num_ignored = 0;
strbuf_init(&buf, 0);
strbuf_init(&nbuf, 0);
die("line is badly quoted");
strbuf_swap(&buf, &nbuf);
}
- ALLOC_GROW(pathspec, nr + 1, alloc);
- pathspec[nr] = xcalloc(strlen(buf.buf) + 1, sizeof(*buf.buf));
- strcpy(pathspec[nr++], buf.buf);
+ pathspec[0] = buf.buf;
+ num_ignored += check_ignore(dir, prefix, (const char **)pathspec);
+ maybe_flush_or_die(stdout, "check-ignore to stdout");
}
- ALLOC_GROW(pathspec, nr + 1, alloc);
- pathspec[nr] = NULL;
- num_ignored = check_ignore(prefix, (const char **)pathspec);
- maybe_flush_or_die(stdout, "attribute to stdout");
strbuf_release(&buf);
strbuf_release(&nbuf);
- free(pathspec);
return num_ignored;
}
int cmd_check_ignore(int argc, const char **argv, const char *prefix)
{
int num_ignored;
+ struct dir_struct dir;
git_config(git_default_config, NULL);
if (verbose)
die(_("cannot have both --quiet and --verbose"));
}
+ if (show_non_matching && !verbose)
+ die(_("--non-matching is only valid with --verbose"));
+
+ /* read_cache() is only necessary so we can watch out for submodules. */
+ if (read_cache() < 0)
+ die(_("index file corrupt"));
+
+ memset(&dir, 0, sizeof(dir));
+ setup_standard_excludes(&dir);
if (stdin_paths) {
- num_ignored = check_ignore_stdin_paths(prefix);
+ num_ignored = check_ignore_stdin_paths(&dir, prefix);
} else {
- num_ignored = check_ignore(prefix, argv);
+ num_ignored = check_ignore(&dir, prefix, argv);
maybe_flush_or_die(stdout, "ignore to stdout");
}
+ clear_directory(&dir);
+
return !num_ignored;
}
--- /dev/null
+#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;
+}
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))
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)) {
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;
/* 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;
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) {
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. */
}
struct tracking_name_data {
- const char *name;
- char *remote;
+ /* const */ char *src_ref;
+ char *dst_ref;
+ unsigned char *dst_sha1;
int unique;
};
-static int check_tracking_name(const char *refname, const unsigned char *sha1,
- int flags, void *cb_data)
+static int check_tracking_name(struct remote *remote, void *cb_data)
{
struct tracking_name_data *cb = cb_data;
- const char *slash;
-
- if (prefixcmp(refname, "refs/remotes/"))
- return 0;
- slash = strchr(refname + 13, '/');
- if (!slash || strcmp(slash + 1, cb->name))
+ struct refspec query;
+ memset(&query, 0, sizeof(struct refspec));
+ query.src = cb->src_ref;
+ if (remote_find_tracking(remote, &query) ||
+ get_sha1(query.dst, cb->dst_sha1)) {
+ free(query.dst);
return 0;
- if (cb->remote) {
+ }
+ if (cb->dst_ref) {
+ free(query.dst);
cb->unique = 0;
return 0;
}
- cb->remote = xstrdup(refname);
+ cb->dst_ref = query.dst;
return 0;
}
-static const char *unique_tracking_name(const char *name)
+static const char *unique_tracking_name(const char *name, unsigned char *sha1)
{
- struct tracking_name_data cb_data = { NULL, NULL, 1 };
- cb_data.name = name;
- for_each_ref(check_tracking_name, &cb_data);
+ struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
+ char src_ref[PATH_MAX];
+ snprintf(src_ref, PATH_MAX, "refs/heads/%s", name);
+ cb_data.src_ref = src_ref;
+ cb_data.dst_sha1 = sha1;
+ for_each_remote(check_tracking_name, &cb_data);
if (cb_data.unique)
- return cb_data.remote;
- free(cb_data.remote);
+ return cb_data.dst_ref;
+ free(cb_data.dst_ref);
return NULL;
}
if (dwim_new_local_branch_ok &&
!check_filename(NULL, arg) &&
argc == 1) {
- const char *remote = unique_tracking_name(arg);
- if (!remote || get_sha1(remote, rev))
+ const char *remote = unique_tracking_name(arg, rev);
+ if (!remote)
return argcount;
*new_branch = arg;
arg = remote;
#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
};
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)
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);
}
/* 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;
}
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;
} 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;
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;
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"),
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"));
}
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
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);
}
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);
}
#include "transport.h"
#include "strbuf.h"
#include "dir.h"
-#include "pack-refs.h"
#include "sigchain.h"
#include "branch.h"
#include "remote.h"
{
const struct ref *r;
+ lock_packed_refs(LOCK_DIE_ON_ERROR);
+
for (r = local_refs; r; r = r->next) {
if (!r->peer_ref)
continue;
add_packed_ref(r->peer_ref->name, r->old_sha1);
}
- pack_refs(PACK_REFS_ALL);
+ if (commit_packed_refs())
+ die_errno("unable to overwrite old ref-pack file");
}
static void write_followtags(const struct ref *refs, const char *msg)
const struct ref *mapped_refs,
const struct ref *remote_head_points_at,
const char *branch_top,
- const char *msg)
+ const char *msg,
+ struct transport *transport,
+ int check_connectivity)
{
const struct ref *rm = mapped_refs;
- if (check_everything_connected(iterate_ref_map, 0, &rm))
- die(_("remote did not send all necessary objects"));
+ 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);
/*
* 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 {
is_local = option_local != 0 && path && !is_bundle;
if (is_local && option_depth)
warning(_("--depth is ignored in local clones; use file:// instead."));
+ if (option_local > 0 && !is_local)
+ warning(_("--local is ignored"));
if (argc == 2)
dir = xstrdup(argv[1]);
if (option_upload_pack)
transport_set_option(transport, TRANS_OPT_UPLOADPACK,
option_upload_pack);
+
+ if (transport->smart_options && !option_depth)
+ transport->smart_options->check_self_contained_and_connected = 1;
}
refs = transport_get_remote_refs(transport);
transport_fetch_refs(transport, mapped_refs);
update_remote_refs(refs, mapped_refs, remote_head_points_at,
- branch_top.buf, reflog_msg.buf);
+ branch_top.buf, reflog_msg.buf, transport, !is_local);
update_head(our_head_points_at, remote_head, reflog_msg.buf);
#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>..."),
"If you wish to commit it anyway, use:\n"
"\n"
" git commit --allow-empty\n"
+"\n");
+
+static const char empty_cherry_pick_advice_single[] =
+N_("Otherwise, please use 'git reset'\n");
+
+static const char empty_cherry_pick_advice_multi[] =
+N_("If you wish to skip this commit, use:\n"
+"\n"
+" git reset\n"
"\n"
-"Otherwise, please use 'git reset'\n");
+"Then \"git cherry-pick --continue\" will resume cherry-picking\n"
+"the remaining commits.\n");
static const char *use_message_buffer;
static const char commit_editmsg[] = "COMMIT_EDITMSG";
static const char *cleanup_arg;
static enum commit_whence whence;
+static int sequencer_in_use;
static int use_editor = 1, include_status = 1;
-static int show_ignored_in_status;
+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)
{
struct strbuf *buf = opt->value;
- if (unset)
+ if (unset) {
+ have_option_m = 0;
strbuf_setlen(buf, 0);
- else {
+ } else {
+ have_option_m = 1;
if (buf->len)
strbuf_addch(buf, '\n');
strbuf_addstr(buf, arg);
{
if (file_exists(git_path("MERGE_HEAD")))
whence = FROM_MERGE;
- else if (file_exists(git_path("CHERRY_PICK_HEAD")))
+ else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
whence = FROM_CHERRY_PICK;
+ if (file_exists(git_path("sequencer")))
+ sequencer_in_use = 1;
+ }
else
whence = FROM_COMMIT;
if (s)
}
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)
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);
(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 = '@';
run_status(stdout, index_file, prefix, 0, s);
if (amend)
fputs(_(empty_amend_advice), stderr);
- else if (whence == FROM_CHERRY_PICK)
+ else if (whence == FROM_CHERRY_PICK) {
fputs(_(empty_cherry_pick_advice), stderr);
+ if (!sequencer_in_use)
+ fputs(_(empty_cherry_pick_advice_single), stderr);
+ else
+ fputs(_(empty_cherry_pick_advice_multi), stderr);
+ }
return 0;
}
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[],
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);
if (force_author && renew_authorship)
die(_("Using both --reset-author and --author does not make sense"));
- if (logfile || message.len || use_message || fixup_message)
+ if (logfile || have_option_m || use_message || fixup_message)
use_editor = 0;
if (0 <= edit_flag)
use_editor = edit_flag;
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;
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;
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),
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)
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;
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,
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;
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);
}
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;
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),
}
git_config_with_options(collect_config, &values,
- given_config_file, respect_includes);
+ given_config_file, given_config_blob,
+ respect_includes);
ret = !values.nr;
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);
{
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"))
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) {
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;
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);
}
*/
die("$HOME not set");
- if (access_or_warn(user_config, R_OK) &&
- xdg_config && !access_or_warn(xdg_config, R_OK))
+ if (access_or_warn(user_config, R_OK, 0) &&
+ xdg_config && !access_or_warn(xdg_config, R_OK, 0))
given_config_file = xdg_config;
else
given_config_file = user_config;
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'",
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"),
}
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);
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,
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,
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]);
}
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);
#include "parse-options.h"
#include "diff.h"
#include "hash.h"
+#include "argv-array.h"
#define SEEN (1u<<0)
#define MAX_TAGS (FLAG_BITS - 1)
static int all; /* Any valid ref can be used */
static int tags; /* Allow lightweight tags */
static int longformat;
+static int first_parent;
static int abbrev = -1; /* unspecified */
static int max_candidates = 10;
static struct hash_table names;
unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
unsigned name_checked:1;
unsigned char sha1[20];
- const char *path;
+ char *path;
};
static const char *prio_names[] = {
"head", "lightweight", "annotated",
} else {
e->next = NULL;
}
+ e->path = NULL;
}
e->tag = tag;
e->prio = prio;
e->name_checked = 0;
hashcpy(e->sha1, sha1);
- e->path = path;
+ free(e->path);
+ e->path = xstrdup(path);
}
}
commit_list_insert_by_date(p, &list);
p->object.flags |= c->object.flags;
parents = parents->next;
+
+ if (first_parent)
+ break;
}
}
OPT_BOOLEAN(0, "all", &all, N_("use any ref")),
OPT_BOOLEAN(0, "tags", &tags, N_("use any tag, even unannotated")),
OPT_BOOLEAN(0, "long", &longformat, N_("always use long format")),
+ OPT_BOOLEAN(0, "first-parent", &first_parent, N_("only follow first parent")),
OPT__ABBREV(&abbrev),
OPT_SET_INT(0, "exact-match", &max_candidates,
N_("only output exact matches"), 0),
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);
static int builtin_diff_tree(struct rev_info *revs,
int argc, const char **argv,
- struct object_array_entry *ent)
+ struct object_array_entry *ent0,
+ struct object_array_entry *ent1)
{
const unsigned char *(sha1[2]);
int swap = 0;
if (argc > 1)
usage(builtin_diff_usage);
- /* We saw two trees, ent[0] and ent[1].
- * if ent[1] is uninteresting, they are swapped
+ /*
+ * We saw two trees, ent0 and ent1. If ent1 is uninteresting,
+ * swap them.
*/
- if (ent[1].item->flags & UNINTERESTING)
+ if (ent1->item->flags & UNINTERESTING)
swap = 1;
- sha1[swap] = ent[0].item->sha1;
- sha1[1-swap] = ent[1].item->sha1;
+ sha1[swap] = ent0->item->sha1;
+ sha1[1-swap] = ent1->item->sha1;
diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
log_tree_diff_flush(revs);
return 0;
{
int i;
struct rev_info rev;
- struct object_array_entry ent[100];
- int ents = 0, blobs = 0, paths = 0;
+ struct object_array ent = OBJECT_ARRAY_INIT;
+ int blobs = 0, paths = 0;
const char *path = NULL;
struct blobinfo blob[2];
int nongit;
}
for (i = 0; i < rev.pending.nr; i++) {
- struct object_array_entry *list = rev.pending.objects+i;
- struct object *obj = list->item;
- const char *name = list->name;
+ struct object_array_entry *entry = &rev.pending.objects[i];
+ struct object *obj = entry->item;
+ const char *name = entry->name;
int flags = (obj->flags & UNINTERESTING);
if (!obj->parsed)
obj = parse_object(obj->sha1);
die(_("invalid object '%s' given."), name);
if (obj->type == OBJ_COMMIT)
obj = &((struct commit *)obj)->tree->object;
+
if (obj->type == OBJ_TREE) {
- if (ARRAY_SIZE(ent) <= ents)
- die(_("more than %d trees given: '%s'"),
- (int) ARRAY_SIZE(ent), name);
obj->flags |= flags;
- ent[ents].item = obj;
- ent[ents].name = name;
- ents++;
- continue;
- }
- if (obj->type == OBJ_BLOB) {
+ add_object_array(obj, name, &ent);
+ } else if (obj->type == OBJ_BLOB) {
if (2 <= blobs)
die(_("more than two blobs given: '%s'"), name);
hashcpy(blob[blobs].sha1, obj->sha1);
blob[blobs].name = name;
- blob[blobs].mode = list->mode;
+ blob[blobs].mode = entry->mode;
blobs++;
- continue;
+ } else {
+ die(_("unhandled object '%s' given."), name);
}
- die(_("unhandled object '%s' given."), name);
}
if (rev.prune_data.nr) {
if (!path)
/*
* Now, do the arguments look reasonable?
*/
- if (!ents) {
+ if (!ent.nr) {
switch (blobs) {
case 0:
result = builtin_diff_files(&rev, argc, argv);
}
else if (blobs)
usage(builtin_diff_usage);
- else if (ents == 1)
+ else if (ent.nr == 1)
result = builtin_diff_index(&rev, argc, argv);
- else if (ents == 2)
- result = builtin_diff_tree(&rev, argc, argv, ent);
- else if (ent[0].item->flags & UNINTERESTING) {
+ else if (ent.nr == 2)
+ result = builtin_diff_tree(&rev, argc, argv,
+ &ent.objects[0], &ent.objects[1]);
+ else if (ent.objects[0].item->flags & UNINTERESTING) {
/*
* diff A...B where there is at least one merge base
- * between A and B. We have ent[0] == merge-base,
- * ent[ents-2] == A, and ent[ents-1] == B. Show diff
- * between the base and B. Note that we pick one
- * merge base at random if there are more than one.
+ * between A and B. We have ent.objects[0] ==
+ * merge-base, ent.objects[ents-2] == A, and
+ * ent.objects[ents-1] == B. Show diff between the
+ * base and B. Note that we pick one merge base at
+ * random if there are more than one.
*/
- ent[1] = ent[ents-1];
- result = builtin_diff_tree(&rev, argc, argv, ent);
+ result = builtin_diff_tree(&rev, argc, argv,
+ &ent.objects[0],
+ &ent.objects[ent.nr-1]);
} else
result = builtin_diff_combined(&rev, argc, argv,
- ent, ents);
+ ent.objects, ent.nr);
result = diff_result_code(&rev.diffopt, result);
if (1 < rev.diffopt.skip_stat_unmatch)
refresh_index_quietly();
char *line_end, *mark_end;
unsigned char sha1[20];
struct object *object;
+ struct commit *commit;
+ enum object_type type;
line_end = strchr(line, '\n');
if (line[0] != ':' || !line_end)
mark = strtoumax(line + 1, &mark_end, 10);
if (!mark || mark_end == line + 1
- || *mark_end != ' ' || get_sha1(mark_end + 1, sha1))
+ || *mark_end != ' ' || get_sha1_hex(mark_end + 1, sha1))
die("corrupt mark line: %s", line);
if (last_idnum < mark)
last_idnum = mark;
- object = parse_object(sha1);
- if (!object)
+ type = sha1_object_info(sha1, NULL);
+ if (type < 0)
+ die("object not found: %s", sha1_to_hex(sha1));
+
+ if (type != OBJ_COMMIT)
+ /* only commits */
continue;
+ commit = lookup_commit(sha1);
+ if (!commit)
+ die("not a commit? can't happen: %s", sha1_to_hex(sha1));
+
+ object = &commit->object;
+
if (object->flags & SHOWN)
error("Object %s already has a mark", sha1_to_hex(sha1));
- if (object->type != OBJ_COMMIT)
- /* only commits */
- continue;
-
mark_object(object, mark);
object->flags |= SHOWN;
for (rm = *head; rm; rm = rm->next) {
if (branch_merge_matches(branch, i, rm->name)) {
- rm->merge = 1;
+ rm->fetch_head_status = FETCH_HEAD_MERGE;
break;
}
}
refspec.src = branch->merge[i]->src;
get_fetch_map(remote_refs, &refspec, tail, 1);
for (rm = *old_tail; rm; rm = rm->next)
- rm->merge = 1;
+ rm->fetch_head_status = FETCH_HEAD_MERGE;
}
}
const struct ref *remote_refs = transport_get_remote_refs(transport);
if (ref_count || tags == TAGS_SET) {
+ struct ref **old_tail;
+
for (i = 0; i < ref_count; i++) {
get_fetch_map(remote_refs, &refs[i], &tail, 0);
if (refs[i].dst && refs[i].dst[0])
}
/* Merge everything on the command line, but not --tags */
for (rm = ref_map; rm; rm = rm->next)
- rm->merge = 1;
+ rm->fetch_head_status = FETCH_HEAD_MERGE;
if (tags == TAGS_SET)
get_fetch_map(remote_refs, tag_refspec, &tail, 0);
+
+ /*
+ * For any refs that we happen to be fetching via command-line
+ * arguments, take the opportunity to update their configured
+ * counterparts. However, we do not want to mention these
+ * entries in FETCH_HEAD at all, as they would simply be
+ * duplicates of existing entries.
+ */
+ old_tail = tail;
+ for (i = 0; i < transport->remote->fetch_refspec_nr; i++)
+ get_fetch_map(ref_map, &transport->remote->fetch[i],
+ &tail, 1);
+ for (rm = *old_tail; rm; rm = rm->next)
+ rm->fetch_head_status = FETCH_HEAD_IGNORE;
} else {
/* Use the defaults */
struct remote *remote = transport->remote;
*autotags = 1;
if (!i && !has_merge && ref_map &&
!remote->fetch[0].pattern)
- ref_map->merge = 1;
+ ref_map->fetch_head_status = FETCH_HEAD_MERGE;
}
/*
* if the remote we're fetching from is the same
ref_map = get_remote_ref(remote_refs, "HEAD");
if (!ref_map)
die(_("Couldn't find remote ref HEAD"));
- ref_map->merge = 1;
+ ref_map->fetch_head_status = FETCH_HEAD_MERGE;
tail = &ref_map->next;
}
}
const char *what, *kind;
struct ref *rm;
char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
- int want_merge;
+ int want_status;
fp = fopen(filename, "a");
if (!fp)
}
/*
- * The first pass writes objects to be merged and then the
- * second pass writes the rest, in order to allow using
- * FETCH_HEAD as a refname to refer to the ref to be merged.
+ * We do a pass for each fetch_head_status type in their enum order, so
+ * merged entries are written before not-for-merge. That lets readers
+ * use FETCH_HEAD as a refname to refer to the ref to be merged.
*/
- for (want_merge = 1; 0 <= want_merge; want_merge--) {
+ for (want_status = FETCH_HEAD_MERGE;
+ want_status <= FETCH_HEAD_IGNORE;
+ want_status++) {
for (rm = ref_map; rm; rm = rm->next) {
struct ref *ref = NULL;
+ const char *merge_status_marker = "";
commit = lookup_commit_reference_gently(rm->old_sha1, 1);
if (!commit)
- rm->merge = 0;
+ rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
- if (rm->merge != want_merge)
+ if (rm->fetch_head_status != want_status)
continue;
if (rm->peer_ref) {
strbuf_addf(¬e, "%s ", kind);
strbuf_addf(¬e, "'%s' of ", what);
}
- fprintf(fp, "%s\t%s\t%s",
- sha1_to_hex(rm->old_sha1),
- rm->merge ? "" : "not-for-merge",
- note.buf);
- for (i = 0; i < url_len; ++i)
- if ('\n' == url[i])
- fputs("\\n", fp);
- else
- fputc(url[i], fp);
- fputc('\n', fp);
+ switch (rm->fetch_head_status) {
+ case FETCH_HEAD_NOT_FOR_MERGE:
+ merge_status_marker = "not-for-merge";
+ /* fall-through */
+ case FETCH_HEAD_MERGE:
+ fprintf(fp, "%s\t%s\t%s",
+ sha1_to_hex(rm->old_sha1),
+ merge_status_marker,
+ note.buf);
+ for (i = 0; i < url_len; ++i)
+ if ('\n' == url[i])
+ fputs("\\n", fp);
+ else
+ fputc(url[i], fp);
+ fputc('\n', fp);
+ break;
+ default:
+ /* do not write anything to FETCH_HEAD */
+ break;
+ }
strbuf_reset(¬e);
if (ref) {
{
struct string_list *list = (struct string_list *)cbdata;
struct string_list_item *item = string_list_insert(list, refname);
- item->util = (void *)sha1;
+ item->util = xmalloc(20);
+ hashcpy(item->util, sha1);
return 0;
}
struct ref **head,
struct ref ***tail)
{
- struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+ struct string_list existing_refs = STRING_LIST_INIT_DUP;
struct string_list remote_refs = STRING_LIST_INIT_NODUP;
const struct ref *ref;
struct string_list_item *item = NULL;
item = string_list_insert(&remote_refs, ref->name);
item->util = (void *)ref->old_sha1;
}
- string_list_clear(&existing_refs, 0);
+ string_list_clear(&existing_refs, 1);
/*
* We may have a final lightweight tag that needs to be
static int do_fetch(struct transport *transport,
struct refspec *refs, int ref_count)
{
- struct string_list existing_refs = STRING_LIST_INIT_NODUP;
- struct string_list_item *peer_item = NULL;
+ struct string_list existing_refs = STRING_LIST_INIT_DUP;
struct ref *ref_map;
struct ref *rm;
int autotags = (transport->remote->fetch_tags == 1);
+ int retcode = 0;
for_each_ref(add_existing, &existing_refs);
/* if not appending, truncate FETCH_HEAD */
if (!append && !dry_run) {
- int errcode = truncate_fetch_head();
- if (errcode)
- return errcode;
+ retcode = truncate_fetch_head();
+ if (retcode)
+ goto cleanup;
}
ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
for (rm = ref_map; rm; rm = rm->next) {
if (rm->peer_ref) {
- peer_item = string_list_lookup(&existing_refs,
- rm->peer_ref->name);
+ struct string_list_item *peer_item =
+ string_list_lookup(&existing_refs,
+ rm->peer_ref->name);
if (peer_item)
hashcpy(rm->peer_ref->old_sha1,
peer_item->util);
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
if (fetch_refs(transport, ref_map)) {
free_refs(ref_map);
- return 1;
+ retcode = 1;
+ goto cleanup;
}
if (prune) {
/* If --tags was specified, pretend the user gave us the canonical tags refspec */
free_refs(ref_map);
}
- return 0;
+ cleanup:
+ string_list_clear(&existing_refs, 1);
+ return retcode;
}
static void set_option(const char *name, const char *value)
return 1;
}
- add_object_array(obj, (void *) parent, &pending);
+ add_object_array(obj, NULL, &pending);
return 0;
}
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);
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);
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))
/*
- * builtin-help.c
- *
* Builtin help command
*/
#include "cache.h"
static int from_stdin;
static int strict;
+static int do_fsck_object;
static int verbose;
static int show_stat;
+static int check_self_contained_and_connected;
static struct progress *progress;
/* The content of each linked object must have been checked
or it must be already present in the object database */
-static void check_object(struct object *obj)
+static unsigned check_object(struct object *obj)
{
if (!obj)
- return;
+ return 0;
if (!(obj->flags & FLAG_LINK))
- return;
+ return 0;
if (!(obj->flags & FLAG_CHECKED)) {
unsigned long size;
if (type != obj->type || type <= 0)
die(_("object of unexpected type"));
obj->flags |= FLAG_CHECKED;
- return;
+ return 1;
}
+
+ return 0;
}
-static void check_objects(void)
+static unsigned check_objects(void)
{
- unsigned i, max;
+ unsigned i, max, foreign_nr = 0;
max = get_max_object_index();
for (i = 0; i < max; i++)
- check_object(get_indexed_object(i));
+ foreign_nr += check_object(get_indexed_object(i));
+ return foreign_nr;
}
int eaten;
void *buf = (void *) data;
- if (!buf)
- buf = new_data = get_data_from_pack(obj_entry);
+ assert(data && "data can only be NULL for large _blobs_");
/*
* we do not need to free the memory here, as the
obj = parse_object_buffer(sha1, type, size, buf, &eaten);
if (!obj)
die(_("invalid %s"), typename(type));
- if (fsck_object(obj, 1, fsck_error_function))
+ if (do_fsck_object &&
+ fsck_object(obj, 1, fsck_error_function))
die(_("Error in object"));
if (fsck_walk(obj, mark_link, NULL))
die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));
struct pack_idx_entry **idx_objects;
struct pack_idx_option opts;
unsigned char pack_sha1[20];
+ unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(index_pack_usage);
fix_thin_pack = 1;
} else if (!strcmp(arg, "--strict")) {
strict = 1;
+ do_fsck_object = 1;
+ } else if (!strcmp(arg, "--check-self-contained-and-connected")) {
+ strict = 1;
+ check_self_contained_and_connected = 1;
} else if (!strcmp(arg, "--verify")) {
verify = 1;
} else if (!strcmp(arg, "--verify-stat")) {
conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
free(deltas);
if (strict)
- check_objects();
+ foreign_nr = check_objects();
if (show_stat)
show_pack_info(stat_only);
if (index_name == NULL)
free((void *) curr_index);
+ /*
+ * Let the caller know this pack is not self contained
+ */
+ if (check_self_contained_and_connected && foreign_nr)
+ return 1;
+
return 0;
}
#include "remote.h"
#include "string-list.h"
#include "parse-options.h"
+#include "line-log.h"
#include "branch.h"
#include "streaming.h"
#include "version.h"
NULL
};
+struct line_opt_callback_data {
+ struct rev_info *rev;
+ const char *prefix;
+ struct string_list args;
+};
+
static int parse_decoration_style(const char *var, const char *value)
{
switch (git_config_maybe_bool(var, value)) {
return 0;
}
+static int log_line_range_callback(const struct option *option, const char *arg, int unset)
+{
+ struct line_opt_callback_data *data = option->value;
+
+ if (!arg)
+ return -1;
+
+ data->rev->line_level_traverse = 1;
+ string_list_append(&data->args, arg);
+
+ return 0;
+}
+
static void cmd_log_init_defaults(struct rev_info *rev)
{
if (fmt_pretty)
{
struct userformat_want w;
int quiet = 0, source = 0, mailmap = 0;
+ static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
const struct option builtin_log_options[] = {
OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")),
OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
{ OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
PARSE_OPT_OPTARG, decorate_callback},
+ OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
+ "Process line range n,m in file, counting from 1",
+ log_line_range_callback),
OPT_END()
};
+ line_cb.rev = rev;
+ line_cb.prefix = prefix;
+
mailmap = use_mailmap_config;
argc = parse_options(argc, argv, prefix,
builtin_log_options, builtin_log_usage,
rev->show_decorations = 1;
load_ref_decorations(decoration_style);
}
+
+ if (rev->line_level_traverse)
+ line_log_init(rev, line_cb.prefix, &line_cb.args);
+
setup_pager();
}
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)) {
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;
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"),
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"),
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;
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)
return;
fputs(tag, stdout);
- write_name(ent->name, ent->len);
+ write_name(ent->name);
}
static void show_other_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;
find_unique_abbrev(ce->sha1,abbrev),
ce_stage(ce));
}
- write_name(ce->name, ce_namelen(ce));
+ write_name(ce->name);
if (debug_mode) {
- printf(" ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
- printf(" mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
- printf(" dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino);
- printf(" uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid);
- printf(" size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags);
+ 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);
+ printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
+ printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
+ printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
}
}
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);
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;
(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) &&
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;
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++;
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)
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);
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))
{
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;
for (i = 0; i < 3; i++) {
mask |= (1 << i);
- if (n[i].mode && S_ISDIR(n[i].mode))
+ /*
+ * Treat missing entries as directories so that we return
+ * after unresolved_directory has handled this.
+ */
+ if (!n[i].mode || S_ISDIR(n[i].mode))
dirmask |= (1 << i);
}
};
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;
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)
{
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"),
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")),
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")) {
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);
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);
}
/*
- * 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)
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;
}
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);
for (i = 0; i < argc; i++) {
struct commit *commit = get_merge_parent(argv[i]);
if (!commit)
- die(_("%s - not something we can merge"), argv[i]);
+ help_unknown_ref(argv[i], "merge",
+ "not something we can merge");
remotes = &commit_list_insert(commit, remotes)->next;
}
*remotes = NULL;
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)
"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);
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)
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;
}
*/
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. */
* 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"));
}
}
- if (fast_forward_only)
+ if (fast_forward == FF_ONLY)
die(_("Not possible to fast-forward, aborting."));
/* We are going to make a new commit. */
#include "tag.h"
#include "refs.h"
#include "parse-options.h"
+#include "sha1-lookup.h"
#define CUTOFF_DATE_SLOP 86400 /* one day */
}
}
+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;
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)
{
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)
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)")),
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(),
};
for (; argc; argc--, argv++) {
unsigned char sha1[20];
- struct object *o;
+ struct object *object;
struct commit *commit;
if (get_sha1(*argv, sha1)) {
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)
* 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"
#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>]]"),
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);
}
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;
}
#include "builtin.h"
#include "parse-options.h"
-#include "pack-refs.h"
+#include "refs.h"
static char const * const pack_refs_usage[] = {
N_("git pack-refs [options]"),
NULL
};
-#define DRY_RUN 01
-#define VERBOSE 02
-
static struct progress *progress;
static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
if (!has_sha1_pack(sha1))
continue;
memcpy(pathname + len, de->d_name, 38);
- if (opts & DRY_RUN)
+ if (opts & PRUNE_PACKED_DRY_RUN)
printf("rm -f %s\n", pathname);
else
unlink_or_warn(pathname);
const char *dir = get_object_directory();
int len = strlen(dir);
- if (opts == VERBOSE)
+ if (opts & PRUNE_PACKED_VERBOSE)
progress = start_progress_delay("Removing duplicate objects",
256, 95, 2);
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
{
- int opts = isatty(2) ? VERBOSE : 0;
+ int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0;
const struct option prune_packed_options[] = {
- OPT_BIT('n', "dry-run", &opts, N_("dry run"), DRY_RUN),
- OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"), VERBOSE),
+ OPT_BIT('n', "dry-run", &opts, N_("dry run"),
+ PRUNE_PACKED_DRY_RUN),
+ OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"),
+ PRUNE_PACKED_VERBOSE),
OPT_END()
};
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
OPT__VERBOSE(&verbose, N_("report pruned objects")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
- OPT_DATE(0, "expire", &expire,
- N_("expire objects older than <time>")),
+ OPT_EXPIRY_DATE(0, "expire", &expire,
+ N_("expire objects older than <time>")),
OPT_END()
};
char *s;
stop_progress(&progress);
prune_object_dir(get_object_directory());
- prune_packed_objects(show_only);
+ prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);
remove_temporary_files(get_object_directory());
s = mkpathdup("%s/pack", get_object_directory());
remove_temporary_files(s);
remote->name, branch->name, advice_maybe);
}
-static void setup_push_upstream(struct remote *remote, int simple)
+static const char message_detached_head_die[] =
+ N_("You are not currently on a branch.\n"
+ "To push the history leading to the current (detached HEAD)\n"
+ "state now, use\n"
+ "\n"
+ " git push %s HEAD:<name-of-remote-branch>\n");
+
+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(_("You are not currently on a branch.\n"
- "To push the history leading to the current (detached HEAD)\n"
- "state now, use\n"
- "\n"
- " git push %s HEAD:<name-of-remote-branch>\n"),
- remote->name);
+ die(_(message_detached_head_die), remote->name);
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
die(_("The current branch %s has no upstream branch.\n"
"To push the current branch and set the remote as upstream, use\n"
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"
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 = branch_get(NULL);
+ int triangular = is_workflow_triangular(remote);
+
switch (push_default) {
default:
case PUSH_DEFAULT_UNSPECIFIED:
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:
- add_refspec("HEAD");
+ setup_push_current(remote, branch);
break;
case PUSH_DEFAULT_NOTHING:
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[] =
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[] =
return 0;
}
-static void debug_stage(const char *label, struct cache_entry *ce,
+static void debug_stage(const char *label, const struct cache_entry *ce,
struct unpack_trees_options *o)
{
printf("%s ", label);
sha1_to_hex(ce->sha1));
}
-static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o)
+static int debug_merge(const struct cache_entry * const *stages,
+ struct unpack_trees_options *o)
{
int i;
{
if (!value)
return config_error_nonbool(var);
- if (!strcmp(value, "never") || !strcmp(value, "false")) {
- *expire = 0;
- return 0;
- }
- *expire = approxidate(value);
+ if (parse_expiry_date(value, expire))
+ return error(_("%s' for '%s' is not a valid timestamp"),
+ value, var);
return 0;
}
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
cb.dry_run = 1;
else if (!prefixcmp(arg, "--expire=")) {
- cb.expire_total = approxidate(arg + 9);
+ if (parse_expiry_date(arg + 9, &cb.expire_total))
+ die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_TOTAL;
}
else if (!prefixcmp(arg, "--expire-unreachable=")) {
- cb.expire_unreachable = approxidate(arg + 21);
+ if (parse_expiry_date(arg + 21, &cb.expire_unreachable))
+ die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_UNREACH;
}
else if (!strcmp(arg, "--stale-fix"))
*
* 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.
*/
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;
}
else
printf("\n");
+ logmsg_free(msg, commit);
}
static void update_index_from_diff(struct diff_queue_struct *q,
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) {
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;
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;
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;
}
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")) {
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;
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);
}
return 0;
}
+LAST_ARG_MUST_BE_NULL
static void verify_opt_compatible(const char *me, const char *base_opt, ...)
{
const char *this_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;
#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[] = {
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));
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;
}
*/
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;
*/
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;
}
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);
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) {
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;
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"),
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)
const char *hex;
unsigned char peeled[20];
+ if (show_head && !strcmp(refname, "HEAD"))
+ goto match;
+
if (tags_only || heads_only) {
int match;
*/
static int exclude_existing(const char *match)
{
- static struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+ static struct string_list existing_refs = STRING_LIST_INIT_DUP;
char buf[1024];
int matchlen = match ? strlen(match) : 0;
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"),
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;
/* 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! */
/* 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;
{
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))
/* 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))
*/
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,
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;
if (!get_sha1_hex(buf.buf + 1, sha1)) {
struct object *object = parse_object_or_die(sha1, buf.buf);
object->flags |= UNINTERESTING;
- add_pending_object(&revs, object, xstrdup(buf.buf));
+ add_pending_object(&revs, object, buf.buf);
}
} else if (!get_sha1_hex(buf.buf, sha1)) {
struct object *object = parse_object_or_die(sha1, buf.buf);
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;
/* 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;
}
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,
*/
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;
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;
}
int cache_tree_update(struct cache_tree *it,
- struct cache_entry **cache,
+ const struct cache_entry * const *cache,
int entries,
int flags)
{
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) &&
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);
}
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);
unsigned int nsec;
};
+struct stat_data {
+ struct cache_time sd_ctime;
+ struct cache_time sd_mtime;
+ unsigned int sd_dev;
+ unsigned int sd_ino;
+ unsigned int sd_uid;
+ unsigned int sd_gid;
+ unsigned int sd_size;
+};
+
struct cache_entry {
- struct cache_time ce_ctime;
- struct cache_time ce_mtime;
- unsigned int ce_dev;
- unsigned int ce_ino;
+ struct stat_data ce_stat_data;
unsigned int ce_mode;
- unsigned int ce_uid;
- unsigned int ce_gid;
- unsigned int ce_size;
unsigned int ce_flags;
unsigned int ce_namelen;
unsigned char sha1[20];
* another. But we never change the name, or the hash state!
*/
#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
-static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
+static inline void copy_cache_entry(struct cache_entry *dst,
+ const struct cache_entry *src)
{
unsigned int state = dst->ce_flags & CE_STATE_MASK;
return S_IFGITLINK;
return S_IFREG | ce_permissions(mode);
}
-static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
+static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,
+ unsigned int mode)
{
extern int trust_executable_bit, has_symlinks;
if (!has_symlinks && S_ISREG(mode) &&
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)
/*
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 *);
#define CE_MATCH_RACY_IS_DIRTY 02
/* do stat comparison even if CE_SKIP_WORKTREE is true */
#define CE_MATCH_IGNORE_SKIP_WORKTREE 04
-extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
-extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
+extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
+extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
#define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */
#define HASH_FORMAT_CHECK 2
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
+
+/*
+ * Record to sd the data from st that we use to check whether a file
+ * might have changed.
+ */
+extern void fill_stat_data(struct stat_data *sd, struct stat *st);
+
+/*
+ * Return 0 if st is consistent with a file not having been changed
+ * since sd was filled. If there are differences, return a
+ * combination of MTIME_CHANGED, CTIME_CHANGED, OWNER_CHANGED,
+ * INODE_CHANGED, and DATA_CHANGED.
+ */
+extern int match_stat_data(const struct stat_data *sd, struct stat *st);
+
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
#define REFRESH_REALLY 0x0001 /* ignore_valid */
extern int prefer_symlink_refs;
extern int log_all_ref_updates;
extern int warn_ambiguous_refs;
+extern int warn_on_object_refname_ambiguity;
extern int shared_repository;
extern const char *apply_default_whitespace;
extern const char *apply_default_ignorewhitespace;
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);
/* 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);
struct strbuf *timebuf);
int parse_date(const char *date, char *buf, int bufsize);
int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
+int parse_expiry_date(const char *date, unsigned long *timestamp);
void datestamp(char *buf, int bufsize);
#define approxidate(s) approxidate_careful((s), NULL)
unsigned long approxidate_careful(const char *, int *);
unsigned int
force:1,
forced_update:1,
- merge:1,
deletion:1,
matched:1;
+
+ /*
+ * Order is important here, as we write to FETCH_HEAD
+ * in numeric order. And the default NOT_FOR_MERGE
+ * should be 0, so that xcalloc'd structures get it
+ * by default.
+ */
+ enum {
+ FETCH_HEAD_MERGE = -1,
+ FETCH_HEAD_NOT_FOR_MERGE = 0,
+ FETCH_HEAD_IGNORE = 1
+ } fetch_head_status;
+
enum {
REF_STATUS_NONE = 0,
REF_STATUS_OK,
struct object_info {
/* Request */
+ enum object_type *typep;
unsigned long *sizep;
+ unsigned long *disk_sizep;
/* Response */
enum {
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 *);
int sane_execvp(const char *file, char *const argv[]);
+/*
+ * A struct to encapsulate the concept of whether a file has changed
+ * since we last checked it. This uses criteria similar to those used
+ * for the index.
+ */
+struct stat_validity {
+ struct stat_data *sd;
+};
+
+void stat_validity_clear(struct stat_validity *sv);
+
+/*
+ * Returns 1 if the path is a regular file (or a symlink to a regular
+ * file) and matches the saved stat_validity, 0 otherwise. A missing
+ * or inaccessible file is considered a match if the struct was just
+ * initialized, or if the previous update found an inaccessible file.
+ */
+int stat_validity_check(struct stat_validity *sv, const char *path);
+
+/*
+ * Update the stat_validity from a file opened at descriptor fd. If
+ * the file is missing, inaccessible, or not a regular file, then
+ * future calls to stat_validity_check will match iff one of those
+ * conditions continues to be true.
+ */
+void stat_validity_update(struct stat_validity *sv, int fd);
+
#endif /* CACHE_H */
#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;
/*
unsigned long k;
/* Paint a few lines before the first interesting line. */
- while (j < i)
- sline[j++].flag |= mark | no_pre_delete;
+ while (j < i) {
+ if (!(sline[j].flag & mark))
+ sline[j].flag |= no_pre_delete;
+ sline[j++].flag |= mark;
+ }
again:
/* we know up to i is to be included. where does the
int i, num_paths, needsep, show_log_first, num_parent = parents->nr;
diffopts = *opt;
+ diff_tree_setup_paths(diffopts.pathspec.raw, &diffopts);
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
DIFF_OPT_SET(&diffopts, RECURSIVE);
DIFF_OPT_CLR(&diffopts, ALLOW_EXTERNAL);
paths = paths->next;
free(tmp);
}
+
+ diff_tree_release_paths(&diffopts);
}
void diff_tree_combined_merge(const struct commit *commit, int dense,
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
--- /dev/null
+#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(elemtype) * 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; \
+ \
+ nth_slab = c->index / s->slab_size; \
+ nth_slot = c->index % 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) * s->stride); \
+ return &s->slab[nth_slab][nth_slot * s->stride]; \
+} \
+ \
+static int stat_ ##slabname## realloc
+
+#endif /* COMMIT_SLAB_H */
#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,
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);
return item;
}
+/*
+ * 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;
}
}
*
* 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;
/*
* 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 */
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++;
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;
}
*
* 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)
{
#include "strbuf.h"
#include "decorate.h"
#include "gpg-interface.h"
+#include "string-list.h"
struct commit_list {
struct commit *item;
struct commit {
struct object object;
void *util;
- unsigned int indegree;
+ unsigned int index;
unsigned long date;
struct commit_list *parents;
struct tree *tree;
};
struct pretty_print_context {
+ /*
+ * Callers should tweak these to change the behavior of pp_* functions.
+ */
enum cmit_fmt fmt;
int abbrev;
const char *subject;
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.
+ */
+ struct string_list in_body_headers;
};
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);
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];
extern int is_repository_shallow(void);
extern struct commit_list *get_shallow_commits(struct object_array *heads,
int depth, int shallow_flag, int not_shallow_flag);
+extern void check_shallow_file_for_update(void);
+extern void set_alternate_shallow_file(const char *path);
int is_descendant_of(struct commit *, struct commit_list *);
int in_merge_bases(struct commit *, struct commit *);
*/
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 */
--- /dev/null
+#include "../git-compat-util.h"
+#undef write
+
+/*
+ * Version of write that will write at most INT_MAX bytes.
+ * Workaround a xnu bug on Mac OS X
+ */
+ssize_t clipped_write(int fildes, const void *buf, size_t nbyte)
+{
+ if (nbyte > INT_MAX)
+ nbyte = INT_MAX;
+ return write(fildes, buf, nbyte);
+}
#define CYGWIN_C
#define WIN32_LEAN_AND_MEAN
-#ifdef CYGWIN_V15_WIN32API
-#include "../git-compat-util.h"
-#include "win32.h"
-#else
#include <sys/stat.h>
#include <sys/errno.h>
#include "win32.h"
#include "../git-compat-util.h"
-#endif
#include "../cache.h" /* to read configuration */
/*
# define _GNU_SOURCE 1
#endif
+#include <stddef.h>
#include <errno.h>
#include <fnmatch.h>
#include <ctype.h>
whose names are inconsistent. */
# if !defined _LIBC && !defined getenv
-extern char *getenv ();
+extern char *getenv (const char *name);
# endif
# ifndef errno
struct pinfo_t *next;
pid_t pid;
HANDLE proc;
-} pinfo_t;
-struct pinfo_t *pinfo = NULL;
+};
+static struct pinfo_t *pinfo = NULL;
CRITICAL_SECTION pinfo_cs;
static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
else
sin->sin_addr.s_addr = INADDR_LOOPBACK;
ai->ai_addr = (struct sockaddr *)sin;
- ai->ai_next = 0;
+ ai->ai_next = NULL;
return 0;
}
#undef signal
sig_handler_t mingw_signal(int sig, sig_handler_t handler)
{
- sig_handler_t old = timer_fn;
+ sig_handler_t old;
switch (sig) {
case SIGALRM:
+ old = timer_fn;
timer_fn = handler;
break;
case SIGINT:
+ old = sigint_fn;
sigint_fn = handler;
break;
char **make_augmented_environ(const char *const *vars);
void free_environ(char **env);
+/*
+ * A critical section used in the implementation of the spawn
+ * functions (mingw_spawnv[p]e()) and waitpid(). Intialised in
+ * the replacement main() macro below.
+ */
+extern CRITICAL_SECTION pinfo_cs;
+
/*
* A replacement of main() that ensures that argv[0] has a path
* and that default fmode and std(in|out|err) are in binary mode
*/
#define main(c,v) dummy_decl_mingw_main(); \
-static int mingw_main(); \
-int main(int argc, const char **argv) \
+static int mingw_main(c,v); \
+int main(int argc, char **argv) \
{ \
extern CRITICAL_SECTION pinfo_cs; \
_fmode = _O_BINARY; \
v1.04alpha_svn915 7th October 2006:
* Fixed failure to unlock thread cache list if allocating a new list failed.
-Thanks to Dmitry Chichkov for reporting this. Futher thanks to Aleksey Sanin.
+Thanks to Dmitry Chichkov for reporting this. Further thanks to Aleksey Sanin.
* Fixed realloc(0, <size>) segfaulting. Thanks to Dmitry Chichkov for
reporting this.
* Made config defines #ifndef so they can be overridden by the build system.
#define DLMALLOC_VERSION 20804
#endif /* DLMALLOC_VERSION */
+#if defined(linux)
+#define _GNU_SOURCE 1
+#endif
+
#ifndef WIN32
#ifdef _WIN32
#define WIN32 1
static MLOCK_T malloc_global_mutex = { 0, 0, 0};
-static FORCEINLINE long win32_getcurrentthreadid() {
+static FORCEINLINE long win32_getcurrentthreadid(void) {
#ifdef _MSC_VER
#if defined(_M_IX86)
long *threadstruct=(long *)__readfsdword(0x18);
and choose its bk node as its replacement.
2. If x was the last node of its size, but not a leaf node, it must
be replaced with a leaf node (not merely one with an open left or
- right), to make sure that lefts and rights of descendents
- correspond properly to bit masks. We use the rightmost descendent
+ right), to make sure that lefts and rights of descendants
+ correspond properly to bit masks. We use the rightmost descendant
of x. We could use any other leaf, but this is easy to locate and
tends to counteract removal of leftmosts elsewhere, and so keeps
paths shorter than minimally guaranteed. This doesn't loop much
#endif
int nedmallopt(int parno, int value) THROWSPEC { return nedpmallopt(0, parno, value); }
int nedmalloc_trim(size_t pad) THROWSPEC { return nedpmalloc_trim(0, pad); }
-void nedmalloc_stats() THROWSPEC { nedpmalloc_stats(0); }
-size_t nedmalloc_footprint() THROWSPEC { return nedpmalloc_footprint(0); }
+void nedmalloc_stats(void) THROWSPEC { nedpmalloc_stats(0); }
+size_t nedmalloc_footprint(void) THROWSPEC { return nedpmalloc_footprint(0); }
void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC { return nedpindependent_calloc(0, elemsno, elemsize, chunks); }
void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC { return nedpindependent_comalloc(0, elems, sizes, chunks); }
{
/* It's a socket. */
WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
- WSAEventSelect ((SOCKET) h, 0, 0);
+ WSAEventSelect ((SOCKET) h, NULL, 0);
/* If we're lucky, WSAEnumNetworkEvents already provided a way
to distinguish FD_READ and FD_ACCEPT; this saves a recv later. */
Build the range expression which starts from START_ELEM, and ends
at END_ELEM. The result are written to MBCSET and SBCSET.
RANGE_ALLOC is the allocated size of mbcset->range_starts, and
- mbcset->range_ends, is a pointer argument sinse we may
+ mbcset->range_ends, is a pointer argument since we may
update it. */
static reg_errcode_t
const int32_t *symb_table;
const unsigned char *extra;
- /* Local function for parse_bracket_exp used in _LIBC environement.
+ /* Local function for parse_bracket_exp used in _LIBC environment.
Seek the collating symbol entry correspondings to NAME.
Return the index of the symbol in the SYMB_TABLE. */
return UINT_MAX;
}
- /* Local function for parse_bracket_exp used in _LIBC environement.
+ /* Local function for parse_bracket_exp used in _LIBC environment.
Build the range expression which starts from START_ELEM, and ends
at END_ELEM. The result are written to MBCSET and SBCSET.
RANGE_ALLOC is the allocated size of mbcset->range_starts, and
- mbcset->range_ends, is a pointer argument sinse we may
+ mbcset->range_ends, is a pointer argument since we may
update it. */
auto inline reg_errcode_t
return REG_NOERROR;
}
- /* Local function for parse_bracket_exp used in _LIBC environement.
+ /* Local function for parse_bracket_exp used in _LIBC environment.
Build the collating element which is represented by NAME.
The result are written to MBCSET and SBCSET.
COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a
- pointer argument sinse we may update it. */
+ pointer argument since we may update it. */
auto inline reg_errcode_t
__attribute ((always_inline))
Build the equivalence class which is represented by NAME.
The result are written to MBCSET and SBCSET.
EQUIV_CLASS_ALLOC is the allocated size of mbcset->equiv_classes,
- is a pointer argument sinse we may update it. */
+ is a pointer argument since we may update it. */
static reg_errcode_t
#ifdef RE_ENABLE_I18N
Build the character class which is represented by NAME.
The result are written to MBCSET and SBCSET.
CHAR_CLASS_ALLOC is the allocated size of mbcset->char_classes,
- is a pointer argument sinse we may update it. */
+ is a pointer argument since we may update it. */
static reg_errcode_t
#ifdef RE_ENABLE_I18N
rval = 0;
- /* I hope we needn't fill ther regs with -1's when no match was found. */
+ /* I hope we needn't fill their regs with -1's when no match was found. */
if (result != REG_NOERROR)
rval = -1;
else if (regs != NULL)
FL_LONGEST_MATCH means we want the POSIX longest matching.
If P_MATCH_FIRST is not NULL, and the match fails, it is set to the
next place where we may want to try matching.
- Note that the matcher assume that the maching starts from the current
+ Note that the matcher assume that the matching starts from the current
index of the buffer. */
static int
dfa->nexts[node_idx]))
/* The node can't accept the `multi byte', or the
destination was already thrown away, then the node
- could't accept the current input `multi byte'. */
+ couldn't accept the current input `multi byte'. */
naccepted = 0;
/* Otherwise, it is sure that the node could accept
`naccepted' bytes input. */
}
/* Update the state_log if we need */
-re_dfastate_t *
+static re_dfastate_t *
internal_function
merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx,
re_dfastate_t *next_state)
mctx->state_log[cur_idx] = next_state;
mctx->state_log_top = cur_idx;
}
- else if (mctx->state_log[cur_idx] == 0)
+ else if (mctx->state_log[cur_idx] == NULL)
{
mctx->state_log[cur_idx] = next_state;
}
/* Skip bytes in the input that correspond to part of a
multi-byte match, then look in the log for a state
from which to restart matching. */
-re_dfastate_t *
+static re_dfastate_t *
internal_function
find_recover_state (reg_errcode_t *err, re_match_context_t *mctx)
{
#include "sigchain.h"
#include "strbuf.h"
-#if defined(HAVE_DEV_TTY) || defined(WIN32)
+#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
static void restore_term(void);
return -1;
}
-#elif defined(WIN32)
+#elif defined(GIT_WINDOWS_NATIVE)
#define INPUT_PATH "CONIN$"
#define OUTPUT_PATH "CONOUT$"
void gitunsetenv (const char *name)
{
+#if !defined(__MINGW32__)
extern char **environ;
+#endif
int src, dst;
size_t nmln;
#define WIN32_H
/* common Win32 functions for MinGW and Cygwin */
-#ifndef WIN32 /* Not defined by Cygwin */
+#ifndef GIT_WINDOWS_NATIVE /* Not defined for Cygwin */
#include <windows.h>
#endif
pthread_t pthread_self(void)
{
- pthread_t t = { 0 };
+ pthread_t t = { NULL };
t.tid = GetCurrentThreadId();
return t;
}
if (!(flags & MAP_PRIVATE))
die("Invalid usage of mmap when built with USE_WIN32_MMAP");
- hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), 0, PAGE_WRITECOPY,
- 0, 0, 0);
+ hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), NULL,
+ PAGE_WRITECOPY, 0, 0, NULL);
if (!hmap)
return MAP_FAILED;
#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"
path = buf.buf;
}
- if (!access_or_die(path, R_OK)) {
+ if (!access_or_die(path, R_OK, 0)) {
if (++inc->depth > MAX_INCLUDE_DEPTH)
die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
cf && cf->name ? cf->name : "the command line");
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;
}
}
}
-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;
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)
trust_ctime = git_config_bool(var, value);
return 0;
}
- if (!strcmp(var, "core.statinfo")) {
+ if (!strcmp(var, "core.statinfo") ||
+ !strcmp(var, "core.checkstat")) {
+ /*
+ * NEEDSWORK: statinfo was a typo in v1.8.2 that has
+ * never been advertised. we will remove it at Git
+ * 2.0 boundary.
+ */
+ if (!strcmp(var, "core.statinfo")) {
+ static int warned;
+ if (!warned++) {
+ warning("'core.statinfo' will be removed in Git 2.0; "
+ "use 'core.checkstat' instead.");
+ }
+ }
if (!strcasecmp(value, "default"))
check_stat = 1;
else if (!strcasecmp(value, "minimal"))
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)
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;
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 = ⊤
-
- ret = git_parse_file(fn, data);
+ top.die_on_error = 1;
+ top.fgetc = config_file_fgetc;
+ top.ungetc = config_file_ungetc;
+ top.ftell = config_file_ftell;
- /* 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;
home_config_paths(&user_config, &xdg_config, "config");
- if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK)) {
+ if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
data);
found += 1;
}
- if (xdg_config && !access_or_die(xdg_config, R_OK)) {
+ if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) {
ret += git_config_from_file(fn, xdg_config, data);
found += 1;
}
- if (user_config && !access_or_die(user_config, R_OK)) {
+ if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) {
ret += git_config_from_file(fn, user_config, data);
found += 1;
}
- if (repo_config && !access_or_die(repo_config, R_OK)) {
+ if (repo_config && !access_or_die(repo_config, R_OK, 0)) {
ret += git_config_from_file(fn, repo_config, data);
found += 1;
}
}
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;
*/
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);
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);
}
/*
{
const char *ep;
size_t section_len;
- FILE *f = cf->f;
switch (store.state) {
case KEY_SEEN:
return 1;
}
- store.offset[store.seen] = ftell(f);
+ store.offset[store.seen] = cf->ftell(cf);
store.seen++;
}
break;
* 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);
}
}
}
NO_MEMMEM = YesPlease
USE_ST_TIMESPEC = YesPlease
HAVE_DEV_TTY = YesPlease
+ NEEDS_CLIPPED_WRITE = YesPlease
COMPAT_OBJS += compat/precompose_utf8.o
BASIC_CFLAGS += -DPRECOMPOSE_UNICODE
endif
NO_SYMLINK_HEAD = YesPlease
NO_IPV6 = YesPlease
OLD_ICONV = UnfortunatelyYes
- CYGWIN_V15_WIN32API = YesPlease
+ NO_THREAD_SAFE_PREAD = YesPlease
+ # There are conflicting reports about this.
+ # On some boxes NO_MMAP is needed, and not so elsewhere.
+ # Try commenting this out if you suspect MMAP is more efficient
+ NO_MMAP = YesPlease
+ else
+ NO_REGEX = UnfortunatelyYes
endif
- NO_THREAD_SAFE_PREAD = YesPlease
NEEDS_LIBICONV = YesPlease
NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
- NO_TRUSTABLE_FILEMODE = UnfortunatelyYes
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
- # There are conflicting reports about this.
- # On some boxes NO_MMAP is needed, and not so elsewhere.
- # Try commenting this out if you suspect MMAP is more efficient
- NO_MMAP = YesPlease
X = .exe
COMPAT_OBJS += compat/cygwin.o
UNRELIABLE_FSTAT = UnfortunatelyYes
NO_FNMATCH = YesPlease
NO_MEMMEM = YesPlease
NEEDS_LIBICONV = YesPlease
- OLD_ICONV = YesPlease
NO_STRTOUMAX = YesPlease
NO_MKDTEMP = YesPlease
NO_MKSTEMPS = YesPlease
NO_INET_PTON = YesPlease
NO_INET_NTOP = YesPlease
NO_POSIX_GOODIES = UnfortunatelyYes
+ DEFAULT_HELP_FORMAT = html
COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
compat/win32/pthread.o compat/win32/syslog.o \
compat/win32/dirent.o
+ BASIC_LDFLAGS += -Wl,--large-address-aware
EXTLIBS += -lws2_32
+ GITLIBS += git.res
PTHREAD_LIBS =
+ RC = windres -O coff
NATIVE_CRLF = YesPlease
X = .exe
SPARSE_FLAGS = -Wno-one-bit-signed-bitfield
NO_R_TO_GCC_LINKER = YesPlease
INTERNAL_QSORT = YesPlease
HAVE_LIBCHARSET_H = YesPlease
+ NO_GETTEXT = YesPlease
else
NO_CURL = YesPlease
endif
[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])
path = strchr(end, c);
if (path && !has_dos_drive_prefix(end)) {
if (c == ':') {
- protocol = PROTO_SSH;
- *path++ = '\0';
+ if (path < strchrnul(host, '/')) {
+ protocol = PROTO_SSH;
+ *path++ = '\0';
+ } else /* '/' in the host part, assume local path */
+ path = end;
}
} else
path = end;
#include "run-command.h"
#include "sigchain.h"
#include "connected.h"
+#include "transport.h"
+int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
+{
+ return check_everything_connected_with_transport(fn, quiet, cb_data, NULL);
+}
/*
* If we feed all the commits we want to verify to this command
*
*
* Returns 0 if everything is connected, non-zero otherwise.
*/
-int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data)
+int check_everything_connected_with_transport(sha1_iterate_fn fn,
+ int quiet,
+ void *cb_data,
+ struct transport *transport)
{
struct child_process rev_list;
const char *argv[] = {"rev-list", "--objects",
char commit[41];
unsigned char sha1[20];
int err = 0;
+ struct packed_git *new_pack = NULL;
if (fn(cb_data, sha1))
return err;
+ if (transport && transport->smart_options &&
+ transport->smart_options->self_contained_and_connected &&
+ transport->pack_lockfile &&
+ !suffixcmp(transport->pack_lockfile, ".keep")) {
+ struct strbuf idx_file = STRBUF_INIT;
+ strbuf_addstr(&idx_file, transport->pack_lockfile);
+ strbuf_setlen(&idx_file, idx_file.len - 5); /* ".keep" */
+ strbuf_addstr(&idx_file, ".idx");
+ new_pack = add_packed_git(idx_file.buf, idx_file.len, 1);
+ strbuf_release(&idx_file);
+ }
+
if (quiet)
argv[5] = "--quiet";
commit[40] = '\n';
do {
+ /*
+ * If index-pack already checked that:
+ * - there are no dangling pointers in the new pack
+ * - the pack is self contained
+ * Then if the updated ref is in the new pack, then we
+ * are sure the ref is good and not sending it to
+ * rev-list for verification.
+ */
+ if (new_pack && find_pack_entry_one(sha1, new_pack))
+ continue;
+
memcpy(commit, sha1_to_hex(sha1), 40);
if (write_in_full(rev_list.in, commit, 41) < 0) {
if (errno != EPIPE && errno != EINVAL)
#ifndef CONNECTED_H
#define CONNECTED_H
+struct transport;
+
/*
* Take callback data, and return next object name in the buffer.
* When called after returning the name for the last object, return -1
* Return 0 if Ok, non zero otherwise (i.e. some missing objects)
*/
extern int check_everything_connected(sha1_iterate_fn, int quiet, void *cb_data);
+extern int check_everything_connected_with_transport(sha1_iterate_fn, int quiet,
+ void *cb_data,
+ struct transport *transport);
#endif /* CONNECTED_H */
+++ /dev/null
-This is a sample program to use 'git-blame --incremental', based
-on this message.
-
-From: Jeff King <peff@peff.net>
-Subject: Re: More precise tag following
-To: Linus Torvalds <torvalds@linux-foundation.org>
-Cc: git@vger.kernel.org
-Date: Sat, 27 Jan 2007 18:52:38 -0500
-Message-ID: <20070127235238.GA28706@coredump.intra.peff.net>
+++ /dev/null
-#!/usr/bin/perl
-
-use Gtk2 -init;
-use Gtk2::SimpleList;
-
-my $hash;
-my $fn;
-if ( @ARGV == 1 ) {
- $hash = "HEAD";
- $fn = shift;
-} elsif ( @ARGV == 2 ) {
- $hash = shift;
- $fn = shift;
-} else {
- die "Usage blameview [<rev>] <filename>";
-}
-
-Gtk2::Rc->parse_string(<<'EOS');
-style "treeview_style"
-{
- GtkTreeView::vertical-separator = 0
-}
-class "GtkTreeView" style "treeview_style"
-EOS
-
-my $window = Gtk2::Window->new('toplevel');
-$window->signal_connect(destroy => sub { Gtk2->main_quit });
-my $vpan = Gtk2::VPaned->new();
-$window->add($vpan);
-my $scrolled_window = Gtk2::ScrolledWindow->new;
-$vpan->pack1($scrolled_window, 1, 1);
-my $fileview = Gtk2::SimpleList->new(
- 'Commit' => 'text',
- 'FileLine' => 'text',
- 'Data' => 'text'
-);
-$scrolled_window->add($fileview);
-$fileview->get_column(0)->set_spacing(0);
-$fileview->set_size_request(1024, 768);
-$fileview->set_rules_hint(1);
-$fileview->signal_connect (row_activated => sub {
- my ($sl, $path, $column) = @_;
- my $row_ref = $sl->get_row_data_from_path ($path);
- system("blameview @$row_ref[0]~1 $fn &");
- });
-
-my $commitwindow = Gtk2::ScrolledWindow->new();
-$commitwindow->set_policy ('GTK_POLICY_AUTOMATIC','GTK_POLICY_AUTOMATIC');
-$vpan->pack2($commitwindow, 1, 1);
-my $commit_text = Gtk2::TextView->new();
-my $commit_buffer = Gtk2::TextBuffer->new();
-$commit_text->set_buffer($commit_buffer);
-$commitwindow->add($commit_text);
-
-$fileview->signal_connect (cursor_changed => sub {
- my ($sl) = @_;
- my ($path, $focus_column) = $sl->get_cursor();
- my $row_ref = $sl->get_row_data_from_path ($path);
- my $c_fh;
- open($c_fh, '-|', "git cat-file commit @$row_ref[0]")
- or die "unable to find commit @$row_ref[0]";
- my @buffer = <$c_fh>;
- $commit_buffer->set_text("@buffer");
- close($c_fh);
- });
-
-my $fh;
-open($fh, '-|', "git cat-file blob $hash:$fn")
- or die "unable to open $fn: $!";
-
-while(<$fh>) {
- chomp;
- $fileview->{data}->[$.] = ['HEAD', "$fn:$.", $_];
-}
-
-my $blame;
-open($blame, '-|', qw(git blame --incremental --), $fn, $hash)
- or die "cannot start git-blame $fn";
-
-Glib::IO->add_watch(fileno($blame), 'in', \&read_blame_line);
-
-$window->show_all;
-Gtk2->main;
-exit 0;
-
-my %commitinfo = ();
-
-sub flush_blame_line {
- my ($attr) = @_;
-
- return unless defined $attr;
-
- my ($commit, $s_lno, $lno, $cnt) =
- @{$attr}{qw(COMMIT S_LNO LNO CNT)};
-
- my ($filename, $author, $author_time, $author_tz) =
- @{$commitinfo{$commit}}{qw(FILENAME AUTHOR AUTHOR-TIME AUTHOR-TZ)};
- my $info = $author . ' ' . format_time($author_time, $author_tz);
-
- for(my $i = 0; $i < $cnt; $i++) {
- @{$fileview->{data}->[$lno+$i-1]}[0,1,2] =
- (substr($commit, 0, 8), $filename . ':' . ($s_lno+$i));
- }
-}
-
-my $buf;
-my $current;
-sub read_blame_line {
-
- my $r = sysread($blame, $buf, 1024, length($buf));
- die "I/O error" unless defined $r;
-
- if ($r == 0) {
- flush_blame_line($current);
- $current = undef;
- return 0;
- }
-
- while ($buf =~ s/([^\n]*)\n//) {
- my $line = $1;
-
- if (($commit, $s_lno, $lno, $cnt) =
- ($line =~ /^([0-9a-f]{40}) (\d+) (\d+) (\d+)$/)) {
- flush_blame_line($current);
- $current = +{
- COMMIT => $1,
- S_LNO => $2,
- LNO => $3,
- CNT => $4,
- };
- next;
- }
-
- # extended attribute values
- if ($line =~ /^(author|author-mail|author-time|author-tz|committer|committer-mail|committer-time|committer-tz|summary|filename) (.*)$/) {
- my $commit = $current->{COMMIT};
- $commitinfo{$commit}{uc($1)} = $2;
- next;
- }
- }
- return 1;
-}
-
-sub format_time {
- my $time = shift;
- my $tz = shift;
-
- my $minutes = $tz < 0 ? 0-$tz : $tz;
- $minutes = ($minutes / 100)*60 + ($minutes % 100);
- $minutes = $tz < 0 ? 0-$minutes : $minutes;
- $time += $minutes * 60;
- my @t = gmtime($time);
- return sprintf('%04d-%02d-%02d %02d:%02d:%02d %s',
- $t[5] + 1900, @t[4,3,2,1,0], $tz);
-}
# 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"
# since tilde expansion is not applied.
# This means that COMPREPLY will be empty and Bash default
# completion will be used.
- COMPREPLY=($(compgen -P "${2-}" -W "$1" -- "${3-$cur}"))
+ __gitcompadd "$1" "${2-}" "${3-$cur}" ""
- # Tell Bash that compspec generates filenames.
- compopt -o filenames 2>/dev/null
+ # use a hack to enable file mode in bash < 4
+ compopt -o filenames +o nospace 2>/dev/null ||
+ compgen -f /non-existing-dir/ > /dev/null
}
-__git_index_file_list_filter_compat ()
-{
- local path
-
- while read -r path; do
- case "$path" in
- ?*/*) echo "${path%%/*}/" ;;
- *) echo "$path" ;;
- esac
- done
-}
-
-__git_index_file_list_filter_bash ()
-{
- local path
-
- while read -r path; do
- case "$path" in
- ?*/*)
- # XXX if we append a slash to directory names when using
- # `compopt -o filenames`, Bash will append another slash.
- # This is pretty stupid, and this the reason why we have to
- # define a compatible version for this function.
- echo "${path%%/*}" ;;
- *)
- echo "$path" ;;
- esac
- done
-}
-
-# Process path list returned by "ls-files" and "diff-index --name-only"
-# commands, in order to list only file names relative to a specified
-# directory, and append a slash to directory names.
-__git_index_file_list_filter ()
-{
- # Default to Bash >= 4.x
- __git_index_file_list_filter_bash
-}
-
-# Execute git ls-files, returning paths relative to the directory
-# specified in the first argument, and using the options specified in
-# the second argument.
+# Execute 'git ls-files', unless the --committable option is specified, in
+# which case it runs 'git diff-index' to find out the files that can be
+# committed. It return paths relative to the directory specified in the first
+# argument, and using the options specified in the second argument.
__git_ls_files_helper ()
{
(
test -n "${CDPATH+set}" && unset CDPATH
- # NOTE: $2 is not quoted in order to support multiple options
- cd "$1" && git ls-files --exclude-standard $2
+ cd "$1"
+ if [ "$2" == "--committable" ]; then
+ git diff-index --name-only --relative HEAD
+ else
+ # NOTE: $2 is not quoted in order to support multiple options
+ git ls-files --exclude-standard $2
+ fi
) 2>/dev/null
}
-# Execute git diff-index, returning paths relative to the directory
-# specified in the first argument, and using the tree object id
-# specified in the second argument.
-__git_diff_index_helper ()
-{
- (
- test -n "${CDPATH+set}" && unset CDPATH
- cd "$1" && git diff-index --name-only --relative "$2"
- ) 2>/dev/null
-}
-
# __git_index_files accepts 1 or 2 arguments:
# 1: Options to pass to ls-files (required).
-# Supported options are --cached, --modified, --deleted, --others,
-# and --directory.
# 2: A directory path (optional).
# If provided, only files within the specified directory are listed.
# Sub directories are never recursed. Path must have a trailing
# slash.
__git_index_files ()
{
- local dir="$(__gitdir)" root="${2-.}"
+ local dir="$(__gitdir)" root="${2-.}" file
if [ -d "$dir" ]; then
- __git_ls_files_helper "$root" "$1" | __git_index_file_list_filter |
- sort | uniq
- fi
-}
-
-# __git_diff_index_files accepts 1 or 2 arguments:
-# 1) The id of a tree object.
-# 2) A directory path (optional).
-# If provided, only files within the specified directory are listed.
-# Sub directories are never recursed. Path must have a trailing
-# slash.
-__git_diff_index_files ()
-{
- local dir="$(__gitdir)" root="${2-.}"
-
- if [ -d "$dir" ]; then
- __git_diff_index_helper "$root" "$1" | __git_index_file_list_filter |
- sort | uniq
+ __git_ls_files_helper "$root" "$1" |
+ while read -r file; do
+ case "$file" in
+ ?*/*) echo "${file%%/*}" ;;
+ *) echo "$file" ;;
+ esac
+ done | sort | uniq
fi
}
done
;;
*)
- git ls-remote "$dir" HEAD ORIG_HEAD 'refs/tags/*' 'refs/heads/*' 'refs/remotes/*' 2>/dev/null | \
- while read -r hash i; do
- case "$i" in
- *^{}) ;;
- refs/*) echo "${i#refs/*/}" ;;
- *) echo "$i" ;;
- esac
- done
+ echo "HEAD"
+ git for-each-ref --format="%(refname:short)" -- "refs/remotes/$dir/" | sed -e "s#^$dir/##"
;;
esac
}
}
-# __git_complete_index_file requires 1 argument: the options to pass to
-# ls-file
+# __git_complete_index_file requires 1 argument:
+# 1: the options to pass to ls-file
+#
+# The exception is --committable, which finds the files appropriate commit.
__git_complete_index_file ()
{
- local pfx cur_="$cur"
+ local pfx="" cur_="$cur"
case "$cur_" in
?*/*)
pfx="${cur_%/*}"
cur_="${cur_##*/}"
pfx="${pfx}/"
-
- __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_"
- ;;
- *)
- __gitcomp_file "$(__git_index_files "$1")" "" "$cur_"
;;
esac
-}
-
-# __git_complete_diff_index_file requires 1 argument: the id of a tree
-# object
-__git_complete_diff_index_file ()
-{
- local pfx cur_="$cur"
-
- case "$cur_" in
- ?*/*)
- pfx="${cur_%/*}"
- cur_="${cur_##*/}"
- pfx="${pfx}/"
- __gitcomp_file "$(__git_diff_index_files "$1" "$pfx")" "$pfx" "$cur_"
- ;;
- *)
- __gitcomp_file "$(__git_diff_index_files "$1")" "" "$cur_"
- ;;
- esac
+ __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_"
}
__git_complete_file ()
cat-file) : plumbing;;
check-attr) : plumbing;;
check-ignore) : plumbing;;
+ check-mailmap) : plumbing;;
check-ref-format) : plumbing;;
checkout-index) : plumbing;;
commit-tree) : plumbing;;
esac
if git rev-parse --verify --quiet HEAD >/dev/null; then
- __git_complete_diff_index_file "HEAD"
+ __git_complete_index_file "--committable"
else
# This is the first commit
__git_complete_index_file "--cached"
--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=
return
;;
esac
- __git_complete_file
+ __git_complete_revlist_file
}
__git_fetch_options="
local remote="${prev#remote.}"
remote="${remote%.fetch}"
if [ -z "$cur" ]; then
- __gitcompadd "refs/heads/" "" "" ""
+ __gitcomp_nl "refs/heads/" "" "" ""
return
fi
__gitcomp_nl "$(__git_refs_remotes "$remote")"
return
;;
esac
- __git_complete_file
+ __git_complete_revlist_file
}
_git_show_branch ()
--no-metadata --use-svm-props --use-svnsync-props
--log-window-size= --no-checkout --quiet
--repack-flags --use-log-author --localtime
- --ignore-paths= $remote_opts
+ --ignore-paths= --include-paths= $remote_opts
"
local init_opts="
--template= --shared= --trunk= --tags=
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
--exec-path
--exec-path=
--html-path
+ --man-path
--info-path
--work-tree=
--namespace=
compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
}
- __git_zsh_helper ()
- {
- emulate -L ksh
- local cur cword prev
- cur=${words[CURRENT-1]}
- prev=${words[CURRENT-2]}
- let cword=CURRENT-1
- __${service}_main
- }
-
_git ()
{
- emulate -L zsh
- local _ret=1
- __git_zsh_helper
- let _ret && _default -S '' && _ret=0
+ local _ret=1 cur cword prev
+ cur=${words[CURRENT]}
+ prev=${words[CURRENT-1]}
+ let cword=CURRENT-1
+ emulate ksh -c __${service}_main
+ let _ret && _default && _ret=0
return _ret
}
compdef _git git gitk
return
-elif [[ -n ${BASH_VERSION-} ]]; then
- if ((${BASH_VERSINFO[0]} < 4)); then
- # compopt is not supported
- __git_index_file_list_filter ()
- {
- __git_index_file_list_filter_compat
- }
- fi
fi
__git_func_wrap ()
#
# Copyright (c) 2012-2013 Felipe Contreras <felipe.contreras@gmail.com>
#
-# You need git's bash completion script installed somewhere, by default on the
-# same directory as this script.
+# You need git's bash completion script installed somewhere, by default it
+# would be the location bash-completion uses.
#
-# If your script is on ~/.git-completion.sh instead, you can configure it on
-# your ~/.zshrc:
+# If your script is somewhere else, you can configure it on your ~/.zshrc:
#
# zstyle ':completion:*:*:git:*' script ~/.git-completion.sh
#
-# The recommended way to install this script is to copy to
-# '~/.zsh/completion/_git', and then add the following to your ~/.zshrc file:
+# The recommended way to install this script is to copy to '~/.zsh/_git', and
+# then add the following to your ~/.zshrc file:
#
-# fpath=(~/.zsh/completion $fpath)
+# fpath=(~/.zsh $fpath)
complete ()
{
zstyle ':completion:*:*:git:*' tag-order 'common-commands'
zstyle -s ":completion:*:*:git:*" script script
-test -z "$script" && script="$(dirname ${funcsourcetrace[1]%:*})"/git-completion.bash
+if [ -z "$script" ]; then
+ local -a locations
+ local e
+ locations=(
+ '/etc/bash_completion.d/git' # fedora, old debian
+ '/usr/share/bash-completion/completions/git' # arch, ubuntu, new debian
+ '/usr/share/bash-completion/git' # gentoo
+ $(dirname ${funcsourcetrace[1]%:*})/git-completion.bash
+ )
+ for e in $locations; do
+ test -f $e && script="$e" && break
+ done
+fi
ZSH_VERSION='' . "$script"
__gitcomp ()
# 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:
#
# 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.
-# 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.
+# 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, 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
#
# 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
fi
;;
svn-remote.*.url)
- svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+ svn_remote[$((${#svn_remote[@]} + 1))]="$value"
svn_url_pattern+="\\|$value"
upstream=svn+git # default upstream is SVN if available, else git
;;
svn*)
# get the upstream from the "git-svn-id: ..." in a commit message
# (git-svn uses essentially the same procedure internally)
- local svn_upstream=($(git log --first-parent -1 \
+ local -a svn_upstream
+ svn_upstream=($(git log --first-parent -1 \
--grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
if [[ 0 -ne ${#svn_upstream[@]} ]]; then
- svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
+ svn_upstream=${svn_upstream[${#svn_upstream[@]} - 2]}
svn_upstream=${svn_upstream%@*}
local n_stop="${#svn_remote[@]}"
for ((n=1; n <= n_stop; n++)); do
}
+# Helper function that is meant to be called from __git_ps1. It
+# injects color codes into the appropriate gitstring variables used
+# to build a gitstring.
+__git_ps1_colorize_gitstring ()
+{
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ local c_red='%F{red}'
+ local c_green='%F{green}'
+ local c_lblue='%F{blue}'
+ local c_clear='%f'
+ 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 bad_color=$c_red
+ local ok_color=$c_green
+ local flags_color="$c_lblue"
+
+ local branch_color=""
+ if [ $detached = no ]; then
+ branch_color="$ok_color"
+ else
+ branch_color="$bad_color"
+ fi
+ c="$branch_color$c"
+
+ z="$c_clear$z"
+ if [ "$w" = "*" ]; then
+ w="$bad_color$w"
+ fi
+ if [ -n "$i" ]; then
+ i="$ok_color$i"
+ fi
+ if [ -n "$s" ]; then
+ s="$flags_color$s"
+ fi
+ if [ -n "$u" ]; then
+ u="$bad_color$u"
+ fi
+ r="$c_clear$r"
+}
# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
# when called from PS1 using command substitution
;;
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
- 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
-
- 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
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 f="$w$i$s$u"
- if [ $pcmode = yes ]; then
- local gitstring=
- if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
- 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 z="${GIT_PS1_STATESEPARATOR-" "}"
- if [ $detached = no ]; then
- branch_color="$ok_color"
- else
- branch_color="$bad_color"
- fi
+ # NO color option unless in PROMPT_COMMAND mode
+ if [ $pcmode = yes ] && [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
+ __git_ps1_colorize_gitstring
+ fi
- # Setting gitstring directly with \[ and \] around colors
- # is necessary to prevent wrapping issues!
- gitstring="\[$branch_color\]$branchstring\[$c_clear\]"
+ local f="$w$i$s$u"
+ local gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p"
- if [ -n "$w$i$s$u$r$p" ]; then
- gitstring="$gitstring "
- 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"
- else
- gitstring="$c${b##refs/heads/}${f:+ $f}$r$p"
- fi
+ 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:+ $f}$r$p"
+ printf -v gitstring -- "$printf_format" "$gitstring"
fi
+ PS1="$ps1pc_start$gitstring$ps1pc_end"
+ else
+ printf -- "$printf_format" "$gitstring"
fi
}
--- /dev/null
+#!/usr/bin/perl
+
+# List people who might be interested in a patch. Useful as the argument to
+# git-send-email --cc-cmd option, and in other situations.
+#
+# Usage: git contacts <file | rev-list option> ...
+
+use strict;
+use warnings;
+use IPC::Open2;
+
+my $since = '5-years-ago';
+my $min_percent = 10;
+my $labels_rx = qr/Signed-off-by|Reviewed-by|Acked-by|Cc/i;
+my %seen;
+
+sub format_contact {
+ my ($name, $email) = @_;
+ return "$name <$email>";
+}
+
+sub parse_commit {
+ my ($commit, $data) = @_;
+ my $contacts = $commit->{contacts};
+ my $inbody = 0;
+ for (split(/^/m, $data)) {
+ if (not $inbody) {
+ if (/^author ([^<>]+) <(\S+)> .+$/) {
+ $contacts->{format_contact($1, $2)} = 1;
+ } elsif (/^$/) {
+ $inbody = 1;
+ }
+ } elsif (/^$labels_rx:\s+([^<>]+)\s+<(\S+?)>$/o) {
+ $contacts->{format_contact($1, $2)} = 1;
+ }
+ }
+}
+
+sub import_commits {
+ my ($commits) = @_;
+ return unless %$commits;
+ my $pid = open2 my $reader, my $writer, qw(git cat-file --batch);
+ for my $id (keys(%$commits)) {
+ print $writer "$id\n";
+ my $line = <$reader>;
+ if ($line =~ /^([0-9a-f]{40}) commit (\d+)/) {
+ my ($cid, $len) = ($1, $2);
+ die "expected $id but got $cid\n" unless $id eq $cid;
+ my $data;
+ # cat-file emits newline after data, so read len+1
+ read $reader, $data, $len + 1;
+ parse_commit($commits->{$id}, $data);
+ }
+ }
+ close $reader;
+ close $writer;
+ waitpid($pid, 0);
+ die "git-cat-file error: $?\n" if $?;
+}
+
+sub get_blame {
+ my ($commits, $source, $start, $len, $from) = @_;
+ $len = 1 unless defined($len);
+ return if $len == 0;
+ open my $f, '-|',
+ qw(git blame --porcelain -C), '-L', "$start,+$len",
+ '--since', $since, "$from^", '--', $source or die;
+ while (<$f>) {
+ if (/^([0-9a-f]{40}) \d+ \d+ \d+$/) {
+ my $id = $1;
+ $commits->{$id} = { id => $id, contacts => {} }
+ unless $seen{$id};
+ $seen{$id} = 1;
+ }
+ }
+ close $f;
+}
+
+sub scan_patches {
+ my ($commits, $id, $f) = @_;
+ my $source;
+ while (<$f>) {
+ if (/^From ([0-9a-f]{40}) Mon Sep 17 00:00:00 2001$/) {
+ $id = $1;
+ $seen{$id} = 1;
+ }
+ next unless $id;
+ if (m{^--- (?:a/(.+)|/dev/null)$}) {
+ $source = $1;
+ } elsif (/^--- /) {
+ die "Cannot parse hunk source: $_\n";
+ } elsif (/^@@ -(\d+)(?:,(\d+))?/ && $source) {
+ get_blame($commits, $source, $1, $2, $id);
+ }
+ }
+}
+
+sub scan_patch_file {
+ my ($commits, $file) = @_;
+ open my $f, '<', $file or die "read failure: $file: $!\n";
+ scan_patches($commits, undef, $f);
+ close $f;
+}
+
+sub parse_rev_args {
+ my @args = @_;
+ open my $f, '-|',
+ qw(git rev-parse --revs-only --default HEAD --symbolic), @args
+ or die;
+ my @revs;
+ while (<$f>) {
+ chomp;
+ push @revs, $_;
+ }
+ close $f;
+ return @revs if scalar(@revs) != 1;
+ return "^$revs[0]", 'HEAD' unless $revs[0] =~ /^-/;
+ return $revs[0], 'HEAD';
+}
+
+sub scan_rev_args {
+ my ($commits, $args) = @_;
+ my @revs = parse_rev_args(@$args);
+ open my $f, '-|', qw(git rev-list --reverse), @revs or die;
+ while (<$f>) {
+ chomp;
+ my $id = $_;
+ $seen{$id} = 1;
+ open my $g, '-|', qw(git show -C --oneline), $id or die;
+ scan_patches($commits, $id, $g);
+ close $g;
+ }
+ close $f;
+}
+
+sub mailmap_contacts {
+ my ($contacts) = @_;
+ my %mapped;
+ my $pid = open2 my $reader, my $writer, qw(git check-mailmap --stdin);
+ for my $contact (keys(%$contacts)) {
+ print $writer "$contact\n";
+ my $canonical = <$reader>;
+ chomp $canonical;
+ $mapped{$canonical} += $contacts->{$contact};
+ }
+ close $reader;
+ close $writer;
+ waitpid($pid, 0);
+ die "git-check-mailmap error: $?\n" if $?;
+ return \%mapped;
+}
+
+if (!@ARGV) {
+ die "No input revisions or patch files\n";
+}
+
+my (@files, @rev_args);
+for (@ARGV) {
+ if (-e) {
+ push @files, $_;
+ } else {
+ push @rev_args, $_;
+ }
+}
+
+my %commits;
+for (@files) {
+ scan_patch_file(\%commits, $_);
+}
+if (@rev_args) {
+ scan_rev_args(\%commits, \@rev_args)
+}
+import_commits(\%commits);
+
+my $contacts = {};
+for my $commit (values %commits) {
+ for my $contact (keys %{$commit->{contacts}}) {
+ $contacts->{$contact}++;
+ }
+}
+$contacts = mailmap_contacts($contacts);
+
+my $ncommits = scalar(keys %commits);
+for my $contact (keys %$contacts) {
+ my $percent = $contacts->{$contact} * 100 / $ncommits;
+ next if $percent < $min_percent;
+ print "$contact\n";
+}
--- /dev/null
+git-contacts(1)
+===============
+
+NAME
+----
+git-contacts - List people who might be interested in a set of changes
+
+
+SYNOPSIS
+--------
+[verse]
+'git contacts' (<patch>|<range>|<rev>)...
+
+
+DESCRIPTION
+-----------
+
+Given a set of changes, specified as patch files or revisions, determine people
+who might be interested in those changes. This is done by consulting the
+history of each patch or revision hunk to find people mentioned by commits
+which touched the lines of files under consideration.
+
+Input consists of one or more patch files or revision arguments. A revision
+argument can be a range or a single `<rev>` which is interpreted as
+`<rev>..HEAD`, thus the same revision arguments are accepted as for
+linkgit:git-format-patch[1]. Patch files and revision arguments can be combined
+in the same invocation.
+
+This command can be useful for determining the list of people with whom to
+discuss proposed changes, or for finding the list of recipients to Cc: when
+submitting a patch series via `git send-email`. For the latter case, `git
+contacts` can be used as the argument to `git send-email`'s `--cc-cmd` option.
+
+
+DISCUSSION
+----------
+
+`git blame` is invoked for each hunk in a patch file or revision. For each
+commit mentioned by `git blame`, the commit message is consulted for people who
+authored, reviewed, signed, acknowledged, or were Cc:'d. Once the list of
+participants is known, each person's relevance is computed by considering how
+many commits mentioned that person compared with the total number of commits
+under consideration. The final output consists only of participants who exceed
+a minimum threshold of participation.
+
+
+OUTPUT
+------
+
+For each person of interest, a single line is output, terminated by a newline.
+If the person's name is known, ``Name $$<user@host>$$'' is printed; otherwise
+only ``$$<user@host>$$'' is printed.
+
+
+EXAMPLES
+--------
+
+* Consult patch files:
++
+------------
+$ git contacts feature/*.patch
+------------
+
+* Revision range:
++
+------------
+$ git contacts R1..R2
+------------
+
+* From a single revision to `HEAD`:
++
+------------
+$ git contacts origin
+------------
+
+* Helper for `git send-email`:
++
+------------
+$ git send-email --cc-cmd='git contacts' feature/*.patch
+------------
+
+
+LIMITATIONS
+-----------
+
+Several conditions controlling a person's significance are currently
+hard-coded, such as minimum participation level (10%), blame date-limiting (5
+years), and `-C` level for detecting moved and copied lines (a single `-C`). In
+the future, these conditions may become configurable.
+
+
+GIT
+---
+Part of the linkgit:git[1] suite
+++ /dev/null
-#!/usr/bin/perl
-#
-# A daemon that waits for update events sent by its companion
-# post-receive-cinotify hook, checks out a new copy of source,
-# compiles it, and emails the guilty parties if the compile
-# (and optionally test suite) fails.
-#
-# To use this daemon, configure it and run it. It will disconnect
-# from your terminal and fork into the background. The daemon must
-# have local filesystem access to the source repositories, as it
-# uses objects/info/alternates to avoid copying objects.
-#
-# Add its companion post-receive-cinotify hook as the post-receive
-# hook to each repository that the daemon should monitor. Yes, a
-# single daemon can monitor more than one repository.
-#
-# To use multiple daemons on the same system, give them each a
-# unique queue file and tmpdir.
-#
-# Global Config
-# -------------
-# Reads from a Git style configuration file. This will be
-# ~/.gitconfig by default but can be overridden by setting
-# the GIT_CONFIG_FILE environment variable before starting.
-#
-# cidaemon.smtpHost
-# Hostname of the SMTP server the daemon will send email
-# through. Defaults to 'localhost'.
-#
-# cidaemon.smtpUser
-# Username to authenticate to the SMTP server as. This
-# variable is optional; if it is not supplied then no
-# authentication will be performed.
-#
-# cidaemon.smtpPassword
-# Password to authenticate to the SMTP server as. This
-# variable is optional. If not supplied but smtpUser was,
-# the daemon prompts for the password before forking into
-# the background.
-#
-# cidaemon.smtpAuth
-# Type of authentication to perform with the SMTP server.
-# If set to 'login' and smtpUser was defined, this will
-# use the AUTH LOGIN command, which is suitable for use
-# with at least one version of Microsoft Exchange Server.
-# If not set the daemon will use whatever auth methods
-# are supported by your version of Net::SMTP.
-#
-# cidaemon.email
-# Email address that daemon generated emails will be sent
-# from. This should be a useful email address within your
-# organization. Required.
-#
-# cidaemon.name
-# Human friendly name that the daemon will send emails as.
-# Defaults to 'cidaemon'.
-#
-# cidaemon.scanDelay
-# Number of seconds to sleep between polls of the queue file.
-# Defaults to 60.
-#
-# cidaemon.recentCache
-# Number of recent commit SHA-1s per repository to cache and
-# skip building if they appear again. This is useful to avoid
-# rebuilding the same commit multiple times just because it was
-# pushed into more than one branch. Defaults to 100.
-#
-# cidaemon.tmpdir
-# Scratch directory to create the builds within. The daemon
-# makes a new subdirectory for each build, then deletes it when
-# the build has finished. The pid file is also placed here.
-# Defaults to '/tmp'.
-#
-# cidaemon.queue
-# Path to the queue file that the post-receive-cinotify hook
-# appends events to. This file is polled by the daemon. It
-# must not be on an NFS mount (uses flock). Required.
-#
-# cidaemon.nocc
-# Perl regex patterns to match against author and committer
-# lines. If a pattern matches, that author or committer will
-# not be notified of a build failure.
-#
-# Per Repository Config
-# ----------------------
-# Read from the source repository's config file.
-#
-# builder.command
-# Shell command to execute the build. This command must
-# return 0 on "success" and non-zero on failure. If you
-# also want to run a test suite, make sure your command
-# does that too. Required.
-#
-# builder.queue
-# Queue file to notify the cidaemon through. Should match
-# cidaemon.queue. If not set the hook will not notify the
-# cidaemon.
-#
-# builder.skip
-# Perl regex patterns of refs that should not be sent to
-# cidaemon. Updates of these refs will be ignored.
-#
-# builder.newBranchBase
-# Glob patterns of refs that should be used to form the
-# 'old' revions of a newly created ref. This should set
-# to be globs that match your 'mainline' branches. This
-# way a build failure of a brand new topic branch does not
-# attempt to email everyone since the beginning of time;
-# instead it only emails those authors of commits not in
-# these 'mainline' branches.
-
-local $ENV{PATH} = join ':', qw(
- /opt/git/bin
- /usr/bin
- /bin
- );
-
-use strict;
-use warnings;
-use FindBin qw($RealBin);
-use File::Spec;
-use lib File::Spec->catfile($RealBin, '..', 'perl5');
-use Storable qw(retrieve nstore);
-use Fcntl ':flock';
-use POSIX qw(strftime);
-use Getopt::Long qw(:config no_auto_abbrev auto_help);
-
-sub git_config ($;$)
-{
- my $var = shift;
- my $required = shift || 0;
- local *GIT;
- open GIT, '-|','git','config','--get',$var;
- my $r = <GIT>;
- chop $r if $r;
- close GIT;
- die "error: $var not set.\n" if ($required && !$r);
- return $r;
-}
-
-package EXCHANGE_NET_SMTP;
-
-# Microsoft Exchange Server requires an 'AUTH LOGIN'
-# style of authentication. This is different from
-# the default supported by Net::SMTP so we subclass
-# and override the auth method to support that.
-
-use Net::SMTP;
-use Net::Cmd;
-use MIME::Base64 qw(encode_base64);
-our @ISA = qw(Net::SMTP);
-our $auth_type = ::git_config 'cidaemon.smtpAuth';
-
-sub new
-{
- my $self = shift;
- my $type = ref($self) || $self;
- $type->SUPER::new(@_);
-}
-
-sub auth
-{
- my $self = shift;
- return $self->SUPER::auth(@_) unless $auth_type eq 'login';
-
- my $user = encode_base64 shift, '';
- my $pass = encode_base64 shift, '';
- return 0 unless CMD_MORE == $self->command("AUTH LOGIN")->response;
- return 0 unless CMD_MORE == $self->command($user)->response;
- CMD_OK == $self->command($pass)->response;
-}
-
-package main;
-
-my ($debug_flag, %recent);
-
-my $ex_host = git_config('cidaemon.smtpHost') || 'localhost';
-my $ex_user = git_config('cidaemon.smtpUser');
-my $ex_pass = git_config('cidaemon.smtpPassword');
-
-my $ex_from_addr = git_config('cidaemon.email', 1);
-my $ex_from_name = git_config('cidaemon.name') || 'cidaemon';
-
-my $scan_delay = git_config('cidaemon.scanDelay') || 60;
-my $recent_size = git_config('cidaemon.recentCache') || 100;
-my $tmpdir = git_config('cidaemon.tmpdir') || '/tmp';
-my $queue_name = git_config('cidaemon.queue', 1);
-my $queue_lock = "$queue_name.lock";
-
-my @nocc_list;
-open GIT,'git config --get-all cidaemon.nocc|';
-while (<GIT>) {
- chop;
- push @nocc_list, $_;
-}
-close GIT;
-
-sub nocc_author ($)
-{
- local $_ = shift;
- foreach my $pat (@nocc_list) {
- return 1 if /$pat/;
- }
- 0;
-}
-
-sub input_echo ($)
-{
- my $prompt = shift;
-
- local $| = 1;
- print $prompt;
- my $input = <STDIN>;
- chop $input;
- return $input;
-}
-
-sub input_noecho ($)
-{
- my $prompt = shift;
-
- my $end = sub {system('stty','echo');print "\n";exit};
- local $SIG{TERM} = $end;
- local $SIG{INT} = $end;
- system('stty','-echo');
-
- local $| = 1;
- print $prompt;
- my $input = <STDIN>;
- system('stty','echo');
- print "\n";
- chop $input;
- return $input;
-}
-
-sub rfc2822_date ()
-{
- strftime("%a, %d %b %Y %H:%M:%S %Z", localtime);
-}
-
-sub send_email ($$$)
-{
- my ($subj, $body, $to) = @_;
- my $now = rfc2822_date;
- my $to_str = '';
- my @rcpt_to;
- foreach (@$to) {
- my $s = $_;
- $s =~ s/^/"/;
- $s =~ s/(\s+<)/"$1/;
- $to_str .= ', ' if $to_str;
- $to_str .= $s;
- push @rcpt_to, $1 if $s =~ /<(.*)>/;
- }
- die "Nobody to send to.\n" unless @rcpt_to;
- my $msg = <<EOF;
-From: "$ex_from_name" <$ex_from_addr>
-To: $to_str
-Date: $now
-Subject: $subj
-
-$body
-EOF
-
- my $smtp = EXCHANGE_NET_SMTP->new(Host => $ex_host)
- or die "Cannot connect to $ex_host: $!\n";
- if ($ex_user && $ex_pass) {
- $smtp->auth($ex_user,$ex_pass)
- or die "$ex_host rejected $ex_user\n";
- }
- $smtp->mail($ex_from_addr)
- or die "$ex_host rejected $ex_from_addr\n";
- scalar($smtp->recipient(@rcpt_to, { SkipBad => 1 }))
- or die "$ex_host did not accept any addresses.\n";
- $smtp->data($msg)
- or die "$ex_host rejected message data\n";
- $smtp->quit;
-}
-
-sub pop_queue ()
-{
- open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!";
- flock LOCK, LOCK_EX;
-
- my $queue = -f $queue_name ? retrieve $queue_name : [];
- my $ent = shift @$queue;
- nstore $queue, $queue_name;
-
- flock LOCK, LOCK_UN;
- close LOCK;
- $ent;
-}
-
-sub git_exec (@)
-{
- system('git',@_) == 0 or die "Cannot git " . join(' ', @_) . "\n";
-}
-
-sub git_val (@)
-{
- open(C, '-|','git',@_);
- my $r = <C>;
- chop $r if $r;
- close C;
- $r;
-}
-
-sub do_build ($$)
-{
- my ($git_dir, $new) = @_;
-
- my $tmp = File::Spec->catfile($tmpdir, "builder$$");
- system('rm','-rf',$tmp) == 0 or die "Cannot clear $tmp\n";
- die "Cannot clear $tmp.\n" if -e $tmp;
-
- my $result = 1;
- eval {
- my $command;
- {
- local $ENV{GIT_DIR} = $git_dir;
- $command = git_val 'config','builder.command';
- }
- die "No builder.command for $git_dir.\n" unless $command;
-
- git_exec 'clone','-n','-l','-s',$git_dir,$tmp;
- chmod 0700, $tmp or die "Cannot lock $tmp\n";
- chdir $tmp or die "Cannot enter $tmp\n";
-
- git_exec 'update-ref','HEAD',$new;
- git_exec 'read-tree','-m','-u','HEAD','HEAD';
- system $command;
- if ($? == -1) {
- print STDERR "failed to execute '$command': $!\n";
- $result = 1;
- } elsif ($? & 127) {
- my $sig = $? & 127;
- print STDERR "'$command' died from signal $sig\n";
- $result = 1;
- } else {
- my $r = $? >> 8;
- print STDERR "'$command' exited with $r\n" if $r;
- $result = $r;
- }
- };
- if ($@) {
- $result = 2;
- print STDERR "$@\n";
- }
-
- chdir '/';
- system('rm','-rf',$tmp);
- rmdir $tmp;
- $result;
-}
-
-sub build_failed ($$$$$)
-{
- my ($git_dir, $ref, $old, $new, $msg) = @_;
-
- $git_dir =~ m,/([^/]+)$,;
- my $repo_name = $1;
- $ref =~ s,^refs/(heads|tags)/,,;
-
- my %authors;
- my $shortlog;
- my $revstr;
- {
- local $ENV{GIT_DIR} = $git_dir;
- my @revs = ($new);
- push @revs, '--not', @$old if @$old;
- open LOG,'-|','git','rev-list','--pretty=raw',@revs;
- while (<LOG>) {
- if (s/^(author|committer) //) {
- chomp;
- s/>.*$/>/;
- $authors{$_} = 1 unless nocc_author $_;
- }
- }
- close LOG;
- open LOG,'-|','git','shortlog',@revs;
- $shortlog .= $_ while <LOG>;
- close LOG;
- $revstr = join(' ', @revs);
- }
-
- my @to = sort keys %authors;
- unless (@to) {
- print STDERR "error: No authors in $revstr\n";
- return;
- }
-
- my $subject = "[$repo_name] $ref : Build Failed";
- my $body = <<EOF;
-Project: $git_dir
-Branch: $ref
-Commits: $revstr
-
-$shortlog
-Build Output:
---------------------------------------------------------------
-$msg
-EOF
- send_email($subject, $body, \@to);
-}
-
-sub run_build ($$$$)
-{
- my ($git_dir, $ref, $old, $new) = @_;
-
- if ($debug_flag) {
- my @revs = ($new);
- push @revs, '--not', @$old if @$old;
- print "BUILDING $git_dir\n";
- print " BRANCH: $ref\n";
- print " COMMITS: ", join(' ', @revs), "\n";
- }
-
- local(*R, *W);
- pipe R, W or die "cannot pipe builder: $!";
-
- my $builder = fork();
- if (!defined $builder) {
- die "cannot fork builder: $!";
- } elsif (0 == $builder) {
- close R;
- close STDIN;open(STDIN, '/dev/null');
- open(STDOUT, '>&W');
- open(STDERR, '>&W');
- exit do_build $git_dir, $new;
- } else {
- close W;
- my $out = '';
- $out .= $_ while <R>;
- close R;
- waitpid $builder, 0;
- build_failed $git_dir, $ref, $old, $new, $out if $?;
- }
-
- print "DONE\n\n" if $debug_flag;
-}
-
-sub daemon_loop ()
-{
- my $run = 1;
- my $stop_sub = sub {$run = 0};
- $SIG{HUP} = $stop_sub;
- $SIG{INT} = $stop_sub;
- $SIG{TERM} = $stop_sub;
-
- mkdir $tmpdir, 0755;
- my $pidfile = File::Spec->catfile($tmpdir, "cidaemon.pid");
- open(O, ">$pidfile"); print O "$$\n"; close O;
-
- while ($run) {
- my $ent = pop_queue;
- if ($ent) {
- my ($git_dir, $ref, $old, $new) = @$ent;
-
- $ent = $recent{$git_dir};
- $recent{$git_dir} = $ent = [[], {}] unless $ent;
- my ($rec_arr, $rec_hash) = @$ent;
- next if $rec_hash->{$new}++;
- while (@$rec_arr >= $recent_size) {
- my $to_kill = shift @$rec_arr;
- delete $rec_hash->{$to_kill};
- }
- push @$rec_arr, $new;
-
- run_build $git_dir, $ref, $old, $new;
- } else {
- sleep $scan_delay;
- }
- }
-
- unlink $pidfile;
-}
-
-$debug_flag = 0;
-GetOptions(
- 'debug|d' => \$debug_flag,
- 'smtp-user=s' => \$ex_user,
-) or die "usage: $0 [--debug] [--smtp-user=user]\n";
-
-$ex_pass = input_noecho("$ex_user SMTP password: ")
- if ($ex_user && !$ex_pass);
-
-if ($debug_flag) {
- daemon_loop;
- exit 0;
-}
-
-my $daemon = fork();
-if (!defined $daemon) {
- die "cannot fork daemon: $!";
-} elsif (0 == $daemon) {
- close STDIN;open(STDIN, '/dev/null');
- close STDOUT;open(STDOUT, '>/dev/null');
- close STDERR;open(STDERR, '>/dev/null');
- daemon_loop;
- exit 0;
-} else {
- print "Daemon $daemon running in the background.\n";
-}
+++ /dev/null
-#!/usr/bin/perl
-#
-# A hook that notifies its companion cidaemon through a simple
-# queue file that a ref has been updated via a push (actually
-# by a receive-pack running on the server).
-#
-# See cidaemon for per-repository configuration details.
-#
-# To use this hook, add it as the post-receive hook, make it
-# executable, and set its configuration options.
-#
-
-local $ENV{PATH} = '/opt/git/bin';
-
-use strict;
-use warnings;
-use File::Spec;
-use Storable qw(retrieve nstore);
-use Fcntl ':flock';
-
-my $git_dir = File::Spec->rel2abs($ENV{GIT_DIR});
-my $queue_name = `git config --get builder.queue`;chop $queue_name;
-$queue_name =~ m,^([^\s]+)$,; $queue_name = $1; # untaint
-unless ($queue_name) {
- 1 while <STDIN>;
- print STDERR "\nerror: builder.queue not set. Not enqueing.\n\n";
- exit;
-}
-my $queue_lock = "$queue_name.lock";
-
-my @skip;
-open S, "git config --get-all builder.skip|";
-while (<S>) {
- chop;
- push @skip, $_;
-}
-close S;
-
-my @new_branch_base;
-open S, "git config --get-all builder.newBranchBase|";
-while (<S>) {
- chop;
- push @new_branch_base, $_;
-}
-close S;
-
-sub skip ($)
-{
- local $_ = shift;
- foreach my $p (@skip) {
- return 1 if /^$p/;
- }
- 0;
-}
-
-open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!";
-flock LOCK, LOCK_EX;
-
-my $queue = -f $queue_name ? retrieve $queue_name : [];
-my %existing;
-foreach my $r (@$queue) {
- my ($gd, $ref) = @$r;
- $existing{$gd}{$ref} = $r;
-}
-
-my @new_branch_commits;
-my $loaded_new_branch_commits = 0;
-
-while (<STDIN>) {
- chop;
- my ($old, $new, $ref) = split / /, $_, 3;
-
- next if $old eq $new;
- next if $new =~ /^0{40}$/;
- next if skip $ref;
-
- my $r = $existing{$git_dir}{$ref};
- if ($r) {
- $r->[3] = $new;
- } else {
- if ($old =~ /^0{40}$/) {
- if (!$loaded_new_branch_commits && @new_branch_base) {
- open M,'-|','git','show-ref',@new_branch_base;
- while (<M>) {
- ($_) = split / /, $_;
- push @new_branch_commits, $_;
- }
- close M;
- $loaded_new_branch_commits = 1;
- }
- $old = [@new_branch_commits];
- } else {
- $old = [$old];
- }
-
- $r = [$git_dir, $ref, $old, $new];
- $existing{$git_dir}{$ref} = $r;
- push @$queue, $r;
- }
-}
-nstore $queue, $queue_name;
-
-flock LOCK, LOCK_UN;
-close LOCK;
*v++ = '\0';
if (!strcmp(buf, "protocol")) {
- if (!strcmp(v, "https"))
+ if (!strcmp(v, "imap"))
+ protocol = kSecProtocolTypeIMAP;
+ else if (!strcmp(v, "imaps"))
+ protocol = kSecProtocolTypeIMAPS;
+ else if (!strcmp(v, "ftp"))
+ protocol = kSecProtocolTypeFTP;
+ else if (!strcmp(v, "ftps"))
+ protocol = kSecProtocolTypeFTPS;
+ else if (!strcmp(v, "https"))
protocol = kSecProtocolTypeHTTPS;
else if (!strcmp(v, "http"))
protocol = kSecProtocolTypeHTTP;
+ else if (!strcmp(v, "smtp"))
+ protocol = kSecProtocolTypeSMTP;
else /* we don't yet handle other protocols */
exit(0);
}
--- /dev/null
+ git-multimail
+ =============
+
+git-multimail is a tool for sending notification emails on pushes to a
+Git repository. It includes a Python module called git_multimail.py,
+which can either be used as a hook script directly or can be imported
+as a Python module into another script.
+
+git-multimail is derived from the Git project's old
+contrib/hooks/post-receive-email, and is mostly compatible with that
+script. See README.migrate-from-post-receive-email for details about
+the differences and for how to migrate from post-receive-email to
+git-multimail.
+
+git-multimail, like the rest of the Git project, is licensed under
+GPLv2 (see the COPYING file for details).
+
+Please note: although, as a convenience, git-multimail may be
+distributed along with the main Git project, development of
+git-multimail takes place in its own, separate project. See section
+"Getting involved" below for more information.
+
+
+By default, for each push received by the repository, git-multimail:
+
+1. Outputs one email summarizing each reference that was changed.
+ These "reference change" (called "refchange" below) emails describe
+ the nature of the change (e.g., was the reference created, deleted,
+ fast-forwarded, etc.) and include a one-line summary of each commit
+ that was added to the reference.
+
+2. Outputs one email for each new commit that was introduced by the
+ reference change. These "commit" emails include a list of the
+ files changed by the commit, followed by the diffs of files
+ modified by the commit. The commit emails are threaded to the
+ corresponding reference change email via "In-Reply-To". This style
+ (similar to the "git format-patch" style used on the Git mailing
+ list) makes it easy to scan through the emails, jump to patches
+ that need further attention, and write comments about specific
+ commits. Commits are handled in reverse topological order (i.e.,
+ parents shown before children). For example,
+
+ [git] branch master updated
+ + [git] 01/08: doc: fix xref link from api docs to manual pages
+ + [git] 02/08: api-credentials.txt: show the big picture first
+ + [git] 03/08: api-credentials.txt: mention credential.helper explicitly
+ + [git] 04/08: api-credentials.txt: add "see also" section
+ + [git] 05/08: t3510 (cherry-pick-sequence): add missing '&&'
+ + [git] 06/08: Merge branch 'rr/maint-t3510-cascade-fix'
+ + [git] 07/08: Merge branch 'mm/api-credentials-doc'
+ + [git] 08/08: Git 1.7.11-rc2
+
+ Each commit appears in exactly one commit email, the first time
+ that it is pushed to the repository. If a commit is later merged
+ into another branch, then a one-line summary of the commit is
+ included in the reference change email (as usual), but no
+ additional commit email is generated.
+
+ By default, reference change emails have their "Reply-To" field set
+ to the person who pushed the change, and commit emails have their
+ "Reply-To" field set to the author of the commit.
+
+3. Output one "announce" mail for each new annotated tag, including
+ information about the tag and optionally a shortlog describing the
+ changes since the previous tag. Such emails might be useful if you
+ use annotated tags to mark releases of your project.
+
+
+Requirements
+------------
+
+* Python 2.x, version 2.4 or later. No non-standard Python modules
+ are required. git-multimail does *not* currently work with Python
+ 3.x.
+
+ The example scripts invoke Python using the following shebang line
+ (following PEP 394 [1]):
+
+ #! /usr/bin/env python2
+
+ If your system's Python2 interpreter is not in your PATH or is not
+ called "python2", you can change the lines accordingly. Or you can
+ invoke the Python interpreter explicitly, for example via a tiny
+ shell script like
+
+ #! /bin/sh
+ /usr/local/bin/python /path/to/git_multimail.py "$@"
+
+* The "git" command must be in your PATH. git-multimail is known to
+ work with Git versions back to 1.7.1. (Earlier versions have not
+ been tested; if you do so, please report your results.)
+
+* To send emails using the default configuration, a standard sendmail
+ program must be located at '/usr/sbin/sendmail' and configured
+ correctly to send emails. If this is not the case, see the
+ multimailhook.mailer configuration variable below for how to
+ configure git-multimail to send emails via an SMTP server.
+
+
+Invocation
+----------
+
+git_multimail.py is designed to be used as a "post-receive" hook in a
+Git repository (see githooks(5)). Link or copy it to
+$GIT_DIR/hooks/post-receive within the repository for which email
+notifications are desired. Usually it should be installed on the
+central repository for a project, to which all commits are eventually
+pushed.
+
+For use on pre-v1.5.1 Git servers, git_multimail.py can also work as
+an "update" hook, taking its arguments on the command line. To use
+this script in this manner, link or copy it to $GIT_DIR/hooks/update.
+Please note that the script is not completely reliable in this mode
+[2].
+
+Alternatively, git_multimail.py can be imported as a Python module
+into your own Python post-receive script. This method is a bit more
+work, but allows the behavior of the hook to be customized using
+arbitrary Python code. For example, you can use a custom environment
+(perhaps inheriting from GenericEnvironment or GitoliteEnvironment) to
+
+* change how the user who did the push is determined
+
+* read users' email addresses from an LDAP server or from a database
+
+* decide which users should be notified about which commits based on
+ the contents of the commits (e.g., for users who want to be notified
+ only about changes affecting particular files or subdirectories)
+
+Or you can change how emails are sent by writing your own Mailer
+class. The "post-receive" script in this directory demonstrates how
+to use git_multimail.py as a Python module. (If you make interesting
+changes of this type, please consider sharing them with the
+community.)
+
+
+Configuration
+-------------
+
+By default, git-multimail mostly takes its configuration from the
+following "git config" settings:
+
+multimailhook.environment
+
+ This describes the general environment of the repository.
+ Currently supported values:
+
+ "generic" -- the username of the pusher is read from $USER and the
+ repository name is derived from the repository's path.
+
+ "gitolite" -- the username of the pusher is read from $GL_USER and
+ the repository name from $GL_REPO.
+
+ If neither of these environments is suitable for your setup, then
+ you can implement a Python class that inherits from Environment
+ and instantiate it via a script that looks like the example
+ post-receive script.
+
+ The environment value can be specified on the command line using
+ the --environment option. If it is not specified on the command
+ line or by multimailhook.environment, then it defaults to
+ "gitolite" if the environment contains variables $GL_USER and
+ $GL_REPO; otherwise "generic".
+
+multimailhook.repoName
+
+ A short name of this Git repository, to be used in various places
+ in the notification email text. The default is to use $GL_REPO
+ for gitolite repositories, or otherwise to derive this value from
+ the repository path name.
+
+multimailhook.mailinglist
+
+ The list of email addresses to which notification emails should be
+ sent, as RFC 2822 email addresses separated by commas. This
+ configuration option can be multivalued. Leave it unset or set it
+ to the empty string to not send emails by default. The next few
+ settings can be used to configure specific address lists for
+ specific types of notification email.
+
+multimailhook.refchangeList
+
+ The list of email addresses to which summary emails about
+ reference changes should be sent, as RFC 2822 email addresses
+ separated by commas. This configuration option can be
+ multivalued. The default is the value in
+ multimailhook.mailinglist. Set this value to the empty string to
+ prevent reference change emails from being sent.
+
+multimailhook.announceList
+
+ The list of email addresses to which emails about new annotated
+ tags should be sent, as RFC 2822 email addresses separated by
+ commas. This configuration option can be multivalued. The
+ default is the value in multimailhook.refchangelist or
+ multimailhook.mailinglist. Set this value to the empty string to
+ prevent annotated tag announcement emails from being sent.
+
+multimailhook.commitList
+
+ The list of email addresses to which emails about individual new
+ commits should be sent, as RFC 2822 email addresses separated by
+ commas. This configuration option can be multivalued. The
+ default is the value in multimailhook.mailinglist. Set this value
+ to the empty string to prevent notification emails about
+ individual commits from being sent.
+
+multimailhook.announceShortlog
+
+ If this option is set to true, then emails about changes to
+ annotated tags include a shortlog of changes since the previous
+ tag. This can be useful if the annotated tags represent releases;
+ then the shortlog will be a kind of rough summary of what has
+ happened since the last release. But if your tagging policy is
+ not so straightforward, then the shortlog might be confusing
+ rather than useful. Default is false.
+
+multimailhook.refchangeShowLog
+
+ If this option is set to true, then summary emails about reference
+ changes will include a detailed log of the added commits in
+ addition to the one line summary. The log is generated by running
+ "git log" with the options specified in multimailhook.logOpts.
+ Default is false.
+
+multimailhook.mailer
+
+ This option changes the way emails are sent. Accepted values are:
+
+ - sendmail (the default): use the command /usr/sbin/sendmail or
+ /usr/lib/sendmail (or sendmailCommand, if configured). This
+ mode can be further customized via the following options:
+
+ multimailhook.sendmailCommand
+
+ The command used by mailer "sendmail" to send emails. Shell
+ quoting is allowed in the value of this setting, but remember that
+ Git requires double-quotes to be escaped; e.g.,
+
+ git config multimailhook.sendmailcommand '/usr/sbin/sendmail -t -F \"Git Repo\"'
+
+ Default is '/usr/sbin/sendmail -t' or '/usr/lib/sendmail
+ -t' (depending on which file is present and executable).
+
+ multimailhook.envelopeSender
+
+ If set then pass this value to sendmail via the -f option to set
+ the envelope sender address.
+
+ - smtp: use Python's smtplib. This is useful when the sendmail
+ command is not available on the system. This mode can be
+ further customized via the following options:
+
+ multimailhook.smtpServer
+
+ The name of the SMTP server to connect to. The value can
+ also include a colon and a port number; e.g.,
+ "mail.example.com:25". Default is 'localhost' using port
+ 25.
+
+ multimailhook.envelopeSender
+
+ The sender address to be passed to the SMTP server. If
+ unset, then the value of multimailhook.from is used.
+
+multimailhook.from
+
+ If set then use this value in the From: field of generated emails.
+ If unset, then use the repository's user configuration (user.name
+ and user.email). If user.email is also unset, then use
+ multimailhook.envelopeSender.
+
+multimailhook.administrator
+
+ The name and/or email address of the administrator of the Git
+ repository; used in FOOTER_TEMPLATE. Default is
+ multimailhook.envelopesender if it is set; otherwise a generic
+ string is used.
+
+multimailhook.emailPrefix
+
+ All emails have this string prepended to their subjects, to aid
+ email filtering (though filtering based on the X-Git-* email
+ headers is probably more robust). Default is the short name of
+ the repository in square brackets; e.g., "[myrepo]".
+
+multimailhook.emailMaxLines
+
+ The maximum number of lines that should be included in the body of
+ a generated email. If not specified, there is no limit. Lines
+ beyond the limit are suppressed and counted, and a final line is
+ added indicating the number of suppressed lines.
+
+multimailhook.emailMaxLineLength
+
+ The maximum length of a line in the email body. Lines longer than
+ this limit are truncated to this length with a trailing " [...]"
+ added to indicate the missing text. The default is 500, because
+ (a) diffs with longer lines are probably from binary files, for
+ which a diff is useless, and (b) even if a text file has such long
+ lines, the diffs are probably unreadable anyway. To disable line
+ truncation, set this option to 0.
+
+multimailhook.maxCommitEmails
+
+ The maximum number of commit emails to send for a given change.
+ When the number of patches is larger that this value, only the
+ summary refchange email is sent. This can avoid accidental
+ mailbombing, for example on an initial push. To disable commit
+ emails limit, set this option to 0. The default is 500.
+
+multimailhook.emailStrictUTF8
+
+ If this boolean option is set to "true", then the main part of the
+ email body is forced to be valid UTF-8. Any characters that are
+ not valid UTF-8 are converted to the Unicode replacement
+ character, U+FFFD. The default is "true".
+
+multimailhook.diffOpts
+
+ Options passed to "git diff-tree" when generating the summary
+ information for ReferenceChange emails. Default is "--stat
+ --summary --find-copies-harder". Add -p to those options to
+ include a unified diff of changes in addition to the usual summary
+ output. Shell quoting is allowed; see multimailhook.logOpts for
+ details.
+
+multimailhook.logOpts
+
+ Options passed to "git log" to generate additional info for
+ reference change emails (used only if refchangeShowLog is set).
+ For example, adding --graph will show the graph of revisions, -p
+ will show the complete diff, etc. The default is empty.
+
+ Shell quoting is allowed; for example, a log format that contains
+ spaces can be specified using something like:
+
+ git config multimailhook.logopts '--pretty=format:"%h %aN <%aE>%n%s%n%n%b%n"'
+
+ If you want to set this by editing your configuration file
+ directly, remember that Git requires double-quotes to be escaped
+ (see git-config(1) for more information):
+
+ [multimailhook]
+ logopts = --pretty=format:\"%h %aN <%aE>%n%s%n%n%b%n\"
+
+multimailhook.emailDomain
+
+ Domain name appended to the username of the person doing the push
+ to convert it into an email address (via "%s@%s" % (username,
+ emaildomain)). More complicated schemes can be implemented by
+ overriding Environment and overriding its get_pusher_email()
+ method.
+
+multimailhook.replyTo
+multimailhook.replyToCommit
+multimailhook.replyToRefchange
+
+ Addresses to use in the Reply-To: field for commit emails
+ (replyToCommit) and refchange emails (replyToRefchange).
+ multimailhook.replyTo is used as default when replyToCommit or
+ replyToRefchange is not set. The value for these variables can be
+ either:
+
+ - An email address, which will be used directly.
+
+ - The value "pusher", in which case the pusher's address (if
+ available) will be used. This is the default for refchange
+ emails.
+
+ - The value "author" (meaningful only for replyToCommit), in which
+ case the commit author's address will be used. This is the
+ default for commit emails.
+
+ - The value "none", in which case the Reply-To: field will be
+ omitted.
+
+
+Email filtering aids
+--------------------
+
+All emails include extra headers to enable fine tuned filtering and
+give information for debugging. All emails include the headers
+"X-Git-Repo", "X-Git-Refname", and "X-Git-Reftype". ReferenceChange
+emails also include headers "X-Git-Oldrev" and "X-Git-Newrev";
+Revision emails also include header "X-Git-Rev".
+
+
+Customizing email contents
+--------------------------
+
+git-multimail mostly generates emails by expanding templates. The
+templates can be customized. To avoid the need to edit
+git_multimail.py directly, the preferred way to change the templates
+is to write a separate Python script that imports git_multimail.py as
+a module, then replaces the templates in place. See the provided
+post-receive script for an example of how this is done.
+
+
+Customizing git-multimail for your environment
+----------------------------------------------
+
+git-multimail is mostly customized via an "environment" that describes
+the local environment in which Git is running. Two types of
+environment are built in:
+
+* GenericEnvironment: a stand-alone Git repository.
+
+* GitoliteEnvironment: a Git repository that is managed by gitolite
+ [3]. For such repositories, the identity of the pusher is read from
+ environment variable $GL_USER, and the name of the repository is
+ read from $GL_REPO (if it is not overridden by
+ multimailhook.reponame).
+
+By default, git-multimail assumes GitoliteEnvironment if $GL_USER and
+$GL_REPO are set, and otherwise assumes GenericEnvironment.
+Alternatively, you can choose one of these two environments explicitly
+by setting a "multimailhook.environment" config setting (which can
+have the value "generic" or "gitolite") or by passing an --environment
+option to the script.
+
+If you need to customize the script in ways that are not supported by
+the existing environments, you can define your own environment class
+class using arbitrary Python code. To do so, you need to import
+git_multimail.py as a Python module, as demonstrated by the example
+post-receive script. Then implement your environment class; it should
+usually inherit from one of the existing Environment classes and
+possibly one or more of the EnvironmentMixin classes. Then set the
+"environment" variable to an instance of your own environment class
+and pass it to run_as_post_receive_hook().
+
+The standard environment classes, GenericEnvironment and
+GitoliteEnvironment, are in fact themselves put together out of a
+number of mixin classes, each of which handles one aspect of the
+customization. For the finest control over your configuration, you
+can specify exactly which mixin classes your own environment class
+should inherit from, and override individual methods (or even add your
+own mixin classes) to implement entirely new behaviors. If you
+implement any mixins that might be useful to other people, please
+consider sharing them with the community!
+
+
+Getting involved
+----------------
+
+git-multimail is an open-source project, built by volunteers. We
+would welcome your help!
+
+The current maintainer is Michael Haggerty <mhagger@alum.mit.edu>.
+
+General discussion of git-multimail takes place on the main Git
+mailing list,
+
+ git@vger.kernel.org
+
+Please CC emails regarding git-multimail to me so that I don't
+overlook them.
+
+The git-multimail project itself is currently hosted on GitHub:
+
+ https://github.com/mhagger/git-multimail
+
+We use the GitHub issue tracker to keep track of bugs and feature
+requests, and GitHub pull requests to exchange patches (though, if you
+prefer, you can send patches via the Git mailing list with cc to me).
+
+Please note that although a copy of git-multimail will probably be
+distributed in the "contrib" section of the main Git project,
+development takes place in the separate git-multimail repository on
+GitHub! (Whenever enough changes to git-multimail have accumulated, a
+new code-drop of git-multimail will be submitted for inclusion in the
+Git project.)
+
+
+Footnotes
+---------
+
+[1] http://www.python.org/dev/peps/pep-0394/
+
+[2] Because of the way information is passed to update hooks, the
+ script's method of determining whether a commit has already been
+ seen does not work when it is used as an "update" script. In
+ particular, no notification email will be generated for a new
+ commit that is added to multiple references in the same push.
+
+[3] https://github.com/sitaramc/gitolite
--- /dev/null
+This copy of git-multimail is distributed as part of the "contrib"
+section of the Git project as a convenience to Git users.
+git-multimail is developed as an independent project at the following
+website:
+
+ https://github.com/mhagger/git-multimail
+
+The version in this directory was obtained from the upstream project
+on 2013-07-14 and consists of the "git-multimail" subdirectory from
+revision
+
+ 1a5cb09c698a74d15a715a86b09ead5f56bf4b06
+
+Please see the README file in this directory for information about how
+to report bugs or contribute to git-multimail.
--- /dev/null
+git-multimail is close to, but not exactly, a plug-in replacement for
+the old Git project script contrib/hooks/post-receive-email. This
+document describes the differences and explains how to configure
+git-multimail to get behavior closest to that of post-receive-email.
+
+If you are in a hurry
+=====================
+
+A script called migrate-mailhook-config is included with
+git-multimail. If you run this script within a Git repository that is
+configured to use post-receive-email, it will convert the
+configuration settings into the approximate equivalent settings for
+git-multimail. For more information, run
+
+ migrate-mailhook-config --help
+
+
+Configuration differences
+=========================
+
+* The names of the config options for git-multimail are in namespace
+ "multimailhook.*" instead of "hooks.*". (Editorial comment:
+ post-receive-email should never have used such a generic top-level
+ namespace.)
+
+* In emails about new annotated tags, post-receive-email includes a
+ shortlog of all changes since the previous annotated tag. To get
+ this behavior with git-multimail, you need to set
+ multimailhook.announceshortlog to true:
+
+ git config multimailhook.announceshortlog true
+
+* multimailhook.commitlist -- This is a new configuration variable.
+ Recipients listed here will receive a separate email for each new
+ commit. However, if this variable is *not* set, it defaults to the
+ value of multimailhook.mailinglist. Therefore, if you *don't* want
+ the members of multimailhook.mailinglist to receive one email per
+ commit, then set this value to the empty string:
+
+ git config multimailhook.commitlist ''
+
+* multimailhook.emailprefix -- If this value is not set, then the
+ subjects of generated emails are prefixed with the short name of the
+ repository enclosed in square brackets; e.g., "[myrepo]".
+ post-receive-email defaults to prefix "[SCM]" if this option is not
+ set. So if you were using the old default and want to retain it
+ (for example, to avoid having to change your email filters), set
+ this variable explicitly to the old value:
+
+ git config multimailhook.emailprefix "[SCM]"
+
+* The "multimailhook.showrev" configuration option is not supported.
+ Its main use is obsoleted by the one-email-per-commit feature of
+ git-multimail.
+
+
+Other differences
+=================
+
+This section describes other differences in the behavior of
+git-multimail vs. post-receive-email. For full details, please refer
+to the main README file:
+
+* One email per commit. For each reference change, the script first
+ outputs one email summarizing the reference change (including
+ one-line summaries of the new commits), then it outputs a separate
+ email for each new commit that was introduced, including patches.
+ These one-email-per-commit emails go to the addresses listed in
+ multimailhook.commitlist. post-receive-email sends only one email
+ for each *reference* that is changed, no matter how many commits
+ were added to the reference.
+
+* Better algorithm for detecting new commits. post-receive-email
+ processes one reference change at a time, which causes it to fail to
+ describe new commits that were included in multiple branches. For
+ example, if a single push adds the "*" commits in the diagram below,
+ then post-receive-email would never include the details of the two
+ commits that are common to "master" and "branch" in its
+ notifications.
+
+ o---o---o---*---*---* <-- master
+ \
+ *---* <-- branch
+
+ git-multimail analyzes all reference modifications to determine
+ which commits were not present before the change, therefore avoiding
+ that error.
+
+* In reference change emails, git-multimail tells which commits have
+ been added to the reference vs. are entirely new to the repository,
+ and which commits that have been omitted from the reference
+ vs. entirely discarded from the repository.
+
+* The environment in which Git is running can be configured via an
+ "Environment" abstraction.
+
+* Built-in support for Gitolite-managed repositories.
+
+* Instead of using full SHA1 object names in emails, git-multimail
+ mostly uses abbreviated SHA1s, plus one-line log message summaries
+ where appropriate.
+
+* In the schematic diagrams that explain non-fast-forward commits,
+ git-multimail shows the names of the branches involved.
+
+* The emails generated by git-multimail include the name of the Git
+ repository that was modified; this is convenient for recipients who
+ are monitoring multiple repositories.
+
+* git-multimail allows the email "From" addresses to be configured.
+
+* The recipients lists (multimailhook.mailinglist,
+ multimailhook.refchangelist, multimailhook.announcelist, and
+ multimailhook.commitlist) can be comma-separated values and/or
+ multivalued settings in the config file; e.g.,
+
+ [multimailhook]
+ mailinglist = mr.brown@example.com, mr.black@example.com
+ announcelist = Him <him@example.com>
+ announcelist = Jim <jim@example.com>
+ announcelist = pop@example.com
+
+ This might make it easier to maintain short recipients lists without
+ requiring full-fledged mailing list software.
+
+* By default, git-multimail sets email "Reply-To" headers to reply to
+ the pusher (for reference updates) and to the author (for commit
+ notifications). By default, the pusher's email address is
+ constructed by appending "multimailhook.emaildomain" to the pusher's
+ username.
+
+* The generated emails contain a configurable footer. By default, it
+ lists the name of the administrator who should be contacted to
+ unsubscribe from notification emails.
+
+* New option multimailhook.emailmaxlinelength to limit the length of
+ lines in the main part of the email body. The default limit is 500
+ characters.
+
+* New option multimailhook.emailstrictutf8 to ensure that the main
+ part of the email body is valid UTF-8. Invalid characters are
+ turned into the Unicode replacement character, U+FFFD. By default
+ this option is turned on.
+
+* Written in Python. Easier to add new features.
--- /dev/null
+#! /usr/bin/env python2
+
+# Copyright (c) 2012,2013 Michael Haggerty
+# Derived from contrib/hooks/post-receive-email, which is
+# Copyright (c) 2007 Andy Parkins
+# and also includes contributions by other authors.
+#
+# This file is part of git-multimail.
+#
+# git-multimail is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version
+# 2 as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Generate notification emails for pushes to a git repository.
+
+This hook sends emails describing changes introduced by pushes to a
+git repository. For each reference that was changed, it emits one
+ReferenceChange email summarizing how the reference was changed,
+followed by one Revision email for each new commit that was introduced
+by the reference change.
+
+Each commit is announced in exactly one Revision email. If the same
+commit is merged into another branch in the same or a later push, then
+the ReferenceChange email will list the commit's SHA1 and its one-line
+summary, but no new Revision email will be generated.
+
+This script is designed to be used as a "post-receive" hook in a git
+repository (see githooks(5)). It can also be used as an "update"
+script, but this usage is not completely reliable and is deprecated.
+
+To help with debugging, this script accepts a --stdout option, which
+causes the emails to be written to standard output rather than sent
+using sendmail.
+
+See the accompanying README file for the complete documentation.
+
+"""
+
+import sys
+import os
+import re
+import bisect
+import subprocess
+import shlex
+import optparse
+import smtplib
+
+try:
+ from email.utils import make_msgid
+ from email.utils import getaddresses
+ from email.utils import formataddr
+ from email.header import Header
+except ImportError:
+ # Prior to Python 2.5, the email module used different names:
+ from email.Utils import make_msgid
+ from email.Utils import getaddresses
+ from email.Utils import formataddr
+ from email.Header import Header
+
+
+DEBUG = False
+
+ZEROS = '0' * 40
+LOGBEGIN = '- Log -----------------------------------------------------------------\n'
+LOGEND = '-----------------------------------------------------------------------\n'
+
+
+# It is assumed in many places that the encoding is uniformly UTF-8,
+# so changing these constants is unsupported. But define them here
+# anyway, to make it easier to find (at least most of) the places
+# where the encoding is important.
+(ENCODING, CHARSET) = ('UTF-8', 'utf-8')
+
+
+REF_CREATED_SUBJECT_TEMPLATE = (
+ '%(emailprefix)s%(refname_type)s %(short_refname)s created'
+ ' (now %(newrev_short)s)'
+ )
+REF_UPDATED_SUBJECT_TEMPLATE = (
+ '%(emailprefix)s%(refname_type)s %(short_refname)s updated'
+ ' (%(oldrev_short)s -> %(newrev_short)s)'
+ )
+REF_DELETED_SUBJECT_TEMPLATE = (
+ '%(emailprefix)s%(refname_type)s %(short_refname)s deleted'
+ ' (was %(oldrev_short)s)'
+ )
+
+REFCHANGE_HEADER_TEMPLATE = """\
+To: %(recipients)s
+Subject: %(subject)s
+MIME-Version: 1.0
+Content-Type: text/plain; charset=%(charset)s
+Content-Transfer-Encoding: 8bit
+Message-ID: %(msgid)s
+From: %(fromaddr)s
+Reply-To: %(reply_to)s
+X-Git-Repo: %(repo_shortname)s
+X-Git-Refname: %(refname)s
+X-Git-Reftype: %(refname_type)s
+X-Git-Oldrev: %(oldrev)s
+X-Git-Newrev: %(newrev)s
+Auto-Submitted: auto-generated
+"""
+
+REFCHANGE_INTRO_TEMPLATE = """\
+This is an automated email from the git hooks/post-receive script.
+
+%(pusher)s pushed a change to %(refname_type)s %(short_refname)s
+in repository %(repo_shortname)s.
+
+"""
+
+
+FOOTER_TEMPLATE = """\
+
+-- \n\
+To stop receiving notification emails like this one, please contact
+%(administrator)s.
+"""
+
+
+REWIND_ONLY_TEMPLATE = """\
+This update removed existing revisions from the reference, leaving the
+reference pointing at a previous point in the repository history.
+
+ * -- * -- N %(refname)s (%(newrev_short)s)
+ \\
+ O -- O -- O (%(oldrev_short)s)
+
+Any revisions marked "omits" are not gone; other references still
+refer to them. Any revisions marked "discards" are gone forever.
+"""
+
+
+NON_FF_TEMPLATE = """\
+This update added new revisions after undoing existing revisions.
+That is to say, some revisions that were in the old version of the
+%(refname_type)s are not in the new version. This situation occurs
+when a user --force pushes a change and generates a repository
+containing something like this:
+
+ * -- * -- B -- O -- O -- O (%(oldrev_short)s)
+ \\
+ N -- N -- N %(refname)s (%(newrev_short)s)
+
+You should already have received notification emails for all of the O
+revisions, and so the following emails describe only the N revisions
+from the common base, B.
+
+Any revisions marked "omits" are not gone; other references still
+refer to them. Any revisions marked "discards" are gone forever.
+"""
+
+
+NO_NEW_REVISIONS_TEMPLATE = """\
+No new revisions were added by this update.
+"""
+
+
+DISCARDED_REVISIONS_TEMPLATE = """\
+This change permanently discards the following revisions:
+"""
+
+
+NO_DISCARDED_REVISIONS_TEMPLATE = """\
+The revisions that were on this %(refname_type)s are still contained in
+other references; therefore, this change does not discard any commits
+from the repository.
+"""
+
+
+NEW_REVISIONS_TEMPLATE = """\
+The %(tot)s revisions listed above as "new" are entirely new to this
+repository and will be described in separate emails. The revisions
+listed as "adds" were already present in the repository and have only
+been added to this reference.
+
+"""
+
+
+TAG_CREATED_TEMPLATE = """\
+ at %(newrev_short)-9s (%(newrev_type)s)
+"""
+
+
+TAG_UPDATED_TEMPLATE = """\
+*** WARNING: tag %(short_refname)s was modified! ***
+
+ from %(oldrev_short)-9s (%(oldrev_type)s)
+ to %(newrev_short)-9s (%(newrev_type)s)
+"""
+
+
+TAG_DELETED_TEMPLATE = """\
+*** WARNING: tag %(short_refname)s was deleted! ***
+
+"""
+
+
+# The template used in summary tables. It looks best if this uses the
+# same alignment as TAG_CREATED_TEMPLATE and TAG_UPDATED_TEMPLATE.
+BRIEF_SUMMARY_TEMPLATE = """\
+%(action)10s %(rev_short)-9s %(text)s
+"""
+
+
+NON_COMMIT_UPDATE_TEMPLATE = """\
+This is an unusual reference change because the reference did not
+refer to a commit either before or after the change. We do not know
+how to provide full information about this reference change.
+"""
+
+
+REVISION_HEADER_TEMPLATE = """\
+To: %(recipients)s
+Subject: %(emailprefix)s%(num)02d/%(tot)02d: %(oneline)s
+MIME-Version: 1.0
+Content-Type: text/plain; charset=%(charset)s
+Content-Transfer-Encoding: 8bit
+From: %(fromaddr)s
+Reply-To: %(reply_to)s
+In-Reply-To: %(reply_to_msgid)s
+References: %(reply_to_msgid)s
+X-Git-Repo: %(repo_shortname)s
+X-Git-Refname: %(refname)s
+X-Git-Reftype: %(refname_type)s
+X-Git-Rev: %(rev)s
+Auto-Submitted: auto-generated
+"""
+
+REVISION_INTRO_TEMPLATE = """\
+This is an automated email from the git hooks/post-receive script.
+
+%(pusher)s pushed a commit to %(refname_type)s %(short_refname)s
+in repository %(repo_shortname)s.
+
+"""
+
+
+REVISION_FOOTER_TEMPLATE = FOOTER_TEMPLATE
+
+
+class CommandError(Exception):
+ def __init__(self, cmd, retcode):
+ self.cmd = cmd
+ self.retcode = retcode
+ Exception.__init__(
+ self,
+ 'Command "%s" failed with retcode %s' % (' '.join(cmd), retcode,)
+ )
+
+
+class ConfigurationException(Exception):
+ pass
+
+
+def read_git_output(args, input=None, keepends=False, **kw):
+ """Read the output of a Git command."""
+
+ return read_output(
+ ['git', '-c', 'i18n.logoutputencoding=%s' % (ENCODING,)] + args,
+ input=input, keepends=keepends, **kw
+ )
+
+
+def read_output(cmd, input=None, keepends=False, **kw):
+ if input:
+ stdin = subprocess.PIPE
+ else:
+ stdin = None
+ p = subprocess.Popen(
+ cmd, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kw
+ )
+ (out, err) = p.communicate(input)
+ retcode = p.wait()
+ if retcode:
+ raise CommandError(cmd, retcode)
+ if not keepends:
+ out = out.rstrip('\n\r')
+ return out
+
+
+def read_git_lines(args, keepends=False, **kw):
+ """Return the lines output by Git command.
+
+ Return as single lines, with newlines stripped off."""
+
+ return read_git_output(args, keepends=True, **kw).splitlines(keepends)
+
+
+class Config(object):
+ def __init__(self, section, git_config=None):
+ """Represent a section of the git configuration.
+
+ If git_config is specified, it is passed to "git config" in
+ the GIT_CONFIG environment variable, meaning that "git config"
+ will read the specified path rather than the Git default
+ config paths."""
+
+ self.section = section
+ if git_config:
+ self.env = os.environ.copy()
+ self.env['GIT_CONFIG'] = git_config
+ else:
+ self.env = None
+
+ @staticmethod
+ def _split(s):
+ """Split NUL-terminated values."""
+
+ words = s.split('\0')
+ assert words[-1] == ''
+ return words[:-1]
+
+ def get(self, name, default=None):
+ try:
+ values = self._split(read_git_output(
+ ['config', '--get', '--null', '%s.%s' % (self.section, name)],
+ env=self.env, keepends=True,
+ ))
+ assert len(values) == 1
+ return values[0]
+ except CommandError:
+ return default
+
+ def get_bool(self, name, default=None):
+ try:
+ value = read_git_output(
+ ['config', '--get', '--bool', '%s.%s' % (self.section, name)],
+ env=self.env,
+ )
+ except CommandError:
+ return default
+ return value == 'true'
+
+ def get_all(self, name, default=None):
+ """Read a (possibly multivalued) setting from the configuration.
+
+ Return the result as a list of values, or default if the name
+ is unset."""
+
+ try:
+ return self._split(read_git_output(
+ ['config', '--get-all', '--null', '%s.%s' % (self.section, name)],
+ env=self.env, keepends=True,
+ ))
+ except CommandError, e:
+ if e.retcode == 1:
+ # "the section or key is invalid"; i.e., there is no
+ # value for the specified key.
+ return default
+ else:
+ raise
+
+ def get_recipients(self, name, default=None):
+ """Read a recipients list from the configuration.
+
+ Return the result as a comma-separated list of email
+ addresses, or default if the option is unset. If the setting
+ has multiple values, concatenate them with comma separators."""
+
+ lines = self.get_all(name, default=None)
+ if lines is None:
+ return default
+ return ', '.join(line.strip() for line in lines)
+
+ def set(self, name, value):
+ read_git_output(
+ ['config', '%s.%s' % (self.section, name), value],
+ env=self.env,
+ )
+
+ def add(self, name, value):
+ read_git_output(
+ ['config', '--add', '%s.%s' % (self.section, name), value],
+ env=self.env,
+ )
+
+ def has_key(self, name):
+ return self.get_all(name, default=None) is not None
+
+ def unset_all(self, name):
+ try:
+ read_git_output(
+ ['config', '--unset-all', '%s.%s' % (self.section, name)],
+ env=self.env,
+ )
+ except CommandError, e:
+ if e.retcode == 5:
+ # The name doesn't exist, which is what we wanted anyway...
+ pass
+ else:
+ raise
+
+ def set_recipients(self, name, value):
+ self.unset_all(name)
+ for pair in getaddresses([value]):
+ self.add(name, formataddr(pair))
+
+
+def generate_summaries(*log_args):
+ """Generate a brief summary for each revision requested.
+
+ log_args are strings that will be passed directly to "git log" as
+ revision selectors. Iterate over (sha1_short, subject) for each
+ commit specified by log_args (subject is the first line of the
+ commit message as a string without EOLs)."""
+
+ cmd = [
+ 'log', '--abbrev', '--format=%h %s',
+ ] + list(log_args) + ['--']
+ for line in read_git_lines(cmd):
+ yield tuple(line.split(' ', 1))
+
+
+def limit_lines(lines, max_lines):
+ for (index, line) in enumerate(lines):
+ if index < max_lines:
+ yield line
+
+ if index >= max_lines:
+ yield '... %d lines suppressed ...\n' % (index + 1 - max_lines,)
+
+
+def limit_linelength(lines, max_linelength):
+ for line in lines:
+ # Don't forget that lines always include a trailing newline.
+ if len(line) > max_linelength + 1:
+ line = line[:max_linelength - 7] + ' [...]\n'
+ yield line
+
+
+class CommitSet(object):
+ """A (constant) set of object names.
+
+ The set should be initialized with full SHA1 object names. The
+ __contains__() method returns True iff its argument is an
+ abbreviation of any the names in the set."""
+
+ def __init__(self, names):
+ self._names = sorted(names)
+
+ def __len__(self):
+ return len(self._names)
+
+ def __contains__(self, sha1_abbrev):
+ """Return True iff this set contains sha1_abbrev (which might be abbreviated)."""
+
+ i = bisect.bisect_left(self._names, sha1_abbrev)
+ return i < len(self) and self._names[i].startswith(sha1_abbrev)
+
+
+class GitObject(object):
+ def __init__(self, sha1, type=None):
+ if sha1 == ZEROS:
+ self.sha1 = self.type = self.commit_sha1 = None
+ else:
+ self.sha1 = sha1
+ self.type = type or read_git_output(['cat-file', '-t', self.sha1])
+
+ if self.type == 'commit':
+ self.commit_sha1 = self.sha1
+ elif self.type == 'tag':
+ try:
+ self.commit_sha1 = read_git_output(
+ ['rev-parse', '--verify', '%s^0' % (self.sha1,)]
+ )
+ except CommandError:
+ # Cannot deref tag to determine commit_sha1
+ self.commit_sha1 = None
+ else:
+ self.commit_sha1 = None
+
+ self.short = read_git_output(['rev-parse', '--short', sha1])
+
+ def get_summary(self):
+ """Return (sha1_short, subject) for this commit."""
+
+ if not self.sha1:
+ raise ValueError('Empty commit has no summary')
+
+ return iter(generate_summaries('--no-walk', self.sha1)).next()
+
+ def __eq__(self, other):
+ return isinstance(other, GitObject) and self.sha1 == other.sha1
+
+ def __hash__(self):
+ return hash(self.sha1)
+
+ def __nonzero__(self):
+ return bool(self.sha1)
+
+ def __str__(self):
+ return self.sha1 or ZEROS
+
+
+class Change(object):
+ """A Change that has been made to the Git repository.
+
+ Abstract class from which both Revisions and ReferenceChanges are
+ derived. A Change knows how to generate a notification email
+ describing itself."""
+
+ def __init__(self, environment):
+ self.environment = environment
+ self._values = None
+
+ def _compute_values(self):
+ """Return a dictionary {keyword : expansion} for this Change.
+
+ Derived classes overload this method to add more entries to
+ the return value. This method is used internally by
+ get_values(). The return value should always be a new
+ dictionary."""
+
+ return self.environment.get_values()
+
+ def get_values(self, **extra_values):
+ """Return a dictionary {keyword : expansion} for this Change.
+
+ Return a dictionary mapping keywords to the values that they
+ should be expanded to for this Change (used when interpolating
+ template strings). If any keyword arguments are supplied, add
+ those to the return value as well. The return value is always
+ a new dictionary."""
+
+ if self._values is None:
+ self._values = self._compute_values()
+
+ values = self._values.copy()
+ if extra_values:
+ values.update(extra_values)
+ return values
+
+ def expand(self, template, **extra_values):
+ """Expand template.
+
+ Expand the template (which should be a string) using string
+ interpolation of the values for this Change. If any keyword
+ arguments are provided, also include those in the keywords
+ available for interpolation."""
+
+ return template % self.get_values(**extra_values)
+
+ def expand_lines(self, template, **extra_values):
+ """Break template into lines and expand each line."""
+
+ values = self.get_values(**extra_values)
+ for line in template.splitlines(True):
+ yield line % values
+
+ def expand_header_lines(self, template, **extra_values):
+ """Break template into lines and expand each line as an RFC 2822 header.
+
+ Encode values and split up lines that are too long. Silently
+ skip lines that contain references to unknown variables."""
+
+ values = self.get_values(**extra_values)
+ for line in template.splitlines():
+ (name, value) = line.split(':', 1)
+
+ try:
+ value = value % values
+ except KeyError, e:
+ if DEBUG:
+ sys.stderr.write(
+ 'Warning: unknown variable %r in the following line; line skipped:\n'
+ ' %s\n'
+ % (e.args[0], line,)
+ )
+ else:
+ try:
+ h = Header(value, header_name=name)
+ except UnicodeDecodeError:
+ h = Header(value, header_name=name, charset=CHARSET, errors='replace')
+ for splitline in ('%s: %s\n' % (name, h.encode(),)).splitlines(True):
+ yield splitline
+
+ def generate_email_header(self):
+ """Generate the RFC 2822 email headers for this Change, a line at a time.
+
+ The output should not include the trailing blank line."""
+
+ raise NotImplementedError()
+
+ def generate_email_intro(self):
+ """Generate the email intro for this Change, a line at a time.
+
+ The output will be used as the standard boilerplate at the top
+ of the email body."""
+
+ raise NotImplementedError()
+
+ def generate_email_body(self):
+ """Generate the main part of the email body, a line at a time.
+
+ The text in the body might be truncated after a specified
+ number of lines (see multimailhook.emailmaxlines)."""
+
+ raise NotImplementedError()
+
+ def generate_email_footer(self):
+ """Generate the footer of the email, a line at a time.
+
+ The footer is always included, irrespective of
+ multimailhook.emailmaxlines."""
+
+ raise NotImplementedError()
+
+ def generate_email(self, push, body_filter=None):
+ """Generate an email describing this change.
+
+ Iterate over the lines (including the header lines) of an
+ email describing this change. If body_filter is not None,
+ then use it to filter the lines that are intended for the
+ email body."""
+
+ for line in self.generate_email_header():
+ yield line
+ yield '\n'
+ for line in self.generate_email_intro():
+ yield line
+
+ body = self.generate_email_body(push)
+ if body_filter is not None:
+ body = body_filter(body)
+ for line in body:
+ yield line
+
+ for line in self.generate_email_footer():
+ yield line
+
+
+class Revision(Change):
+ """A Change consisting of a single git commit."""
+
+ def __init__(self, reference_change, rev, num, tot):
+ Change.__init__(self, reference_change.environment)
+ self.reference_change = reference_change
+ self.rev = rev
+ self.change_type = self.reference_change.change_type
+ self.refname = self.reference_change.refname
+ self.num = num
+ self.tot = tot
+ self.author = read_git_output(['log', '--no-walk', '--format=%aN <%aE>', self.rev.sha1])
+ self.recipients = self.environment.get_revision_recipients(self)
+
+ def _compute_values(self):
+ values = Change._compute_values(self)
+
+ oneline = read_git_output(
+ ['log', '--format=%s', '--no-walk', self.rev.sha1]
+ )
+
+ values['rev'] = self.rev.sha1
+ values['rev_short'] = self.rev.short
+ values['change_type'] = self.change_type
+ values['refname'] = self.refname
+ values['short_refname'] = self.reference_change.short_refname
+ values['refname_type'] = self.reference_change.refname_type
+ values['reply_to_msgid'] = self.reference_change.msgid
+ values['num'] = self.num
+ values['tot'] = self.tot
+ values['recipients'] = self.recipients
+ values['oneline'] = oneline
+ values['author'] = self.author
+
+ reply_to = self.environment.get_reply_to_commit(self)
+ if reply_to:
+ values['reply_to'] = reply_to
+
+ return values
+
+ def generate_email_header(self):
+ for line in self.expand_header_lines(REVISION_HEADER_TEMPLATE):
+ yield line
+
+ def generate_email_intro(self):
+ for line in self.expand_lines(REVISION_INTRO_TEMPLATE):
+ yield line
+
+ def generate_email_body(self, push):
+ """Show this revision."""
+
+ return read_git_lines(
+ [
+ 'log', '-C',
+ '--stat', '-p', '--cc',
+ '-1', self.rev.sha1,
+ ],
+ keepends=True,
+ )
+
+ def generate_email_footer(self):
+ return self.expand_lines(REVISION_FOOTER_TEMPLATE)
+
+
+class ReferenceChange(Change):
+ """A Change to a Git reference.
+
+ An abstract class representing a create, update, or delete of a
+ Git reference. Derived classes handle specific types of reference
+ (e.g., tags vs. branches). These classes generate the main
+ reference change email summarizing the reference change and
+ whether it caused any any commits to be added or removed.
+
+ ReferenceChange objects are usually created using the static
+ create() method, which has the logic to decide which derived class
+ to instantiate."""
+
+ REF_RE = re.compile(r'^refs\/(?P<area>[^\/]+)\/(?P<shortname>.*)$')
+
+ @staticmethod
+ def create(environment, oldrev, newrev, refname):
+ """Return a ReferenceChange object representing the change.
+
+ Return an object that represents the type of change that is being
+ made. oldrev and newrev should be SHA1s or ZEROS."""
+
+ old = GitObject(oldrev)
+ new = GitObject(newrev)
+ rev = new or old
+
+ # The revision type tells us what type the commit is, combined with
+ # the location of the ref we can decide between
+ # - working branch
+ # - tracking branch
+ # - unannotated tag
+ # - annotated tag
+ m = ReferenceChange.REF_RE.match(refname)
+ if m:
+ area = m.group('area')
+ short_refname = m.group('shortname')
+ else:
+ area = ''
+ short_refname = refname
+
+ if rev.type == 'tag':
+ # Annotated tag:
+ klass = AnnotatedTagChange
+ elif rev.type == 'commit':
+ if area == 'tags':
+ # Non-annotated tag:
+ klass = NonAnnotatedTagChange
+ elif area == 'heads':
+ # Branch:
+ klass = BranchChange
+ elif area == 'remotes':
+ # Tracking branch:
+ sys.stderr.write(
+ '*** Push-update of tracking branch %r\n'
+ '*** - incomplete email generated.\n'
+ % (refname,)
+ )
+ klass = OtherReferenceChange
+ else:
+ # Some other reference namespace:
+ sys.stderr.write(
+ '*** Push-update of strange reference %r\n'
+ '*** - incomplete email generated.\n'
+ % (refname,)
+ )
+ klass = OtherReferenceChange
+ else:
+ # Anything else (is there anything else?)
+ sys.stderr.write(
+ '*** Unknown type of update to %r (%s)\n'
+ '*** - incomplete email generated.\n'
+ % (refname, rev.type,)
+ )
+ klass = OtherReferenceChange
+
+ return klass(
+ environment,
+ refname=refname, short_refname=short_refname,
+ old=old, new=new, rev=rev,
+ )
+
+ def __init__(self, environment, refname, short_refname, old, new, rev):
+ Change.__init__(self, environment)
+ self.change_type = {
+ (False, True) : 'create',
+ (True, True) : 'update',
+ (True, False) : 'delete',
+ }[bool(old), bool(new)]
+ self.refname = refname
+ self.short_refname = short_refname
+ self.old = old
+ self.new = new
+ self.rev = rev
+ self.msgid = make_msgid()
+ self.diffopts = environment.diffopts
+ self.logopts = environment.logopts
+ self.showlog = environment.refchange_showlog
+
+ def _compute_values(self):
+ values = Change._compute_values(self)
+
+ values['change_type'] = self.change_type
+ values['refname_type'] = self.refname_type
+ values['refname'] = self.refname
+ values['short_refname'] = self.short_refname
+ values['msgid'] = self.msgid
+ values['recipients'] = self.recipients
+ values['oldrev'] = str(self.old)
+ values['oldrev_short'] = self.old.short
+ values['newrev'] = str(self.new)
+ values['newrev_short'] = self.new.short
+
+ if self.old:
+ values['oldrev_type'] = self.old.type
+ if self.new:
+ values['newrev_type'] = self.new.type
+
+ reply_to = self.environment.get_reply_to_refchange(self)
+ if reply_to:
+ values['reply_to'] = reply_to
+
+ return values
+
+ def get_subject(self):
+ template = {
+ 'create' : REF_CREATED_SUBJECT_TEMPLATE,
+ 'update' : REF_UPDATED_SUBJECT_TEMPLATE,
+ 'delete' : REF_DELETED_SUBJECT_TEMPLATE,
+ }[self.change_type]
+ return self.expand(template)
+
+ def generate_email_header(self):
+ for line in self.expand_header_lines(
+ REFCHANGE_HEADER_TEMPLATE, subject=self.get_subject(),
+ ):
+ yield line
+
+ def generate_email_intro(self):
+ for line in self.expand_lines(REFCHANGE_INTRO_TEMPLATE):
+ yield line
+
+ def generate_email_body(self, push):
+ """Call the appropriate body-generation routine.
+
+ Call one of generate_create_summary() /
+ generate_update_summary() / generate_delete_summary()."""
+
+ change_summary = {
+ 'create' : self.generate_create_summary,
+ 'delete' : self.generate_delete_summary,
+ 'update' : self.generate_update_summary,
+ }[self.change_type](push)
+ for line in change_summary:
+ yield line
+
+ for line in self.generate_revision_change_summary(push):
+ yield line
+
+ def generate_email_footer(self):
+ return self.expand_lines(FOOTER_TEMPLATE)
+
+ def generate_revision_change_log(self, new_commits_list):
+ if self.showlog:
+ yield '\n'
+ yield 'Detailed log of new commits:\n\n'
+ for line in read_git_lines(
+ ['log', '--no-walk']
+ + self.logopts
+ + new_commits_list
+ + ['--'],
+ keepends=True,
+ ):
+ yield line
+
+ def generate_revision_change_summary(self, push):
+ """Generate a summary of the revisions added/removed by this change."""
+
+ if self.new.commit_sha1 and not self.old.commit_sha1:
+ # A new reference was created. List the new revisions
+ # brought by the new reference (i.e., those revisions that
+ # were not in the repository before this reference
+ # change).
+ sha1s = list(push.get_new_commits(self))
+ sha1s.reverse()
+ tot = len(sha1s)
+ new_revisions = [
+ Revision(self, GitObject(sha1), num=i+1, tot=tot)
+ for (i, sha1) in enumerate(sha1s)
+ ]
+
+ if new_revisions:
+ yield self.expand('This %(refname_type)s includes the following new commits:\n')
+ yield '\n'
+ for r in new_revisions:
+ (sha1, subject) = r.rev.get_summary()
+ yield r.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='new', text=subject,
+ )
+ yield '\n'
+ for line in self.expand_lines(NEW_REVISIONS_TEMPLATE, tot=tot):
+ yield line
+ for line in self.generate_revision_change_log([r.rev.sha1 for r in new_revisions]):
+ yield line
+ else:
+ for line in self.expand_lines(NO_NEW_REVISIONS_TEMPLATE):
+ yield line
+
+ elif self.new.commit_sha1 and self.old.commit_sha1:
+ # A reference was changed to point at a different commit.
+ # List the revisions that were removed and/or added *from
+ # that reference* by this reference change, along with a
+ # diff between the trees for its old and new values.
+
+ # List of the revisions that were added to the branch by
+ # this update. Note this list can include revisions that
+ # have already had notification emails; we want such
+ # revisions in the summary even though we will not send
+ # new notification emails for them.
+ adds = list(generate_summaries(
+ '--topo-order', '--reverse', '%s..%s'
+ % (self.old.commit_sha1, self.new.commit_sha1,)
+ ))
+
+ # List of the revisions that were removed from the branch
+ # by this update. This will be empty except for
+ # non-fast-forward updates.
+ discards = list(generate_summaries(
+ '%s..%s' % (self.new.commit_sha1, self.old.commit_sha1,)
+ ))
+
+ if adds:
+ new_commits_list = push.get_new_commits(self)
+ else:
+ new_commits_list = []
+ new_commits = CommitSet(new_commits_list)
+
+ if discards:
+ discarded_commits = CommitSet(push.get_discarded_commits(self))
+ else:
+ discarded_commits = CommitSet([])
+
+ if discards and adds:
+ for (sha1, subject) in discards:
+ if sha1 in discarded_commits:
+ action = 'discards'
+ else:
+ action = 'omits'
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action=action,
+ rev_short=sha1, text=subject,
+ )
+ for (sha1, subject) in adds:
+ if sha1 in new_commits:
+ action = 'new'
+ else:
+ action = 'adds'
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action=action,
+ rev_short=sha1, text=subject,
+ )
+ yield '\n'
+ for line in self.expand_lines(NON_FF_TEMPLATE):
+ yield line
+
+ elif discards:
+ for (sha1, subject) in discards:
+ if sha1 in discarded_commits:
+ action = 'discards'
+ else:
+ action = 'omits'
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action=action,
+ rev_short=sha1, text=subject,
+ )
+ yield '\n'
+ for line in self.expand_lines(REWIND_ONLY_TEMPLATE):
+ yield line
+
+ elif adds:
+ (sha1, subject) = self.old.get_summary()
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='from',
+ rev_short=sha1, text=subject,
+ )
+ for (sha1, subject) in adds:
+ if sha1 in new_commits:
+ action = 'new'
+ else:
+ action = 'adds'
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action=action,
+ rev_short=sha1, text=subject,
+ )
+
+ yield '\n'
+
+ if new_commits:
+ for line in self.expand_lines(NEW_REVISIONS_TEMPLATE, tot=len(new_commits)):
+ yield line
+ for line in self.generate_revision_change_log(new_commits_list):
+ yield line
+ else:
+ for line in self.expand_lines(NO_NEW_REVISIONS_TEMPLATE):
+ yield line
+
+ # The diffstat is shown from the old revision to the new
+ # revision. This is to show the truth of what happened in
+ # this change. There's no point showing the stat from the
+ # base to the new revision because the base is effectively a
+ # random revision at this point - the user will be interested
+ # in what this revision changed - including the undoing of
+ # previous revisions in the case of non-fast-forward updates.
+ yield '\n'
+ yield 'Summary of changes:\n'
+ for line in read_git_lines(
+ ['diff-tree']
+ + self.diffopts
+ + ['%s..%s' % (self.old.commit_sha1, self.new.commit_sha1,)],
+ keepends=True,
+ ):
+ yield line
+
+ elif self.old.commit_sha1 and not self.new.commit_sha1:
+ # A reference was deleted. List the revisions that were
+ # removed from the repository by this reference change.
+
+ sha1s = list(push.get_discarded_commits(self))
+ tot = len(sha1s)
+ discarded_revisions = [
+ Revision(self, GitObject(sha1), num=i+1, tot=tot)
+ for (i, sha1) in enumerate(sha1s)
+ ]
+
+ if discarded_revisions:
+ for line in self.expand_lines(DISCARDED_REVISIONS_TEMPLATE):
+ yield line
+ yield '\n'
+ for r in discarded_revisions:
+ (sha1, subject) = r.rev.get_summary()
+ yield r.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='discards', text=subject,
+ )
+ else:
+ for line in self.expand_lines(NO_DISCARDED_REVISIONS_TEMPLATE):
+ yield line
+
+ elif not self.old.commit_sha1 and not self.new.commit_sha1:
+ for line in self.expand_lines(NON_COMMIT_UPDATE_TEMPLATE):
+ yield line
+
+ def generate_create_summary(self, push):
+ """Called for the creation of a reference."""
+
+ # This is a new reference and so oldrev is not valid
+ (sha1, subject) = self.new.get_summary()
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='at',
+ rev_short=sha1, text=subject,
+ )
+ yield '\n'
+
+ def generate_update_summary(self, push):
+ """Called for the change of a pre-existing branch."""
+
+ return iter([])
+
+ def generate_delete_summary(self, push):
+ """Called for the deletion of any type of reference."""
+
+ (sha1, subject) = self.old.get_summary()
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='was',
+ rev_short=sha1, text=subject,
+ )
+ yield '\n'
+
+
+class BranchChange(ReferenceChange):
+ refname_type = 'branch'
+
+ def __init__(self, environment, refname, short_refname, old, new, rev):
+ ReferenceChange.__init__(
+ self, environment,
+ refname=refname, short_refname=short_refname,
+ old=old, new=new, rev=rev,
+ )
+ self.recipients = environment.get_refchange_recipients(self)
+
+
+class AnnotatedTagChange(ReferenceChange):
+ refname_type = 'annotated tag'
+
+ def __init__(self, environment, refname, short_refname, old, new, rev):
+ ReferenceChange.__init__(
+ self, environment,
+ refname=refname, short_refname=short_refname,
+ old=old, new=new, rev=rev,
+ )
+ self.recipients = environment.get_announce_recipients(self)
+ self.show_shortlog = environment.announce_show_shortlog
+
+ ANNOTATED_TAG_FORMAT = (
+ '%(*objectname)\n'
+ '%(*objecttype)\n'
+ '%(taggername)\n'
+ '%(taggerdate)'
+ )
+
+ def describe_tag(self, push):
+ """Describe the new value of an annotated tag."""
+
+ # Use git for-each-ref to pull out the individual fields from
+ # the tag
+ [tagobject, tagtype, tagger, tagged] = read_git_lines(
+ ['for-each-ref', '--format=%s' % (self.ANNOTATED_TAG_FORMAT,), self.refname],
+ )
+
+ yield self.expand(
+ BRIEF_SUMMARY_TEMPLATE, action='tagging',
+ rev_short=tagobject, text='(%s)' % (tagtype,),
+ )
+ if tagtype == 'commit':
+ # If the tagged object is a commit, then we assume this is a
+ # release, and so we calculate which tag this tag is
+ # replacing
+ try:
+ prevtag = read_git_output(['describe', '--abbrev=0', '%s^' % (self.new,)])
+ except CommandError:
+ prevtag = None
+ if prevtag:
+ yield ' replaces %s\n' % (prevtag,)
+ else:
+ prevtag = None
+ yield ' length %s bytes\n' % (read_git_output(['cat-file', '-s', tagobject]),)
+
+ yield ' tagged by %s\n' % (tagger,)
+ yield ' on %s\n' % (tagged,)
+ yield '\n'
+
+ # Show the content of the tag message; this might contain a
+ # change log or release notes so is worth displaying.
+ yield LOGBEGIN
+ contents = list(read_git_lines(['cat-file', 'tag', self.new.sha1], keepends=True))
+ contents = contents[contents.index('\n') + 1:]
+ if contents and contents[-1][-1:] != '\n':
+ contents.append('\n')
+ for line in contents:
+ yield line
+
+ if self.show_shortlog and tagtype == 'commit':
+ # Only commit tags make sense to have rev-list operations
+ # performed on them
+ yield '\n'
+ if prevtag:
+ # Show changes since the previous release
+ revlist = read_git_output(
+ ['rev-list', '--pretty=short', '%s..%s' % (prevtag, self.new,)],
+ keepends=True,
+ )
+ else:
+ # No previous tag, show all the changes since time
+ # began
+ revlist = read_git_output(
+ ['rev-list', '--pretty=short', '%s' % (self.new,)],
+ keepends=True,
+ )
+ for line in read_git_lines(['shortlog'], input=revlist, keepends=True):
+ yield line
+
+ yield LOGEND
+ yield '\n'
+
+ def generate_create_summary(self, push):
+ """Called for the creation of an annotated tag."""
+
+ for line in self.expand_lines(TAG_CREATED_TEMPLATE):
+ yield line
+
+ for line in self.describe_tag(push):
+ yield line
+
+ def generate_update_summary(self, push):
+ """Called for the update of an annotated tag.
+
+ This is probably a rare event and may not even be allowed."""
+
+ for line in self.expand_lines(TAG_UPDATED_TEMPLATE):
+ yield line
+
+ for line in self.describe_tag(push):
+ yield line
+
+ def generate_delete_summary(self, push):
+ """Called when a non-annotated reference is updated."""
+
+ for line in self.expand_lines(TAG_DELETED_TEMPLATE):
+ yield line
+
+ yield self.expand(' tag was %(oldrev_short)s\n')
+ yield '\n'
+
+
+class NonAnnotatedTagChange(ReferenceChange):
+ refname_type = 'tag'
+
+ def __init__(self, environment, refname, short_refname, old, new, rev):
+ ReferenceChange.__init__(
+ self, environment,
+ refname=refname, short_refname=short_refname,
+ old=old, new=new, rev=rev,
+ )
+ self.recipients = environment.get_refchange_recipients(self)
+
+ def generate_create_summary(self, push):
+ """Called for the creation of an annotated tag."""
+
+ for line in self.expand_lines(TAG_CREATED_TEMPLATE):
+ yield line
+
+ def generate_update_summary(self, push):
+ """Called when a non-annotated reference is updated."""
+
+ for line in self.expand_lines(TAG_UPDATED_TEMPLATE):
+ yield line
+
+ def generate_delete_summary(self, push):
+ """Called when a non-annotated reference is updated."""
+
+ for line in self.expand_lines(TAG_DELETED_TEMPLATE):
+ yield line
+
+ for line in ReferenceChange.generate_delete_summary(self, push):
+ yield line
+
+
+class OtherReferenceChange(ReferenceChange):
+ refname_type = 'reference'
+
+ def __init__(self, environment, refname, short_refname, old, new, rev):
+ # We use the full refname as short_refname, because otherwise
+ # the full name of the reference would not be obvious from the
+ # text of the email.
+ ReferenceChange.__init__(
+ self, environment,
+ refname=refname, short_refname=refname,
+ old=old, new=new, rev=rev,
+ )
+ self.recipients = environment.get_refchange_recipients(self)
+
+
+class Mailer(object):
+ """An object that can send emails."""
+
+ def send(self, lines, to_addrs):
+ """Send an email consisting of lines.
+
+ lines must be an iterable over the lines constituting the
+ header and body of the email. to_addrs is a list of recipient
+ addresses (can be needed even if lines already contains a
+ "To:" field). It can be either a string (comma-separated list
+ of email addresses) or a Python list of individual email
+ addresses.
+
+ """
+
+ raise NotImplementedError()
+
+
+class SendMailer(Mailer):
+ """Send emails using 'sendmail -t'."""
+
+ SENDMAIL_CANDIDATES = [
+ '/usr/sbin/sendmail',
+ '/usr/lib/sendmail',
+ ]
+
+ @staticmethod
+ def find_sendmail():
+ for path in SendMailer.SENDMAIL_CANDIDATES:
+ if os.access(path, os.X_OK):
+ return path
+ else:
+ raise ConfigurationException(
+ 'No sendmail executable found. '
+ 'Try setting multimailhook.sendmailCommand.'
+ )
+
+ def __init__(self, command=None, envelopesender=None):
+ """Construct a SendMailer instance.
+
+ command should be the command and arguments used to invoke
+ sendmail, as a list of strings. If an envelopesender is
+ provided, it will also be passed to the command, via '-f
+ envelopesender'."""
+
+ if command:
+ self.command = command[:]
+ else:
+ self.command = [self.find_sendmail(), '-t']
+
+ if envelopesender:
+ self.command.extend(['-f', envelopesender])
+
+ def send(self, lines, to_addrs):
+ try:
+ p = subprocess.Popen(self.command, stdin=subprocess.PIPE)
+ except OSError, e:
+ sys.stderr.write(
+ '*** Cannot execute command: %s\n' % ' '.join(self.command)
+ + '*** %s\n' % str(e)
+ + '*** Try setting multimailhook.mailer to "smtp"\n'
+ '*** to send emails without using the sendmail command.\n'
+ )
+ sys.exit(1)
+ try:
+ p.stdin.writelines(lines)
+ except:
+ sys.stderr.write(
+ '*** Error while generating commit email\n'
+ '*** - mail sending aborted.\n'
+ )
+ p.terminate()
+ raise
+ else:
+ p.stdin.close()
+ retcode = p.wait()
+ if retcode:
+ raise CommandError(self.command, retcode)
+
+
+class SMTPMailer(Mailer):
+ """Send emails using Python's smtplib."""
+
+ def __init__(self, envelopesender, smtpserver):
+ if not envelopesender:
+ sys.stderr.write(
+ 'fatal: git_multimail: cannot use SMTPMailer without a sender address.\n'
+ 'please set either multimailhook.envelopeSender or user.email\n'
+ )
+ sys.exit(1)
+ self.envelopesender = envelopesender
+ self.smtpserver = smtpserver
+ try:
+ self.smtp = smtplib.SMTP(self.smtpserver)
+ except Exception, e:
+ sys.stderr.write('*** Error establishing SMTP connection to %s***\n' % self.smtpserver)
+ sys.stderr.write('*** %s\n' % str(e))
+ sys.exit(1)
+
+ def __del__(self):
+ self.smtp.quit()
+
+ def send(self, lines, to_addrs):
+ try:
+ msg = ''.join(lines)
+ # turn comma-separated list into Python list if needed.
+ if isinstance(to_addrs, basestring):
+ to_addrs = [email for (name, email) in getaddresses([to_addrs])]
+ self.smtp.sendmail(self.envelopesender, to_addrs, msg)
+ except Exception, e:
+ sys.stderr.write('*** Error sending email***\n')
+ sys.stderr.write('*** %s\n' % str(e))
+ self.smtp.quit()
+ sys.exit(1)
+
+
+class OutputMailer(Mailer):
+ """Write emails to an output stream, bracketed by lines of '=' characters.
+
+ This is intended for debugging purposes."""
+
+ SEPARATOR = '=' * 75 + '\n'
+
+ def __init__(self, f):
+ self.f = f
+
+ def send(self, lines, to_addrs):
+ self.f.write(self.SEPARATOR)
+ self.f.writelines(lines)
+ self.f.write(self.SEPARATOR)
+
+
+def get_git_dir():
+ """Determine GIT_DIR.
+
+ Determine GIT_DIR either from the GIT_DIR environment variable or
+ from the working directory, using Git's usual rules."""
+
+ try:
+ return read_git_output(['rev-parse', '--git-dir'])
+ except CommandError:
+ sys.stderr.write('fatal: git_multimail: not in a git directory\n')
+ sys.exit(1)
+
+
+class Environment(object):
+ """Describes the environment in which the push is occurring.
+
+ An Environment object encapsulates information about the local
+ environment. For example, it knows how to determine:
+
+ * the name of the repository to which the push occurred
+
+ * what user did the push
+
+ * what users want to be informed about various types of changes.
+
+ An Environment object is expected to have the following methods:
+
+ get_repo_shortname()
+
+ Return a short name for the repository, for display
+ purposes.
+
+ get_repo_path()
+
+ Return the absolute path to the Git repository.
+
+ get_emailprefix()
+
+ Return a string that will be prefixed to every email's
+ subject.
+
+ get_pusher()
+
+ Return the username of the person who pushed the changes.
+ This value is used in the email body to indicate who
+ pushed the change.
+
+ get_pusher_email() (may return None)
+
+ Return the email address of the person who pushed the
+ changes. The value should be a single RFC 2822 email
+ address as a string; e.g., "Joe User <user@example.com>"
+ if available, otherwise "user@example.com". If set, the
+ value is used as the Reply-To address for refchange
+ emails. If it is impossible to determine the pusher's
+ email, this attribute should be set to None (in which case
+ no Reply-To header will be output).
+
+ get_sender()
+
+ Return the address to be used as the 'From' email address
+ in the email envelope.
+
+ get_fromaddr()
+
+ Return the 'From' email address used in the email 'From:'
+ headers. (May be a full RFC 2822 email address like 'Joe
+ User <user@example.com>'.)
+
+ get_administrator()
+
+ Return the name and/or email of the repository
+ administrator. This value is used in the footer as the
+ person to whom requests to be removed from the
+ notification list should be sent. Ideally, it should
+ include a valid email address.
+
+ get_reply_to_refchange()
+ get_reply_to_commit()
+
+ Return the address to use in the email "Reply-To" header,
+ as a string. These can be an RFC 2822 email address, or
+ None to omit the "Reply-To" header.
+ get_reply_to_refchange() is used for refchange emails;
+ get_reply_to_commit() is used for individual commit
+ emails.
+
+ They should also define the following attributes:
+
+ announce_show_shortlog (bool)
+
+ True iff announce emails should include a shortlog.
+
+ refchange_showlog (bool)
+
+ True iff refchanges emails should include a detailed log.
+
+ diffopts (list of strings)
+
+ The options that should be passed to 'git diff' for the
+ summary email. The value should be a list of strings
+ representing words to be passed to the command.
+
+ logopts (list of strings)
+
+ Analogous to diffopts, but contains options passed to
+ 'git log' when generating the detailed log for a set of
+ commits (see refchange_showlog)
+
+ """
+
+ REPO_NAME_RE = re.compile(r'^(?P<name>.+?)(?:\.git)$')
+
+ def __init__(self, osenv=None):
+ self.osenv = osenv or os.environ
+ self.announce_show_shortlog = False
+ self.maxcommitemails = 500
+ self.diffopts = ['--stat', '--summary', '--find-copies-harder']
+ self.logopts = []
+ self.refchange_showlog = False
+
+ self.COMPUTED_KEYS = [
+ 'administrator',
+ 'charset',
+ 'emailprefix',
+ 'fromaddr',
+ 'pusher',
+ 'pusher_email',
+ 'repo_path',
+ 'repo_shortname',
+ 'sender',
+ ]
+
+ self._values = None
+
+ def get_repo_shortname(self):
+ """Use the last part of the repo path, with ".git" stripped off if present."""
+
+ basename = os.path.basename(os.path.abspath(self.get_repo_path()))
+ m = self.REPO_NAME_RE.match(basename)
+ if m:
+ return m.group('name')
+ else:
+ return basename
+
+ def get_pusher(self):
+ raise NotImplementedError()
+
+ def get_pusher_email(self):
+ return None
+
+ def get_administrator(self):
+ return 'the administrator of this repository'
+
+ def get_emailprefix(self):
+ return ''
+
+ def get_repo_path(self):
+ if read_git_output(['rev-parse', '--is-bare-repository']) == 'true':
+ path = get_git_dir()
+ else:
+ path = read_git_output(['rev-parse', '--show-toplevel'])
+ return os.path.abspath(path)
+
+ def get_charset(self):
+ return CHARSET
+
+ def get_values(self):
+ """Return a dictionary {keyword : expansion} for this Environment.
+
+ This method is called by Change._compute_values(). The keys
+ in the returned dictionary are available to be used in any of
+ the templates. The dictionary is created by calling
+ self.get_NAME() for each of the attributes named in
+ COMPUTED_KEYS and recording those that do not return None.
+ The return value is always a new dictionary."""
+
+ if self._values is None:
+ values = {}
+
+ for key in self.COMPUTED_KEYS:
+ value = getattr(self, 'get_%s' % (key,))()
+ if value is not None:
+ values[key] = value
+
+ self._values = values
+
+ return self._values.copy()
+
+ def get_refchange_recipients(self, refchange):
+ """Return the recipients for notifications about refchange.
+
+ Return the list of email addresses to which notifications
+ about the specified ReferenceChange should be sent."""
+
+ raise NotImplementedError()
+
+ def get_announce_recipients(self, annotated_tag_change):
+ """Return the recipients for notifications about annotated_tag_change.
+
+ Return the list of email addresses to which notifications
+ about the specified AnnotatedTagChange should be sent."""
+
+ raise NotImplementedError()
+
+ def get_reply_to_refchange(self, refchange):
+ return self.get_pusher_email()
+
+ def get_revision_recipients(self, revision):
+ """Return the recipients for messages about revision.
+
+ Return the list of email addresses to which notifications
+ about the specified Revision should be sent. This method
+ could be overridden, for example, to take into account the
+ contents of the revision when deciding whom to notify about
+ it. For example, there could be a scheme for users to express
+ interest in particular files or subdirectories, and only
+ receive notification emails for revisions that affecting those
+ files."""
+
+ raise NotImplementedError()
+
+ def get_reply_to_commit(self, revision):
+ return revision.author
+
+ def filter_body(self, lines):
+ """Filter the lines intended for an email body.
+
+ lines is an iterable over the lines that would go into the
+ email body. Filter it (e.g., limit the number of lines, the
+ line length, character set, etc.), returning another iterable.
+ See FilterLinesEnvironmentMixin and MaxlinesEnvironmentMixin
+ for classes implementing this functionality."""
+
+ return lines
+
+
+class ConfigEnvironmentMixin(Environment):
+ """A mixin that sets self.config to its constructor's config argument.
+
+ This class's constructor consumes the "config" argument.
+
+ Mixins that need to inspect the config should inherit from this
+ class (1) to make sure that "config" is still in the constructor
+ arguments with its own constructor runs and/or (2) to be sure that
+ self.config is set after construction."""
+
+ def __init__(self, config, **kw):
+ super(ConfigEnvironmentMixin, self).__init__(**kw)
+ self.config = config
+
+
+class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin):
+ """An Environment that reads most of its information from "git config"."""
+
+ def __init__(self, config, **kw):
+ super(ConfigOptionsEnvironmentMixin, self).__init__(
+ config=config, **kw
+ )
+
+ self.announce_show_shortlog = config.get_bool(
+ 'announceshortlog', default=self.announce_show_shortlog
+ )
+
+ self.refchange_showlog = config.get_bool(
+ 'refchangeshowlog', default=self.refchange_showlog
+ )
+
+ maxcommitemails = config.get('maxcommitemails')
+ if maxcommitemails is not None:
+ try:
+ self.maxcommitemails = int(maxcommitemails)
+ except ValueError:
+ sys.stderr.write(
+ '*** Malformed value for multimailhook.maxCommitEmails: %s\n' % maxcommitemails
+ + '*** Expected a number. Ignoring.\n'
+ )
+
+ diffopts = config.get('diffopts')
+ if diffopts is not None:
+ self.diffopts = shlex.split(diffopts)
+
+ logopts = config.get('logopts')
+ if logopts is not None:
+ self.logopts = shlex.split(logopts)
+
+ reply_to = config.get('replyTo')
+ self.__reply_to_refchange = config.get('replyToRefchange', default=reply_to)
+ if (
+ self.__reply_to_refchange is not None
+ and self.__reply_to_refchange.lower() == 'author'
+ ):
+ raise ConfigurationException(
+ '"author" is not an allowed setting for replyToRefchange'
+ )
+ self.__reply_to_commit = config.get('replyToCommit', default=reply_to)
+
+ def get_administrator(self):
+ return (
+ self.config.get('administrator')
+ or self.get_sender()
+ or super(ConfigOptionsEnvironmentMixin, self).get_administrator()
+ )
+
+ def get_repo_shortname(self):
+ return (
+ self.config.get('reponame')
+ or super(ConfigOptionsEnvironmentMixin, self).get_repo_shortname()
+ )
+
+ def get_emailprefix(self):
+ emailprefix = self.config.get('emailprefix')
+ if emailprefix and emailprefix.strip():
+ return emailprefix.strip() + ' '
+ else:
+ return '[%s] ' % (self.get_repo_shortname(),)
+
+ def get_sender(self):
+ return self.config.get('envelopesender')
+
+ def get_fromaddr(self):
+ fromaddr = self.config.get('from')
+ if fromaddr:
+ return fromaddr
+ else:
+ config = Config('user')
+ fromname = config.get('name', default='')
+ fromemail = config.get('email', default='')
+ if fromemail:
+ return formataddr([fromname, fromemail])
+ else:
+ return self.get_sender()
+
+ def get_reply_to_refchange(self, refchange):
+ if self.__reply_to_refchange is None:
+ return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_refchange(refchange)
+ elif self.__reply_to_refchange.lower() == 'pusher':
+ return self.get_pusher_email()
+ elif self.__reply_to_refchange.lower() == 'none':
+ return None
+ else:
+ return self.__reply_to_refchange
+
+ def get_reply_to_commit(self, revision):
+ if self.__reply_to_commit is None:
+ return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_commit(revision)
+ elif self.__reply_to_commit.lower() == 'author':
+ return revision.get_author()
+ elif self.__reply_to_commit.lower() == 'pusher':
+ return self.get_pusher_email()
+ elif self.__reply_to_commit.lower() == 'none':
+ return None
+ else:
+ return self.__reply_to_commit
+
+
+class FilterLinesEnvironmentMixin(Environment):
+ """Handle encoding and maximum line length of body lines.
+
+ emailmaxlinelength (int or None)
+
+ The maximum length of any single line in the email body.
+ Longer lines are truncated at that length with ' [...]'
+ appended.
+
+ strict_utf8 (bool)
+
+ If this field is set to True, then the email body text is
+ expected to be UTF-8. Any invalid characters are
+ converted to U+FFFD, the Unicode replacement character
+ (encoded as UTF-8, of course).
+
+ """
+
+ def __init__(self, strict_utf8=True, emailmaxlinelength=500, **kw):
+ super(FilterLinesEnvironmentMixin, self).__init__(**kw)
+ self.__strict_utf8 = strict_utf8
+ self.__emailmaxlinelength = emailmaxlinelength
+
+ def filter_body(self, lines):
+ lines = super(FilterLinesEnvironmentMixin, self).filter_body(lines)
+ if self.__strict_utf8:
+ lines = (line.decode(ENCODING, 'replace') for line in lines)
+ # Limit the line length in Unicode-space to avoid
+ # splitting characters:
+ if self.__emailmaxlinelength:
+ lines = limit_linelength(lines, self.__emailmaxlinelength)
+ lines = (line.encode(ENCODING, 'replace') for line in lines)
+ elif self.__emailmaxlinelength:
+ lines = limit_linelength(lines, self.__emailmaxlinelength)
+
+ return lines
+
+
+class ConfigFilterLinesEnvironmentMixin(
+ ConfigEnvironmentMixin,
+ FilterLinesEnvironmentMixin,
+ ):
+ """Handle encoding and maximum line length based on config."""
+
+ def __init__(self, config, **kw):
+ strict_utf8 = config.get_bool('emailstrictutf8', default=None)
+ if strict_utf8 is not None:
+ kw['strict_utf8'] = strict_utf8
+
+ emailmaxlinelength = config.get('emailmaxlinelength')
+ if emailmaxlinelength is not None:
+ kw['emailmaxlinelength'] = int(emailmaxlinelength)
+
+ super(ConfigFilterLinesEnvironmentMixin, self).__init__(
+ config=config, **kw
+ )
+
+
+class MaxlinesEnvironmentMixin(Environment):
+ """Limit the email body to a specified number of lines."""
+
+ def __init__(self, emailmaxlines, **kw):
+ super(MaxlinesEnvironmentMixin, self).__init__(**kw)
+ self.__emailmaxlines = emailmaxlines
+
+ def filter_body(self, lines):
+ lines = super(MaxlinesEnvironmentMixin, self).filter_body(lines)
+ if self.__emailmaxlines:
+ lines = limit_lines(lines, self.__emailmaxlines)
+ return lines
+
+
+class ConfigMaxlinesEnvironmentMixin(
+ ConfigEnvironmentMixin,
+ MaxlinesEnvironmentMixin,
+ ):
+ """Limit the email body to the number of lines specified in config."""
+
+ def __init__(self, config, **kw):
+ emailmaxlines = int(config.get('emailmaxlines', default='0'))
+ super(ConfigMaxlinesEnvironmentMixin, self).__init__(
+ config=config,
+ emailmaxlines=emailmaxlines,
+ **kw
+ )
+
+
+class PusherDomainEnvironmentMixin(ConfigEnvironmentMixin):
+ """Deduce pusher_email from pusher by appending an emaildomain."""
+
+ def __init__(self, **kw):
+ super(PusherDomainEnvironmentMixin, self).__init__(**kw)
+ self.__emaildomain = self.config.get('emaildomain')
+
+ def get_pusher_email(self):
+ if self.__emaildomain:
+ # Derive the pusher's full email address in the default way:
+ return '%s@%s' % (self.get_pusher(), self.__emaildomain)
+ else:
+ return super(PusherDomainEnvironmentMixin, self).get_pusher_email()
+
+
+class StaticRecipientsEnvironmentMixin(Environment):
+ """Set recipients statically based on constructor parameters."""
+
+ def __init__(
+ self,
+ refchange_recipients, announce_recipients, revision_recipients,
+ **kw
+ ):
+ super(StaticRecipientsEnvironmentMixin, self).__init__(**kw)
+
+ # The recipients for various types of notification emails, as
+ # RFC 2822 email addresses separated by commas (or the empty
+ # string if no recipients are configured). Although there is
+ # a mechanism to choose the recipient lists based on on the
+ # actual *contents* of the change being reported, we only
+ # choose based on the *type* of the change. Therefore we can
+ # compute them once and for all:
+ self.__refchange_recipients = refchange_recipients
+ self.__announce_recipients = announce_recipients
+ self.__revision_recipients = revision_recipients
+
+ def get_refchange_recipients(self, refchange):
+ return self.__refchange_recipients
+
+ def get_announce_recipients(self, annotated_tag_change):
+ return self.__announce_recipients
+
+ def get_revision_recipients(self, revision):
+ return self.__revision_recipients
+
+
+class ConfigRecipientsEnvironmentMixin(
+ ConfigEnvironmentMixin,
+ StaticRecipientsEnvironmentMixin
+ ):
+ """Determine recipients statically based on config."""
+
+ def __init__(self, config, **kw):
+ super(ConfigRecipientsEnvironmentMixin, self).__init__(
+ config=config,
+ refchange_recipients=self._get_recipients(
+ config, 'refchangelist', 'mailinglist',
+ ),
+ announce_recipients=self._get_recipients(
+ config, 'announcelist', 'refchangelist', 'mailinglist',
+ ),
+ revision_recipients=self._get_recipients(
+ config, 'commitlist', 'mailinglist',
+ ),
+ **kw
+ )
+
+ def _get_recipients(self, config, *names):
+ """Return the recipients for a particular type of message.
+
+ Return the list of email addresses to which a particular type
+ of notification email should be sent, by looking at the config
+ value for "multimailhook.$name" for each of names. Use the
+ value from the first name that is configured. The return
+ value is a (possibly empty) string containing RFC 2822 email
+ addresses separated by commas. If no configuration could be
+ found, raise a ConfigurationException."""
+
+ for name in names:
+ retval = config.get_recipients(name)
+ if retval is not None:
+ return retval
+ if len(names) == 1:
+ hint = 'Please set "%s.%s"' % (config.section, name)
+ else:
+ hint = (
+ 'Please set one of the following:\n "%s"'
+ % ('"\n "'.join('%s.%s' % (config.section, name) for name in names))
+ )
+
+ raise ConfigurationException(
+ 'The list of recipients for %s is not configured.\n%s' % (names[0], hint)
+ )
+
+
+class ProjectdescEnvironmentMixin(Environment):
+ """Make a "projectdesc" value available for templates.
+
+ By default, it is set to the first line of $GIT_DIR/description
+ (if that file is present and appears to be set meaningfully)."""
+
+ def __init__(self, **kw):
+ super(ProjectdescEnvironmentMixin, self).__init__(**kw)
+ self.COMPUTED_KEYS += ['projectdesc']
+
+ def get_projectdesc(self):
+ """Return a one-line descripition of the project."""
+
+ git_dir = get_git_dir()
+ try:
+ projectdesc = open(os.path.join(git_dir, 'description')).readline().strip()
+ if projectdesc and not projectdesc.startswith('Unnamed repository'):
+ return projectdesc
+ except IOError:
+ pass
+
+ return 'UNNAMED PROJECT'
+
+
+class GenericEnvironmentMixin(Environment):
+ def get_pusher(self):
+ return self.osenv.get('USER', 'unknown user')
+
+
+class GenericEnvironment(
+ ProjectdescEnvironmentMixin,
+ ConfigMaxlinesEnvironmentMixin,
+ ConfigFilterLinesEnvironmentMixin,
+ ConfigRecipientsEnvironmentMixin,
+ PusherDomainEnvironmentMixin,
+ ConfigOptionsEnvironmentMixin,
+ GenericEnvironmentMixin,
+ Environment,
+ ):
+ pass
+
+
+class GitoliteEnvironmentMixin(Environment):
+ def get_repo_shortname(self):
+ # The gitolite environment variable $GL_REPO is a pretty good
+ # repo_shortname (though it's probably not as good as a value
+ # the user might have explicitly put in his config).
+ return (
+ self.osenv.get('GL_REPO', None)
+ or super(GitoliteEnvironmentMixin, self).get_repo_shortname()
+ )
+
+ def get_pusher(self):
+ return self.osenv.get('GL_USER', 'unknown user')
+
+
+class GitoliteEnvironment(
+ ProjectdescEnvironmentMixin,
+ ConfigMaxlinesEnvironmentMixin,
+ ConfigFilterLinesEnvironmentMixin,
+ ConfigRecipientsEnvironmentMixin,
+ PusherDomainEnvironmentMixin,
+ ConfigOptionsEnvironmentMixin,
+ GitoliteEnvironmentMixin,
+ Environment,
+ ):
+ pass
+
+
+class Push(object):
+ """Represent an entire push (i.e., a group of ReferenceChanges).
+
+ It is easy to figure out what commits were added to a *branch* by
+ a Reference change:
+
+ git rev-list change.old..change.new
+
+ or removed from a *branch*:
+
+ git rev-list change.new..change.old
+
+ But it is not quite so trivial to determine which entirely new
+ commits were added to the *repository* by a push and which old
+ commits were discarded by a push. A big part of the job of this
+ class is to figure out these things, and to make sure that new
+ commits are only detailed once even if they were added to multiple
+ references.
+
+ The first step is to determine the "other" references--those
+ unaffected by the current push. They are computed by
+ Push._compute_other_ref_sha1s() by listing all references then
+ removing any affected by this push.
+
+ The commits contained in the repository before this push were
+
+ git rev-list other1 other2 other3 ... change1.old change2.old ...
+
+ Where "changeN.old" is the old value of one of the references
+ affected by this push.
+
+ The commits contained in the repository after this push are
+
+ git rev-list other1 other2 other3 ... change1.new change2.new ...
+
+ The commits added by this push are the difference between these
+ two sets, which can be written
+
+ git rev-list \
+ ^other1 ^other2 ... \
+ ^change1.old ^change2.old ... \
+ change1.new change2.new ...
+
+ The commits removed by this push can be computed by
+
+ git rev-list \
+ ^other1 ^other2 ... \
+ ^change1.new ^change2.new ... \
+ change1.old change2.old ...
+
+ The last point is that it is possible that other pushes are
+ occurring simultaneously to this one, so reference values can
+ change at any time. It is impossible to eliminate all race
+ conditions, but we reduce the window of time during which problems
+ can occur by translating reference names to SHA1s as soon as
+ possible and working with SHA1s thereafter (because SHA1s are
+ immutable)."""
+
+ # A map {(changeclass, changetype) : integer} specifying the order
+ # that reference changes will be processed if multiple reference
+ # changes are included in a single push. The order is significant
+ # mostly because new commit notifications are threaded together
+ # with the first reference change that includes the commit. The
+ # following order thus causes commits to be grouped with branch
+ # changes (as opposed to tag changes) if possible.
+ SORT_ORDER = dict(
+ (value, i) for (i, value) in enumerate([
+ (BranchChange, 'update'),
+ (BranchChange, 'create'),
+ (AnnotatedTagChange, 'update'),
+ (AnnotatedTagChange, 'create'),
+ (NonAnnotatedTagChange, 'update'),
+ (NonAnnotatedTagChange, 'create'),
+ (BranchChange, 'delete'),
+ (AnnotatedTagChange, 'delete'),
+ (NonAnnotatedTagChange, 'delete'),
+ (OtherReferenceChange, 'update'),
+ (OtherReferenceChange, 'create'),
+ (OtherReferenceChange, 'delete'),
+ ])
+ )
+
+ def __init__(self, changes):
+ self.changes = sorted(changes, key=self._sort_key)
+
+ # The SHA-1s of commits referred to by references unaffected
+ # by this push:
+ other_ref_sha1s = self._compute_other_ref_sha1s()
+
+ self._old_rev_exclusion_spec = self._compute_rev_exclusion_spec(
+ other_ref_sha1s.union(
+ change.old.sha1
+ for change in self.changes
+ if change.old.type in ['commit', 'tag']
+ )
+ )
+ self._new_rev_exclusion_spec = self._compute_rev_exclusion_spec(
+ other_ref_sha1s.union(
+ change.new.sha1
+ for change in self.changes
+ if change.new.type in ['commit', 'tag']
+ )
+ )
+
+ @classmethod
+ def _sort_key(klass, change):
+ return (klass.SORT_ORDER[change.__class__, change.change_type], change.refname,)
+
+ def _compute_other_ref_sha1s(self):
+ """Return the GitObjects referred to by references unaffected by this push."""
+
+ # The refnames being changed by this push:
+ updated_refs = set(
+ change.refname
+ for change in self.changes
+ )
+
+ # The SHA-1s of commits referred to by all references in this
+ # repository *except* updated_refs:
+ sha1s = set()
+ fmt = (
+ '%(objectname) %(objecttype) %(refname)\n'
+ '%(*objectname) %(*objecttype) %(refname)'
+ )
+ for line in read_git_lines(['for-each-ref', '--format=%s' % (fmt,)]):
+ (sha1, type, name) = line.split(' ', 2)
+ if sha1 and type == 'commit' and name not in updated_refs:
+ sha1s.add(sha1)
+
+ return sha1s
+
+ def _compute_rev_exclusion_spec(self, sha1s):
+ """Return an exclusion specification for 'git rev-list'.
+
+ git_objects is an iterable over GitObject instances. Return a
+ string that can be passed to the standard input of 'git
+ rev-list --stdin' to exclude all of the commits referred to by
+ git_objects."""
+
+ return ''.join(
+ ['^%s\n' % (sha1,) for sha1 in sorted(sha1s)]
+ )
+
+ def get_new_commits(self, reference_change=None):
+ """Return a list of commits added by this push.
+
+ Return a list of the object names of commits that were added
+ by the part of this push represented by reference_change. If
+ reference_change is None, then return a list of *all* commits
+ added by this push."""
+
+ if not reference_change:
+ new_revs = sorted(
+ change.new.sha1
+ for change in self.changes
+ if change.new
+ )
+ elif not reference_change.new.commit_sha1:
+ return []
+ else:
+ new_revs = [reference_change.new.commit_sha1]
+
+ cmd = ['rev-list', '--stdin'] + new_revs
+ return read_git_lines(cmd, input=self._old_rev_exclusion_spec)
+
+ def get_discarded_commits(self, reference_change):
+ """Return a list of commits discarded by this push.
+
+ Return a list of the object names of commits that were
+ entirely discarded from the repository by the part of this
+ push represented by reference_change."""
+
+ if not reference_change.old.commit_sha1:
+ return []
+ else:
+ old_revs = [reference_change.old.commit_sha1]
+
+ cmd = ['rev-list', '--stdin'] + old_revs
+ return read_git_lines(cmd, input=self._new_rev_exclusion_spec)
+
+ def send_emails(self, mailer, body_filter=None):
+ """Use send all of the notification emails needed for this push.
+
+ Use send all of the notification emails (including reference
+ change emails and commit emails) needed for this push. Send
+ the emails using mailer. If body_filter is not None, then use
+ it to filter the lines that are intended for the email
+ body."""
+
+ # The sha1s of commits that were introduced by this push.
+ # They will be removed from this set as they are processed, to
+ # guarantee that one (and only one) email is generated for
+ # each new commit.
+ unhandled_sha1s = set(self.get_new_commits())
+ for change in self.changes:
+ # Check if we've got anyone to send to
+ if not change.recipients:
+ sys.stderr.write(
+ '*** no recipients configured so no email will be sent\n'
+ '*** for %r update %s->%s\n'
+ % (change.refname, change.old.sha1, change.new.sha1,)
+ )
+ else:
+ sys.stderr.write('Sending notification emails to: %s\n' % (change.recipients,))
+ mailer.send(change.generate_email(self, body_filter), change.recipients)
+
+ sha1s = []
+ for sha1 in reversed(list(self.get_new_commits(change))):
+ if sha1 in unhandled_sha1s:
+ sha1s.append(sha1)
+ unhandled_sha1s.remove(sha1)
+
+ max_emails = change.environment.maxcommitemails
+ if max_emails and len(sha1s) > max_emails:
+ sys.stderr.write(
+ '*** Too many new commits (%d), not sending commit emails.\n' % len(sha1s)
+ + '*** Try setting multimailhook.maxCommitEmails to a greater value\n'
+ + '*** Currently, multimailhook.maxCommitEmails=%d\n' % max_emails
+ )
+ return
+
+ for (num, sha1) in enumerate(sha1s):
+ rev = Revision(change, GitObject(sha1), num=num+1, tot=len(sha1s))
+ if rev.recipients:
+ mailer.send(rev.generate_email(self, body_filter), rev.recipients)
+
+ # Consistency check:
+ if unhandled_sha1s:
+ sys.stderr.write(
+ 'ERROR: No emails were sent for the following new commits:\n'
+ ' %s\n'
+ % ('\n '.join(sorted(unhandled_sha1s)),)
+ )
+
+
+def run_as_post_receive_hook(environment, mailer):
+ changes = []
+ for line in sys.stdin:
+ (oldrev, newrev, refname) = line.strip().split(' ', 2)
+ changes.append(
+ ReferenceChange.create(environment, oldrev, newrev, refname)
+ )
+ push = Push(changes)
+ push.send_emails(mailer, body_filter=environment.filter_body)
+
+
+def run_as_update_hook(environment, mailer, refname, oldrev, newrev):
+ changes = [
+ ReferenceChange.create(
+ environment,
+ read_git_output(['rev-parse', '--verify', oldrev]),
+ read_git_output(['rev-parse', '--verify', newrev]),
+ refname,
+ ),
+ ]
+ push = Push(changes)
+ push.send_emails(mailer, body_filter=environment.filter_body)
+
+
+def choose_mailer(config, environment):
+ mailer = config.get('mailer', default='sendmail')
+
+ if mailer == 'smtp':
+ smtpserver = config.get('smtpserver', default='localhost')
+ mailer = SMTPMailer(
+ envelopesender=(environment.get_sender() or environment.get_fromaddr()),
+ smtpserver=smtpserver,
+ )
+ elif mailer == 'sendmail':
+ command = config.get('sendmailcommand')
+ if command:
+ command = shlex.split(command)
+ mailer = SendMailer(command=command, envelopesender=environment.get_sender())
+ else:
+ sys.stderr.write(
+ 'fatal: multimailhook.mailer is set to an incorrect value: "%s"\n' % mailer
+ + 'please use one of "smtp" or "sendmail".\n'
+ )
+ sys.exit(1)
+ return mailer
+
+
+KNOWN_ENVIRONMENTS = {
+ 'generic' : GenericEnvironmentMixin,
+ 'gitolite' : GitoliteEnvironmentMixin,
+ }
+
+
+def choose_environment(config, osenv=None, env=None, recipients=None):
+ if not osenv:
+ osenv = os.environ
+
+ environment_mixins = [
+ ProjectdescEnvironmentMixin,
+ ConfigMaxlinesEnvironmentMixin,
+ ConfigFilterLinesEnvironmentMixin,
+ PusherDomainEnvironmentMixin,
+ ConfigOptionsEnvironmentMixin,
+ ]
+ environment_kw = {
+ 'osenv' : osenv,
+ 'config' : config,
+ }
+
+ if not env:
+ env = config.get('environment')
+
+ if not env:
+ if 'GL_USER' in osenv and 'GL_REPO' in osenv:
+ env = 'gitolite'
+ else:
+ env = 'generic'
+
+ environment_mixins.append(KNOWN_ENVIRONMENTS[env])
+
+ if recipients:
+ environment_mixins.insert(0, StaticRecipientsEnvironmentMixin)
+ environment_kw['refchange_recipients'] = recipients
+ environment_kw['announce_recipients'] = recipients
+ environment_kw['revision_recipients'] = recipients
+ else:
+ environment_mixins.insert(0, ConfigRecipientsEnvironmentMixin)
+
+ environment_klass = type(
+ 'EffectiveEnvironment',
+ tuple(environment_mixins) + (Environment,),
+ {},
+ )
+ return environment_klass(**environment_kw)
+
+
+def main(args):
+ parser = optparse.OptionParser(
+ description=__doc__,
+ usage='%prog [OPTIONS]\n or: %prog [OPTIONS] REFNAME OLDREV NEWREV',
+ )
+
+ parser.add_option(
+ '--environment', '--env', action='store', type='choice',
+ choices=['generic', 'gitolite'], default=None,
+ help=(
+ 'Choose type of environment is in use. Default is taken from '
+ 'multimailhook.environment if set; otherwise "generic".'
+ ),
+ )
+ parser.add_option(
+ '--stdout', action='store_true', default=False,
+ help='Output emails to stdout rather than sending them.',
+ )
+ parser.add_option(
+ '--recipients', action='store', default=None,
+ help='Set list of email recipients for all types of emails.',
+ )
+ parser.add_option(
+ '--show-env', action='store_true', default=False,
+ help=(
+ 'Write to stderr the values determined for the environment '
+ '(intended for debugging purposes).'
+ ),
+ )
+
+ (options, args) = parser.parse_args(args)
+
+ config = Config('multimailhook')
+
+ try:
+ environment = choose_environment(
+ config, osenv=os.environ,
+ env=options.environment,
+ recipients=options.recipients,
+ )
+
+ if options.show_env:
+ sys.stderr.write('Environment values:\n')
+ for (k,v) in sorted(environment.get_values().items()):
+ sys.stderr.write(' %s : %r\n' % (k,v))
+ sys.stderr.write('\n')
+
+ if options.stdout:
+ mailer = OutputMailer(sys.stdout)
+ else:
+ mailer = choose_mailer(config, environment)
+
+ # Dual mode: if arguments were specified on the command line, run
+ # like an update hook; otherwise, run as a post-receive hook.
+ if args:
+ if len(args) != 3:
+ parser.error('Need zero or three non-option arguments')
+ (refname, oldrev, newrev) = args
+ run_as_update_hook(environment, mailer, refname, oldrev, newrev)
+ else:
+ run_as_post_receive_hook(environment, mailer)
+ except ConfigurationException, e:
+ sys.exit(str(e))
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
--- /dev/null
+#! /usr/bin/env python2
+
+"""Migrate a post-receive-email configuration to be usable with git_multimail.py.
+
+See README.migrate-from-post-receive-email for more information.
+
+"""
+
+import sys
+import optparse
+
+from git_multimail import CommandError
+from git_multimail import Config
+from git_multimail import read_output
+
+
+OLD_NAMES = [
+ 'mailinglist',
+ 'announcelist',
+ 'envelopesender',
+ 'emailprefix',
+ 'showrev',
+ 'emailmaxlines',
+ 'diffopts',
+ ]
+
+NEW_NAMES = [
+ 'environment',
+ 'reponame',
+ 'mailinglist',
+ 'refchangelist',
+ 'commitlist',
+ 'announcelist',
+ 'announceshortlog',
+ 'envelopesender',
+ 'administrator',
+ 'emailprefix',
+ 'emailmaxlines',
+ 'diffopts',
+ 'emaildomain',
+ ]
+
+
+INFO = """\
+
+SUCCESS!
+
+Your post-receive-email configuration has been converted to
+git-multimail format. Please see README and
+README.migrate-from-post-receive-email to learn about other
+git-multimail configuration possibilities.
+
+For example, git-multimail has the following new options with no
+equivalent in post-receive-email. You might want to read about them
+to see if they would be useful in your situation:
+
+"""
+
+
+def _check_old_config_exists(old):
+ """Check that at least one old configuration value is set."""
+
+ for name in OLD_NAMES:
+ if old.has_key(name):
+ return True
+
+ return False
+
+
+def _check_new_config_clear(new):
+ """Check that none of the new configuration names are set."""
+
+ retval = True
+ for name in NEW_NAMES:
+ if new.has_key(name):
+ if retval:
+ sys.stderr.write('INFO: The following configuration values already exist:\n\n')
+ sys.stderr.write(' "%s.%s"\n' % (new.section, name))
+ retval = False
+
+ return retval
+
+
+def erase_values(config, names):
+ for name in names:
+ if config.has_key(name):
+ try:
+ sys.stderr.write('...unsetting "%s.%s"\n' % (config.section, name))
+ config.unset_all(name)
+ except CommandError:
+ sys.stderr.write(
+ '\nWARNING: could not unset "%s.%s". '
+ 'Perhaps it is not set at the --local level?\n\n'
+ % (config.section, name)
+ )
+
+
+def is_section_empty(section, local):
+ """Return True iff the specified configuration section is empty.
+
+ Iff local is True, use the --local option when invoking 'git
+ config'."""
+
+ if local:
+ local_option = ['--local']
+ else:
+ local_option = []
+
+ try:
+ read_output(
+ ['git', 'config']
+ + local_option
+ + ['--get-regexp', '^%s\.' % (section,)]
+ )
+ except CommandError, e:
+ if e.retcode == 1:
+ # This means that no settings were found.
+ return True
+ else:
+ raise
+ else:
+ return False
+
+
+def remove_section_if_empty(section):
+ """If the specified configuration section is empty, delete it."""
+
+ try:
+ empty = is_section_empty(section, local=True)
+ except CommandError:
+ # Older versions of git do not support the --local option, so
+ # if the first attempt fails, try without --local.
+ try:
+ empty = is_section_empty(section, local=False)
+ except CommandError:
+ sys.stderr.write(
+ '\nINFO: If configuration section "%s.*" is empty, you might want '
+ 'to delete it.\n\n'
+ % (section,)
+ )
+ return
+
+ if empty:
+ sys.stderr.write('...removing section "%s.*"\n' % (section,))
+ read_output(['git', 'config', '--remove-section', section])
+ else:
+ sys.stderr.write(
+ '\nINFO: Configuration section "%s.*" still has contents. '
+ 'It will not be deleted.\n\n'
+ % (section,)
+ )
+
+
+def migrate_config(strict=False, retain=False, overwrite=False):
+ old = Config('hooks')
+ new = Config('multimailhook')
+ if not _check_old_config_exists(old):
+ sys.exit(
+ 'Your repository has no post-receive-email configuration. '
+ 'Nothing to do.'
+ )
+ if not _check_new_config_clear(new):
+ if overwrite:
+ sys.stderr.write('\nWARNING: Erasing the above values...\n\n')
+ erase_values(new, NEW_NAMES)
+ else:
+ sys.exit(
+ '\nERROR: Refusing to overwrite existing values. Use the --overwrite\n'
+ 'option to continue anyway.'
+ )
+
+ name = 'showrev'
+ if old.has_key(name):
+ msg = 'git-multimail does not support "%s.%s"' % (old.section, name,)
+ if strict:
+ sys.exit(
+ 'ERROR: %s.\n'
+ 'Please unset that value then try again, or run without --strict.'
+ % (msg,)
+ )
+ else:
+ sys.stderr.write('\nWARNING: %s (ignoring).\n\n' % (msg,))
+
+ for name in ['mailinglist', 'announcelist']:
+ if old.has_key(name):
+ sys.stderr.write(
+ '...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
+ )
+ new.set_recipients(name, old.get_recipients(name))
+
+ if strict:
+ sys.stderr.write(
+ '...setting "%s.commitlist" to the empty string\n' % (new.section,)
+ )
+ new.set_recipients('commitlist', '')
+ sys.stderr.write(
+ '...setting "%s.announceshortlog" to "true"\n' % (new.section,)
+ )
+ new.set('announceshortlog', 'true')
+
+ for name in ['envelopesender', 'emailmaxlines', 'diffopts']:
+ if old.has_key(name):
+ sys.stderr.write(
+ '...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
+ )
+ new.set(name, old.get(name))
+
+ name = 'emailprefix'
+ if old.has_key(name):
+ sys.stderr.write(
+ '...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name)
+ )
+ new.set(name, old.get(name))
+ elif strict:
+ sys.stderr.write(
+ '...setting "%s.%s" to "[SCM]" to preserve old subject lines\n'
+ % (new.section, name)
+ )
+ new.set(name, '[SCM]')
+
+ if not retain:
+ erase_values(old, OLD_NAMES)
+ remove_section_if_empty(old.section)
+
+ sys.stderr.write(INFO)
+ for name in NEW_NAMES:
+ if name not in OLD_NAMES:
+ sys.stderr.write(' "%s.%s"\n' % (new.section, name,))
+ sys.stderr.write('\n')
+
+
+def main(args):
+ parser = optparse.OptionParser(
+ description=__doc__,
+ usage='%prog [OPTIONS]',
+ )
+
+ parser.add_option(
+ '--strict', action='store_true', default=False,
+ help=(
+ 'Slavishly configure git-multimail as closely as possible to '
+ 'the post-receive-email configuration. Default is to turn '
+ 'on some new features that have no equivalent in post-receive-email.'
+ ),
+ )
+ parser.add_option(
+ '--retain', action='store_true', default=False,
+ help=(
+ 'Retain the post-receive-email configuration values. '
+ 'Default is to delete them after the new values are set.'
+ ),
+ )
+ parser.add_option(
+ '--overwrite', action='store_true', default=False,
+ help=(
+ 'Overwrite any existing git-multimail configuration settings. '
+ 'Default is to abort if such settings already exist.'
+ ),
+ )
+
+ (options, args) = parser.parse_args(args)
+
+ if args:
+ parser.error('Unexpected arguments: %s' % (' '.join(args),))
+
+ migrate_config(strict=options.strict, retain=options.retain, overwrite=options.overwrite)
+
+
+main(sys.argv[1:])
--- /dev/null
+#! /usr/bin/env python2
+
+"""Example post-receive hook based on git-multimail.
+
+This script is a simple example of a post-receive hook implemented
+using git_multimail.py as a Python module. It is intended to be
+customized before use; see the comments in the script to help you get
+started.
+
+It is possible to use git_multimail.py itself as a post-receive or
+update hook, configured via git config settings and/or command-line
+parameters. But for more flexibility, it can also be imported as a
+Python module by a custom post-receive script as done here. The
+latter has the following advantages:
+
+* The tool's behavior can be customized using arbitrary Python code,
+ without having to edit git_multimail.py.
+
+* Configuration settings can be read from other sources; for example,
+ user names and email addresses could be read from LDAP or from a
+ database. Or the settings can even be hardcoded in the importing
+ Python script, if this is preferred.
+
+This script is a very basic example of how to use git_multimail.py as
+a module. The comments below explain some of the points at which the
+script's behavior could be changed or customized.
+
+"""
+
+import sys
+import os
+
+# If necessary, add the path to the directory containing
+# git_multimail.py to the Python path as follows. (This is not
+# necessary if git_multimail.py is in the same directory as this
+# script):
+
+#LIBDIR = 'path/to/directory/containing/module'
+#sys.path.insert(0, LIBDIR)
+
+import git_multimail
+
+
+# It is possible to modify the output templates here; e.g.:
+
+#git_multimail.FOOTER_TEMPLATE = """\
+#
+#-- \n\
+#This email was generated by the wonderful git-multimail tool.
+#"""
+
+
+# Specify which "git config" section contains the configuration for
+# git-multimail:
+config = git_multimail.Config('multimailhook')
+
+
+# Select the type of environment:
+environment = git_multimail.GenericEnvironment(config=config)
+#environment = git_multimail.GitoliteEnvironment(config=config)
+
+
+# Choose the method of sending emails based on the git config:
+mailer = git_multimail.choose_mailer(config, environment)
+
+# Alternatively, you may hardcode the mailer using code like one of
+# the following:
+
+# Use "/usr/sbin/sendmail -t" to send emails. The envelopesender
+# argument is optional:
+#mailer = git_multimail.SendMailer(
+# command=['/usr/sbin/sendmail', '-t'],
+# envelopesender='git-repo@example.com',
+# )
+
+# Use Python's smtplib to send emails. Both arguments are required.
+#mailer = git_multimail.SMTPMailer(
+# envelopesender='git-repo@example.com',
+# # The smtpserver argument can also include a port number; e.g.,
+# # smtpserver='mail.example.com:25'
+# smtpserver='mail.example.com',
+# )
+
+# OutputMailer is intended only for testing; it writes the emails to
+# the specified file stream.
+#mailer = git_multimail.OutputMailer(sys.stdout)
+
+
+# Read changes from stdin and send notification emails:
+git_multimail.run_as_post_receive_hook(environment, mailer)
#
# Copyright (c) 2007 Andy Parkins
#
-# An example hook script to mail out commit update information. This hook
-# sends emails listing new revisions to the repository introduced by the
-# change being reported. The rule is that (for branch updates) each commit
-# will appear on one email and one email only.
+# An example hook script to mail out commit update information.
+#
+# NOTE: This script is no longer under active development. There
+# is another script, git-multimail, which is more capable and
+# configurable and is largely backwards-compatible with this script;
+# please see "contrib/hooks/multimail/". For instructions on how to
+# migrate from post-receive-email to git-multimail, please see
+# "README.migrate-from-post-receive-email" in that directory.
+#
+# This hook sends emails listing new revisions to the repository
+# introduced by the change being reported. The rule is that (for
+# branch updates) each commit will appear on one email and one email
+# only.
#
# This hook is stored in the contrib/hooks directory. Your distribution
# will have put this somewhere standard. You should make this script
--- /dev/null
+# 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]
--- /dev/null
+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
# 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
--- /dev/null
+#!/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" "$@"
--- /dev/null
+#!/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;
+}
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.
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
# 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
# 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;
}
########################## 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";
+ print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+ if ($url =~ /^https/) {
+ print STDERR "fatal: make sure '$url/api.php' is a valid page\n";
+ print STDERR "fatal: and the SSL certificate is correct.\n";
+ } else {
+ print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
}
+ print STDERR "fatal: (error " .
+ $mediawiki->{error}->{code} . ': ' .
+ $mediawiki->{error}->{details} . ")\n";
+ exit 1;
}
## Functions for listing pages on the remote wiki
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 {
# 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',
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 {
aplimit => 'max'
});
if (!defined($mw_pages)) {
- print STDERR "fatal: could not get the list of wiki pages.\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;
+ fatal_mw_error("get the list of wiki 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
titles => $titles,
});
if (!defined($mw_pages)) {
- print STDERR "fatal: could not query the list of wiki pages.\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;
+ fatal_mw_error("query the list of wiki 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;
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;
}
# $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;
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;
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);
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) {
@titles = @titles[($batch+1)..$#titles];
}
+ return;
}
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',
$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',
# 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};
$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;
}
# 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;
}
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 {
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 {
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;
}
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);
$path =~ s/\\/\\\\/g;
$path =~ s/"/\\"/g;
$path =~ s/\n/\\n/g;
- return '"' . $path . '"';
+ return qq("${path}");
}
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
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 {
# 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 {
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);
}
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++;
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})) {
}
if (!defined($result->{query}->{pages})) {
- die "Invalid revision $pagerevid.";
+ die "Invalid revision ${pagerevid}.\n";
}
my @result_pages = values(%{$result->{query}->{pages}});
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;
}
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->{'*'});
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);
}
}
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;
}
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,
}, {
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;
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) {
# 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',
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)) {
}
# 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 &&
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]);
} 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);
}
}
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) {
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
# 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',
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;
}
# 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);
}
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 = {
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;
}
# 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;
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
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 ||
# 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"
}
# 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
+++ /dev/null
-From: Ismail Dönmez <ismail@pardus.org.tr>
-
-Trying to build the documentation with docbook-xsl 1.73 may result in
-the following error. This patch fixes it.
-
-$ xmlto -m callouts.xsl man git-add.xml
-runtime error: file
-file:///usr/share/sgml/docbook/xsl-stylesheets-1.73.0/manpages/other.xsl line
-129 element call-template
-The called template 'read-character-map' was not found.
-
---- docbook-xsl-1.73.0/manpages/docbook.xsl.manpages-charmap 2007-07-23 16:24:23.000000000 +0100
-+++ docbook-xsl-1.73.0/manpages/docbook.xsl 2007-07-23 16:25:16.000000000 +0100
-@@ -37,6 +37,7 @@
- <xsl:include href="lists.xsl"/>
- <xsl:include href="endnotes.xsl"/>
- <xsl:include href="table.xsl"/>
-+ <xsl:include href="../common/charmap.xsl"/>
-
- <!-- * we rename the following just to avoid using params with "man" -->
- <!-- * prefixes in the table.xsl stylesheet (because that stylesheet -->
# or
# % git clone bzr::lp:myrepo
#
+# If you want to specify which branches you want track (per repo):
+# git config remote-bzr.branches 'trunk, devel, test'
+#
import sys
import bzrlib.transport
import bzrlib.errors
import bzrlib.ui
+import bzrlib.urlutils
+import bzrlib.branch
import sys
import os
NAME_RE = re.compile('^([^<>]+)')
AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
+EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)')
def die(msg, *args):
def gittz(tz):
return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
+def get_config(config):
+ cmd = ['git', 'config', '--get', config]
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ output, _ = process.communicate()
+ return output
+
class Marks:
def __init__(self, path):
return self.marks[rev]
def to_rev(self, mark):
- return self.rev_marks[mark]
+ return str(self.rev_marks[mark])
def next_mark(self):
self.last_mark += 1
self.last_mark = mark
def get_tip(self, branch):
- return self.tips.get(branch, None)
+ try:
+ return str(self.tips[branch])
+ except KeyError:
+ return None
def set_tip(self, branch, tip):
self.tips[branch] = tip
name = m.group(1)
mail = m.group(2).strip()
else:
- m = NAME_RE.match(user)
+ m = EMAIL_RE.match(user)
if m:
- name = m.group(1).strip()
+ name = m.group(1)
+ mail = m.group(2)
+ else:
+ m = NAME_RE.match(user)
+ if m:
+ name = m.group(1).strip()
+
+ if not name:
+ name = 'unknown'
+ if not mail:
+ mail = 'Unknown'
return '%s <%s>' % (name, mail)
return final
-def export_branch(branch, name):
+def export_branch(repo, name):
global prefix
ref = '%s/heads/%s' % (prefix, name)
tip = marks.get_tip(name)
+ branch = get_remote_branch(name)
repo = branch.repository
- repo.lock_read()
+
+ branch.lock_read()
revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
- count = 0
+ try:
+ tip_revno = branch.revision_id_to_revno(tip)
+ last_revno, _ = branch.last_revision_info()
+ total = last_revno - tip_revno
+ except bzrlib.errors.NoSuchRevision:
+ tip_revno = 0
+ total = 0
- revs = [revid for revid, _, _, _ in revs if not marks.is_marked(revid)]
+ for revid, _, seq, _ in revs:
- for revid in revs:
+ if marks.is_marked(revid):
+ continue
rev = repo.get_revision(revid)
+ revno = seq[0]
parents = rev.parent_ids
time = rev.timestamp
print "M %s :%u %s" % f
print
- count += 1
- if (count % 100 == 0):
- print "progress revision %s (%d/%d)" % (revid, count, len(revs))
- print "#############################################################"
+ if len(seq) > 1:
+ # let's skip branch revisions from the progress report
+ continue
+
+ progress = (revno - tip_revno)
+ if (progress % 100 == 0):
+ if total:
+ print "progress revision %d '%s' (%d/%d)" % (revno, name, progress, total)
+ else:
+ print "progress revision %d '%s' (%d)" % (revno, name, progress)
- repo.unlock()
+ branch.unlock()
revid = branch.last_revision()
def do_import(parser):
global dirname
- branch = parser.repo
+ repo = parser.repo
path = os.path.join(dirname, 'marks-git')
print "feature done"
if os.path.exists(path):
print "feature import-marks=%s" % path
print "feature export-marks=%s" % path
+ print "feature force"
sys.stdout.flush()
while parser.check('import'):
ref = parser[1]
if ref.startswith('refs/heads/'):
name = ref[len('refs/heads/'):]
- export_branch(branch, name)
+ export_branch(repo, name)
if ref.startswith('refs/tags/'):
name = ref[len('refs/tags/'):]
- export_tag(branch, name)
+ export_tag(repo, name)
parser.next()
print 'done'
class CustomTree():
- def __init__(self, repo, revid, parents, files):
+ def __init__(self, branch, revid, parents, files):
global files_cache
- self.repo = repo
- self.revid = revid
- self.parents = parents
self.updates = {}
+ self.branch = branch
def copy_tree(revid):
files = files_cache[revid] = {}
- tree = repo.repository.revision_tree(revid)
- repo.lock_read()
+ branch.lock_read()
+ tree = branch.repository.revision_tree(revid)
try:
for path, entry in tree.iter_entries_by_dir():
- files[path] = entry.file_id
+ files[path] = [entry.file_id, None]
finally:
- repo.unlock()
+ branch.unlock()
return files
if len(parents) == 0:
self.base_files = copy_tree(self.base_id)
self.files = files_cache[revid] = self.base_files.copy()
+ self.rev_files = {}
+
+ for path, data in self.files.iteritems():
+ fid, mark = data
+ self.rev_files[fid] = [path, mark]
for path, f in files.iteritems():
- fid = self.files.get(path, None)
+ fid, mark = self.files.get(path, [None, None])
if not fid:
fid = bzrlib.generate_ids.gen_file_id(path)
f['path'] = path
+ self.rev_files[fid] = [path, mark]
self.updates[fid] = f
def last_revision(self):
changes = []
def get_parent(dirname, basename):
- parent_fid = self.base_files.get(dirname, None)
+ parent_fid, mark = self.base_files.get(dirname, [None, None])
if parent_fid:
return parent_fid
- parent_fid = self.files.get(dirname, None)
+ parent_fid, mark = self.files.get(dirname, [None, None])
if parent_fid:
return parent_fid
if basename == '':
return None
fid = bzrlib.generate_ids.gen_file_id(path)
- d = add_entry(fid, dirname, 'directory')
+ add_entry(fid, dirname, 'directory')
return fid
def add_entry(fid, path, kind, mode = None):
(None, basename),
(None, kind),
(None, executable))
- self.files[path] = change[0]
+ self.files[path] = [change[0], None]
changes.append(change)
- return change
def update_entry(fid, path, kind, mode = None):
dirname, basename = os.path.split(path)
(None, basename),
(None, kind),
(None, executable))
- self.files[path] = change[0]
+ self.files[path] = [change[0], None]
changes.append(change)
- return change
def remove_entry(fid, path, kind):
dirname, basename = os.path.split(path)
(None, None))
del self.files[path]
changes.append(change)
- return change
for fid, f in self.updates.iteritems():
path = f['path']
else:
add_entry(fid, path, 'file', f['mode'])
+ self.files[path][1] = f['mark']
+ self.rev_files[fid][1] = f['mark']
+
return changes
+ def get_content(self, file_id):
+ path, mark = self.rev_files[file_id]
+ if mark:
+ return blob_marks[mark]
+
+ # last resort
+ tree = self.branch.repository.revision_tree(self.base_id)
+ return tree.get_file_text(file_id)
+
def get_file_with_stat(self, file_id, path=None):
- return (StringIO.StringIO(self.updates[file_id]['data']), None)
+ content = self.get_content(file_id)
+ return (StringIO.StringIO(content), None)
def get_symlink_target(self, file_id):
- return self.updates[file_id]['data']
+ return self.get_content(file_id)
+
+ def id2path(self, file_id):
+ path, mark = self.rev_files[file_id]
+ return path
def c_style_unescape(string):
if string[0] == string[-1] == '"':
ref = parser[1]
parser.next()
- if ref != 'refs/heads/master':
- die("bzr doesn't support multiple branches; use 'master'")
+ if ref.startswith('refs/heads/'):
+ name = ref[len('refs/heads/'):]
+ branch = get_remote_branch(name)
+ else:
+ die('unknown ref')
commit_mark = parser.get_mark()
parser.next()
if parser.check('M'):
t, m, mark_ref, path = line.split(' ', 3)
mark = int(mark_ref[1:])
- f = { 'mode' : m, 'data' : blob_marks[mark] }
+ f = { 'mode' : m, 'mark' : mark }
elif parser.check('D'):
- t, path = line.split(' ')
+ t, path = line.split(' ', 1)
f = { 'deleted' : True }
else:
die('Unknown file command: %s' % line)
path = c_style_unescape(path).decode('utf-8')
files[path] = f
- repo = parser.repo
-
committer, date, tz = committer
- parents = [str(mark_to_rev(p)) for p in parents]
+ parents = [mark_to_rev(p) for p in parents]
revid = bzrlib.generate_ids.gen_revision_id(committer, date)
props = {}
- props['branch-nick'] = repo.nick
+ props['branch-nick'] = branch.nick
- mtree = CustomTree(repo, revid, parents, files)
+ mtree = CustomTree(branch, revid, parents, files)
changes = mtree.iter_changes()
- repo.lock_write()
+ branch.lock_write()
try:
- builder = repo.get_commit_builder(parents, None, date, tz, committer, props, revid)
+ builder = branch.get_commit_builder(parents, None, date, tz, committer, props, revid)
try:
list(builder.record_iter_changes(mtree, mtree.last_revision(), changes))
builder.finish_inventory()
builder.abort()
raise
finally:
- repo.unlock()
+ branch.unlock()
parsed_refs[ref] = revid
marks.new_mark(revid, commit_mark)
ref = parser[1]
parser.next()
- if ref != 'refs/heads/master':
- die("bzr doesn't support multiple branches; use 'master'")
-
# ugh
if parser.check('commit'):
parse_commit(parser)
parsed_refs[ref] = mark_to_rev(from_mark)
def do_export(parser):
- global parsed_refs, dirname, peer
+ global parsed_refs, dirname
parser.next()
else:
die('unhandled export command: %s' % line)
- repo = parser.repo
-
for ref, revid in parsed_refs.iteritems():
- if ref == 'refs/heads/master':
- repo.generate_revision_history(revid, marks.get_tip('master'))
- if peer:
+ if ref.startswith('refs/heads/'):
+ name = ref[len('refs/heads/'):]
+ branch = get_remote_branch(name)
+ branch.generate_revision_history(revid, marks.get_tip(name))
+
+ if name in peers:
+ peer = bzrlib.branch.Branch.open(peers[name])
try:
- repo.push(peer, stop_revision=revid)
+ peer.bzrdir.push_branch(branch, revision_id=revid)
except bzrlib.errors.DivergedBranches:
print "error %s non-fast forward" % ref
continue
- else:
- wt = repo.bzrdir.open_workingtree()
+
+ try:
+ wt = branch.bzrdir.open_workingtree()
wt.update()
+ except bzrlib.errors.NoWorkingTree:
+ pass
+ elif ref.startswith('refs/tags/'):
+ # TODO: implement tag push
+ print "error %s pushing tags not supported" % ref
+ continue
+ else:
+ # transport-helper/fast-export bugs
+ continue
+
print "ok %s" % ref
print
def do_list(parser):
global tags
- print "? refs/heads/%s" % 'master'
- branch = parser.repo
+ master_branch = None
+
+ for name in branches:
+ if not master_branch:
+ master_branch = name
+ print "? refs/heads/%s" % name
+
+ branch = get_remote_branch(master_branch)
branch.lock_read()
for tag, revid in branch.tags.get_tag_dict().items():
try:
print "? refs/tags/%s" % tag
tags[tag] = revid
branch.unlock()
- print "@refs/heads/%s HEAD" % 'master'
+
+ print "@refs/heads/%s HEAD" % master_branch
print
+def clone(path, remote_branch):
+ try:
+ bdir = bzrlib.bzrdir.BzrDir.create(path)
+ except bzrlib.errors.AlreadyControlDirError:
+ bdir = bzrlib.bzrdir.BzrDir.open(path)
+ repo = bdir.find_repository()
+ repo.fetch(remote_branch.repository)
+ return remote_branch.sprout(bdir, repository=repo)
+
+def get_remote_branch(name):
+ global dirname, branches
+
+ remote_branch = bzrlib.branch.Branch.open(branches[name])
+ if isinstance(remote_branch.user_transport, bzrlib.transport.local.LocalTransport):
+ return remote_branch
+
+ branch_path = os.path.join(dirname, 'clone', name)
+
+ try:
+ branch = bzrlib.branch.Branch.open(branch_path)
+ except bzrlib.errors.NotBranchError:
+ # clone
+ branch = clone(branch_path, remote_branch)
+ else:
+ # pull
+ try:
+ branch.pull(remote_branch, overwrite=True)
+ except bzrlib.errors.DivergedBranches:
+ # use remote branch for now
+ return remote_branch
+
+ return branch
+
+def find_branches(repo):
+ transport = repo.bzrdir.root_transport
+
+ for fn in transport.iter_files_recursive():
+ if not fn.endswith('.bzr/branch-format'):
+ continue
+
+ name = subdir = fn[:-len('/.bzr/branch-format')]
+ name = name if name != '' else 'master'
+ name = name.replace('/', '+')
+
+ try:
+ cur = transport.clone(subdir)
+ branch = bzrlib.branch.Branch.open_from_transport(cur)
+ except bzrlib.errors.NotBranchError:
+ continue
+ else:
+ yield name, branch.base
+
def get_repo(url, alias):
- global dirname, peer
+ global dirname, peer, branches
+ normal_url = bzrlib.urlutils.normalize_url(url)
origin = bzrlib.bzrdir.BzrDir.open(url)
- branch = origin.open_branch()
+ is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
+
+ shared_path = os.path.join(gitdir, 'bzr')
+ try:
+ shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path)
+ except bzrlib.errors.NotBranchError:
+ shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path)
+ try:
+ shared_repo = shared_dir.open_repository()
+ except bzrlib.errors.NoRepositoryPresent:
+ shared_repo = shared_dir.create_repository(shared=True)
- if not isinstance(origin.transport, bzrlib.transport.local.LocalTransport):
+ if not is_local:
clone_path = os.path.join(dirname, 'clone')
- remote_branch = branch
- if os.path.exists(clone_path):
- # pull
- d = bzrlib.bzrdir.BzrDir.open(clone_path)
- branch = d.open_branch()
- result = branch.pull(remote_branch, [], None, False)
+ if not os.path.exists(clone_path):
+ os.mkdir(clone_path)
else:
- # clone
- d = origin.sprout(clone_path, None,
- hardlink=True, create_tree_if_local=False,
- source_branch=remote_branch)
- branch = d.open_branch()
- branch.bind(remote_branch)
-
- peer = remote_branch
+ # check and remove old organization
+ try:
+ bdir = bzrlib.bzrdir.BzrDir.open(clone_path)
+ bdir.destroy_repository()
+ except bzrlib.errors.NotBranchError:
+ pass
+ except bzrlib.errors.NoRepositoryPresent:
+ pass
+
+ wanted = get_config('remote-bzr.branches').rstrip().split(', ')
+ # stupid python
+ wanted = [e for e in wanted if e]
+
+ if not wanted:
+ try:
+ repo = origin.open_repository()
+ if not repo.user_transport.listable():
+ # this repository is not usable for us
+ raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
+ except bzrlib.errors.NoRepositoryPresent:
+ wanted = ['master']
+
+ if wanted:
+ def list_wanted(url, wanted):
+ for name in wanted:
+ subdir = name if name != 'master' else ''
+ yield name, bzrlib.urlutils.join(url, subdir)
+
+ branch_list = list_wanted(url, wanted)
else:
- peer = None
+ branch_list = find_branches(repo)
- return branch
+ for name, url in branch_list:
+ if not is_local:
+ peers[name] = url
+ branches[name] = url
+
+ return origin
def fix_path(alias, orig_url):
url = urlparse.urlparse(orig_url, 'file')
subprocess.call(cmd)
def main(args):
- global marks, prefix, dirname
+ global marks, prefix, gitdir, dirname
global tags, filenodes
global blob_marks
global parsed_refs
global files_cache
global is_tmp
+ global branches, peers
alias = args[1]
url = args[2]
parsed_refs = {}
files_cache = {}
marks = None
+ branches = {}
+ peers = {}
if alias[5:] == url:
is_tmp = True
if not os.path.exists(dirname):
os.makedirs(dirname)
- bzrlib.ui.ui_factory.be_quiet(True)
+ if hasattr(bzrlib.ui.ui_factory, 'be_quiet'):
+ bzrlib.ui.ui_factory.be_quiet(True)
repo = get_repo(url, alias)
# For remote repositories a local clone is stored in
# "$GIT_DIR/hg/origin/clone/.hg/".
-from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions
+from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions, discovery, util
import re
import sys
import atexit
import urlparse, hashlib
-#
-# If you want to switch to hg-git compatibility mode:
-# git config --global remote-hg.hg-git-compat true
#
# If you are not in hg-git-compat mode and want to disable the tracking of
# named branches:
# git config --global remote-hg.track-branches false
#
-# If you don't want to force pushes (and thus risk creating new remote heads):
-# git config --global remote-hg.force-push false
-#
# If you want the equivalent of hg's clone/pull--insecure option:
-# git config remote-hg.insecure true
+# git config --global remote-hg.insecure true
+#
+# If you want to switch to hg-git compatibility mode:
+# git config --global remote-hg.hg-git-compat true
#
# git:
# Sensible defaults for git.
AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
+VERSION = 2
+
def die(msg, *args):
sys.stderr.write('ERROR: %s\n' % (msg % args))
sys.exit(1)
m = { '100755': 'x', '120000': 'l' }
return m.get(mode, '')
-def hghex(node):
- return hg.node.hex(node)
+def hghex(n):
+ return node.hex(n)
+
+def hgbin(n):
+ return node.bin(n)
def hgref(ref):
return ref.replace('___', ' ')
def gitref(ref):
return ref.replace(' ', '___')
+def check_version(*check):
+ if not hg_version:
+ return True
+ return hg_version >= check
+
def get_config(config):
cmd = ['git', 'config', '--get', config]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
output, _ = process.communicate()
return output
+def get_config_bool(config, default=False):
+ value = get_config(config).rstrip('\n')
+ if value == "true":
+ return True
+ elif value == "false":
+ return False
+ else:
+ return default
+
class Marks:
- def __init__(self, path):
+ def __init__(self, path, repo):
self.path = path
+ self.repo = repo
+ self.clear()
+ self.load()
+
+ if self.version < VERSION:
+ if self.version == 1:
+ self.upgrade_one()
+
+ # upgraded?
+ if self.version < VERSION:
+ self.clear()
+ self.version = VERSION
+
+ def clear(self):
self.tips = {}
self.marks = {}
self.rev_marks = {}
self.last_mark = 0
-
- self.load()
+ self.version = 0
def load(self):
if not os.path.exists(self.path):
self.tips = tmp['tips']
self.marks = tmp['marks']
self.last_mark = tmp['last-mark']
+ self.version = tmp.get('version', 1)
for rev, mark in self.marks.iteritems():
- self.rev_marks[mark] = int(rev)
+ self.rev_marks[mark] = rev
+
+ def upgrade_one(self):
+ def get_id(rev):
+ return hghex(self.repo.changelog.node(int(rev)))
+ self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
+ self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
+ self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
+ self.version = 2
def dict(self):
- return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
+ return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version }
def store(self):
json.dump(self.dict(), open(self.path, 'w'))
return str(self.dict())
def from_rev(self, rev):
- return self.marks[str(rev)]
+ return self.marks[rev]
def to_rev(self, mark):
- return self.rev_marks[mark]
+ return str(self.rev_marks[mark])
def next_mark(self):
self.last_mark += 1
def get_mark(self, rev):
self.last_mark += 1
- self.marks[str(rev)] = self.last_mark
+ self.marks[rev] = self.last_mark
return self.last_mark
def new_mark(self, rev, mark):
- self.marks[str(rev)] = mark
+ self.marks[rev] = mark
self.rev_marks[mark] = rev
self.last_mark = mark
def is_marked(self, rev):
- return str(rev) in self.marks
+ return rev in self.marks
def get_tip(self, branch):
- return self.tips.get(branch, 0)
+ return str(self.tips[branch])
def set_tip(self, branch, tip):
self.tips[branch] = tip
removed = set()
# load earliest manifest first for caching reasons
- prev = repo[parent].manifest().copy()
+ prev = parent.manifest().copy()
cur = ctx.manifest()
for fn in cur:
return '%s <%s>' % (name, mail)
+def updatebookmarks(repo, peer):
+ remotemarks = peer.listkeys('bookmarks')
+ localmarks = repo._bookmarks
+
+ if not remotemarks:
+ return
+
+ for k, v in remotemarks.iteritems():
+ localmarks[k] = hgbin(v)
+
+ if hasattr(localmarks, 'write'):
+ localmarks.write()
+ else:
+ bookmarks.write(repo)
+
def get_repo(url, alias):
global dirname, peer
myui.setconfig('ui', 'interactive', 'off')
myui.fout = sys.stderr
- try:
- if get_config('remote-hg.insecure') == 'true\n':
- myui.setconfig('web', 'cacerts', '')
- except subprocess.CalledProcessError:
- pass
+ if get_config_bool('remote-hg.insecure'):
+ myui.setconfig('web', 'cacerts', '')
- try:
- mod = extensions.load(myui, 'hgext.schemes', None)
- mod.extsetup(myui)
- except ImportError:
- pass
+ extensions.loadall(myui)
- if hg.islocal(url):
+ if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
repo = hg.repository(myui, url)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
else:
- local_path = os.path.join(dirname, 'clone')
- if not os.path.exists(local_path):
- try:
- peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True)
- except:
- die('Repository error')
- repo = dstpeer.local()
- else:
- repo = hg.repository(myui, local_path)
+ shared_path = os.path.join(gitdir, 'hg')
+ if not os.path.exists(shared_path):
try:
- peer = hg.peer(myui, {}, url)
+ hg.clone(myui, {}, url, shared_path, update=False, pull=True)
except:
die('Repository error')
- repo.pull(peer, heads=None, force=True)
+
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+ local_path = os.path.join(dirname, 'clone')
+ if not os.path.exists(local_path):
+ hg.share(myui, shared_path, local_path, update=False)
+
+ repo = hg.repository(myui, local_path)
+ try:
+ peer = hg.peer(myui, {}, url)
+ except:
+ die('Repository error')
+ repo.pull(peer, heads=None, force=True)
+
+ updatebookmarks(repo, peer)
return repo
def rev_to_mark(rev):
global marks
- return marks.from_rev(rev)
+ return marks.from_rev(rev.hex())
def mark_to_rev(mark):
global marks
global prefix, marks, mode
ename = '%s/%s' % (kind, name)
- tip = marks.get_tip(ename)
+ try:
+ tip = marks.get_tip(ename)
+ tip = repo[tip].rev()
+ except:
+ tip = 0
revs = xrange(tip, head.rev() + 1)
- count = 0
-
- revs = [rev for rev in revs if not marks.is_marked(rev)]
+ total = len(revs)
for rev in revs:
c = repo[rev]
- (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
+ node = c.node()
+
+ if marks.is_marked(c.hex()):
+ continue
+
+ (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
rev_branch = extra['branch']
author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
else:
committer = author
- parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
+ parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
if len(parents) == 0:
modified = c.manifest().keys()
modified_final = export_files(c.filectx(f) for f in modified)
print "commit %s/%s" % (prefix, ename)
- print "mark :%d" % (marks.get_mark(rev))
+ print "mark :%d" % (marks.get_mark(c.hex()))
print "author %s" % (author)
print "committer %s" % (committer)
print "data %d" % (len(desc))
if len(parents) > 1:
print "merge :%s" % (rev_to_mark(parents[1]))
- for f in modified_final:
- print "M %s :%u %s" % f
for f in removed:
print "D %s" % (fix_file_path(f))
+ for f in modified_final:
+ print "M %s :%u %s" % f
print
- count += 1
- if (count % 100 == 0):
- print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
- print "#############################################################"
+ progress = (rev - tip)
+ if (progress % 100 == 0):
+ print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
# make sure the ref is updated
print "reset %s/%s" % (prefix, ename)
- print "from :%u" % rev_to_mark(rev)
+ print "from :%u" % rev_to_mark(head)
print
- marks.set_tip(ename, rev)
+ marks.set_tip(ename, head.hex())
def export_tag(repo, tag):
export_ref(repo, tag, 'tags', repo[hgref(tag)])
if os.path.exists(path):
print "*import-marks %s" % path
print "*export-marks %s" % path
+ print "option"
print
-def branch_tip(repo, branch):
- # older versions of mercurial don't have this
- if hasattr(repo, 'branchtip'):
- return repo.branchtip(branch)
- else:
- return repo.branchtags()[branch]
+def branch_tip(branch):
+ return branches[branch][-1]
def get_branch_tip(repo, branch):
global branches
# verify there's only one head
if (len(heads) > 1):
warn("Branch '%s' has more than one head, consider merging" % branch)
- return branch_tip(repo, hgref(branch))
+ return branch_tip(hgref(branch))
return heads[0]
def list_head(repo, cur):
- global g_head, bmarks
+ global g_head, bmarks, fake_bmark
- head = bookmarks.readcurrent(repo)
- if head:
- node = repo[head]
- else:
- # fake bookmark from current branch
- head = cur
- node = repo['.']
- if not node:
- node = repo['tip']
- if not node:
- return
- if head == 'default':
- head = 'master'
- bmarks[head] = node
+ if 'default' not in branches:
+ # empty repo
+ return
+
+ node = repo[branch_tip('default')]
+ head = 'master' if not 'master' in bmarks else 'default'
+ fake_bmark = head
+ bmarks[head] = node
head = gitref(head)
print "@refs/heads/%s HEAD" % head
g_head = (head, node)
def do_list(parser):
- global branches, bmarks, mode, track_branches
+ global branches, bmarks, track_branches
repo = parser.repo
for bmark, node in bookmarks.listbookmarks(repo).iteritems():
bmarks[bmark] = repo[node]
cur = repo.dirstate.branch()
+ orig = peer if peer else repo
+
+ for branch, heads in orig.branchmap().iteritems():
+ # only open heads
+ heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
+ if heads:
+ branches[branch] = heads
list_head(repo, cur)
if track_branches:
- for branch in repo.branchmap():
- heads = repo.branchheads(branch)
- if len(heads):
- branches[branch] = heads
-
for branch in branches:
print "? refs/heads/branches/%s" % gitref(branch)
if os.path.exists(path):
print "feature import-marks=%s" % path
print "feature export-marks=%s" % path
+ print "feature force"
sys.stdout.flush()
tmp = encoding.encoding
die('Unknown file command: %s' % line)
files[path] = f
+ # only export the commits if we are on an internal proxy repo
+ if dry_run and not peer:
+ parsed_refs[ref] = None
+ return
+
def getfilectx(repo, memctx, f):
of = files[f]
if 'deleted' in of:
extra['committer'] = "%s %u %u" % committer
if from_mark:
- p1 = repo.changelog.node(mark_to_rev(from_mark))
+ p1 = mark_to_rev(from_mark)
else:
- p1 = '\0' * 20
+ p1 = '0' * 40
if merge_mark:
- p2 = repo.changelog.node(mark_to_rev(merge_mark))
+ p2 = mark_to_rev(merge_mark)
else:
- p2 = '\0' * 20
+ p2 = '0' * 40
#
# If files changed from any of the parents, hg wants to know, but in git if
tmp = encoding.encoding
encoding.encoding = 'utf-8'
- node = repo.commitctx(ctx)
+ node = hghex(repo.commitctx(ctx))
encoding.encoding = tmp
- rev = repo[node].rev()
-
parsed_refs[ref] = node
- marks.new_mark(rev, commit_mark)
+ marks.new_mark(node, commit_mark)
def parse_reset(parser):
global parsed_refs
from_mark = parser.get_mark()
parser.next()
- node = parser.repo.changelog.node(mark_to_rev(from_mark))
- parsed_refs[ref] = node
+ try:
+ rev = mark_to_rev(from_mark)
+ except KeyError:
+ rev = None
+ parsed_refs[ref] = rev
def parse_tag(parser):
name = parser[1]
def write_tag(repo, tag, node, msg, author):
branch = repo[node].branch()
- tip = branch_tip(repo, branch)
+ tip = branch_tip(branch)
tip = repo[tip]
def getfilectx(repo, memctx, f):
data = fctx.data()
except error.ManifestLookupError:
data = ""
- content = data + "%s %s\n" % (hghex(node), tag)
+ content = data + "%s %s\n" % (node, tag)
return context.memfilectx(f, content, False, False, None)
p1 = tip.hex()
- p2 = '\0' * 20
- if not author:
- author = (None, 0, 0)
- user, date, tz = author
+ p2 = '0' * 40
+ if author:
+ user, date, tz = author
+ date_tz = (date, tz)
+ else:
+ cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ output, _ = process.communicate()
+ m = re.match('^.* <.*>', output)
+ if m:
+ user = m.group(0)
+ else:
+ user = repo.ui.username()
+ date_tz = None
ctx = context.memctx(repo, (p1, p2), msg,
['.hgtags'], getfilectx,
- user, (date, tz), {'branch' : branch})
+ user, date_tz, {'branch' : branch})
tmp = encoding.encoding
encoding.encoding = 'utf-8'
encoding.encoding = tmp
- return tagnode
+ return (tagnode, branch)
+
+def checkheads_bmark(repo, ref, ctx):
+ bmark = ref[len('refs/heads/'):]
+ if not bmark in bmarks:
+ # new bmark
+ return True
+
+ ctx_old = bmarks[bmark]
+ ctx_new = ctx
+ if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
+ if force_push:
+ print "ok %s forced update" % ref
+ else:
+ print "error %s non-fast forward" % ref
+ return False
+
+ return True
+
+def checkheads(repo, remote, p_revs):
+
+ remotemap = remote.branchmap()
+ if not remotemap:
+ # empty repo
+ return True
+
+ new = {}
+ ret = True
+
+ for node, ref in p_revs.iteritems():
+ ctx = repo[node]
+ branch = ctx.branch()
+ if not branch in remotemap:
+ # new branch
+ continue
+ if not ref.startswith('refs/heads/branches'):
+ if ref.startswith('refs/heads/'):
+ if not checkheads_bmark(repo, ref, ctx):
+ ret = False
+
+ # only check branches
+ continue
+ new.setdefault(branch, []).append(ctx.rev())
+
+ for branch, heads in new.iteritems():
+ old = [repo.changelog.rev(x) for x in remotemap[branch]]
+ for rev in heads:
+ if check_version(2, 3):
+ ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
+ else:
+ ancestors = repo.changelog.ancestors(rev)
+ found = False
+
+ for x in old:
+ if x in ancestors:
+ found = True
+ break
+
+ if found:
+ continue
+
+ node = repo.changelog.node(rev)
+ ref = p_revs[node]
+ if force_push:
+ print "ok %s forced update" % ref
+ else:
+ print "error %s non-fast forward" % ref
+ ret = False
+
+ return ret
+
+def push_unsafe(repo, remote, parsed_refs, p_revs):
+
+ force = force_push
+
+ fci = discovery.findcommonincoming
+ commoninc = fci(repo, remote, force=force)
+ common, _, remoteheads = commoninc
+
+ if not checkheads(repo, remote, p_revs):
+ return None
+
+ cg = repo.getbundle('push', heads=list(p_revs), common=common)
+
+ unbundle = remote.capable('unbundle')
+ if unbundle:
+ if force:
+ remoteheads = ['force']
+ return remote.unbundle(cg, remoteheads, 'push')
+ else:
+ return remote.addchangegroup(cg, 'push', repo.url())
+
+def push(repo, remote, parsed_refs, p_revs):
+ if hasattr(remote, 'canpush') and not remote.canpush():
+ print "error cannot push"
+
+ if not p_revs:
+ # nothing to push
+ return
+
+ lock = None
+ unbundle = remote.capable('unbundle')
+ if not unbundle:
+ lock = remote.lock()
+ try:
+ ret = push_unsafe(repo, remote, parsed_refs, p_revs)
+ finally:
+ if lock is not None:
+ lock.release()
+
+ return ret
+
+def check_tip(ref, kind, name, heads):
+ try:
+ ename = '%s/%s' % (kind, name)
+ tip = marks.get_tip(ename)
+ except KeyError:
+ return True
+ else:
+ return tip in heads
def do_export(parser):
global parsed_refs, bmarks, peer
p_bmarks = []
+ p_revs = {}
parser.next()
else:
die('unhandled export command: %s' % line)
+ need_fetch = False
+
for ref, node in parsed_refs.iteritems():
+ bnode = hgbin(node) if node else None
if ref.startswith('refs/heads/branches'):
branch = ref[len('refs/heads/branches/'):]
- if branch in branches and node in branches[branch]:
+ if branch in branches and bnode in branches[branch]:
# up to date
continue
+
+ if peer:
+ remotemap = peer.branchmap()
+ if remotemap and branch in remotemap:
+ heads = [hghex(e) for e in remotemap[branch]]
+ if not check_tip(ref, 'branches', branch, heads):
+ print "error %s fetch first" % ref
+ need_fetch = True
+ continue
+
+ p_revs[bnode] = ref
print "ok %s" % ref
elif ref.startswith('refs/heads/'):
bmark = ref[len('refs/heads/'):]
- p_bmarks.append((bmark, node))
- continue
+ new = node
+ old = bmarks[bmark].hex() if bmark in bmarks else ''
+
+ if old == new:
+ continue
+
+ print "ok %s" % ref
+ if bmark != fake_bmark and \
+ not (bmark == 'master' and bmark not in parser.repo._bookmarks):
+ p_bmarks.append((ref, bmark, old, new))
+
+ if peer:
+ remote_old = peer.listkeys('bookmarks').get(bmark)
+ if remote_old:
+ if not check_tip(ref, 'bookmarks', bmark, remote_old):
+ print "error %s fetch first" % ref
+ need_fetch = True
+ continue
+
+ p_revs[bnode] = ref
elif ref.startswith('refs/tags/'):
+ if dry_run:
+ print "ok %s" % ref
+ continue
tag = ref[len('refs/tags/'):]
tag = hgref(tag)
author, msg = parsed_tags.get(tag, (None, None))
if mode == 'git':
if not msg:
- msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
- write_tag(parser.repo, tag, node, msg, author)
+ msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
+ tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
+ p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
else:
fp = parser.repo.opener('localtags', 'a')
- fp.write('%s %s\n' % (hghex(node), tag))
+ fp.write('%s %s\n' % (node, tag))
fp.close()
+ p_revs[bnode] = ref
print "ok %s" % ref
else:
# transport-helper/fast-export bugs
continue
- if peer:
- parser.repo.push(peer, force=force_push)
-
- # handle bookmarks
- for bmark, node in p_bmarks:
- ref = 'refs/heads/' + bmark
- new = hghex(node)
-
- if bmark in bmarks:
- old = bmarks[bmark].hex()
- else:
- old = ''
+ if need_fetch:
+ print
+ return
- if old == new:
- continue
+ if dry_run:
+ if peer and not force_push:
+ checkheads(parser.repo, peer, p_revs)
+ print
+ return
- if bmark == 'master' and 'master' not in parser.repo._bookmarks:
- # fake bookmark
- pass
- elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
- # updated locally
- pass
- else:
- print "error %s" % ref
- continue
+ if peer:
+ if not push(parser.repo, peer, parsed_refs, p_revs):
+ # do not update bookmarks
+ print
+ return
- if peer:
- rb = peer.listkeys('bookmarks')
- old = rb.get(bmark, '')
+ # update remote bookmarks
+ remote_bmarks = peer.listkeys('bookmarks')
+ for ref, bmark, old, new in p_bmarks:
+ if force_push:
+ old = remote_bmarks.get(bmark, '')
if not peer.pushkey('bookmarks', bmark, old, new):
print "error %s" % ref
- continue
-
- print "ok %s" % ref
+ else:
+ # update local bookmarks
+ for ref, bmark, old, new in p_bmarks:
+ if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
+ print "error %s" % ref
print
+def do_option(parser):
+ global dry_run, force_push
+ _, key, value = parser.line.split(' ')
+ if key == 'dry-run':
+ dry_run = (value == 'true')
+ print 'ok'
+ elif key == 'force':
+ force_push = (value == 'true')
+ print 'ok'
+ else:
+ print 'unsupported'
+
def fix_path(alias, repo, orig_url):
url = urlparse.urlparse(orig_url, 'file')
if url.scheme != 'file' or os.path.isabs(url.path):
subprocess.call(cmd)
def main(args):
- global prefix, dirname, branches, bmarks
+ global prefix, gitdir, dirname, branches, bmarks
global marks, blob_marks, parsed_refs
global peer, mode, bad_mail, bad_name
global track_branches, force_push, is_tmp
global parsed_tags
global filenodes
+ global fake_bmark, hg_version
+ global dry_run
alias = args[1]
url = args[2]
peer = None
- hg_git_compat = False
- track_branches = True
- force_push = True
-
- try:
- if get_config('remote-hg.hg-git-compat') == 'true\n':
- hg_git_compat = True
- track_branches = False
- if get_config('remote-hg.track-branches') == 'false\n':
- track_branches = False
- if get_config('remote-hg.force-push') == 'false\n':
- force_push = False
- except subprocess.CalledProcessError:
- pass
+ hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
+ track_branches = get_config_bool('remote-hg.track-branches', True)
+ force_push = False
if hg_git_compat:
mode = 'hg'
marks = None
parsed_tags = {}
filenodes = {}
+ fake_bmark = None
+ try:
+ hg_version = tuple(int(e) for e in util.version().split('.'))
+ except:
+ hg_version = None
+ dry_run = False
repo = get_repo(url, alias)
prefix = 'refs/hg/%s' % alias
if not is_tmp:
fix_path(alias, peer or repo, url)
- if not os.path.exists(dirname):
- os.makedirs(dirname)
-
marks_path = os.path.join(dirname, 'marks-hg')
- marks = Marks(marks_path)
+ marks = Marks(marks_path, repo)
+
+ if sys.platform == 'win32':
+ import msvcrt
+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
parser = Parser(repo)
for line in parser:
do_import(parser)
elif parser.check('export'):
do_export(parser)
+ elif parser.check('option'):
+ do_option(parser)
else:
die('unhandled command: %s' % line)
sys.stdout.flush()
test_done
fi
-if ! "$PYTHON_PATH" -c 'import bzrlib'; then
+if ! python -c 'import bzrlib'; then
skip_all='skipping remote-bzr tests; bzr not available'
test_done
fi
check () {
- (cd $1 &&
- git log --format='%s' -1 &&
- git symbolic-ref HEAD) > actual &&
- (echo $2 &&
- echo "refs/heads/$3") > expected &&
+ echo $3 > expected &&
+ git --git-dir=$1/.git log --format='%s' -1 $2 > actual
test_cmp expected actual
}
bzr whoami "A U Thor <author@example.com>"
test_expect_success 'cloning' '
- (bzr init bzrrepo &&
- cd bzrrepo &&
- echo one > content &&
- bzr add content &&
- bzr commit -m one
- ) &&
-
- git clone "bzr::$PWD/bzrrepo" gitrepo &&
- check gitrepo one master
+ (
+ bzr init bzrrepo &&
+ cd bzrrepo &&
+ echo one > content &&
+ bzr add content &&
+ bzr commit -m one
+ ) &&
+
+ git clone "bzr::bzrrepo" gitrepo &&
+ check gitrepo HEAD one
'
test_expect_success 'pulling' '
- (cd bzrrepo &&
- echo two > content &&
- bzr commit -m two
- ) &&
+ (
+ cd bzrrepo &&
+ echo two > content &&
+ bzr commit -m two
+ ) &&
- (cd gitrepo && git pull) &&
+ (cd gitrepo && git pull) &&
- check gitrepo two master
+ check gitrepo HEAD two
'
test_expect_success 'pushing' '
- (cd gitrepo &&
- echo three > content &&
- git commit -a -m three &&
- git push
- ) &&
-
- echo three > expected &&
- cat bzrrepo/content > actual &&
- test_cmp expected actual
+ (
+ cd gitrepo &&
+ echo three > content &&
+ git commit -a -m three &&
+ git push
+ ) &&
+
+ echo three > expected &&
+ cat bzrrepo/content > actual &&
+ test_cmp expected actual
'
test_expect_success 'roundtrip' '
- (cd gitrepo &&
- git pull &&
- git log --format="%s" -1 origin/master > actual) &&
- echo three > expected &&
- test_cmp expected actual &&
+ (
+ cd gitrepo &&
+ git pull &&
+ git log --format="%s" -1 origin/master > actual
+ ) &&
+ echo three > expected &&
+ test_cmp expected actual &&
- (cd gitrepo && git push && git pull) &&
+ (cd gitrepo && git push && git pull) &&
- (cd bzrrepo &&
- echo four > content &&
- bzr commit -m four
- ) &&
+ (
+ cd bzrrepo &&
+ echo four > content &&
+ bzr commit -m four
+ ) &&
- (cd gitrepo && git pull && git push) &&
+ (cd gitrepo && git pull && git push) &&
- check gitrepo four master &&
+ check gitrepo HEAD four &&
- (cd gitrepo &&
- echo five > content &&
- git commit -a -m five &&
- git push && git pull
- ) &&
+ (
+ cd gitrepo &&
+ echo five > content &&
+ git commit -a -m five &&
+ git push && git pull
+ ) &&
- (cd bzrrepo && bzr revert) &&
+ (cd bzrrepo && bzr revert) &&
- echo five > expected &&
- cat bzrrepo/content > actual &&
- test_cmp expected actual
+ echo five > expected &&
+ cat bzrrepo/content > actual &&
+ test_cmp expected actual
'
cat > expected <<EOF
EOF
test_expect_success 'special modes' '
- (cd bzrrepo &&
- echo exec > executable
- chmod +x executable &&
- bzr add executable
- bzr commit -m exec &&
- ln -s content link
- bzr add link
- bzr commit -m link &&
- mkdir dir &&
- bzr add dir &&
- bzr commit -m dir) &&
-
- (cd gitrepo &&
- git pull
- git ls-tree HEAD > ../actual) &&
-
- test_cmp expected actual &&
-
- (cd gitrepo &&
- git cat-file -p HEAD:link > ../actual) &&
-
- printf content > expected &&
- test_cmp expected actual
+ (
+ cd bzrrepo &&
+ echo exec > executable
+ chmod +x executable &&
+ bzr add executable
+ bzr commit -m exec &&
+ ln -s content link
+ bzr add link
+ bzr commit -m link &&
+ mkdir dir &&
+ bzr add dir &&
+ bzr commit -m dir
+ ) &&
+
+ (
+ cd gitrepo &&
+ git pull
+ git ls-tree HEAD > ../actual
+ ) &&
+
+ test_cmp expected actual &&
+
+ (
+ cd gitrepo &&
+ git cat-file -p HEAD:link > ../actual
+ ) &&
+
+ printf content > expected &&
+ test_cmp expected actual
'
cat > expected <<EOF
EOF
test_expect_success 'moving directory' '
- (cd bzrrepo &&
- mkdir movedir &&
- echo one > movedir/one &&
- echo two > movedir/two &&
- bzr add movedir &&
- bzr commit -m movedir &&
- bzr mv movedir movedir-new &&
- bzr commit -m movedir-new) &&
-
- (cd gitrepo &&
- git pull &&
- git ls-tree HEAD > ../actual) &&
-
- test_cmp expected actual
+ (
+ cd bzrrepo &&
+ mkdir movedir &&
+ echo one > movedir/one &&
+ echo two > movedir/two &&
+ bzr add movedir &&
+ bzr commit -m movedir &&
+ bzr mv movedir movedir-new &&
+ bzr commit -m movedir-new
+ ) &&
+
+ (
+ cd gitrepo &&
+ git pull &&
+ git ls-tree HEAD > ../actual
+ ) &&
+
+ test_cmp expected actual
'
test_expect_success 'different authors' '
- (cd bzrrepo &&
- echo john >> content &&
- bzr commit -m john \
- --author "Jane Rey <jrey@example.com>" \
- --author "John Doe <jdoe@example.com>") &&
-
- (cd gitrepo &&
- git pull &&
- git show --format="%an <%ae>, %cn <%ce>" --quiet > ../actual) &&
-
- echo "Jane Rey <jrey@example.com>, A U Thor <author@example.com>" > expected &&
- test_cmp expected actual
+ (
+ cd bzrrepo &&
+ echo john >> content &&
+ bzr commit -m john \
+ --author "Jane Rey <jrey@example.com>" \
+ --author "John Doe <jdoe@example.com>"
+ ) &&
+
+ (
+ cd gitrepo &&
+ git pull &&
+ git show --format="%an <%ae>, %cn <%ce>" --quiet > ../actual
+ ) &&
+
+ echo "Jane Rey <jrey@example.com>, A U Thor <author@example.com>" > expected &&
+ test_cmp expected actual
'
+# cleanup previous stuff
+rm -rf bzrrepo gitrepo
+
test_expect_success 'fetch utf-8 filenames' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp && LC_ALL=C" &&
-
- LC_ALL=en_US.UTF-8
- export LC_ALL
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
-
- echo test >> "ærø" &&
- bzr add "ærø" &&
- echo test >> "ø~?" &&
- bzr add "ø~?" &&
- bzr commit -m add-utf-8 &&
- echo test >> "ærø" &&
- bzr commit -m test-utf-8 &&
- bzr rm "ø~?" &&
- bzr mv "ærø" "ø~?" &&
- bzr commit -m bzr-mv-utf-8
- ) &&
-
- (
- git clone "bzr::$PWD/bzrrepo" gitrepo &&
- cd gitrepo &&
- git -c core.quotepath=false ls-files > ../actual
- ) &&
- echo "ø~?" > expected &&
- test_cmp expected actual
+ test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C" &&
+
+ LC_ALL=en_US.UTF-8
+ export LC_ALL
+
+ (
+ bzr init bzrrepo &&
+ cd bzrrepo &&
+
+ echo test >> "ærø" &&
+ bzr add "ærø" &&
+ echo test >> "ø~?" &&
+ bzr add "ø~?" &&
+ bzr commit -m add-utf-8 &&
+ echo test >> "ærø" &&
+ bzr commit -m test-utf-8 &&
+ bzr rm "ø~?" &&
+ bzr mv "ærø" "ø~?" &&
+ bzr commit -m bzr-mv-utf-8
+ ) &&
+
+ (
+ git clone "bzr::bzrrepo" gitrepo &&
+ cd gitrepo &&
+ git -c core.quotepath=false ls-files > ../actual
+ ) &&
+ echo "ø~?" > expected &&
+ test_cmp expected actual
'
test_expect_success 'push utf-8 filenames' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp && LC_ALL=C" &&
+ test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C" &&
+
+ mkdir -p tmp && cd tmp &&
+
+ LC_ALL=en_US.UTF-8
+ export LC_ALL
+
+ (
+ bzr init bzrrepo &&
+ cd bzrrepo &&
+
+ echo one >> content &&
+ bzr add content &&
+ bzr commit -m one
+ ) &&
+
+ (
+ git clone "bzr::bzrrepo" gitrepo &&
+ cd gitrepo &&
+
+ echo test >> "ærø" &&
+ git add "ærø" &&
+ git commit -m utf-8 &&
+
+ git push
+ ) &&
+
+ (cd bzrrepo && bzr ls > ../actual) &&
+ printf "content\nærø\n" > expected &&
+ test_cmp expected actual
+'
- LC_ALL=en_US.UTF-8
- export LC_ALL
+test_expect_success 'pushing a merge' '
+ test_when_finished "rm -rf bzrrepo gitrepo" &&
+
+ (
+ bzr init bzrrepo &&
+ cd bzrrepo &&
+ echo one > content &&
+ bzr add content &&
+ bzr commit -m one
+ ) &&
+
+ git clone "bzr::bzrrepo" gitrepo &&
+
+ (
+ cd bzrrepo &&
+ echo two > content &&
+ bzr commit -m two
+ ) &&
+
+ (
+ cd gitrepo &&
+ echo three > content &&
+ git commit -a -m three &&
+ git fetch &&
+ git merge origin/master || true &&
+ echo three > content &&
+ git commit -a --no-edit &&
+ git push
+ ) &&
+
+ echo three > expected &&
+ cat bzrrepo/content > actual &&
+ test_cmp expected actual
+'
+
+cat > expected <<EOF
+origin/HEAD
+origin/branch
+origin/trunk
+EOF
+
+test_expect_success 'proper bzr repo' '
+ test_when_finished "rm -rf bzrrepo gitrepo" &&
+
+ bzr init-repo bzrrepo &&
+
+ (
+ bzr init bzrrepo/trunk &&
+ cd bzrrepo/trunk &&
+ echo one >> content &&
+ bzr add content &&
+ bzr commit -m one
+ ) &&
+
+ (
+ bzr branch bzrrepo/trunk bzrrepo/branch &&
+ cd bzrrepo/branch &&
+ echo two >> content &&
+ bzr commit -m one
+ ) &&
- (
- bzr init bzrrepo &&
- cd bzrrepo &&
+ (
+ git clone "bzr::bzrrepo" gitrepo &&
+ cd gitrepo &&
+ git for-each-ref --format "%(refname:short)" refs/remotes/origin > ../actual
+ ) &&
- echo one >> content &&
- bzr add content &&
- bzr commit -m one
- ) &&
+ test_cmp expected actual
+'
+
+test_expect_success 'strip' '
+ test_when_finished "rm -rf bzrrepo gitrepo" &&
+
+ (
+ bzr init bzrrepo &&
+ cd bzrrepo &&
+
+ echo one >> content &&
+ bzr add content &&
+ bzr commit -m one &&
- (
- git clone "bzr::$PWD/bzrrepo" gitrepo &&
- cd gitrepo &&
+ echo two >> content &&
+ bzr commit -m two
+ ) &&
- echo test >> "ærø" &&
- git add "ærø" &&
- git commit -m utf-8 &&
+ git clone "bzr::bzrrepo" gitrepo &&
- git push
- ) &&
+ (
+ cd bzrrepo &&
+ bzr uncommit --force &&
- (cd bzrrepo && bzr ls > ../actual) &&
- printf "content\nærø\n" > expected &&
- test_cmp expected actual
+ echo three >> content &&
+ bzr commit -m three &&
+
+ echo four >> content &&
+ bzr commit -m four &&
+ bzr log --line | sed -e "s/^[0-9][0-9]*: //" > ../expected
+ ) &&
+
+ (
+ cd gitrepo &&
+ git fetch &&
+ git log --format="%an %ad %s" --date=short origin/master > ../actual
+ ) &&
+
+ test_cmp expected actual
'
test_done
test_done
fi
-if ! "$PYTHON_PATH" -c 'import mercurial'; then
+if ! python -c 'import mercurial'; then
skip_all='skipping remote-hg tests; mercurial not available'
test_done
fi
# clone to a git repo
git_clone () {
- git clone -q "hg::$PWD/$1" $2
+ git clone -q "hg::$1" $2
}
# clone to an hg repo
hg_clone () {
(
hg init $2 &&
- hg -R $2 bookmark -i master &&
cd $1 &&
- git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
+ git push -q "hg::../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
) &&
(cd $2 && hg -q update)
hg_push () {
(
cd $2
- old=$(git symbolic-ref --short HEAD)
git checkout -q -b tmp &&
- git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
- git checkout -q $old &&
+ git fetch -q "hg::../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
+ git checkout -q @{-1} &&
git branch -q -D tmp 2> /dev/null || true
)
}
hg_log () {
- hg -R $1 log --graph --debug >log &&
- grep -v 'tag: *default/' log
+ hg -R $1 log --graph --debug
}
setup () {
echo "graphlog ="
) >> "$HOME"/.hgrc &&
git config --global remote-hg.hg-git-compat true
+ git config --global remote-hg.track-branches true
HGEDITOR=/usr/bin/true
GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230"
setup
test_expect_success 'encoding' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
'
test_expect_success 'file removal' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
'
test_expect_success 'git tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
'
test_expect_success 'hg branch' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
hg_clone gitrepo hgrepo &&
cd hgrepo &&
- hg -q co master &&
+ hg -q co default &&
hg mv alpha beta &&
hg -q commit -m "rename alpha to beta" &&
hg branch gamma | grep -v "permanent and global" &&
'
test_expect_success 'hg tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
hg_clone gitrepo hgrepo &&
cd hgrepo &&
- hg co master &&
+ hg co default &&
hg tag alpha
) &&
test_done
fi
-if ! "$PYTHON_PATH" -c 'import mercurial'; then
+if ! python -c 'import mercurial'; then
skip_all='skipping remote-hg tests; mercurial not available'
test_done
fi
-if ! "$PYTHON_PATH" -c 'import hggit'; then
+if ! python -c 'import hggit'; then
skip_all='skipping remote-hg tests; hg-git not available'
test_done
fi
# clone to a git repo with git
git_clone_git () {
- git clone -q "hg::$PWD/$1" $2
+ git clone -q "hg::$1" $2 &&
+ (cd $2 && git checkout master && git branch -D default)
}
# clone to an hg repo with git
hg init $2 &&
hg -R $2 bookmark -i master &&
cd $1 &&
- git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
+ git push -q "hg::../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
) &&
(cd $2 && hg -q update)
hg_push_git () {
(
cd $2
- old=$(git symbolic-ref --short HEAD)
git checkout -q -b tmp &&
- git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
- git checkout -q $old &&
+ git fetch -q "hg::../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
+ git branch -D default &&
+ git checkout -q @{-1} &&
git branch -q -D tmp 2> /dev/null || true
)
}
) >> "$HOME"/.hgrc &&
git config --global receive.denycurrentbranch warn
git config --global remote-hg.hg-git-compat true
+ git config --global remote-hg.track-branches false
- HGEDITOR=/usr/bin/true
+ HGEDITOR=true
+ HGMERGE=true
GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230"
GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
- export HGEDITOR GIT_AUTHOR_DATE GIT_COMMITTER_DATE
+ export HGEDITOR HGMERGE GIT_AUTHOR_DATE GIT_COMMITTER_DATE
}
setup
test_expect_success 'executable bit' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
'
test_expect_success 'symlink' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
'
test_expect_success 'merge conflict 1' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
hg init hgrepo1 &&
echo C > afile &&
hg ci -m "A->C" &&
- hg merge -r1 || true &&
+ hg merge -r1 &&
echo C > afile &&
hg resolve -m afile &&
hg ci -m "merge to C"
'
test_expect_success 'merge conflict 2' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
hg init hgrepo1 &&
'
test_expect_success 'converged merge' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
hg init hgrepo1 &&
'
test_expect_success 'encoding' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
'
test_expect_success 'file removal' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
'
test_expect_success 'git tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
(
git init -q gitrepo &&
'
test_expect_success 'hg author' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
for x in hg git; do
(
git_log gitrepo-$x > git-log-$x
done &&
- test_cmp git-log-hg git-log-git &&
-
test_cmp hg-log-hg hg-log-git &&
test_cmp git-log-hg git-log-git
'
test_expect_success 'hg branch' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
for x in hg git; do
(
'
test_expect_success 'hg tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
for x in hg git; do
(
test_done
fi
-if ! "$PYTHON_PATH" -c 'import mercurial'; then
+if ! python -c 'import mercurial'; then
skip_all='skipping remote-hg tests; mercurial not available'
test_done
fi
check () {
- (cd $1 &&
- git log --format='%s' -1 &&
- git symbolic-ref HEAD) > actual &&
- (echo $2 &&
- echo "refs/heads/$3") > expected &&
+ echo $3 > expected &&
+ git --git-dir=$1/.git log --format='%s' -1 $2 > actual
test_cmp expected actual
}
+check_branch () {
+ if [ -n "$3" ]; then
+ echo $3 > expected &&
+ hg -R $1 log -r $2 --template '{desc}\n' > actual &&
+ test_cmp expected actual
+ else
+ hg -R $1 branches > out &&
+ ! grep $2 out
+ fi
+}
+
+check_bookmark () {
+ if [ -n "$3" ]; then
+ echo $3 > expected &&
+ hg -R $1 log -r "bookmark('$2')" --template '{desc}\n' > actual &&
+ test_cmp expected actual
+ else
+ hg -R $1 bookmarks > out &&
+ ! grep $2 out
+ fi
+}
+
+check_push () {
+ local expected_ret=$1 ret=0 ref_ret=0 IFS=':'
+
+ shift
+ git push origin "$@" 2> error
+ ret=$?
+ cat error
+
+ while read branch kind
+ do
+ case "$kind" in
+ 'new')
+ grep "^ \* \[new branch\] *${branch} -> ${branch}$" error || ref_ret=1
+ ;;
+ 'non-fast-forward')
+ grep "^ ! \[rejected\] *${branch} -> ${branch} (non-fast-forward)$" error || ref_ret=1
+ ;;
+ 'fetch-first')
+ grep "^ ! \[rejected\] *${branch} -> ${branch} (fetch first)$" error || ref_ret=1
+ ;;
+ 'forced-update')
+ grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *${branch} -> ${branch} (forced update)$" error || ref_ret=1
+ ;;
+ '')
+ grep "^ [a-f0-9]*\.\.[a-f0-9]* *${branch} -> ${branch}$" error || ref_ret=1
+ ;;
+ esac
+ let 'ref_ret' && echo "match for '$branch' failed" && break
+ done
+
+ if let 'expected_ret != ret || ref_ret'
+ then
+ return 1
+ fi
+
+ return 0
+}
+
setup () {
(
echo "[ui]"
echo "username = H G Wells <wells@example.com>"
- ) >> "$HOME"/.hgrc
+ echo "[extensions]"
+ echo "mq ="
+ ) >> "$HOME"/.hgrc &&
+
+ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" &&
+ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" &&
+ export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
}
setup
test_expect_success 'cloning' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
- echo zero > content &&
- hg add content &&
- hg commit -m zero
- ) &&
+ test_when_finished "rm -rf gitrepo*" &&
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo zero master
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo HEAD zero
'
test_expect_success 'cloning with branches' '
- test_when_finished "rm -rf gitrepo*" &&
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ cd hgrepo &&
+ hg branch next &&
+ echo next > content &&
+ hg commit -m next
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo origin/branches/next next
+'
+
+test_expect_success 'cloning with bookmarks' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ cd hgrepo &&
+ hg checkout default &&
+ hg bookmark feature-a &&
+ echo feature-a > content &&
+ hg commit -m feature-a
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo origin/feature-a feature-a
+'
+
+test_expect_success 'update bookmark' '
+ test_when_finished "rm -rf gitrepo*" &&
- (
- cd hgrepo &&
- hg branch next &&
- echo next > content &&
- hg commit -m next
- ) &&
+ (
+ cd hgrepo &&
+ hg bookmark devel
+ ) &&
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo next next &&
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git checkout --quiet devel &&
+ echo devel > content &&
+ git commit -a -m devel &&
+ git push --quiet
+ ) &&
+
+ check_bookmark hgrepo devel devel
+'
- (cd hgrepo && hg checkout default) &&
+test_expect_success 'new bookmark' '
+ test_when_finished "rm -rf gitrepo*" &&
- git clone "hg::$PWD/hgrepo" gitrepo2 &&
- check gitrepo2 zero master
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git checkout --quiet -b feature-b &&
+ echo feature-b > content &&
+ git commit -a -m feature-b &&
+ git push --quiet origin feature-b
+ ) &&
+
+ check_bookmark hgrepo feature-b feature-b
'
-test_expect_success 'cloning with bookmarks' '
- test_when_finished "rm -rf gitrepo*" &&
+# cleanup previous stuff
+rm -rf hgrepo
+
+author_test () {
+ echo $1 >> content &&
+ hg commit -u "$2" -m "add $1" &&
+ echo "$3" >> ../expected
+}
- (
- cd hgrepo &&
- hg bookmark feature-a &&
- echo feature-a > content &&
- hg commit -m feature-a
- ) &&
+test_expect_success 'authors' '
+ test_when_finished "rm -rf hgrepo gitrepo" &&
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+
+ touch content &&
+ hg add content &&
+
+ > ../expected &&
+ author_test alpha "" "H G Wells <wells@example.com>" &&
+ author_test beta "test" "test <unknown>" &&
+ author_test beta "test <test@example.com> (comment)" "test <test@example.com>" &&
+ author_test gamma "<test@example.com>" "Unknown <test@example.com>" &&
+ author_test delta "name<test@example.com>" "name <test@example.com>" &&
+ author_test epsilon "name <test@example.com" "name <test@example.com>" &&
+ author_test zeta " test " "test <unknown>" &&
+ author_test eta "test < test@example.com >" "test <test@example.com>" &&
+ author_test theta "test >test@example.com>" "test <test@example.com>" &&
+ author_test iota "test < test <at> example <dot> com>" "test <unknown>" &&
+ author_test kappa "test@example.com" "Unknown <test@example.com>"
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual &&
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo feature-a feature-a
+ test_cmp expected actual
'
-test_expect_success 'cloning with detached head' '
- test_when_finished "rm -rf gitrepo*" &&
+test_expect_success 'strip' '
+ test_when_finished "rm -rf hgrepo gitrepo" &&
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+
+ echo one >> content &&
+ hg add content &&
+ hg commit -m one &&
+
+ echo two >> content &&
+ hg commit -m two
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+
+ (
+ cd hgrepo &&
+ hg strip 1 &&
- (
- cd hgrepo &&
- hg update -r 0
- ) &&
+ echo three >> content &&
+ hg commit -m three &&
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo zero master
+ echo four >> content &&
+ hg commit -m four
+ ) &&
+
+ (
+ cd gitrepo &&
+ git fetch &&
+ git log --format="%s" origin/master > ../actual
+ ) &&
+
+ hg -R hgrepo log --template "{desc}\n" > expected &&
+ test_cmp actual expected
'
-test_expect_success 'update bookmark' '
- test_when_finished "rm -rf gitrepo*" &&
+test_expect_success 'remote push with master bookmark' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
- (
- cd hgrepo &&
- hg bookmark devel
- ) &&
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero &&
+ hg bookmark master &&
+ echo one > content &&
+ hg commit -m one
+ ) &&
- (
- git clone "hg::$PWD/hgrepo" gitrepo &&
- cd gitrepo &&
- git checkout devel &&
- echo devel > content &&
- git commit -a -m devel &&
- git push
- ) &&
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ echo two > content &&
+ git commit -a -m two &&
+ git push
+ ) &&
+
+ check_branch hgrepo default two
+'
- hg -R hgrepo bookmarks | egrep "devel[ ]+3:"
+cat > expected <<EOF
+changeset: 0:6e2126489d3d
+tag: tip
+user: A U Thor <author@example.com>
+date: Mon Jan 01 00:00:00 2007 +0230
+summary: one
+
+EOF
+
+test_expect_success 'remote push from master branch' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ hg init hgrepo &&
+
+ (
+ git init gitrepo &&
+ cd gitrepo &&
+ git remote add origin "hg::../hgrepo" &&
+ echo one > content &&
+ git add content &&
+ git commit -a -m one &&
+ git push origin master
+ ) &&
+
+ hg -R hgrepo log > actual &&
+ cat actual &&
+ test_cmp expected actual &&
+
+ check_branch hgrepo default one
'
-author_test () {
- echo $1 >> content &&
- hg commit -u "$2" -m "add $1" &&
- echo "$3" >> ../expected
+GIT_REMOTE_HG_TEST_REMOTE=1
+export GIT_REMOTE_HG_TEST_REMOTE
+
+test_expect_success 'remote cloning' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo HEAD zero
+'
+
+test_expect_success 'remote update bookmark' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ cd hgrepo &&
+ hg bookmark devel
+ ) &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git checkout --quiet devel &&
+ echo devel > content &&
+ git commit -a -m devel &&
+ git push --quiet
+ ) &&
+
+ check_bookmark hgrepo devel devel
+'
+
+test_expect_success 'remote new bookmark' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git checkout --quiet -b feature-b &&
+ echo feature-b > content &&
+ git commit -a -m feature-b &&
+ git push --quiet origin feature-b
+ ) &&
+
+ check_bookmark hgrepo feature-b feature-b
+'
+
+test_expect_success 'remote push diverged' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ git clone "hg::hgrepo" gitrepo &&
+
+ (
+ cd hgrepo &&
+ hg checkout default &&
+ echo bump > content &&
+ hg commit -m bump
+ ) &&
+
+ (
+ cd gitrepo &&
+ echo diverge > content &&
+ git commit -a -m diverged &&
+ check_push 1 <<-EOF
+ master:non-fast-forward
+ EOF
+ ) &&
+
+ check_branch hgrepo default bump
+'
+
+test_expect_success 'remote update bookmark diverge' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ cd hgrepo &&
+ hg checkout tip^ &&
+ hg bookmark diverge
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+
+ (
+ cd hgrepo &&
+ echo "bump bookmark" > content &&
+ hg commit -m "bump bookmark"
+ ) &&
+
+ (
+ cd gitrepo &&
+ git checkout --quiet diverge &&
+ echo diverge > content &&
+ git commit -a -m diverge &&
+ check_push 1 <<-EOF
+ diverge:fetch-first
+ EOF
+ ) &&
+
+ check_bookmark hgrepo diverge "bump bookmark"
+'
+
+test_expect_success 'remote new bookmark multiple branch head' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git checkout --quiet -b feature-c HEAD^ &&
+ echo feature-c > content &&
+ git commit -a -m feature-c &&
+ git push --quiet origin feature-c
+ ) &&
+
+ check_bookmark hgrepo feature-c feature-c
+'
+
+# cleanup previous stuff
+rm -rf hgrepo
+
+setup_big_push () {
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero &&
+ hg bookmark bad_bmark1 &&
+ echo one > content &&
+ hg commit -m one &&
+ hg bookmark bad_bmark2 &&
+ hg bookmark good_bmark &&
+ hg bookmark -i good_bmark &&
+ hg -q branch good_branch &&
+ echo "good branch" > content &&
+ hg commit -m "good branch" &&
+ hg -q branch bad_branch &&
+ echo "bad branch" > content &&
+ hg commit -m "bad branch"
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+
+ (
+ cd gitrepo &&
+ echo two > content &&
+ git commit -q -a -m two &&
+
+ git checkout -q good_bmark &&
+ echo three > content &&
+ git commit -q -a -m three &&
+
+ git checkout -q bad_bmark1 &&
+ git reset --hard HEAD^ &&
+ echo four > content &&
+ git commit -q -a -m four &&
+
+ git checkout -q bad_bmark2 &&
+ git reset --hard HEAD^ &&
+ echo five > content &&
+ git commit -q -a -m five &&
+
+ git checkout -q -b new_bmark master &&
+ echo six > content &&
+ git commit -q -a -m six &&
+
+ git checkout -q branches/good_branch &&
+ echo seven > content &&
+ git commit -q -a -m seven &&
+ echo eight > content &&
+ git commit -q -a -m eight &&
+
+ git checkout -q branches/bad_branch &&
+ git reset --hard HEAD^ &&
+ echo nine > content &&
+ git commit -q -a -m nine &&
+
+ git checkout -q -b branches/new_branch master &&
+ echo ten > content &&
+ git commit -q -a -m ten
+ )
}
-test_expect_success 'authors' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
-
- touch content &&
- hg add content &&
-
- author_test alpha "" "H G Wells <wells@example.com>" &&
- author_test beta "test" "test <unknown>" &&
- author_test beta "test <test@example.com> (comment)" "test <test@example.com>" &&
- author_test gamma "<test@example.com>" "Unknown <test@example.com>" &&
- author_test delta "name<test@example.com>" "name <test@example.com>" &&
- author_test epsilon "name <test@example.com" "name <test@example.com>" &&
- author_test zeta " test " "test <unknown>" &&
- author_test eta "test < test@example.com >" "test <test@example.com>" &&
- author_test theta "test >test@example.com>" "test <test@example.com>" &&
- author_test iota "test < test <at> example <dot> com>" "test <unknown>" &&
- author_test kappa "test@example.com" "Unknown <test@example.com>"
- ) &&
-
- git clone "hg::$PWD/hgrepo" gitrepo &&
- git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual &&
-
- test_cmp expected actual
+test_expect_success 'remote big push' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ setup_big_push
+
+ (
+ cd gitrepo &&
+
+ check_push 1 --all <<-EOF
+ master
+ good_bmark
+ branches/good_branch
+ new_bmark:new
+ branches/new_branch:new
+ bad_bmark1:non-fast-forward
+ bad_bmark2:non-fast-forward
+ branches/bad_branch:non-fast-forward
+ EOF
+ ) &&
+
+ check_branch hgrepo default one &&
+ check_branch hgrepo good_branch "good branch" &&
+ check_branch hgrepo bad_branch "bad branch" &&
+ check_branch hgrepo new_branch '' &&
+ check_bookmark hgrepo good_bmark one &&
+ check_bookmark hgrepo bad_bmark1 one &&
+ check_bookmark hgrepo bad_bmark2 one &&
+ check_bookmark hgrepo new_bmark ''
+'
+
+test_expect_success 'remote big push fetch first' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero &&
+ hg bookmark bad_bmark &&
+ hg bookmark good_bmark &&
+ hg bookmark -i good_bmark &&
+ hg -q branch good_branch &&
+ echo "good branch" > content &&
+ hg commit -m "good branch" &&
+ hg -q branch bad_branch &&
+ echo "bad branch" > content &&
+ hg commit -m "bad branch"
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+
+ (
+ cd hgrepo &&
+ hg bookmark -f bad_bmark &&
+ echo update_bmark > content &&
+ hg commit -m "update bmark"
+ ) &&
+
+ (
+ cd gitrepo &&
+ echo two > content &&
+ git commit -q -a -m two &&
+
+ git checkout -q good_bmark &&
+ echo three > content &&
+ git commit -q -a -m three &&
+
+ git checkout -q bad_bmark &&
+ echo four > content &&
+ git commit -q -a -m four &&
+
+ git checkout -q branches/bad_branch &&
+ echo five > content &&
+ git commit -q -a -m five &&
+
+ check_push 1 --all <<-EOF
+ master
+ good_bmark
+ new_bmark:new
+ new_branch:new
+ bad_bmark:fetch-first
+ branches/bad_branch:festch-first
+ EOF
+
+ git fetch &&
+
+ check_push 1 --all <<-EOF
+ master
+ good_bmark
+ bad_bmark:non-fast-forward
+ branches/bad_branch:non-fast-forward
+ EOF
+ )
+'
+
+test_expect_failure 'remote big push force' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ setup_big_push
+
+ (
+ cd gitrepo &&
+
+ check_push 0 --force --all <<-EOF
+ master
+ good_bmark
+ branches/good_branch
+ new_bmark:new
+ branches/new_branch:new
+ bad_bmark1:forced-update
+ bad_bmark2:forced-update
+ branches/bad_branch:forced-update
+ EOF
+ ) &&
+
+ check_branch hgrepo default six &&
+ check_branch hgrepo good_branch eight &&
+ check_branch hgrepo bad_branch nine &&
+ check_branch hgrepo new_branch ten &&
+ check_bookmark hgrepo good_bmark three &&
+ check_bookmark hgrepo bad_bmark1 four &&
+ check_bookmark hgrepo bad_bmark2 five &&
+ check_bookmark hgrepo new_bmark six
+'
+
+test_expect_failure 'remote big push dry-run' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ setup_big_push
+
+ (
+ cd gitrepo &&
+
+ check_push 0 --dry-run --all <<-EOF
+ master
+ good_bmark
+ branches/good_branch
+ new_bmark:new
+ branches/new_branch:new
+ bad_bmark1:non-fast-forward
+ bad_bmark2:non-fast-forward
+ branches/bad_branch:non-fast-forward
+ EOF
+
+ check_push 0 --dry-run master good_bmark new_bmark branches/good_branch branches/new_branch <<-EOF
+ master
+ good_bmark
+ branches/good_branch
+ new_bmark:new
+ branches/new_branch:new
+ EOF
+ ) &&
+
+ check_branch hgrepo default one &&
+ check_branch hgrepo good_branch "good branch" &&
+ check_branch hgrepo bad_branch "bad branch" &&
+ check_branch hgrepo new_branch '' &&
+ check_bookmark hgrepo good_bmark one &&
+ check_bookmark hgrepo bad_bmark1 one &&
+ check_bookmark hgrepo bad_bmark2 one &&
+ check_bookmark hgrepo new_bmark ''
+'
+
+test_expect_success 'remote double failed push' '
+ test_when_finished "rm -rf hgrepo gitrepo*" &&
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+ echo zero > content &&
+ hg add content &&
+ hg commit -m zero &&
+ echo one > content &&
+ hg commit -m one
+ ) &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git reset --hard HEAD^ &&
+ echo two > content &&
+ git commit -a -m two &&
+ test_expect_code 1 git push &&
+ test_expect_code 1 git push
+ )
'
test_done
-#!/bin/bash
+#!/bin/sh
#
# git-subtree.sh: split/join git repositories in subdirectories of this one
#
repository=$1
refspec=$2
echo "git push using: " $repository $refspec
- git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec
+ localrev=$(git subtree split --prefix="$prefix") || die
+ git push $repository $localrev:refs/heads/$refspec
else
die "'$dir' must already exist. Try 'git subtree add'."
fi
/*****************************************************************
*
- * Streaming converison support
+ * Streaming conversion support
*
*****************************************************************/
/*****************************************************************
*
- * Streaming converison support
+ * Streaming conversion support
*
*****************************************************************/
return c->username && c->password;
}
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
const char * const usage[] = {
"git credential-store [options] <action>",
umask(077);
- argc = parse_options(argc, argv, NULL, options, usage, 0);
+ argc = parse_options(argc, (const char **)argv, NULL, options, usage, 0);
if (argc != 1)
usage_with_options(usage, options);
op = argv[0];
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;
}
}
-/* 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;
return 0; /* success */
}
+int parse_expiry_date(const char *date, unsigned long *timestamp)
+{
+ int errors = 0;
+
+ if (!strcmp(date, "never") || !strcmp(date, "false"))
+ *timestamp = 0;
+ else if (!strcmp(date, "all") || !strcmp(date, "now"))
+ /*
+ * We take over "now" here, which usually translates
+ * to the current timestamp. This is because the user
+ * really means to expire everything she has done in
+ * the past, and by definition reflogs are the record
+ * of the past, and there is nothing from the future
+ * to be kept.
+ */
+ *timestamp = ULONG_MAX;
+ else
+ *timestamp = approxidate_careful(date, &errors);
+
+ return errors;
+}
+
int parse_date(const char *date, char *result, int maxlen)
{
unsigned long timestamp;
const struct object *base = old_hash[i].base;
void *decoration = old_hash[i].decoration;
- if (!base)
+ if (!decoration)
continue;
insert_decoration(n, base, decoration);
}
* commits, untracked content and/or modified content).
*/
static int match_stat_with_submodule(struct diff_options *diffopt,
- struct cache_entry *ce, struct stat *st,
- unsigned ce_option, unsigned *dirty_submodule)
+ const struct cache_entry *ce,
+ struct stat *st, unsigned ce_option,
+ unsigned *dirty_submodule)
{
int changed = ce_match_stat(ce, st, ce_option);
if (S_ISGITLINK(ce->ce_mode)) {
/* A file entry went away or appeared */
static void diff_index_show_file(struct rev_info *revs,
const char *prefix,
- struct cache_entry *ce,
+ const struct cache_entry *ce,
const unsigned char *sha1, int sha1_valid,
unsigned int mode,
unsigned dirty_submodule)
sha1, sha1_valid, ce->name, dirty_submodule);
}
-static int get_stat_data(struct cache_entry *ce,
+static int get_stat_data(const struct cache_entry *ce,
const unsigned char **sha1p,
unsigned int *modep,
int cached, int match_missing,
}
static void show_new_file(struct rev_info *revs,
- struct cache_entry *new,
+ const struct cache_entry *new,
int cached, int match_missing)
{
const unsigned char *sha1;
}
static int show_modified(struct rev_info *revs,
- struct cache_entry *old,
- struct cache_entry *new,
+ const struct cache_entry *old,
+ const struct cache_entry *new,
int report_missing,
int cached, int match_missing)
{
* give you the position and number of entries in the index).
*/
static void do_oneway_diff(struct unpack_trees_options *o,
- struct cache_entry *idx,
- struct cache_entry *tree)
+ const struct cache_entry *idx,
+ const struct cache_entry *tree)
{
struct rev_info *revs = o->unpack_data;
int match_missing, cached;
* the fairly complex unpack_trees() semantic requirements, including
* the skipping, the path matching, the type conflict cases etc.
*/
-static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
+static int oneway_diff(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
{
- struct cache_entry *idx = src[0];
- struct cache_entry *tree = src[1];
+ const struct cache_entry *idx = src[0];
+ const struct cache_entry *tree = src[1];
struct rev_info *revs = o->unpack_data;
/*
if (!path || !strcmp(path, "/dev/null"))
*mode = 0;
-#ifdef _WIN32
+#ifdef GIT_WINDOWS_NATIVE
else if (!strcasecmp(path, "nul"))
*mode = 0;
#endif
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;
*/
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;
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))
/*
* 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;
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];
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;
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 */
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"))
/* 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_OPT_TST(options, DIFF_FROM_CONTENTS)) {
/*
* run diff_flush_patch for the exit status. setting
- * options->file to /dev/null should be safe, becaue we
+ * options->file to /dev/null should be safe, because we
* aren't supposed to produce any output anyway.
*/
if (options->close_file)
char errbuf[1024];
regerror(err, ®ex, errbuf, 1024);
regfree(®ex);
- die("invalid log-grep regex: %s", errbuf);
+ die("invalid regex: %s", errbuf);
}
pickaxe(&diff_queued_diff, o, ®ex, NULL, diff_grep);
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;
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,
char errbuf[1024];
regerror(err, ®ex, errbuf, 1024);
regfree(®ex);
- die("invalid pickaxe regex: %s", errbuf);
+ die("invalid regex: %s", errbuf);
}
regexp = ®ex;
} else {
dir->basebuf, stk->baselen - 1,
dir->basebuf + current, &dt);
dir->basebuf[stk->baselen - 1] = '/';
+ if (dir->exclude &&
+ dir->exclude->flags & EXC_FLAG_NEGATIVE)
+ dir->exclude = NULL;
if (dir->exclude) {
dir->basebuf[stk->baselen] = 0;
dir->exclude_stack = stk;
*/
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)
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))
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)
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) {
home_config_paths(NULL, &xdg_path, "ignore");
excludes_file = xdg_path;
}
- if (!access_or_warn(path, R_OK))
+ if (!access_or_warn(path, R_OK, 0))
add_excludes_from_file(dir, path);
- if (excludes_file && !access_or_warn(excludes_file, R_OK))
+ if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
add_excludes_from_file(dir, excludes_file);
}
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);
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) {
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)
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;
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);
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;
int is_bare_repository_cfg = -1; /* unspecified */
int log_all_ref_updates = -1; /* unspecified */
int warn_ambiguous_refs = 1;
+int warn_on_object_refname_ambiguity = 1;
int repository_format_version;
const char *git_commit_encoding;
const char *git_log_output_encoding;
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;
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);
static FILE *pack_edges;
static unsigned int show_stats = 1;
static int global_argc;
-static const char **global_argv;
+static char **global_argv;
/* Memory pools */
static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool);
*end = 0;
mark = strtoumax(line + 1, &end, 10);
if (!mark || end == line + 1
- || *end != ' ' || get_sha1(end + 1, sha1))
+ || *end != ' ' || get_sha1_hex(end + 1, sha1))
die("corrupt mark line: %s", line);
e = find_object(sha1);
if (!e) {
read_marks();
}
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
unsigned int i;
#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;
static int fetch_fsck_objects = -1;
static int transfer_fsck_objects = -1;
static int agent_supported;
+static struct lock_file shallow_lock;
+static const char *alternate_shallow_file;
#define COMPLETE (1U << 0)
#define COMMON (1U << 1)
*/
#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)
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++;
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;
mark_common(parents->item, 1, 0);
parents = parents->next;
}
-
- rev_list = rev_list->next;
}
return commit->object.sha1;
in_vain = 0;
got_continue = 1;
if (ack == ACK_ready) {
- rev_list = NULL;
+ clear_prio_queue(&rev_list);
got_ready = 1;
}
break;
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;
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);
}
int xd[2], char **pack_lockfile)
{
struct async demux;
- const char *argv[20];
+ const char *argv[22];
char keep_arg[256];
char hdr_arg[256];
const char **av;
int do_keep = args->keep_pack;
struct child_process cmd;
+ int ret;
memset(&demux, 0, sizeof(demux));
if (use_sideband) {
do_keep = 1;
}
+ if (alternate_shallow_file) {
+ *av++ = "--shallow-file";
+ *av++ = alternate_shallow_file;
+ }
+
if (do_keep) {
if (pack_lockfile)
cmd.out = -1;
strcpy(keep_arg + s, "localhost");
*av++ = keep_arg;
}
+ if (args->check_self_contained_and_connected)
+ *av++ = "--check-self-contained-and-connected";
}
else {
*av++ = "unpack-objects";
if (args->quiet || args->no_progress)
*av++ = "-q";
+ args->check_self_contained_and_connected = 0;
}
if (*hdr_arg)
*av++ = hdr_arg;
close(cmd.out);
}
- if (finish_command(&cmd))
+ ret = finish_command(&cmd);
+ if (!ret || (args->check_self_contained_and_connected && ret == 1))
+ args->self_contained_and_connected =
+ args->check_self_contained_and_connected &&
+ ret == 0;
+ else
die("%s failed", argv[0]);
if (use_sideband && finish_async(&demux))
die("error in sideband demultiplexer");
return strcmp(a->name, b->name);
}
+static void setup_alternate_shallow(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int fd;
+
+ check_shallow_file_for_update();
+ fd = hold_lock_file_for_update(&shallow_lock, git_path("shallow"),
+ LOCK_DIE_ON_ERROR);
+ if (write_shallow_commits(&sb, 0)) {
+ if (write_in_full(fd, sb.buf, sb.len) != sb.len)
+ die_errno("failed to write to %s", shallow_lock.filename);
+ alternate_shallow_file = shallow_lock.filename;
+ } else
+ /*
+ * is_repository_shallow() sees empty string as "no
+ * shallow file".
+ */
+ alternate_shallow_file = "";
+ strbuf_release(&sb);
+}
+
static struct ref *do_fetch_pack(struct fetch_pack_args *args,
int fd[2],
const struct ref *orig_ref,
if (args->stateless_rpc)
packet_flush(fd[1]);
+ if (args->depth > 0)
+ setup_alternate_shallow();
if (get_pack(args, fd, pack_lockfile))
die("git fetch-pack: fetch failed.");
struct ref **sought, int nr_sought,
char **pack_lockfile)
{
- struct stat st;
struct ref *ref_cpy;
fetch_pack_setup();
- if (args->depth > 0) {
- if (stat(git_path("shallow"), &st))
- st.st_mtime = 0;
- }
-
if (nr_sought)
nr_sought = remove_duplicates_in_refs(sought, nr_sought);
}
ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, pack_lockfile);
- if (args->depth > 0) {
- static struct lock_file lock;
- struct cache_time mtime;
- struct strbuf sb = STRBUF_INIT;
- char *shallow = git_path("shallow");
- int fd;
-
- mtime.sec = st.st_mtime;
- mtime.nsec = ST_MTIME_NSEC(st);
- if (stat(shallow, &st)) {
- if (mtime.sec)
- die("shallow file was removed during fetch");
- } else if (st.st_mtime != mtime.sec
-#ifdef USE_NSEC
- || ST_MTIME_NSEC(st) != mtime.nsec
-#endif
- )
- die("shallow file was changed during fetch");
-
- fd = hold_lock_file_for_update(&lock, shallow,
- LOCK_DIE_ON_ERROR);
- if (!write_shallow_commits(&sb, 0)
- || write_in_full(fd, sb.buf, sb.len) != sb.len) {
- unlink_or_warn(shallow);
- rollback_lock_file(&lock);
- } else {
- commit_lock_file(&lock);
- }
- strbuf_release(&sb);
+ if (alternate_shallow_file) {
+ if (*alternate_shallow_file == '\0') { /* --unshallow */
+ unlink_or_warn(git_path("shallow"));
+ rollback_lock_file(&shallow_lock);
+ } else
+ commit_lock_file(&shallow_lock);
}
reprepare_packed_git();
verbose:1,
no_progress:1,
include_tag:1,
- stateless_rpc:1;
+ stateless_rpc:1,
+ check_self_contained_and_connected:1,
+ self_contained_and_connected:1;
};
/*
+++ /dev/null
-#!/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
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;
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;
}
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)
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\"."
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.")"
# - 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"
elif test $res -eq 2
then
echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
- good_revs=$(git for-each-ref --format="--not %(objectname)" "refs/bisect/good-*")
- for skipped in $(git rev-list refs/bisect/bad $good_revs)
+ good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/good-*")
+ for skipped in $(git rev-list refs/bisect/bad --not $good_revs)
do
skipped_commit=$(git show-branch $skipped)
echo "# possible first bad commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
#define _NETBSD_SOURCE 1
#define _SGI_SOURCE 1
-#ifdef WIN32 /* Both MinGW and MSVC */
+#if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */
# if defined (_MSC_VER)
# define _WIN32_WINNT 0x0502
# endif
#define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */
#include <winsock2.h>
#include <windows.h>
+#define GIT_WINDOWS_NATIVE
#endif
#include <unistd.h>
#else
#include <poll.h>
#endif
+
+extern int get_st_mode_bits(const char *path, int *mode);
+
#if defined(__MINGW32__)
/* pull in Windows compatibility stuff */
#include "compat/mingw.h"
typedef long intptr_t;
typedef unsigned long uintptr_t;
#endif
-int get_st_mode_bits(const char *path, int *mode);
#if defined(__CYGWIN__)
#undef _XOPEN_SOURCE
#include <grp.h>
#define probe_utf8_pathname_composition(a,b)
#endif
+#ifdef NEEDS_CLIPPED_WRITE
+ssize_t clipped_write(int fildes, const void *buf, size_t nbyte);
+#define write(x,y,z) clipped_write((x),(y),(z))
+#endif
+
#ifdef MKDIR_WO_TRAILING_SLASH
#define mkdir(a,b) compat_mkdir_wo_trailing_slash((a),(b))
extern int compat_mkdir_wo_trailing_slash(const char*, mode_t);
#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
* Call access(2), but warn for any error except "missing file"
* (ENOENT or ENOTDIR).
*/
-int access_or_warn(const char *path, int mode);
-int access_or_die(const char *path, int mode);
+#define ACCESS_EACCES_OK (1U << 0)
+int access_or_warn(const char *path, int mode, unsigned flag);
+int access_or_die(const char *path, int mode, unsigned flag);
/* Warn on an inaccessible file that ought to be accessible */
void warn_on_inaccessible(const char *path);
sub use_wt_file
{
- my ($repo, $workdir, $file, $sha1, $symlinks) = @_;
+ my ($repo, $workdir, $file, $sha1) = @_;
my $null_sha1 = '0' x 40;
- if ($sha1 ne $null_sha1 and not $symlinks) {
- return 0;
+ if (! -e "$workdir/$file") {
+ # If the file doesn't exist in the working tree, we cannot
+ # use it.
+ return (0, $null_sha1);
}
my $wt_sha1 = $repo->command_oneline('hash-object', "$workdir/$file");
if ($rmode ne $null_mode) {
my ($use, $wt_sha1) = use_wt_file($repo, $workdir,
- $dst_path, $rsha1,
- $symlinks);
+ $dst_path, $rsha1);
if ($use) {
push @working_tree, $dst_path;
$wtindex .= "$rmode $wt_sha1\t$dst_path\0";
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=0.17.GITGUI
+DEF_VER=0.18.GITGUI
LF='
'
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 >>$@ && \
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
::msgcat::mcload $oguimsg
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 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 {}} {
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
}
# 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
}
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 {
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 \
}
} 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
#: 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)"
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"
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
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
# 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 () {
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:
# 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
;;
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
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
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 () {
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"
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
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
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
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 ;;
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"
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?"
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
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" at any time.
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
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
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
# 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
+++ /dev/null
-#!/usr/bin/env bash
-# Copyright (c) 2012 Felipe Contreras
-
-alias=$1
-url=$2
-
-dir="$GIT_DIR/testgit/$alias"
-prefix="refs/testgit/$alias"
-
-default_refspec="refs/heads/*:${prefix}/heads/*"
-
-refspec="${GIT_REMOTE_TESTGIT_REFSPEC-$default_refspec}"
-
-test -z "$refspec" && prefix="refs"
-
-export GIT_DIR="$url/.git"
-
-mkdir -p "$dir"
-
-if test -z "$GIT_REMOTE_TESTGIT_NO_MARKS"
-then
- gitmarks="$dir/git.marks"
- testgitmarks="$dir/testgit.marks"
- test -e "$gitmarks" || >"$gitmarks"
- test -e "$testgitmarks" || >"$testgitmarks"
- testgitmarks_args=( "--"{import,export}"-marks=$testgitmarks" )
-fi
-
-while read line
-do
- case $line in
- capabilities)
- echo 'import'
- echo 'export'
- test -n "$refspec" && echo "refspec $refspec"
- if test -n "$gitmarks"
- then
- echo "*import-marks $gitmarks"
- echo "*export-marks $gitmarks"
- fi
- test -n "$GIT_REMOTE_TESTGIT_SIGNED_TAGS" && echo "signed-tags"
- echo
- ;;
- list)
- git for-each-ref --format='? %(refname)' 'refs/heads/'
- head=$(git symbolic-ref HEAD)
- echo "@$head HEAD"
- echo
- ;;
- import*)
- # read all import lines
- while true
- do
- ref="${line#* }"
- refs="$refs $ref"
- read line
- test "${line%% *}" != "import" && break
- done
-
- if test -n "$gitmarks"
- then
- echo "feature import-marks=$gitmarks"
- echo "feature export-marks=$gitmarks"
- fi
- echo "feature done"
- git fast-export "${testgitmarks_args[@]}" $refs |
- sed -e "s#refs/heads/#${prefix}/heads/#g"
- echo "done"
- ;;
- export)
- before=$(git for-each-ref --format='%(refname) %(objectname)')
-
- git fast-import "${testgitmarks_args[@]}" --quiet
-
- after=$(git for-each-ref --format='%(refname) %(objectname)')
-
- # figure out which refs were updated
- join -e 0 -o '0 1.2 2.2' -a 2 <(echo "$before") <(echo "$after") |
- while read ref a b
- do
- test $a == $b && continue
- echo "ok $ref"
- done
-
- echo
- ;;
- '')
- exit
- ;;
- esac
-done
--- /dev/null
+#!/bin/sh
+# Copyright (c) 2012 Felipe Contreras
+
+alias=$1
+url=$2
+
+dir="$GIT_DIR/testgit/$alias"
+prefix="refs/testgit/$alias"
+
+default_refspec="refs/heads/*:${prefix}/heads/*"
+
+refspec="${GIT_REMOTE_TESTGIT_REFSPEC-$default_refspec}"
+
+test -z "$refspec" && prefix="refs"
+
+export GIT_DIR="$url/.git"
+
+mkdir -p "$dir"
+
+if test -z "$GIT_REMOTE_TESTGIT_NO_MARKS"
+then
+ gitmarks="$dir/git.marks"
+ testgitmarks="$dir/testgit.marks"
+ test -e "$gitmarks" || >"$gitmarks"
+ test -e "$testgitmarks" || >"$testgitmarks"
+fi
+
+while read line
+do
+ case $line in
+ capabilities)
+ echo 'import'
+ echo 'export'
+ test -n "$refspec" && echo "refspec $refspec"
+ if test -n "$gitmarks"
+ then
+ echo "*import-marks $gitmarks"
+ echo "*export-marks $gitmarks"
+ fi
+ test -n "$GIT_REMOTE_TESTGIT_SIGNED_TAGS" && echo "signed-tags"
+ echo
+ ;;
+ list)
+ git for-each-ref --format='? %(refname)' 'refs/heads/'
+ head=$(git symbolic-ref HEAD)
+ echo "@$head HEAD"
+ echo
+ ;;
+ import*)
+ # read all import lines
+ while true
+ do
+ ref="${line#* }"
+ refs="$refs $ref"
+ read line
+ test "${line%% *}" != "import" && break
+ done
+
+ if test -n "$gitmarks"
+ then
+ echo "feature import-marks=$gitmarks"
+ echo "feature export-marks=$gitmarks"
+ fi
+
+ if test -n "$GIT_REMOTE_TESTGIT_FAILURE"
+ then
+ echo "feature done"
+ exit 1
+ fi
+
+ echo "feature done"
+ git fast-export \
+ ${testgitmarks:+"--import-marks=$testgitmarks"} \
+ ${testgitmarks:+"--export-marks=$testgitmarks"} \
+ $refs |
+ sed -e "s#refs/heads/#${prefix}/heads/#g"
+ echo "done"
+ ;;
+ export)
+ if test -n "$GIT_REMOTE_TESTGIT_FAILURE"
+ then
+ # consume input so fast-export doesn't get SIGPIPE;
+ # git would also notice that case, but we want
+ # to make sure we are exercising the later
+ # error checks
+ while read line; do
+ test "done" = "$line" && break
+ done
+ exit 1
+ fi
+
+ before=$(git for-each-ref --format=' %(refname) %(objectname) ')
+
+ git fast-import \
+ ${testgitmarks:+"--import-marks=$testgitmarks"} \
+ ${testgitmarks:+"--export-marks=$testgitmarks"} \
+ --quiet
+
+ # figure out which refs were updated
+ git for-each-ref --format='%(refname) %(objectname)' |
+ while read ref a
+ do
+ case "$before" in
+ *" $ref $a "*)
+ continue ;; # unchanged
+ esac
+ if test -z "$GIT_REMOTE_TESTGIT_PUSH_ERROR"
+ then
+ echo "ok $ref"
+ else
+ echo "error $ref $GIT_REMOTE_TESTGIT_PUSH_ERROR"
+ fi
+ done
+
+ echo
+ ;;
+ '')
+ exit
+ ;;
+ esac
+done
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"
--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.
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);
my ($debug_net_smtp) = 0; # Net::SMTP, see send_message()
-my $not_set_by_user = "true but not set by the user";
-
my %config_bool_settings = (
"thread" => [\$thread, 1],
- "chainreplyto" => [\$chain_reply_to, $not_set_by_user],
+ "chainreplyto" => [\$chain_reply_to, 0],
"suppressfrom" => [\$suppress_from, undef],
"signedoffbycc" => [\$signed_off_by_cc, undef],
"signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated
"smtpserveroption" => \@smtp_server_options,
"smtpuser" => \$smtp_authuser,
"smtppass" => \$smtp_authpass,
+ "smtpsslcertpath" => \$smtp_ssl_cert_path,
"smtpdomain" => \$smtp_domain,
"to" => \@initial_to,
"tocmd" => \$to_cmd,
"aliasesfile" => \@alias_files,
);
-# Help users prepare for 1.7.0
-sub chain_reply_to {
- if (defined $chain_reply_to &&
- $chain_reply_to eq $not_set_by_user) {
- print STDERR
- "In git 1.7.0, the default has changed to --no-chain-reply-to\n" .
- "Set sendemail.chainreplyto configuration variable to true if\n" .
- "you want to keep --chain-reply-to as your default.\n";
- $chain_reply_to = 0;
- }
- return $chain_reply_to;
-}
-
# Handle Uncouth Termination
sub signal_handler {
"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,
$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)? ",
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.
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
}
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;
}
$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
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;
}
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;
}
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'});
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;
$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) {
# set up for the next message
if ($thread && $message_was_sent &&
- (chain_reply_to() || !defined $reply_to || length($reply_to) == 0 ||
+ ($chain_reply_to || !defined $reply_to || length($reply_to) == 0 ||
$message_num == 1)) {
$reply_to = $message_id;
if (length $references > 0) {
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'";
$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;
}
: ${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
+}
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
-u|--include-untracked)
untracked=untracked
;;
+ -f|--force)
+ force=t
+ ;;
-a|--all)
untracked=all
;;
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"
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 "$@"
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=
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
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 '
my ($null_sha1) = ("0" x 40);
my @out = ();
my $unmatched = 0;
+ $/ = "\0";
while (<STDIN>) {
if (/^unmatched pathspec/) {
$unmatched = 1;
name=$2
url=$3
reference="$4"
+ depth="$5"
quiet=
if test -n "$GIT_QUIET"
then
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")"
cmd_add()
{
# parse $args after "submodule ... add".
+ reference_path=
while test $# -ne 0
do
case "$1" in
;;
--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
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
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
;;
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" &&
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
}
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
;;
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
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
}
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\.")"
# 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
}
--checkout)
update="checkout"
;;
+ --depth)
+ case "$2" in '') usage ;; esac
+ depth="--depth=$2"
+ shift
+ ;;
+ --depth=*)
+ depth=$1
+ ;;
--)
shift
break
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
# 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"
(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?
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
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"
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' |
! 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=
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
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"
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
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
$_template, $_shared,
$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
$_before, $_after,
- $_merge, $_strategy, $_preserve_merges, $_dry_run, $_local,
+ $_merge, $_strategy, $_preserve_merges, $_dry_run, $_parents, $_local,
$_prefix, $_no_checkout, $_url, $_verbose,
$_commit_url, $_tag, $_merge_info, $_interactive);
'config-dir=s' => \$Git::SVN::Ra::config_dir,
'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
'ignore-paths=s' => \$Git::SVN::Fetcher::_ignore_regex,
+ 'include-paths=s' => \$Git::SVN::Fetcher::_include_regex,
'ignore-refs=s' => \$Git::SVN::Ra::_ignore_refs_regex );
my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
'authors-file|A=s' => \$_authors,
{ 'message|m=s' => \$_message,
'destination|d=s' => \$_branch_dest,
'dry-run|n' => \$_dry_run,
+ 'parents' => \$_parents,
'tag|t' => \$_tag,
'username=s' => \$Git::SVN::Prompt::_username,
'commit-url=s' => \$_commit_url } ],
{ 'message|m=s' => \$_message,
'destination|d=s' => \$_branch_dest,
'dry-run|n' => \$_dry_run,
+ 'parents' => \$_parents,
'username=s' => \$Git::SVN::Prompt::_username,
'commit-url=s' => \$_commit_url } ],
'set-tree' => [ \&cmd_set_tree,
my $ignore_paths_regex = \$Git::SVN::Fetcher::_ignore_regex;
command_noisy('config', "$pfx.ignore-paths", $$ignore_paths_regex)
if defined $$ignore_paths_regex;
+ my $include_paths_regex = \$Git::SVN::Fetcher::_include_regex;
+ command_noisy('config', "$pfx.include-paths", $$include_paths_regex)
+ if defined $$include_paths_regex;
my $ignore_refs_regex = \$Git::SVN::Ra::_ignore_refs_regex;
command_noisy('config', "$pfx.ignore-refs", $$ignore_refs_regex)
if defined $$ignore_refs_regex;
}
sub merge_merge_info {
- my ($mergeinfo_one, $mergeinfo_two) = @_;
+ my ($mergeinfo_one, $mergeinfo_two, $ignore_branch) = @_;
my %result_hash = ();
merge_revs_into_hash(\%result_hash, $mergeinfo_one);
merge_revs_into_hash(\%result_hash, $mergeinfo_two);
+ delete $result_hash{$ignore_branch} if $ignore_branch;
+
my $result = '';
# Sort below is for consistency's sake
for my $branchname (sort keys(%result_hash)) {
my $all_parents_ok = 1;
my $aggregate_mergeinfo = '';
my $rooturl = $gs->repos_root;
+ my ($target_branch) = $gs->full_pushurl =~ /^\Q$rooturl\E(.*)/;
if (defined($rewritten_parent)) {
# Replace first parent with newly-rewritten version
# Merge previous mergeinfo values
$aggregate_mergeinfo =
merge_merge_info($aggregate_mergeinfo,
- $par_mergeinfo, 0);
+ $par_mergeinfo,
+ $target_branch);
next if $parent eq $parents[0]; # Skip first parent
# Add new changes being placed in tree by merge
my $newmergeinfo = "$branchpath:" . join(',', @revsin);
$aggregate_mergeinfo =
merge_merge_info($aggregate_mergeinfo,
- $newmergeinfo, 1);
+ $newmergeinfo,
+ $target_branch);
}
if ($all_parents_ok and $aggregate_mergeinfo) {
return $aggregate_mergeinfo;
sub cmd_dcommit {
my $head = shift;
command_noisy(qw/update-index --refresh/);
- git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) }
+ git_cmd_try { command_oneline(qw/diff-index --quiet HEAD --/) }
'Cannot dcommit with a dirty index. Commit your changes first, '
. "or stash them with `git stash'.\n";
$head ||= 'HEAD';
$ctx->ls($dst, 'HEAD', 0);
} and die "branch ${branch_name} already exists\n";
+ if ($_parents) {
+ mk_parent_dirs($ctx, $dst);
+ }
+
print "Copying ${src} at r${rev} to ${dst}...\n";
$ctx->copy($src, $rev, $dst)
unless $_dry_run;
$gs->fetch_all;
}
+sub mk_parent_dirs {
+ my ($ctx, $parent) = @_;
+ $parent =~ s{/[^/]*$}{};
+
+ if (!eval{$ctx->ls($parent, 'HEAD', 0)}) {
+ mk_parent_dirs($ctx, $parent);
+ print "Creating parent folder ${parent} ...\n";
+ $ctx->mkdir($parent) unless $_dry_run;
+ }
+}
+
sub cmd_find_rev {
my $revision_or_hash = shift or die "SVN or git revision required ",
"as a command-line argument\n";
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;
}
sub working_head_info {
my ($head, $refs) = @_;
my @args = qw/rev-list --first-parent --pretty=medium/;
- my ($fh, $ctx) = command_output_pipe(@args, $head);
+ my ($fh, $ctx) = command_output_pipe(@args, $head, "--");
my $hash;
my %max;
while (<$fh>) {
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
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
;;
esac
;;
-w3m|elinks|links|lynx|open)
+w3m|elinks|links|lynx|open|cygstart)
"$browser_path" "$@"
;;
start)
#include "help.h"
#include "quote.h"
#include "run-command.h"
+#include "commit.h"
const char git_usage_string[] =
"git [--version] [--help] [-c name=value]\n"
setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "0", 1);
if (envchanged)
*envchanged = 1;
+ } else if (!strcmp(cmd, "--shallow-file")) {
+ (*argv)++;
+ (*argc)--;
+ set_alternate_shallow_file((*argv)[0]);
+ if (envchanged)
+ *envchanged = 1;
} else {
fprintf(stderr, "Unknown option: %s\n", cmd);
usage(git_usage_string);
{ "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,
}
-int main(int argc, const char **argv)
+int main(int argc, char **av)
{
+ const char **argv = (const char **) av;
const char *cmd;
startup_info = &git_startup_info;
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();
/*
--- /dev/null
+1 VERSIONINFO
+FILEVERSION MAJOR,MINOR,PATCH,0
+PRODUCTVERSION MAJOR,MINOR,PATCH,0
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" /* LANG_ENGLISH/SUBLANG_ENGLISH_US, Unicode CP */
+ BEGIN
+ VALUE "CompanyName", "The Git Development Community\0"
+ VALUE "FileDescription", "Git for Windows\0"
+ VALUE "InternalName", "git\0"
+ VALUE "OriginalFilename", "git.exe\0"
+ VALUE "ProductName", "Git\0"
+ VALUE "ProductVersion", GIT_VERSION "\0"
+ END
+ END
+
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
return [string map {&& & & {}} [mc $str]]
}
+proc cleardropsel {w} {
+ $w selection clear
+}
proc makedroplist {w varname args} {
global use_ttk
if {$use_ttk} {
if {$cx > $width} {set width $cx}
}
set gm [ttk::combobox $w -width $width -state readonly\
- -textvariable $varname -values $args]
+ -textvariable $varname -values $args \
+ -exportselection false]
+ bind $gm <<ComboboxSelected>> [list $gm selection clear]
} else {
set gm [eval [linsert $args 0 tk_optionMenu $w $varname]]
}
global highlight_files gdttype
global searchstring sstring
global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
+ global uifgcolor uifgdisabledcolor
+ global filesepbgcolor filesepfgcolor
+ global mergecolors foundbgcolor currentsearchhitbgcolor
global headctxmenu progresscanv progressitem progresscoords statusw
global fprogitem fprogcoord lastprogupdate progupdatepending
global rprogitem rprogcoord rownumsel numcommits
0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
}
- image create bitmap bm-left -data $bm_left_data
- image create bitmap bm-left-gray -data $bm_left_data -foreground "#999"
- image create bitmap bm-right -data $bm_right_data
- image create bitmap bm-right-gray -data $bm_right_data -foreground "#999"
+ image create bitmap bm-left -data $bm_left_data -foreground $uifgcolor
+ image create bitmap bm-left-gray -data $bm_left_data -foreground $uifgdisabledcolor
+ image create bitmap bm-right -data $bm_right_data -foreground $uifgcolor
+ image create bitmap bm-right-gray -data $bm_right_data -foreground $uifgdisabledcolor
${NS}::button .tf.bar.leftbut -command goback -state disabled -width 26
if {$use_ttk} {
set gm [makedroplist .tf.lbar.gdttype gdttype \
[mc "containing:"] \
[mc "touching paths:"] \
- [mc "adding/removing string:"]]
+ [mc "adding/removing string:"] \
+ [mc "changing lines matching:"]]
trace add variable gdttype write gdttype_change
pack .tf.lbar.gdttype -side left -fill y
lappend fglist $ctext
$ctext tag conf comment -wrap $wrapcomment
- $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
+ $ctext tag conf filesep -font textfontbold -fore $filesepfgcolor -back $filesepbgcolor
$ctext tag conf hunksep -fore [lindex $diffcolors 2]
$ctext tag conf d0 -fore [lindex $diffcolors 0]
$ctext tag conf dresult -fore [lindex $diffcolors 1]
- $ctext tag conf m0 -fore red
- $ctext tag conf m1 -fore blue
- $ctext tag conf m2 -fore green
- $ctext tag conf m3 -fore purple
- $ctext tag conf m4 -fore brown
- $ctext tag conf m5 -fore "#009090"
- $ctext tag conf m6 -fore magenta
- $ctext tag conf m7 -fore "#808000"
- $ctext tag conf m8 -fore "#009000"
- $ctext tag conf m9 -fore "#ff0080"
- $ctext tag conf m10 -fore cyan
- $ctext tag conf m11 -fore "#b07070"
- $ctext tag conf m12 -fore "#70b0f0"
- $ctext tag conf m13 -fore "#70f0b0"
- $ctext tag conf m14 -fore "#f0b070"
- $ctext tag conf m15 -fore "#ff70b0"
+ $ctext tag conf m0 -fore [lindex $mergecolors 0]
+ $ctext tag conf m1 -fore [lindex $mergecolors 1]
+ $ctext tag conf m2 -fore [lindex $mergecolors 2]
+ $ctext tag conf m3 -fore [lindex $mergecolors 3]
+ $ctext tag conf m4 -fore [lindex $mergecolors 4]
+ $ctext tag conf m5 -fore [lindex $mergecolors 5]
+ $ctext tag conf m6 -fore [lindex $mergecolors 6]
+ $ctext tag conf m7 -fore [lindex $mergecolors 7]
+ $ctext tag conf m8 -fore [lindex $mergecolors 8]
+ $ctext tag conf m9 -fore [lindex $mergecolors 9]
+ $ctext tag conf m10 -fore [lindex $mergecolors 10]
+ $ctext tag conf m11 -fore [lindex $mergecolors 11]
+ $ctext tag conf m12 -fore [lindex $mergecolors 12]
+ $ctext tag conf m13 -fore [lindex $mergecolors 13]
+ $ctext tag conf m14 -fore [lindex $mergecolors 14]
+ $ctext tag conf m15 -fore [lindex $mergecolors 15]
$ctext tag conf mmax -fore darkgrey
set mergemax 16
$ctext tag conf mresult -font textfontbold
$ctext tag conf msep -font textfontbold
- $ctext tag conf found -back yellow
- $ctext tag conf currentsearchhit -back orange
+ $ctext tag conf found -back $foundbgcolor
+ $ctext tag conf currentsearchhit -back $currentsearchhitbgcolor
$ctext tag conf wwrap -wrap word
.pwbottom add .bleft
{mc "Compare with marked commit" command compare_commits}
{mc "Diff this -> marked commit" command {diffvsmark 0}}
{mc "Diff marked commit -> this" command {diffvsmark 1}}
+ {mc "Revert this commit" command revert}
}
$rowctxmenu configure -tearoff 0
global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
global cmitmode wrapcomment datetimeformat limitdiffs
global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor
+ global uifgcolor uifgdisabledcolor
+ global headbgcolor headfgcolor headoutlinecolor remotebgcolor
+ global tagbgcolor tagfgcolor tagoutlinecolor
+ global reflinecolor filesepbgcolor filesepfgcolor
+ global mergecolors foundbgcolor currentsearchhitbgcolor
+ global linehoverbgcolor linehoverfgcolor linehoveroutlinecolor circlecolors
+ global mainheadcirclecolor workingfilescirclecolor indexcirclecolor
+ global linkfgcolor circleoutlinecolor
global autoselect autosellen extdifftool perfile_attrs markbgcolor use_ttk
global hideremotes want_ttk maxrefs
puts $f [list set want_ttk $want_ttk]
puts $f [list set bgcolor $bgcolor]
puts $f [list set fgcolor $fgcolor]
+ puts $f [list set uifgcolor $uifgcolor]
+ puts $f [list set uifgdisabledcolor $uifgdisabledcolor]
puts $f [list set colors $colors]
puts $f [list set diffcolors $diffcolors]
+ puts $f [list set mergecolors $mergecolors]
puts $f [list set markbgcolor $markbgcolor]
puts $f [list set diffcontext $diffcontext]
puts $f [list set selectbgcolor $selectbgcolor]
+ puts $f [list set foundbgcolor $foundbgcolor]
+ puts $f [list set currentsearchhitbgcolor $currentsearchhitbgcolor]
puts $f [list set extdifftool $extdifftool]
puts $f [list set perfile_attrs $perfile_attrs]
+ puts $f [list set headbgcolor $headbgcolor]
+ puts $f [list set headfgcolor $headfgcolor]
+ puts $f [list set headoutlinecolor $headoutlinecolor]
+ puts $f [list set remotebgcolor $remotebgcolor]
+ puts $f [list set tagbgcolor $tagbgcolor]
+ puts $f [list set tagfgcolor $tagfgcolor]
+ puts $f [list set tagoutlinecolor $tagoutlinecolor]
+ puts $f [list set reflinecolor $reflinecolor]
+ puts $f [list set filesepbgcolor $filesepbgcolor]
+ puts $f [list set filesepfgcolor $filesepfgcolor]
+ puts $f [list set linehoverbgcolor $linehoverbgcolor]
+ puts $f [list set linehoverfgcolor $linehoverfgcolor]
+ puts $f [list set linehoveroutlinecolor $linehoveroutlinecolor]
+ puts $f [list set mainheadcirclecolor $mainheadcirclecolor]
+ puts $f [list set workingfilescirclecolor $workingfilescirclecolor]
+ puts $f [list set indexcirclecolor $indexcirclecolor]
+ puts $f [list set circlecolors $circlecolors]
+ puts $f [list set linkfgcolor $linkfgcolor]
+ puts $f [list set circleoutlinecolor $circleoutlinecolor]
puts $f "set geometry(main) [wm geometry .]"
puts $f "set geometry(state) [wm state .]"
set gdtargs [concat -- $relative_paths]
} elseif {$gdttype eq [mc "adding/removing string:"]} {
set gdtargs [list "-S$highlight_files"]
+ } elseif {$gdttype eq [mc "changing lines matching:"]} {
+ set gdtargs [list "-G$highlight_files"]
} else {
# must be "containing:", i.e. we're searching commit info
return
global linehtag linentag linedtag selectedline
global canvxmax boldids boldnameids fgcolor markedid
global mainheadid nullid nullid2 circleitem circlecolors ctxbut
+ global mainheadcirclecolor workingfilescirclecolor indexcirclecolor
+ global circleoutlinecolor
# listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
set listed $cmitlisted($curview,$id)
if {$id eq $nullid} {
- set ofill red
+ set ofill $workingfilescirclecolor
} elseif {$id eq $nullid2} {
- set ofill green
+ set ofill $indexcirclecolor
} elseif {$id eq $mainheadid} {
- set ofill yellow
+ set ofill $mainheadcirclecolor
} else {
set ofill [lindex $circlecolors $listed]
}
if {$listed <= 2} {
set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
[expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
- -fill $ofill -outline $fgcolor -width 1 -tags circle]
+ -fill $ofill -outline $circleoutlinecolor -width 1 -tags circle]
} elseif {$listed == 3} {
# triangle pointing left for left-side commits
set t [$canv create polygon \
[expr {$x - $orad}] $y \
[expr {$x + $orad - 1}] [expr {$y - $orad}] \
[expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
- -fill $ofill -outline $fgcolor -width 1 -tags circle]
+ -fill $ofill -outline $circleoutlinecolor -width 1 -tags circle]
} else {
# triangle pointing right for right-side commits
set t [$canv create polygon \
[expr {$x + $orad - 1}] $y \
[expr {$x - $orad}] [expr {$y - $orad}] \
[expr {$x - $orad}] [expr {$y + $orad - 1}] \
- -fill $ofill -outline $fgcolor -width 1 -tags circle]
+ -fill $ofill -outline $circleoutlinecolor -width 1 -tags circle]
}
set circleitem($row) $t
$canv raise $t
global idtags idheads idotherrefs mainhead
global linespc lthickness
global canv rowtextx curview fgcolor bgcolor ctxbut
+ global headbgcolor headfgcolor headoutlinecolor remotebgcolor
+ global tagbgcolor tagfgcolor tagoutlinecolor
+ global reflinecolor
set marks {}
set ntags 0
set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
}
set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
- -width $lthickness -fill black -tags tag.$id]
+ -width $lthickness -fill $reflinecolor -tags tag.$id]
$canv lower $t
foreach tag $marks x $xvals wid $wvals {
set tag_quoted [string map {% %%} $tag]
# draw a tag
set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
$xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
- -width 1 -outline black -fill yellow -tags tag.$id]
+ -width 1 -outline $tagoutlinecolor -fill $tagbgcolor \
+ -tags tag.$id]
$canv bind $t <1> [list showtag $tag_quoted 1]
set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
} else {
# draw a head or other ref
if {[incr nheads -1] >= 0} {
- set col green
+ set col $headbgcolor
if {$tag eq $mainhead} {
set font mainfontbold
}
set yti [expr {$yt + 1}]
set xri [expr {$x + $rwid}]
$canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
- -width 0 -fill "#ffddaa" -tags tag.$id
+ -width 0 -fill $remotebgcolor -tags tag.$id
}
}
- set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
+ set t [$canv create text $xl $y1 -anchor w -text $tag -fill $headfgcolor \
-font $font -tags [list tag.$id text]]
if {$ntags >= 0} {
$canv bind $t <1> [list showtag $tag_quoted 1]
proc setlink {id lk} {
global curview ctext pendinglinks
+ global linkfgcolor
if {[string range $id 0 1] eq "-g"} {
set id [string range $id 2 end]
set known [commitinview $id $curview]
}
if {$known} {
- $ctext tag conf $lk -foreground blue -underline 1
+ $ctext tag conf $lk -foreground $linkfgcolor -underline 1
$ctext tag bind $lk <1> [list selbyid $id]
$ctext tag bind $lk <Enter> {linkcursor %W 1}
$ctext tag bind $lk <Leave> {linkcursor %W -1}
}
proc gettreediffs {ids} {
- global treediff treepending
+ global treediff treepending limitdiffs vfilelimit curview
- if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
+ set cmd [diffcmd $ids {--no-commit-id}]
+ if {$limitdiffs && $vfilelimit($curview) ne {}} {
+ set cmd [concat $cmd -- $vfilelimit($curview)]
+ }
+ if {[catch {set gdtf [open $cmd r]}]} return
set treepending $ids
set treediff {}
return [expr {$nr >= $max? 2: 1}]
}
close $gdtf
- if {$limitdiffs && $vfilelimit($curview) ne {}} {
- set flist {}
- foreach f $treediff {
- if {[path_filter $vfilelimit($curview) $f]} {
- lappend flist $f
- }
- }
- set treediffs($ids) $flist
- } else {
- set treediffs($ids) $treediff
- }
+ set treediffs($ids) $treediff
unset treepending
if {$cmitmode eq "tree" && [llength $diffids] == 1} {
gettree $diffids
proc linehover {} {
global hoverx hovery hoverid hovertimer
global canv linespc lthickness
+ global linehoverbgcolor linehoverfgcolor linehoveroutlinecolor
+
global commitinfo
set text [lindex $commitinfo($hoverid) 0]
set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
set y1 [expr {$y + $linespc + 2 * $lthickness}]
set t [$canv create rectangle $x0 $y0 $x1 $y1 \
- -fill \#ffff80 -outline black -width 1 -tags hover]
+ -fill $linehoverbgcolor -outline $linehoveroutlinecolor \
+ -width 1 -tags hover]
$canv raise $t
set t [$canv create text $x $y -anchor nw -text $text -tags hover \
- -font mainfont]
+ -font mainfont -fill $linehoverfgcolor]
$canv raise $t
}
proc redrawtags {id} {
global canv linehtag idpos currentid curview cmitlisted markedid
global canvxmax iddrawn circleitem mainheadid circlecolors
+ global mainheadcirclecolor
if {![commitinview $id $curview]} return
if {![info exists iddrawn($id)]} return
set row [rowofcommit $id]
if {$id eq $mainheadid} {
- set ofill yellow
+ set ofill $mainheadcirclecolor
} else {
set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
}
notbusy cherrypick
}
+proc revert {} {
+ global rowmenuid curview
+ global mainhead mainheadid
+ global gitdir
+
+ set oldhead [exec git rev-parse HEAD]
+ set dheads [descheads $rowmenuid]
+ if { $dheads eq {} || [lsearch -exact $dheads $oldhead] == -1 } {
+ set ok [confirm_popup [mc "Commit %s is not\
+ included in branch %s -- really revert it?" \
+ [string range $rowmenuid 0 7] $mainhead]]
+ if {!$ok} return
+ }
+ nowbusy revert [mc "Reverting"]
+ update
+
+ if [catch {exec git revert --no-edit $rowmenuid} err] {
+ notbusy revert
+ if [regexp {files would be overwritten by merge:(\n(( |\t)+[^\n]+\n)+)}\
+ $err match files] {
+ regsub {\n( |\t)+} $files "\n" files
+ error_popup [mc "Revert failed because of local changes to\
+ the following files:%s Please commit, reset or stash \
+ your changes and try again." $files]
+ } elseif [regexp {error: could not revert} $err] {
+ if [confirm_popup [mc "Revert failed because of merge conflict.\n\
+ Do you wish to run git citool to resolve it?"]] {
+ # Force citool to read MERGE_MSG
+ file delete [file join $gitdir "GITGUI_MSG"]
+ exec_citool {} $rowmenuid
+ }
+ } else { error_popup $err }
+ run updatecommits
+ return
+ }
+
+ set newhead [exec git rev-parse HEAD]
+ if { $newhead eq $oldhead } {
+ notbusy revert
+ error_popup [mc "No changes committed"]
+ return
+ }
+
+ addnewchild $newhead $oldhead
+
+ if [commitinview $oldhead $curview] {
+ # XXX this isn't right if we have a path limit...
+ insertrow $newhead $oldhead $curview
+ if {$mainhead ne {}} {
+ movehead $newhead $mainhead
+ movedhead $newhead $mainhead
+ }
+ set mainheadid $newhead
+ redrawtags $oldhead
+ redrawtags $newhead
+ selbyid $newhead
+ }
+
+ notbusy revert
+}
+
proc resethead {} {
global mainhead rowmenuid confirm_ok resettype NS
set linknum 0
if {![info exists cached_tagcontent($tag)]} {
catch {
- set cached_tagcontent($tag) [exec git cat-file tag $tag]
+ set cached_tagcontent($tag) [exec git cat-file -p $tag]
}
}
if {[info exists cached_tagcontent($tag)]} {
exit 1
}
+# on OSX bring the current Wish process window to front
+if {[tk windowingsystem] eq "aqua"} {
+ exec osascript -e [format {
+ tell application "System Events"
+ set frontmost of processes whose unix id is %d to true
+ end tell
+ } [pid] ]
+}
+
# Unset GIT_TRACE var if set
if { [info exists ::env(GIT_TRACE)] } {
unset ::env(GIT_TRACE)
set colors {green red blue magenta darkgrey brown orange}
if {[tk windowingsystem] eq "win32"} {
set uicolor SystemButtonFace
+ set uifgcolor SystemButtonText
+ set uifgdisabledcolor SystemDisabledText
set bgcolor SystemWindow
- set fgcolor SystemButtonText
+ set fgcolor SystemWindowText
set selectbgcolor SystemHighlight
} else {
set uicolor grey85
+ set uifgcolor black
+ set uifgdisabledcolor "#999"
set bgcolor white
set fgcolor black
set selectbgcolor gray85
}
set diffcolors {red "#00a000" blue}
set diffcontext 3
+set mergecolors {red blue green purple brown "#009090" magenta "#808000" "#009000" "#ff0080" cyan "#b07070" "#70b0f0" "#70f0b0" "#f0b070" "#ff70b0"}
set ignorespace 0
set worddiff ""
set markbgcolor "#e0e0ff"
+set headbgcolor green
+set headfgcolor black
+set headoutlinecolor black
+set remotebgcolor #ffddaa
+set tagbgcolor yellow
+set tagfgcolor black
+set tagoutlinecolor black
+set reflinecolor black
+set filesepbgcolor #aaaaaa
+set filesepfgcolor black
+set linehoverbgcolor #ffff80
+set linehoverfgcolor black
+set linehoveroutlinecolor black
+set mainheadcirclecolor yellow
+set workingfilescirclecolor red
+set indexcirclecolor green
set circlecolors {white blue gray blue blue}
+set linkfgcolor blue
+set circleoutlinecolor $fgcolor
+set foundbgcolor yellow
+set currentsearchhitbgcolor orange
# button for popping up context menus
if {[tk windowingsystem] eq "aqua"} {
# Swedish translation for gitk
-# Copyright (C) 2005-2012 Paul Mackerras
+# Copyright (C) 2005-2013 Paul Mackerras
# This file is distributed under the same license as the gitk package.
#
# Mikael Magnusson <mikachu@gmail.com>, 2008.
-# Peter Krefting <peter@softwolves.pp.se>, 2008, 2009, 2010, 2012.
+# Peter Krefting <peter@softwolves.pp.se>, 2008, 2009, 2010, 2012, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: sv\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-10-03 08:09+0100\n"
-"PO-Revision-Date: 2012-10-03 08:13+0100\n"
+"POT-Creation-Date: 2013-05-16 08:06+0100\n"
+"PO-Revision-Date: 2013-05-16 08:12+0100\n"
"Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
"Language: sv\n"
msgid "Couldn't get list of unmerged files:"
msgstr "Kunde inte hämta lista över ej sammanslagna filer:"
-#: gitk:210 gitk:2317
+#: gitk:210 gitk:2334
msgid "Color words"
msgstr "Färga ord"
-#: gitk:215 gitk:2317 gitk:7888 gitk:7921
+#: gitk:215 gitk:2334 gitk:7977 gitk:8010
msgid "Markup words"
msgstr "Märk upp ord"
msgid "Reading"
msgstr "Läser"
-#: gitk:484 gitk:4353
+#: gitk:484 gitk:4409
msgid "Reading commits..."
msgstr "Läser incheckningar..."
-#: gitk:487 gitk:1625 gitk:4356
+#: gitk:487 gitk:1625 gitk:4412
msgid "No commits selected"
msgstr "Inga incheckningar markerade"
msgid "mc"
msgstr "mc"
-#: gitk:1911 gitk:4146 gitk:9282 gitk:10824 gitk:11100
+#: gitk:1911 gitk:4202 gitk:9437 gitk:10979 gitk:11258
msgid "OK"
msgstr "OK"
-#: gitk:1913 gitk:4148 gitk:8871 gitk:8950 gitk:9065 gitk:9114 gitk:9284
-#: gitk:10825 gitk:11101
+#: gitk:1913 gitk:4204 gitk:8964 gitk:9043 gitk:9159 gitk:9208 gitk:9439
+#: gitk:10980 gitk:11259
msgid "Cancel"
msgstr "Avbryt"
-#: gitk:2040
+#: gitk:2048
msgid "Update"
msgstr "Uppdatera"
-#: gitk:2041
+#: gitk:2049
msgid "Reload"
msgstr "Ladda om"
-#: gitk:2042
+#: gitk:2050
msgid "Reread references"
msgstr "Läs om referenser"
-#: gitk:2043
+#: gitk:2051
msgid "List references"
msgstr "Visa referenser"
-#: gitk:2045
+#: gitk:2053
msgid "Start git gui"
msgstr "Starta git gui"
-#: gitk:2047
+#: gitk:2055
msgid "Quit"
msgstr "Avsluta"
-#: gitk:2039
+#: gitk:2047
msgid "File"
msgstr "Arkiv"
-#: gitk:2051
+#: gitk:2059
msgid "Preferences"
msgstr "Inställningar"
-#: gitk:2050
+#: gitk:2058
msgid "Edit"
msgstr "Redigera"
-#: gitk:2055
+#: gitk:2063
msgid "New view..."
msgstr "Ny vy..."
-#: gitk:2056
+#: gitk:2064
msgid "Edit view..."
msgstr "Ändra vy..."
-#: gitk:2057
+#: gitk:2065
msgid "Delete view"
msgstr "Ta bort vy"
-#: gitk:2059
+#: gitk:2067
msgid "All files"
msgstr "Alla filer"
-#: gitk:2054 gitk:3899
+#: gitk:2062 gitk:3955
msgid "View"
msgstr "Visa"
-#: gitk:2064 gitk:2074 gitk:2869
+#: gitk:2072 gitk:2082 gitk:2925
msgid "About gitk"
msgstr "Om gitk"
-#: gitk:2065 gitk:2079
+#: gitk:2073 gitk:2087
msgid "Key bindings"
msgstr "Tangentbordsbindningar"
-#: gitk:2063 gitk:2078
+#: gitk:2071 gitk:2086
msgid "Help"
msgstr "Hjälp"
-#: gitk:2156 gitk:8330
+#: gitk:2164 gitk:8420
msgid "SHA1 ID:"
msgstr "SHA1-id:"
-#: gitk:2192
+#: gitk:2208
msgid "Row"
msgstr "Rad"
-#: gitk:2230
+#: gitk:2246
msgid "Find"
msgstr "Sök"
-#: gitk:2231
+#: gitk:2247
msgid "next"
msgstr "nästa"
-#: gitk:2232
+#: gitk:2248
msgid "prev"
msgstr "föreg"
-#: gitk:2233
+#: gitk:2249
msgid "commit"
msgstr "incheckning"
-#: gitk:2236 gitk:2238 gitk:4514 gitk:4537 gitk:4561 gitk:6528 gitk:6600
-#: gitk:6685
+#: gitk:2252 gitk:2254 gitk:4570 gitk:4593 gitk:4617 gitk:6592 gitk:6664
+#: gitk:6749
msgid "containing:"
msgstr "som innehåller:"
-#: gitk:2239 gitk:3381 gitk:3386 gitk:4590
+#: gitk:2255 gitk:3437 gitk:3442 gitk:4646
msgid "touching paths:"
msgstr "som rör sökväg:"
-#: gitk:2240 gitk:4604
+#: gitk:2256 gitk:4660
msgid "adding/removing string:"
msgstr "som lägger/till tar bort sträng:"
-#: gitk:2249 gitk:2251 gitk:4593
+#: gitk:2257 gitk:4662
+msgid "changing lines matching:"
+msgstr "ändrar rader som matchar:"
+
+#: gitk:2266 gitk:2268 gitk:4649
msgid "Exact"
msgstr "Exakt"
-#: gitk:2251 gitk:4679 gitk:6496
+#: gitk:2268 gitk:4737 gitk:6560
msgid "IgnCase"
msgstr "IgnVersaler"
-#: gitk:2251 gitk:4563 gitk:4677 gitk:6492
+#: gitk:2268 gitk:4619 gitk:4735 gitk:6556
msgid "Regexp"
msgstr "Reg.uttr."
-#: gitk:2253 gitk:2254 gitk:4699 gitk:4729 gitk:4736 gitk:6621 gitk:6689
+#: gitk:2270 gitk:2271 gitk:4757 gitk:4787 gitk:4794 gitk:6685 gitk:6753
msgid "All fields"
msgstr "Alla fält"
-#: gitk:2254 gitk:4696 gitk:4729 gitk:6559
+#: gitk:2271 gitk:4754 gitk:4787 gitk:6623
msgid "Headline"
msgstr "Rubrik"
-#: gitk:2255 gitk:4696 gitk:6559 gitk:6689 gitk:7126
+#: gitk:2272 gitk:4754 gitk:6623 gitk:6753 gitk:7221
msgid "Comments"
msgstr "Kommentarer"
-#: gitk:2255 gitk:4696 gitk:4701 gitk:4736 gitk:6559 gitk:7061 gitk:8505
-#: gitk:8520
+#: gitk:2272 gitk:4754 gitk:4759 gitk:4794 gitk:6623 gitk:7156 gitk:8598
+#: gitk:8613
msgid "Author"
msgstr "Författare"
-#: gitk:2255 gitk:4696 gitk:6559 gitk:7063
+#: gitk:2272 gitk:4754 gitk:6623 gitk:7158
msgid "Committer"
msgstr "Incheckare"
-#: gitk:2286
+#: gitk:2303
msgid "Search"
msgstr "Sök"
-#: gitk:2294
+#: gitk:2311
msgid "Diff"
msgstr "Diff"
-#: gitk:2296
+#: gitk:2313
msgid "Old version"
msgstr "Gammal version"
-#: gitk:2298
+#: gitk:2315
msgid "New version"
msgstr "Ny version"
-#: gitk:2300
+#: gitk:2317
msgid "Lines of context"
msgstr "Rader sammanhang"
-#: gitk:2310
+#: gitk:2327
msgid "Ignore space change"
msgstr "Ignorera ändringar i blanksteg"
-#: gitk:2314 gitk:2316 gitk:7646 gitk:7874
+#: gitk:2331 gitk:2333 gitk:7735 gitk:7963
msgid "Line diff"
msgstr "Rad-diff"
-#: gitk:2379
+#: gitk:2397
msgid "Patch"
msgstr "Patch"
-#: gitk:2381
+#: gitk:2399
msgid "Tree"
msgstr "Träd"
-#: gitk:2540 gitk:2559
+#: gitk:2557 gitk:2577
msgid "Diff this -> selected"
msgstr "Diff denna -> markerad"
-#: gitk:2541 gitk:2560
+#: gitk:2558 gitk:2578
msgid "Diff selected -> this"
msgstr "Diff markerad -> denna"
-#: gitk:2542 gitk:2561
+#: gitk:2559 gitk:2579
msgid "Make patch"
msgstr "Skapa patch"
-#: gitk:2543 gitk:8929
+#: gitk:2560 gitk:9022
msgid "Create tag"
msgstr "Skapa tagg"
-#: gitk:2544 gitk:9045
+#: gitk:2561 gitk:9139
msgid "Write commit to file"
msgstr "Skriv incheckning till fil"
-#: gitk:2545 gitk:9102
+#: gitk:2562 gitk:9196
msgid "Create new branch"
msgstr "Skapa ny gren"
-#: gitk:2546
+#: gitk:2563
msgid "Cherry-pick this commit"
msgstr "Plocka denna incheckning"
-#: gitk:2547
+#: gitk:2564
msgid "Reset HEAD branch to here"
msgstr "Återställ HEAD-grenen hit"
-#: gitk:2548
+#: gitk:2565
msgid "Mark this commit"
msgstr "Markera denna incheckning"
-#: gitk:2549
+#: gitk:2566
msgid "Return to mark"
msgstr "Återgå till markering"
-#: gitk:2550
+#: gitk:2567
msgid "Find descendant of this and mark"
msgstr "Hitta efterföljare till denna och markera"
-#: gitk:2551
+#: gitk:2568
msgid "Compare with marked commit"
msgstr "Jämför med markerad incheckning"
-#: gitk:2552 gitk:2562
+#: gitk:2569 gitk:2580
msgid "Diff this -> marked commit"
msgstr "Diff denna -> markerad incheckning"
-#: gitk:2553 gitk:2563
+#: gitk:2570 gitk:2581
msgid "Diff marked commit -> this"
msgstr "Diff markerad incheckning -> denna"
-#: gitk:2569
+#: gitk:2571
+msgid "Revert this commit"
+msgstr "Ångra denna incheckning"
+
+#: gitk:2587
msgid "Check out this branch"
msgstr "Checka ut denna gren"
-#: gitk:2570
+#: gitk:2588
msgid "Remove this branch"
msgstr "Ta bort denna gren"
-#: gitk:2577
+#: gitk:2595
msgid "Highlight this too"
msgstr "Markera även detta"
-#: gitk:2578
+#: gitk:2596
msgid "Highlight this only"
msgstr "Markera bara detta"
-#: gitk:2579
+#: gitk:2597
msgid "External diff"
msgstr "Extern diff"
-#: gitk:2580
+#: gitk:2598
msgid "Blame parent commit"
msgstr "Klandra föräldraincheckning"
-#: gitk:2587
+#: gitk:2605
msgid "Show origin of this line"
msgstr "Visa ursprunget för den här raden"
-#: gitk:2588
+#: gitk:2606
msgid "Run git gui blame on this line"
msgstr "Kör git gui blame på den här raden"
-#: gitk:2871
+#: gitk:2927
msgid ""
"\n"
"Gitk - a commit viewer for git\n"
"\n"
"Använd och vidareförmedla enligt villkoren i GNU General Public License"
-#: gitk:2879 gitk:2944 gitk:9468
+#: gitk:2935 gitk:3000 gitk:9623
msgid "Close"
msgstr "Stäng"
-#: gitk:2900
+#: gitk:2956
msgid "Gitk key bindings"
msgstr "Tangentbordsbindningar för Gitk"
-#: gitk:2903
+#: gitk:2959
msgid "Gitk key bindings:"
msgstr "Tangentbordsbindningar för Gitk:"
-#: gitk:2905
+#: gitk:2961
#, tcl-format
msgid "<%s-Q>\t\tQuit"
msgstr "<%s-Q>\t\tAvsluta"
-#: gitk:2906
+#: gitk:2962
#, tcl-format
msgid "<%s-W>\t\tClose window"
msgstr "<%s-W>\t\tStäng fönster"
-#: gitk:2907
+#: gitk:2963
msgid "<Home>\t\tMove to first commit"
msgstr "<Home>\t\tGå till första incheckning"
-#: gitk:2908
+#: gitk:2964
msgid "<End>\t\tMove to last commit"
msgstr "<End>\t\tGå till sista incheckning"
-#: gitk:2909
+#: gitk:2965
msgid "<Up>, p, k\tMove up one commit"
msgstr "<Upp>, p, k\tGå en incheckning upp"
-#: gitk:2910
+#: gitk:2966
msgid "<Down>, n, j\tMove down one commit"
msgstr "<Ned>, n, j\tGå en incheckning ned"
-#: gitk:2911
+#: gitk:2967
msgid "<Left>, z, h\tGo back in history list"
msgstr "<Vänster>, z, h\tGå bakåt i historiken"
-#: gitk:2912
+#: gitk:2968
msgid "<Right>, x, l\tGo forward in history list"
msgstr "<Höger>, x, l\tGå framåt i historiken"
-#: gitk:2913
+#: gitk:2969
msgid "<PageUp>\tMove up one page in commit list"
msgstr "<PageUp>\tGå upp en sida i incheckningslistan"
-#: gitk:2914
+#: gitk:2970
msgid "<PageDown>\tMove down one page in commit list"
msgstr "<PageDown>\tGå ned en sida i incheckningslistan"
-#: gitk:2915
+#: gitk:2971
#, tcl-format
msgid "<%s-Home>\tScroll to top of commit list"
msgstr "<%s-Home>\tRulla till början av incheckningslistan"
-#: gitk:2916
+#: gitk:2972
#, tcl-format
msgid "<%s-End>\tScroll to bottom of commit list"
msgstr "<%s-End>\tRulla till slutet av incheckningslistan"
-#: gitk:2917
+#: gitk:2973
#, tcl-format
msgid "<%s-Up>\tScroll commit list up one line"
msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg"
-#: gitk:2918
+#: gitk:2974
#, tcl-format
msgid "<%s-Down>\tScroll commit list down one line"
msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg"
-#: gitk:2919
+#: gitk:2975
#, tcl-format
msgid "<%s-PageUp>\tScroll commit list up one page"
msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida"
-#: gitk:2920
+#: gitk:2976
#, tcl-format
msgid "<%s-PageDown>\tScroll commit list down one page"
msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida"
-#: gitk:2921
+#: gitk:2977
msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)"
-#: gitk:2922
+#: gitk:2978
msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)"
-#: gitk:2923
+#: gitk:2979
msgid "<Delete>, b\tScroll diff view up one page"
msgstr "<Delete>, b\tRulla diffvisningen upp en sida"
-#: gitk:2924
+#: gitk:2980
msgid "<Backspace>\tScroll diff view up one page"
msgstr "<Baksteg>\tRulla diffvisningen upp en sida"
-#: gitk:2925
+#: gitk:2981
msgid "<Space>\t\tScroll diff view down one page"
msgstr "<Blanksteg>\tRulla diffvisningen ned en sida"
-#: gitk:2926
+#: gitk:2982
msgid "u\t\tScroll diff view up 18 lines"
msgstr "u\t\tRulla diffvisningen upp 18 rader"
-#: gitk:2927
+#: gitk:2983
msgid "d\t\tScroll diff view down 18 lines"
msgstr "d\t\tRulla diffvisningen ned 18 rader"
-#: gitk:2928
+#: gitk:2984
#, tcl-format
msgid "<%s-F>\t\tFind"
msgstr "<%s-F>\t\tSök"
-#: gitk:2929
+#: gitk:2985
#, tcl-format
msgid "<%s-G>\t\tMove to next find hit"
msgstr "<%s-G>\t\tGå till nästa sökträff"
-#: gitk:2930
+#: gitk:2986
msgid "<Return>\tMove to next find hit"
msgstr "<Return>\t\tGå till nästa sökträff"
-#: gitk:2931
+#: gitk:2987
msgid "/\t\tFocus the search box"
msgstr "/\t\tFokusera sökrutan"
-#: gitk:2932
+#: gitk:2988
msgid "?\t\tMove to previous find hit"
msgstr "?\t\tGå till föregående sökträff"
-#: gitk:2933
+#: gitk:2989
msgid "f\t\tScroll diff view to next file"
msgstr "f\t\tRulla diffvisningen till nästa fil"
-#: gitk:2934
+#: gitk:2990
#, tcl-format
msgid "<%s-S>\t\tSearch for next hit in diff view"
msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen"
-#: gitk:2935
+#: gitk:2991
#, tcl-format
msgid "<%s-R>\t\tSearch for previous hit in diff view"
msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen"
-#: gitk:2936
+#: gitk:2992
#, tcl-format
msgid "<%s-KP+>\tIncrease font size"
msgstr "<%s-Num+>\tÖka teckenstorlek"
-#: gitk:2937
+#: gitk:2993
#, tcl-format
msgid "<%s-plus>\tIncrease font size"
msgstr "<%s-plus>\tÖka teckenstorlek"
-#: gitk:2938
+#: gitk:2994
#, tcl-format
msgid "<%s-KP->\tDecrease font size"
msgstr "<%s-Num->\tMinska teckenstorlek"
-#: gitk:2939
+#: gitk:2995
#, tcl-format
msgid "<%s-minus>\tDecrease font size"
msgstr "<%s-minus>\tMinska teckenstorlek"
-#: gitk:2940
+#: gitk:2996
msgid "<F5>\t\tUpdate"
msgstr "<F5>\t\tUppdatera"
-#: gitk:3395 gitk:3404
+#: gitk:3451 gitk:3460
#, tcl-format
msgid "Error creating temporary directory %s:"
msgstr "Fel vid skapande av temporär katalog %s:"
-#: gitk:3417
+#: gitk:3473
#, tcl-format
msgid "Error getting \"%s\" from %s:"
msgstr "Fel vid hämtning av \"%s\" från %s:"
-#: gitk:3480
+#: gitk:3536
msgid "command failed:"
msgstr "kommando misslyckades:"
-#: gitk:3629
+#: gitk:3685
msgid "No such commit"
msgstr "Incheckning saknas"
-#: gitk:3643
+#: gitk:3699
msgid "git gui blame: command failed:"
msgstr "git gui blame: kommando misslyckades:"
-#: gitk:3674
+#: gitk:3730
#, tcl-format
msgid "Couldn't read merge head: %s"
msgstr "Kunde inte läsa sammanslagningshuvud: %s"
-#: gitk:3682
+#: gitk:3738
#, tcl-format
msgid "Error reading index: %s"
msgstr "Fel vid läsning av index: %s"
-#: gitk:3707
+#: gitk:3763
#, tcl-format
msgid "Couldn't start git blame: %s"
msgstr "Kunde inte starta git blame: %s"
-#: gitk:3710 gitk:6527
+#: gitk:3766 gitk:6591
msgid "Searching"
msgstr "Söker"
-#: gitk:3742
+#: gitk:3798
#, tcl-format
msgid "Error running git blame: %s"
msgstr "Fel vid körning av git blame: %s"
-#: gitk:3770
+#: gitk:3826
#, tcl-format
msgid "That line comes from commit %s, which is not in this view"
msgstr "Raden kommer från incheckningen %s, som inte finns i denna vy"
-#: gitk:3784
+#: gitk:3840
msgid "External diff viewer failed:"
msgstr "Externt diff-verktyg misslyckades:"
-#: gitk:3902
+#: gitk:3958
msgid "Gitk view definition"
msgstr "Definition av Gitk-vy"
-#: gitk:3906
+#: gitk:3962
msgid "Remember this view"
msgstr "Spara denna vy"
-#: gitk:3907
+#: gitk:3963
msgid "References (space separated list):"
msgstr "Referenser (blankstegsavdelad lista):"
-#: gitk:3908
+#: gitk:3964
msgid "Branches & tags:"
msgstr "Grenar & taggar:"
-#: gitk:3909
+#: gitk:3965
msgid "All refs"
msgstr "Alla referenser"
-#: gitk:3910
+#: gitk:3966
msgid "All (local) branches"
msgstr "Alla (lokala) grenar"
-#: gitk:3911
+#: gitk:3967
msgid "All tags"
msgstr "Alla taggar"
-#: gitk:3912
+#: gitk:3968
msgid "All remote-tracking branches"
msgstr "Alla fjärrspårande grenar"
-#: gitk:3913
+#: gitk:3969
msgid "Commit Info (regular expressions):"
msgstr "Incheckningsinfo (reguljära uttryck):"
-#: gitk:3914
+#: gitk:3970
msgid "Author:"
msgstr "Författare:"
-#: gitk:3915
+#: gitk:3971
msgid "Committer:"
msgstr "Incheckare:"
-#: gitk:3916
+#: gitk:3972
msgid "Commit Message:"
msgstr "Incheckningsmeddelande:"
-#: gitk:3917
+#: gitk:3973
msgid "Matches all Commit Info criteria"
msgstr "Motsvarar alla kriterier för incheckningsinfo"
-#: gitk:3918
+#: gitk:3974
msgid "Changes to Files:"
msgstr "Ändringar av filer:"
-#: gitk:3919
+#: gitk:3975
msgid "Fixed String"
msgstr "Fast sträng"
-#: gitk:3920
+#: gitk:3976
msgid "Regular Expression"
msgstr "Reguljärt uttryck"
-#: gitk:3921
+#: gitk:3977
msgid "Search string:"
msgstr "Söksträng:"
-#: gitk:3922
+#: gitk:3978
msgid ""
"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
"15:27:38\"):"
"Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
"15:27:38\"):"
-#: gitk:3923
+#: gitk:3979
msgid "Since:"
msgstr "Från:"
-#: gitk:3924
+#: gitk:3980
msgid "Until:"
msgstr "Till:"
-#: gitk:3925
+#: gitk:3981
msgid "Limit and/or skip a number of revisions (positive integer):"
msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):"
-#: gitk:3926
+#: gitk:3982
msgid "Number to show:"
msgstr "Antal att visa:"
-#: gitk:3927
+#: gitk:3983
msgid "Number to skip:"
msgstr "Antal att hoppa över:"
-#: gitk:3928
+#: gitk:3984
msgid "Miscellaneous options:"
msgstr "Diverse alternativ:"
-#: gitk:3929
+#: gitk:3985
msgid "Strictly sort by date"
msgstr "Strikt datumsortering"
-#: gitk:3930
+#: gitk:3986
msgid "Mark branch sides"
msgstr "Markera sidogrenar"
-#: gitk:3931
+#: gitk:3987
msgid "Limit to first parent"
msgstr "Begränsa till första förälder"
-#: gitk:3932
+#: gitk:3988
msgid "Simple history"
msgstr "Enkel historik"
-#: gitk:3933
+#: gitk:3989
msgid "Additional arguments to git log:"
msgstr "Ytterligare argument till git log:"
-#: gitk:3934
+#: gitk:3990
msgid "Enter files and directories to include, one per line:"
msgstr "Ange filer och kataloger att ta med, en per rad:"
-#: gitk:3935
+#: gitk:3991
msgid "Command to generate more commits to include:"
msgstr "Kommando för att generera fler incheckningar att ta med:"
-#: gitk:4059
+#: gitk:4115
msgid "Gitk: edit view"
msgstr "Gitk: redigera vy"
-#: gitk:4067
+#: gitk:4123
msgid "-- criteria for selecting revisions"
msgstr " - kriterier för val av revisioner"
-#: gitk:4072
+#: gitk:4128
msgid "View Name"
msgstr "Namn på vy"
-#: gitk:4147
+#: gitk:4203
msgid "Apply (F5)"
msgstr "Använd (F5)"
-#: gitk:4185
+#: gitk:4241
msgid "Error in commit selection arguments:"
msgstr "Fel i argument för val av incheckningar:"
-#: gitk:4238 gitk:4290 gitk:4749 gitk:4763 gitk:6027 gitk:11849 gitk:11850
+#: gitk:4294 gitk:4346 gitk:4807 gitk:4821 gitk:6087 gitk:12041 gitk:12042
msgid "None"
msgstr "Inget"
-#: gitk:4846 gitk:4851
+#: gitk:4904 gitk:4909
msgid "Descendant"
msgstr "Avkomling"
-#: gitk:4847
+#: gitk:4905
msgid "Not descendant"
msgstr "Inte avkomling"
-#: gitk:4854 gitk:4859
+#: gitk:4912 gitk:4917
msgid "Ancestor"
msgstr "Förfader"
-#: gitk:4855
+#: gitk:4913
msgid "Not ancestor"
msgstr "Inte förfader"
-#: gitk:5145
+#: gitk:5203
msgid "Local changes checked in to index but not committed"
msgstr "Lokala ändringar sparade i indexet men inte incheckade"
-#: gitk:5181
+#: gitk:5239
msgid "Local uncommitted changes, not checked in to index"
msgstr "Lokala ändringar, ej sparade i indexet"
-#: gitk:6882
+#: gitk:6971
+msgid "and many more"
+msgstr "med många flera"
+
+#: gitk:6974
msgid "many"
msgstr "många"
-#: gitk:7065
+#: gitk:7160
msgid "Tags:"
msgstr "Taggar:"
-#: gitk:7082 gitk:7088 gitk:8500
+#: gitk:7177 gitk:7183 gitk:8593
msgid "Parent"
msgstr "Förälder"
-#: gitk:7093
+#: gitk:7188
msgid "Child"
msgstr "Barn"
-#: gitk:7102
+#: gitk:7197
msgid "Branch"
msgstr "Gren"
-#: gitk:7105
+#: gitk:7200
msgid "Follows"
msgstr "Följer"
-#: gitk:7108
+#: gitk:7203
msgid "Precedes"
msgstr "Föregår"
-#: gitk:7653
+#: gitk:7742
#, tcl-format
msgid "Error getting diffs: %s"
msgstr "Fel vid hämtning av diff: %s"
-#: gitk:8328
+#: gitk:8418
msgid "Goto:"
msgstr "Gå till:"
-#: gitk:8349
+#: gitk:8439
#, tcl-format
msgid "Short SHA1 id %s is ambiguous"
msgstr "Förkortat SHA1-id %s är tvetydigt"
-#: gitk:8356
+#: gitk:8446
#, tcl-format
msgid "Revision %s is not known"
msgstr "Revisionen %s är inte känd"
-#: gitk:8366
+#: gitk:8456
#, tcl-format
msgid "SHA1 id %s is not known"
msgstr "SHA-id:t %s är inte känt"
-#: gitk:8368
+#: gitk:8458
#, tcl-format
msgid "Revision %s is not in the current view"
msgstr "Revisionen %s finns inte i den nuvarande vyn"
-#: gitk:8507 gitk:8522
+#: gitk:8600 gitk:8615
msgid "Date"
msgstr "Datum"
-#: gitk:8510
+#: gitk:8603
msgid "Children"
msgstr "Barn"
-#: gitk:8573
+#: gitk:8666
#, tcl-format
msgid "Reset %s branch to here"
msgstr "Återställ grenen %s hit"
-#: gitk:8575
+#: gitk:8668
msgid "Detached head: can't reset"
msgstr "Frånkopplad head: kan inte återställa"
-#: gitk:8680 gitk:8686
+#: gitk:8773 gitk:8779
msgid "Skipping merge commit "
msgstr "Hoppar över sammanslagningsincheckning "
-#: gitk:8695 gitk:8700
+#: gitk:8788 gitk:8793
msgid "Error getting patch ID for "
msgstr "Fel vid hämtning av patch-id för "
-#: gitk:8696 gitk:8701
+#: gitk:8789 gitk:8794
msgid " - stopping\n"
msgstr " - stannar\n"
-#: gitk:8706 gitk:8709 gitk:8717 gitk:8731 gitk:8740
+#: gitk:8799 gitk:8802 gitk:8810 gitk:8824 gitk:8833
msgid "Commit "
msgstr "Incheckning "
-#: gitk:8710
+#: gitk:8803
msgid ""
" is the same patch as\n"
" "
" är samma patch som\n"
" "
-#: gitk:8718
+#: gitk:8811
msgid ""
" differs from\n"
" "
" skiljer sig från\n"
" "
-#: gitk:8720
+#: gitk:8813
msgid ""
"Diff of commits:\n"
"\n"
"Skillnad mellan incheckningar:\n"
"\n"
-#: gitk:8732 gitk:8741
+#: gitk:8825 gitk:8834
#, tcl-format
msgid " has %s children - stopping\n"
msgstr " har %s barn - stannar\n"
-#: gitk:8760
+#: gitk:8853
#, tcl-format
msgid "Error writing commit to file: %s"
msgstr "Fel vid skrivning av incheckning till fil: %s"
-#: gitk:8766
+#: gitk:8859
#, tcl-format
msgid "Error diffing commits: %s"
msgstr "Fel vid jämförelse av incheckningar: %s"
-#: gitk:8812
+#: gitk:8905
msgid "Top"
msgstr "Topp"
-#: gitk:8813
+#: gitk:8906
msgid "From"
msgstr "Från"
-#: gitk:8818
+#: gitk:8911
msgid "To"
msgstr "Till"
-#: gitk:8842
+#: gitk:8935
msgid "Generate patch"
msgstr "Generera patch"
-#: gitk:8844
+#: gitk:8937
msgid "From:"
msgstr "Från:"
-#: gitk:8853
+#: gitk:8946
msgid "To:"
msgstr "Till:"
-#: gitk:8862
+#: gitk:8955
msgid "Reverse"
msgstr "Vänd"
-#: gitk:8864 gitk:9059
+#: gitk:8957 gitk:9153
msgid "Output file:"
msgstr "Utdatafil:"
-#: gitk:8870
+#: gitk:8963
msgid "Generate"
msgstr "Generera"
-#: gitk:8908
+#: gitk:9001
msgid "Error creating patch:"
msgstr "Fel vid generering av patch:"
-#: gitk:8931 gitk:9047 gitk:9104
+#: gitk:9024 gitk:9141 gitk:9198
msgid "ID:"
msgstr "Id:"
-#: gitk:8940
+#: gitk:9033
msgid "Tag name:"
msgstr "Taggnamn:"
-#: gitk:8943
+#: gitk:9036
msgid "Tag message is optional"
msgstr "Taggmeddelandet är valfritt"
-#: gitk:8945
+#: gitk:9038
msgid "Tag message:"
msgstr "Taggmeddelande:"
-#: gitk:8949 gitk:9113
+#: gitk:9042 gitk:9207
msgid "Create"
msgstr "Skapa"
-#: gitk:8967
+#: gitk:9060
msgid "No tag name specified"
msgstr "Inget taggnamn angavs"
-#: gitk:8971
+#: gitk:9064
#, tcl-format
msgid "Tag \"%s\" already exists"
msgstr "Taggen \"%s\" finns redan"
-#: gitk:8981
+#: gitk:9074
msgid "Error creating tag:"
msgstr "Fel vid skapande av tagg:"
-#: gitk:9056
+#: gitk:9150
msgid "Command:"
msgstr "Kommando:"
-#: gitk:9064
+#: gitk:9158
msgid "Write"
msgstr "Skriv"
-#: gitk:9082
+#: gitk:9176
msgid "Error writing commit:"
msgstr "Fel vid skrivning av incheckning:"
-#: gitk:9109
+#: gitk:9203
msgid "Name:"
msgstr "Namn:"
-#: gitk:9132
+#: gitk:9226
msgid "Please specify a name for the new branch"
msgstr "Ange ett namn för den nya grenen"
-#: gitk:9137
+#: gitk:9231
#, tcl-format
msgid "Branch '%s' already exists. Overwrite?"
msgstr "Grenen \"%s\" finns redan. Skriva över?"
-#: gitk:9204
+#: gitk:9298
#, tcl-format
msgid "Commit %s is already included in branch %s -- really re-apply it?"
msgstr ""
"Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras "
"på nytt?"
-#: gitk:9209
+#: gitk:9303
msgid "Cherry-picking"
msgstr "Plockar"
-#: gitk:9218
+#: gitk:9312
#, tcl-format
msgid ""
"Cherry-pick failed because of local changes to file '%s'.\n"
"Checka in, återställ eller spara undan (stash) dina ändringar och försök "
"igen."
-#: gitk:9224
+#: gitk:9318
msgid ""
"Cherry-pick failed because of merge conflict.\n"
"Do you wish to run git citool to resolve it?"
"Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n"
"Vill du köra git citool för att lösa den?"
-#: gitk:9240
+#: gitk:9334 gitk:9392
msgid "No changes committed"
msgstr "Inga ändringar incheckade"
-#: gitk:9266
+#: gitk:9361
+#, tcl-format
+msgid "Commit %s is not included in branch %s -- really revert it?"
+msgstr "Incheckningen %s finns inte på grenen %s -- vill du verkligen ångra?"
+
+#: gitk:9366
+msgid "Reverting"
+msgstr "Ångrar"
+
+#: gitk:9374
+#, tcl-format
+msgid ""
+"Revert failed because of local changes to the following files:%s Please "
+"commit, reset or stash your changes and try again."
+msgstr ""
+"Misslyckades med att ångra på grund av lokala ändringar i följande filer:%s. "
+"Checka in, återställ eller spara undan (stash) dina ändringar och försök "
+"igen."
+
+#: gitk:9378
+msgid ""
+"Revert failed because of merge conflict.\n"
+" Do you wish to run git citool to resolve it?"
+msgstr ""
+"Misslyckades med att ångra på grund av en sammanslagningskonflikt.\n"
+" Vill du köra git citool för att lösa den?"
+
+#: gitk:9421
msgid "Confirm reset"
msgstr "Bekräfta återställning"
-#: gitk:9268
+#: gitk:9423
#, tcl-format
msgid "Reset branch %s to %s?"
msgstr "Återställa grenen %s till %s?"
-#: gitk:9270
+#: gitk:9425
msgid "Reset type:"
msgstr "Typ av återställning:"
-#: gitk:9273
+#: gitk:9428
msgid "Soft: Leave working tree and index untouched"
msgstr "Mjuk: Rör inte utcheckning och index"
-#: gitk:9276
+#: gitk:9431
msgid "Mixed: Leave working tree untouched, reset index"
msgstr "Blandad: Rör inte utcheckning, återställ index"
-#: gitk:9279
+#: gitk:9434
msgid ""
"Hard: Reset working tree and index\n"
"(discard ALL local changes)"
"Hård: Återställ utcheckning och index\n"
"(förkastar ALLA lokala ändringar)"
-#: gitk:9296
+#: gitk:9451
msgid "Resetting"
msgstr "Återställer"
-#: gitk:9356
+#: gitk:9511
msgid "Checking out"
msgstr "Checkar ut"
-#: gitk:9409
+#: gitk:9564
msgid "Cannot delete the currently checked-out branch"
msgstr "Kan inte ta bort den just nu utcheckade grenen"
-#: gitk:9415
+#: gitk:9570
#, tcl-format
msgid ""
"The commits on branch %s aren't on any other branch.\n"
"Incheckningarna på grenen %s existerar inte på någon annan gren.\n"
"Vill du verkligen ta bort grenen %s?"
-#: gitk:9446
+#: gitk:9601
#, tcl-format
msgid "Tags and heads: %s"
msgstr "Taggar och huvuden: %s"
-#: gitk:9461
+#: gitk:9616
msgid "Filter"
msgstr "Filter"
-#: gitk:9757
+#: gitk:9912
msgid ""
"Error reading commit topology information; branch and preceding/following "
"tag information will be incomplete."
"Fel vid läsning av information om incheckningstopologi; information om "
"grenar och föregående/senare taggar kommer inte vara komplett."
-#: gitk:10744
+#: gitk:10899
msgid "Tag"
msgstr "Tagg"
-#: gitk:10744
+#: gitk:10899
msgid "Id"
msgstr "Id"
-#: gitk:10793
+#: gitk:10948
msgid "Gitk font chooser"
msgstr "Teckensnittsväljare för Gitk"
-#: gitk:10810
+#: gitk:10965
msgid "B"
msgstr "F"
-#: gitk:10813
+#: gitk:10968
msgid "I"
msgstr "K"
-#: gitk:10931
+#: gitk:11086
msgid "Commit list display options"
msgstr "Alternativ för incheckningslistvy"
-#: gitk:10934
+#: gitk:11089
msgid "Maximum graph width (lines)"
msgstr "Maximal grafbredd (rader)"
-#: gitk:10937
+#: gitk:11092
#, tcl-format
msgid "Maximum graph width (% of pane)"
msgstr "Maximal grafbredd (% av ruta)"
-#: gitk:10940
+#: gitk:11095
msgid "Show local changes"
msgstr "Visa lokala ändringar"
-#: gitk:10943
+#: gitk:11098
msgid "Auto-select SHA1 (length)"
msgstr "Välj SHA1 (längd) automatiskt"
-#: gitk:10947
+#: gitk:11102
msgid "Hide remote refs"
msgstr "Dölj fjärr-referenser"
-#: gitk:10951
+#: gitk:11106
msgid "Diff display options"
msgstr "Alternativ för diffvy"
-#: gitk:10953
+#: gitk:11108
msgid "Tab spacing"
msgstr "Blanksteg för tabulatortecken"
-#: gitk:10956
-msgid "Display nearby tags"
-msgstr "Visa närliggande taggar"
+#: gitk:11111
+msgid "Display nearby tags/heads"
+msgstr "Visa närliggande taggar/huvuden"
+
+#: gitk:11114
+msgid "Maximum # tags/heads to show"
+msgstr "Maximalt antal taggar/huvuden att visa"
-#: gitk:10959
+#: gitk:11117
msgid "Limit diffs to listed paths"
msgstr "Begränsa diff till listade sökvägar"
-#: gitk:10962
+#: gitk:11120
msgid "Support per-file encodings"
msgstr "Stöd för filspecifika teckenkodningar"
-#: gitk:10968 gitk:11115
+#: gitk:11126 gitk:11273
msgid "External diff tool"
msgstr "Externt diff-verktyg"
-#: gitk:10969
+#: gitk:11127
msgid "Choose..."
msgstr "Välj..."
-#: gitk:10974
+#: gitk:11132
msgid "General options"
msgstr "Allmänna inställningar"
-#: gitk:10977
+#: gitk:11135
msgid "Use themed widgets"
msgstr "Använd tema på fönsterelement"
-#: gitk:10979
+#: gitk:11137
msgid "(change requires restart)"
msgstr "(ändringen kräver omstart)"
-#: gitk:10981
+#: gitk:11139
msgid "(currently unavailable)"
msgstr "(för närvarande inte tillgängligt)"
-#: gitk:10992
+#: gitk:11150
msgid "Colors: press to choose"
msgstr "Färger: tryck för att välja"
-#: gitk:10995
+#: gitk:11153
msgid "Interface"
msgstr "Gränssnitt"
-#: gitk:10996
+#: gitk:11154
msgid "interface"
msgstr "gränssnitt"
-#: gitk:10999
+#: gitk:11157
msgid "Background"
msgstr "Bakgrund"
-#: gitk:11000 gitk:11030
+#: gitk:11158 gitk:11188
msgid "background"
msgstr "bakgrund"
-#: gitk:11003
+#: gitk:11161
msgid "Foreground"
msgstr "Förgrund"
-#: gitk:11004
+#: gitk:11162
msgid "foreground"
msgstr "förgrund"
-#: gitk:11007
+#: gitk:11165
msgid "Diff: old lines"
msgstr "Diff: gamla rader"
-#: gitk:11008
+#: gitk:11166
msgid "diff old lines"
msgstr "diff gamla rader"
-#: gitk:11012
+#: gitk:11170
msgid "Diff: new lines"
msgstr "Diff: nya rader"
-#: gitk:11013
+#: gitk:11171
msgid "diff new lines"
msgstr "diff nya rader"
-#: gitk:11017
+#: gitk:11175
msgid "Diff: hunk header"
msgstr "Diff: delhuvud"
-#: gitk:11019
+#: gitk:11177
msgid "diff hunk header"
msgstr "diff delhuvud"
-#: gitk:11023
+#: gitk:11181
msgid "Marked line bg"
msgstr "Markerad rad bakgrund"
-#: gitk:11025
+#: gitk:11183
msgid "marked line background"
msgstr "markerad rad bakgrund"
-#: gitk:11029
+#: gitk:11187
msgid "Select bg"
msgstr "Markerad bakgrund"
-#: gitk:11038
+#: gitk:11196
msgid "Fonts: press to choose"
msgstr "Teckensnitt: tryck för att välja"
-#: gitk:11040
+#: gitk:11198
msgid "Main font"
msgstr "Huvudteckensnitt"
-#: gitk:11041
+#: gitk:11199
msgid "Diff display font"
msgstr "Teckensnitt för diffvisning"
-#: gitk:11042
+#: gitk:11200
msgid "User interface font"
msgstr "Teckensnitt för användargränssnitt"
-#: gitk:11064
+#: gitk:11222
msgid "Gitk preferences"
msgstr "Inställningar för Gitk"
-#: gitk:11073
+#: gitk:11231
msgid "General"
msgstr "Allmänt"
-#: gitk:11074
+#: gitk:11232
msgid "Colors"
msgstr "Färger"
-#: gitk:11075
+#: gitk:11233
msgid "Fonts"
msgstr "Teckensnitt"
-#: gitk:11125
+#: gitk:11283
#, tcl-format
msgid "Gitk: choose color for %s"
msgstr "Gitk: välj färg för %s"
-#: gitk:11745
+#: gitk:11937
msgid "Cannot find a git repository here."
msgstr "Hittar inget git-arkiv här."
-#: gitk:11792
+#: gitk:11984
#, tcl-format
msgid "Ambiguous argument '%s': both revision and filename"
msgstr "Tvetydigt argument \"%s\": både revision och filnamn"
-#: gitk:11804
+#: gitk:11996
msgid "Bad arguments to gitk:"
msgstr "Felaktiga argument till gitk:"
-#: gitk:11907
+#: gitk:12099
msgid "Command line"
msgstr "Kommandorad"
# 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++"
our $search_use_regexp = $input_params{'search_use_regexp'};
our $searchtext = $input_params{'searchtext'};
- our $search_regexp;
+ our $search_regexp = undef;
if (defined $searchtext) {
if (length($searchtext) < 2) {
die_error(403, "At least two characters are required for search parameter");
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;
#include "string-list.h"
#include "column.h"
#include "version.h"
+#include "refs.h"
void add_cmdname(struct cmdnames *cmds, const char *name, int len)
{
!S_ISREG(st.st_mode))
return 0;
-#if defined(WIN32) || defined(__CYGWIN__)
+#if defined(GIT_WINDOWS_NATIVE) || defined(__CYGWIN__)
#if defined(__CYGWIN__)
if ((st.st_mode & S_IXUSR) == 0)
#endif
printf("git version %s\n", git_version_string);
return 0;
}
+
+struct similar_ref_cb {
+ const char *base_ref;
+ struct string_list *similar_refs;
+};
+
+static int append_similar_ref(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data);
+ char *branch = strrchr(refname, '/') + 1;
+ /* A remote branch of the same name is deemed similar */
+ if (!prefixcmp(refname, "refs/remotes/") &&
+ !strcmp(branch, cb->base_ref))
+ string_list_append(cb->similar_refs,
+ refname + strlen("refs/remotes/"));
+ return 0;
+}
+
+static struct string_list guess_refs(const char *ref)
+{
+ struct similar_ref_cb ref_cb;
+ struct string_list similar_refs = STRING_LIST_INIT_NODUP;
+
+ ref_cb.base_ref = ref;
+ ref_cb.similar_refs = &similar_refs;
+ for_each_ref(append_similar_ref, &ref_cb);
+ return similar_refs;
+}
+
+void help_unknown_ref(const char *ref, const char *cmd, const char *error)
+{
+ int i;
+ struct string_list suggested_refs = guess_refs(ref);
+
+ fprintf_ln(stderr, _("%s: %s - %s"), cmd, ref, error);
+
+ if (suggested_refs.nr > 0) {
+ fprintf_ln(stderr,
+ Q_("\nDid you mean this?",
+ "\nDid you mean one of these?",
+ suggested_refs.nr));
+ for (i = 0; i < suggested_refs.nr; i++)
+ fprintf(stderr, "\t%s\n", suggested_refs.items[i].string);
+ }
+
+ string_list_clear(&suggested_refs, 0);
+ exit(1);
+}
extern int is_in_cmdlist(struct cmdnames *cmds, const char *name);
extern void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds);
+/*
+ * call this to die(), when it is suspected that the user mistyped a
+ * ref to the command, to give suggested "correct" refs.
+ */
+extern void help_unknown_ref(const char *ref, const char *cmd, const char *error);
#endif /* HELP_H */
strbuf_release(&buf);
}
-static int show_head_ref(const char *name, const unsigned char *sha1,
+static int show_head_ref(const char *refname, const unsigned char *sha1,
int flag, void *cb_data)
{
struct strbuf *buf = cb_data;
if (flag & REF_ISSYMREF) {
- unsigned char sha1[20];
- const char *target = resolve_ref_unsafe(name, sha1, 1, NULL);
+ unsigned char unused[20];
+ const char *target = resolve_ref_unsafe(refname, unused, 1, NULL);
const char *target_nons = strip_namespace(target);
strbuf_addf(buf, "ref: %s\n", target_nons);
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 */
#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
#ifdef NO_OPENSSL
typedef void *SSL;
#else
+#ifdef APPLE_COMMON_CRYPTO
+#include <CommonCrypto/CommonHMAC.h>
+#define HMAC_CTX CCHmacContext
+#define HMAC_Init(hmac, key, len, algo) CCHmacInit(hmac, algo, key, len)
+#define HMAC_Update CCHmacUpdate
+#define HMAC_Final(hmac, hash, ptr) CCHmacFinal(hmac, hash)
+#define HMAC_CTX_cleanup(ignore)
+#define EVP_md5() kCCHmacAlgMD5
+#else
#include <openssl/evp.h>
#include <openssl/hmac.h>
+#endif
#include <openssl/x509v3.h>
#endif
struct trie *fail; /* Aho-Corasick failure function. */
int depth; /* Depth of this node from the root. */
int shift; /* Shift function for search failures. */
- int maxshift; /* Max shift of self and descendents. */
+ int maxshift; /* Max shift of self and descendants. */
};
/* Structure returned opaquely to the caller, containing everything. */
treefails(tree->rlink, fail, recourse);
/* Find, in the chain of fails going back to the root, the first
- node that has a descendent on the current label. */
+ node that has a descendant on the current label. */
while (fail)
{
link = fail->links;
computing the delta table, failure function, and shift function. */
for (curr = last = kwset->trie; curr; curr = curr->next)
{
- /* Enqueue the immediate descendents in the level order queue. */
+ /* Enqueue the immediate descendants in the level order queue. */
enqueue(curr->links, &last);
curr->shift = kwset->mind;
curr->maxshift = kwset->mind;
- /* Update the delta table for the descendents of this node. */
+ /* Update the delta table for the descendants of this node. */
treedelta(curr->links, curr->depth, delta);
/* Compute the failure function for the descendants of this node. */
fail->shift = curr->depth - fail->depth;
/* If the current node is accepting then the shift at the
- fail and its descendents should be no larger than the
+ fail and its descendants should be no larger than the
difference of their depths. */
if (curr->accepting && fail->maxshift > curr->depth - fail->depth)
fail->maxshift = curr->depth - fail->depth;
--- /dev/null
+#include "git-compat-util.h"
+#include "line-range.h"
+#include "cache.h"
+#include "tag.h"
+#include "blob.h"
+#include "tree.h"
+#include "diff.h"
+#include "commit.h"
+#include "decorate.h"
+#include "revision.h"
+#include "xdiff-interface.h"
+#include "strbuf.h"
+#include "log-tree.h"
+#include "graph.h"
+#include "userdiff.h"
+#include "line-log.h"
+
+static void range_set_grow(struct range_set *rs, size_t extra)
+{
+ ALLOC_GROW(rs->ranges, rs->nr + extra, rs->alloc);
+}
+
+/* Either initialization would be fine */
+#define RANGE_SET_INIT {0}
+
+static void range_set_init(struct range_set *rs, size_t prealloc)
+{
+ rs->alloc = rs->nr = 0;
+ rs->ranges = NULL;
+ if (prealloc)
+ range_set_grow(rs, prealloc);
+}
+
+static void range_set_release(struct range_set *rs)
+{
+ free(rs->ranges);
+ rs->alloc = rs->nr = 0;
+ rs->ranges = NULL;
+}
+
+/* dst must be uninitialized! */
+static void range_set_copy(struct range_set *dst, struct range_set *src)
+{
+ range_set_init(dst, src->nr);
+ memcpy(dst->ranges, src->ranges, src->nr*sizeof(struct range_set));
+ dst->nr = src->nr;
+}
+static void range_set_move(struct range_set *dst, struct range_set *src)
+{
+ range_set_release(dst);
+ dst->ranges = src->ranges;
+ dst->nr = src->nr;
+ dst->alloc = src->alloc;
+ src->ranges = NULL;
+ src->alloc = src->nr = 0;
+}
+
+/* tack on a _new_ range _at the end_ */
+static void range_set_append_unsafe(struct range_set *rs, long a, long b)
+{
+ assert(a <= b);
+ range_set_grow(rs, 1);
+ rs->ranges[rs->nr].start = a;
+ rs->ranges[rs->nr].end = b;
+ rs->nr++;
+}
+
+static void range_set_append(struct range_set *rs, long a, long b)
+{
+ assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);
+ range_set_append_unsafe(rs, a, b);
+}
+
+static int range_cmp(const void *_r, const void *_s)
+{
+ const struct range *r = _r;
+ const struct range *s = _s;
+
+ /* this could be simply 'return r.start-s.start', but for the types */
+ if (r->start == s->start)
+ return 0;
+ if (r->start < s->start)
+ return -1;
+ return 1;
+}
+
+/*
+ * Check that the ranges are non-empty, sorted and non-overlapping
+ */
+static void range_set_check_invariants(struct range_set *rs)
+{
+ int i;
+
+ if (!rs)
+ return;
+
+ if (rs->nr)
+ assert(rs->ranges[0].start < rs->ranges[0].end);
+
+ for (i = 1; i < rs->nr; i++) {
+ assert(rs->ranges[i-1].end < rs->ranges[i].start);
+ assert(rs->ranges[i].start < rs->ranges[i].end);
+ }
+}
+
+/*
+ * In-place pass of sorting and merging the ranges in the range set,
+ * to establish the invariants when we get the ranges from the user
+ */
+static void sort_and_merge_range_set(struct range_set *rs)
+{
+ int i;
+ int o = 0; /* output cursor */
+
+ qsort(rs->ranges, rs->nr, sizeof(struct range), range_cmp);
+
+ 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;
+ o++;
+ }
+ }
+ assert(o <= rs->nr);
+ rs->nr = o;
+
+ range_set_check_invariants(rs);
+}
+
+/*
+ * Union of range sets (i.e., sets of line numbers). Used to merge
+ * them when searches meet at a common ancestor.
+ *
+ * This is also where the ranges are consolidated into canonical form:
+ * overlapping and adjacent ranges are merged, and empty ranges are
+ * removed.
+ */
+static void range_set_union(struct range_set *out,
+ struct range_set *a, struct range_set *b)
+{
+ int i = 0, j = 0, o = 0;
+ struct range *ra = a->ranges;
+ struct range *rb = b->ranges;
+ /* cannot make an alias of out->ranges: it may change during grow */
+
+ assert(out->nr == 0);
+ while (i < a->nr || j < b->nr) {
+ struct range *new;
+ if (i < a->nr && j < b->nr) {
+ if (ra[i].start < rb[j].start)
+ new = &ra[i++];
+ else if (ra[i].start > rb[j].start)
+ new = &rb[j++];
+ else if (ra[i].end < rb[j].end)
+ new = &ra[i++];
+ else
+ new = &rb[j++];
+ } else if (i < a->nr) /* b exhausted */
+ new = &ra[i++];
+ else /* a exhausted */
+ new = &rb[j++];
+ if (new->start == new->end)
+ ; /* empty range */
+ else if (!o || out->ranges[o-1].end < new->start) {
+ range_set_grow(out, 1);
+ out->ranges[o].start = new->start;
+ out->ranges[o].end = new->end;
+ o++;
+ } else if (out->ranges[o-1].end < new->end) {
+ out->ranges[o-1].end = new->end;
+ }
+ }
+ out->nr = o;
+}
+
+/*
+ * Difference of range sets (out = a \ b). Pass the "interesting"
+ * ranges as 'a' and the target side of the diff as 'b': it removes
+ * the ranges for which the commit is responsible.
+ */
+static void range_set_difference(struct range_set *out,
+ struct range_set *a, struct range_set *b)
+{
+ int i, j = 0;
+ for (i = 0; i < a->nr; i++) {
+ long start = a->ranges[i].start;
+ long end = a->ranges[i].end;
+ while (start < end) {
+ while (j < b->nr && start >= b->ranges[j].end)
+ /*
+ * a: |-------
+ * b: ------|
+ */
+ j++;
+ if (j >= b->nr || end < b->ranges[j].start) {
+ /*
+ * b exhausted, or
+ * a: ----|
+ * b: |----
+ */
+ range_set_append(out, start, end);
+ break;
+ }
+ if (start >= b->ranges[j].start) {
+ /*
+ * a: |--????
+ * b: |------|
+ */
+ start = b->ranges[j].end;
+ } else if (end > b->ranges[j].start) {
+ /*
+ * a: |-----|
+ * b: |--?????
+ */
+ if (start < b->ranges[j].start)
+ range_set_append(out, start, b->ranges[j].start);
+ start = b->ranges[j].end;
+ }
+ }
+ }
+}
+
+static void diff_ranges_init(struct diff_ranges *diff)
+{
+ range_set_init(&diff->parent, 0);
+ range_set_init(&diff->target, 0);
+}
+
+static void diff_ranges_release(struct diff_ranges *diff)
+{
+ range_set_release(&diff->parent);
+ range_set_release(&diff->target);
+}
+
+void line_log_data_init(struct line_log_data *r)
+{
+ memset(r, 0, sizeof(struct line_log_data));
+ range_set_init(&r->ranges, 0);
+}
+
+static void line_log_data_clear(struct line_log_data *r)
+{
+ range_set_release(&r->ranges);
+ if (r->pair)
+ diff_free_filepair(r->pair);
+}
+
+static void free_line_log_data(struct line_log_data *r)
+{
+ while (r) {
+ struct line_log_data *next = r->next;
+ line_log_data_clear(r);
+ free(r);
+ r = next;
+ }
+}
+
+static struct line_log_data *
+search_line_log_data(struct line_log_data *list, const char *path,
+ struct line_log_data **insertion_point)
+{
+ struct line_log_data *p = list;
+ if (insertion_point)
+ *insertion_point = NULL;
+ while (p) {
+ int cmp = strcmp(p->path, path);
+ if (!cmp)
+ return p;
+ if (insertion_point && cmp < 0)
+ *insertion_point = p;
+ p = p->next;
+ }
+ return NULL;
+}
+
+/*
+ * Note: takes ownership of 'path', which happens to be what the only
+ * caller needs.
+ */
+static void line_log_data_insert(struct line_log_data **list,
+ char *path,
+ long begin, long end)
+{
+ struct line_log_data *ip;
+ struct line_log_data *p = search_line_log_data(*list, path, &ip);
+
+ if (p) {
+ range_set_append_unsafe(&p->ranges, begin, end);
+ sort_and_merge_range_set(&p->ranges);
+ free(path);
+ return;
+ }
+
+ 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;
+ } else {
+ p->next = *list;
+ *list = p;
+ }
+}
+
+struct collect_diff_cbdata {
+ struct diff_ranges *diff;
+};
+
+static int collect_diff_cb(long start_a, long count_a,
+ long start_b, long count_b,
+ void *data)
+{
+ struct collect_diff_cbdata *d = data;
+
+ if (count_a >= 0)
+ range_set_append(&d->diff->parent, start_a, start_a + count_a);
+ if (count_b >= 0)
+ range_set_append(&d->diff->target, start_b, start_b + count_b);
+
+ return 0;
+}
+
+static void collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out)
+{
+ struct collect_diff_cbdata cbdata = {NULL};
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+
+ memset(&xpp, 0, sizeof(xpp));
+ memset(&xecfg, 0, sizeof(xecfg));
+ xecfg.ctxlen = xecfg.interhunkctxlen = 0;
+
+ cbdata.diff = out;
+ xecfg.hunk_func = collect_diff_cb;
+ memset(&ecb, 0, sizeof(ecb));
+ ecb.priv = &cbdata;
+ xdi_diff(parent, target, &xpp, &xecfg, &ecb);
+}
+
+/*
+ * These are handy for debugging. Removing them with #if 0 silences
+ * the "unused function" warning.
+ */
+#if 0
+static void dump_range_set(struct range_set *rs, const char *desc)
+{
+ int i;
+ printf("range set %s (%d items):\n", desc, rs->nr);
+ for (i = 0; i < rs->nr; i++)
+ printf("\t[%ld,%ld]\n", rs->ranges[i].start, rs->ranges[i].end);
+}
+
+static void dump_line_log_data(struct line_log_data *r)
+{
+ char buf[4096];
+ while (r) {
+ snprintf(buf, 4096, "file %s\n", r->path);
+ dump_range_set(&r->ranges, buf);
+ r = r->next;
+ }
+}
+
+static void dump_diff_ranges(struct diff_ranges *diff, const char *desc)
+{
+ int i;
+ assert(diff->parent.nr == diff->target.nr);
+ printf("diff ranges %s (%d items):\n", desc, diff->parent.nr);
+ printf("\tparent\ttarget\n");
+ for (i = 0; i < diff->parent.nr; i++) {
+ printf("\t[%ld,%ld]\t[%ld,%ld]\n",
+ diff->parent.ranges[i].start,
+ diff->parent.ranges[i].end,
+ diff->target.ranges[i].start,
+ diff->target.ranges[i].end);
+ }
+}
+#endif
+
+
+static int ranges_overlap(struct range *a, struct range *b)
+{
+ return !(a->end <= b->start || b->end <= a->start);
+}
+
+/*
+ * Given a diff and the set of interesting ranges, determine all hunks
+ * of the diff which touch (overlap) at least one of the interesting
+ * ranges in the target.
+ */
+static void diff_ranges_filter_touched(struct diff_ranges *out,
+ struct diff_ranges *diff,
+ struct range_set *rs)
+{
+ int i, j = 0;
+
+ assert(out->target.nr == 0);
+
+ for (i = 0; i < diff->target.nr; i++) {
+ while (diff->target.ranges[i].start > rs->ranges[j].end) {
+ j++;
+ if (j == rs->nr)
+ return;
+ }
+ if (ranges_overlap(&diff->target.ranges[i], &rs->ranges[j])) {
+ range_set_append(&out->parent,
+ diff->parent.ranges[i].start,
+ diff->parent.ranges[i].end);
+ range_set_append(&out->target,
+ diff->target.ranges[i].start,
+ diff->target.ranges[i].end);
+ }
+ }
+}
+
+/*
+ * Adjust the line counts in 'rs' to account for the lines
+ * added/removed in the diff.
+ */
+static void range_set_shift_diff(struct range_set *out,
+ struct range_set *rs,
+ struct diff_ranges *diff)
+{
+ int i, j = 0;
+ long offset = 0;
+ struct range *src = rs->ranges;
+ struct range *target = diff->target.ranges;
+ struct range *parent = diff->parent.ranges;
+
+ for (i = 0; i < rs->nr; i++) {
+ while (j < diff->target.nr && src[i].start >= target[j].start) {
+ offset += (parent[j].end-parent[j].start)
+ - (target[j].end-target[j].start);
+ j++;
+ }
+ range_set_append(out, src[i].start+offset, src[i].end+offset);
+ }
+}
+
+/*
+ * Given a diff and the set of interesting ranges, map the ranges
+ * across the diff. That is: observe that the target commit takes
+ * blame for all the + (target-side) ranges. So for every pair of
+ * ranges in the diff that was touched, we remove the latter and add
+ * its parent side.
+ */
+static void range_set_map_across_diff(struct range_set *out,
+ struct range_set *rs,
+ struct diff_ranges *diff,
+ struct diff_ranges **touched_out)
+{
+ struct diff_ranges *touched = xmalloc(sizeof(*touched));
+ struct range_set tmp1 = RANGE_SET_INIT;
+ struct range_set tmp2 = RANGE_SET_INIT;
+
+ diff_ranges_init(touched);
+ diff_ranges_filter_touched(touched, diff, rs);
+ range_set_difference(&tmp1, rs, &touched->target);
+ range_set_shift_diff(&tmp2, &tmp1, diff);
+ range_set_union(out, &tmp2, &touched->parent);
+ range_set_release(&tmp1);
+ range_set_release(&tmp2);
+
+ *touched_out = touched;
+}
+
+static struct commit *check_single_commit(struct rev_info *revs)
+{
+ struct object *commit = NULL;
+ int found = -1;
+ int i;
+
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (obj->flags & UNINTERESTING)
+ continue;
+ while (obj->type == OBJ_TAG)
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ die("Non commit %s?", revs->pending.objects[i].name);
+ if (commit)
+ die("More than one commit to dig from: %s and %s?",
+ revs->pending.objects[i].name,
+ revs->pending.objects[found].name);
+ commit = obj;
+ found = i;
+ }
+
+ if (!commit)
+ die("No commit specified?");
+
+ return (struct commit *) commit;
+}
+
+static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec)
+{
+ unsigned mode;
+ unsigned char sha1[20];
+
+ if (get_tree_entry(commit->object.sha1, spec->path,
+ sha1, &mode))
+ die("There is no path %s in the commit", spec->path);
+ fill_filespec(spec, sha1, 1, mode);
+
+ return;
+}
+
+static void fill_line_ends(struct diff_filespec *spec, long *lines,
+ unsigned long **line_ends)
+{
+ int num = 0, size = 50;
+ long cur = 0;
+ unsigned long *ends = NULL;
+ char *data = NULL;
+
+ if (diff_populate_filespec(spec, 0))
+ die("Cannot read blob %s", sha1_to_hex(spec->sha1));
+
+ ends = xmalloc(size * sizeof(*ends));
+ ends[cur++] = 0;
+ data = spec->data;
+ while (num < spec->size) {
+ if (data[num] == '\n' || num == spec->size - 1) {
+ ALLOC_GROW(ends, (cur + 1), size);
+ ends[cur++] = num;
+ }
+ num++;
+ }
+
+ /* shrink the array to fit the elements */
+ ends = xrealloc(ends, cur * sizeof(*ends));
+ *lines = cur-1;
+ *line_ends = ends;
+}
+
+struct nth_line_cb {
+ struct diff_filespec *spec;
+ long lines;
+ unsigned long *line_ends;
+};
+
+static const char *nth_line(void *data, long line)
+{
+ struct nth_line_cb *d = data;
+ assert(d && line <= d->lines);
+ assert(d->spec && d->spec->data);
+
+ if (line == 0)
+ return (char *)d->spec->data;
+ else
+ return (char *)d->spec->data + d->line_ends[line] + 1;
+}
+
+static struct line_log_data *
+parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
+{
+ long lines = 0;
+ unsigned long *ends = NULL;
+ struct nth_line_cb cb_data;
+ struct string_list_item *item;
+ struct line_log_data *ranges = NULL;
+
+ for_each_string_list_item(item, args) {
+ const char *name_part, *range_part;
+ char *full_name;
+ struct diff_filespec *spec;
+ long begin = 0, end = 0;
+
+ name_part = skip_range_arg(item->string);
+ if (!name_part || *name_part != ':' || !name_part[1])
+ die("-L argument '%s' not of the form start,end:file",
+ item->string);
+ range_part = xstrndup(item->string, name_part - item->string);
+ name_part++;
+
+ full_name = prefix_path(prefix, prefix ? strlen(prefix) : 0,
+ name_part);
+
+ spec = alloc_filespec(full_name);
+ fill_blob_sha1(commit, spec);
+ fill_line_ends(spec, &lines, &ends);
+ cb_data.spec = spec;
+ cb_data.lines = lines;
+ cb_data.line_ends = ends;
+
+ if (parse_range_arg(range_part, nth_line, &cb_data,
+ lines, &begin, &end,
+ full_name))
+ die("malformed -L argument '%s'", range_part);
+ if (begin < 1)
+ begin = 1;
+ if (end < 1)
+ end = lines;
+ begin--;
+ if (lines < end || lines < begin)
+ die("file %s has only %ld lines", name_part, lines);
+ line_log_data_insert(&ranges, full_name, begin, end);
+
+ free_filespec(spec);
+ free(ends);
+ ends = NULL;
+ }
+
+ return ranges;
+}
+
+static struct line_log_data *line_log_data_copy_one(struct line_log_data *r)
+{
+ struct line_log_data *ret = xmalloc(sizeof(*ret));
+
+ assert(r);
+ line_log_data_init(ret);
+ range_set_copy(&ret->ranges, &r->ranges);
+
+ ret->path = xstrdup(r->path);
+
+ return ret;
+}
+
+static struct line_log_data *
+line_log_data_copy(struct line_log_data *r)
+{
+ struct line_log_data *ret = NULL;
+ struct line_log_data *tmp = NULL, *prev = NULL;
+
+ assert(r);
+ ret = tmp = prev = line_log_data_copy_one(r);
+ r = r->next;
+ while (r) {
+ tmp = line_log_data_copy_one(r);
+ prev->next = tmp;
+ prev = tmp;
+ r = r->next;
+ }
+
+ return ret;
+}
+
+/* merge two range sets across files */
+static struct line_log_data *line_log_data_merge(struct line_log_data *a,
+ struct line_log_data *b)
+{
+ struct line_log_data *head = NULL, **pp = &head;
+
+ while (a || b) {
+ struct line_log_data *src;
+ struct line_log_data *src2 = NULL;
+ struct line_log_data *d;
+ int cmp;
+ if (!a)
+ cmp = 1;
+ else if (!b)
+ cmp = -1;
+ else
+ cmp = strcmp(a->path, b->path);
+ if (cmp < 0) {
+ src = a;
+ a = a->next;
+ } else if (cmp == 0) {
+ src = a;
+ a = a->next;
+ src2 = b;
+ b = b->next;
+ } else {
+ src = b;
+ b = b->next;
+ }
+ d = xmalloc(sizeof(struct line_log_data));
+ line_log_data_init(d);
+ d->path = xstrdup(src->path);
+ *pp = d;
+ pp = &d->next;
+ if (src2)
+ range_set_union(&d->ranges, &src->ranges, &src2->ranges);
+ else
+ range_set_copy(&d->ranges, &src->ranges);
+ }
+
+ return head;
+}
+
+static void add_line_range(struct rev_info *revs, struct commit *commit,
+ struct line_log_data *range)
+{
+ struct line_log_data *old = NULL;
+ struct line_log_data *new = NULL;
+
+ old = lookup_decoration(&revs->line_log_data, &commit->object);
+ if (old && range) {
+ new = line_log_data_merge(old, range);
+ free_line_log_data(old);
+ } else if (range)
+ new = line_log_data_copy(range);
+
+ if (new)
+ add_decoration(&revs->line_log_data, &commit->object, new);
+}
+
+static void clear_commit_line_range(struct rev_info *revs, struct commit *commit)
+{
+ struct line_log_data *r;
+ r = lookup_decoration(&revs->line_log_data, &commit->object);
+ if (!r)
+ return;
+ free_line_log_data(r);
+ add_decoration(&revs->line_log_data, &commit->object, NULL);
+}
+
+static struct line_log_data *lookup_line_range(struct rev_info *revs,
+ struct commit *commit)
+{
+ struct line_log_data *ret = NULL;
+ struct line_log_data *d;
+
+ ret = lookup_decoration(&revs->line_log_data, &commit->object);
+
+ for (d = ret; d; d = d->next)
+ range_set_check_invariants(&d->ranges);
+
+ return ret;
+}
+
+void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args)
+{
+ struct commit *commit = NULL;
+ struct line_log_data *range;
+
+ commit = check_single_commit(rev);
+ range = parse_lines(commit, prefix, args);
+ add_line_range(rev, commit, range);
+
+ if (!rev->diffopt.detect_rename) {
+ int i, count = 0;
+ struct line_log_data *r = range;
+ const char **paths;
+ while (r) {
+ count++;
+ r = r->next;
+ }
+ paths = xmalloc((count+1)*sizeof(char *));
+ r = range;
+ for (i = 0; i < count; i++) {
+ paths[i] = xstrdup(r->path);
+ r = r->next;
+ }
+ paths[count] = NULL;
+ init_pathspec(&rev->diffopt.pathspec, paths);
+ free(paths);
+ }
+}
+
+static void load_tree_desc(struct tree_desc *desc, void **tree,
+ const unsigned char *sha1)
+{
+ unsigned long size;
+ *tree = read_object_with_reference(sha1, tree_type, &size, NULL);
+ if (!*tree)
+ die("Unable to read tree (%s)", sha1_to_hex(sha1));
+ init_tree_desc(desc, *tree, size);
+}
+
+static int count_parents(struct commit *commit)
+{
+ struct commit_list *parents = commit->parents;
+ int count = 0;
+ while (parents) {
+ count++;
+ parents = parents->next;
+ }
+ return count;
+}
+
+static void move_diff_queue(struct diff_queue_struct *dst,
+ struct diff_queue_struct *src)
+{
+ assert(src != dst);
+ memcpy(dst, src, sizeof(struct diff_queue_struct));
+ DIFF_QUEUE_CLEAR(src);
+}
+
+static void filter_diffs_for_paths(struct line_log_data *range, int keep_deletions)
+{
+ int i;
+ struct diff_queue_struct outq;
+ DIFF_QUEUE_CLEAR(&outq);
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p = diff_queued_diff.queue[i];
+ struct line_log_data *rg = NULL;
+
+ if (!DIFF_FILE_VALID(p->two)) {
+ if (keep_deletions)
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ continue;
+ }
+ for (rg = range; rg; rg = rg->next) {
+ if (!strcmp(rg->path, p->two->path))
+ break;
+ }
+ if (rg)
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ }
+ free(diff_queued_diff.queue);
+ diff_queued_diff = outq;
+}
+
+static inline int diff_might_be_rename(void)
+{
+ int i;
+ for (i = 0; i < diff_queued_diff.nr; i++)
+ if (!DIFF_FILE_VALID(diff_queued_diff.queue[i]->one)) {
+ /* fprintf(stderr, "diff_might_be_rename found creation of: %s\n", */
+ /* diff_queued_diff.queue[i]->two->path); */
+ return 1;
+ }
+ return 0;
+}
+
+static void queue_diffs(struct line_log_data *range,
+ struct diff_options *opt,
+ struct diff_queue_struct *queue,
+ struct commit *commit, struct commit *parent)
+{
+ void *tree1 = NULL, *tree2 = NULL;
+ struct tree_desc desc1, desc2;
+
+ assert(commit);
+ load_tree_desc(&desc2, &tree2, commit->tree->object.sha1);
+ if (parent)
+ load_tree_desc(&desc1, &tree1, parent->tree->object.sha1);
+ else
+ init_tree_desc(&desc1, "", 0);
+
+ DIFF_QUEUE_CLEAR(&diff_queued_diff);
+ diff_tree(&desc1, &desc2, "", opt);
+ if (opt->detect_rename) {
+ filter_diffs_for_paths(range, 1);
+ if (diff_might_be_rename())
+ diffcore_std(opt);
+ filter_diffs_for_paths(range, 0);
+ }
+ move_diff_queue(queue, &diff_queued_diff);
+
+ if (tree1)
+ free(tree1);
+ if (tree2)
+ free(tree2);
+}
+
+static char *get_nth_line(long line, unsigned long *ends, void *data)
+{
+ if (line == 0)
+ return (char *)data;
+ else
+ return (char *)data + ends[line] + 1;
+}
+
+static void print_line(const char *prefix, char first,
+ long line, unsigned long *ends, void *data,
+ const char *color, const char *reset)
+{
+ char *begin = get_nth_line(line, ends, data);
+ char *end = get_nth_line(line+1, ends, data);
+ int had_nl = 0;
+
+ if (end > begin && end[-1] == '\n') {
+ end--;
+ had_nl = 1;
+ }
+
+ fputs(prefix, stdout);
+ fputs(color, stdout);
+ putchar(first);
+ fwrite(begin, 1, end-begin, stdout);
+ fputs(reset, stdout);
+ putchar('\n');
+ if (!had_nl)
+ fputs("\\ No newline at end of file\n", stdout);
+}
+
+static char *output_prefix(struct diff_options *opt)
+{
+ char *prefix = "";
+
+ if (opt->output_prefix) {
+ struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);
+ prefix = sb->buf;
+ }
+
+ return prefix;
+}
+
+static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
+{
+ int i, j = 0;
+ long p_lines, t_lines;
+ unsigned long *p_ends = NULL, *t_ends = NULL;
+ struct diff_filepair *pair = range->pair;
+ struct diff_ranges *diff = &range->diff;
+
+ struct diff_options *opt = &rev->diffopt;
+ char *prefix = output_prefix(opt);
+ const char *c_reset = diff_get_color(opt->use_color, DIFF_RESET);
+ const char *c_frag = diff_get_color(opt->use_color, DIFF_FRAGINFO);
+ const char *c_meta = diff_get_color(opt->use_color, DIFF_METAINFO);
+ const char *c_old = diff_get_color(opt->use_color, DIFF_FILE_OLD);
+ const char *c_new = diff_get_color(opt->use_color, DIFF_FILE_NEW);
+ const char *c_plain = diff_get_color(opt->use_color, DIFF_PLAIN);
+
+ if (!pair || !diff)
+ return;
+
+ if (pair->one->sha1_valid)
+ fill_line_ends(pair->one, &p_lines, &p_ends);
+ fill_line_ends(pair->two, &t_lines, &t_ends);
+
+ printf("%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset);
+ printf("%s%s--- %s%s%s\n", prefix, c_meta,
+ pair->one->sha1_valid ? "a/" : "",
+ pair->one->sha1_valid ? pair->one->path : "/dev/null",
+ c_reset);
+ printf("%s%s+++ b/%s%s\n", prefix, c_meta, pair->two->path, c_reset);
+ for (i = 0; i < range->ranges.nr; i++) {
+ long p_start, p_end;
+ long t_start = range->ranges.ranges[i].start;
+ long t_end = range->ranges.ranges[i].end;
+ long t_cur = t_start;
+ int j_last;
+
+ while (j < diff->target.nr && diff->target.ranges[j].end < t_start)
+ j++;
+ if (j == diff->target.nr || diff->target.ranges[j].start > t_end)
+ continue;
+
+ /* Scan ahead to determine the last diff that falls in this range */
+ j_last = j;
+ while (j_last < diff->target.nr && diff->target.ranges[j_last].start < t_end)
+ j_last++;
+ if (j_last > j)
+ j_last--;
+
+ /*
+ * Compute parent hunk headers: we know that the diff
+ * has the correct line numbers (but not all hunks).
+ * So it suffices to shift the start/end according to
+ * the line numbers of the first/last hunk(s) that
+ * fall in this range.
+ */
+ if (t_start < diff->target.ranges[j].start)
+ p_start = diff->parent.ranges[j].start - (diff->target.ranges[j].start-t_start);
+ else
+ p_start = diff->parent.ranges[j].start;
+ if (t_end > diff->target.ranges[j_last].end)
+ p_end = diff->parent.ranges[j_last].end + (t_end-diff->target.ranges[j_last].end);
+ else
+ p_end = diff->parent.ranges[j_last].end;
+
+ if (!p_start && !p_end) {
+ p_start = -1;
+ p_end = -1;
+ }
+
+ /* Now output a diff hunk for this range */
+ printf("%s%s@@ -%ld,%ld +%ld,%ld @@%s\n",
+ prefix, c_frag,
+ p_start+1, p_end-p_start, t_start+1, t_end-t_start,
+ c_reset);
+ while (j < diff->target.nr && diff->target.ranges[j].start < t_end) {
+ int k;
+ for (; t_cur < diff->target.ranges[j].start; t_cur++)
+ print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
+ c_plain, c_reset);
+ for (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++)
+ print_line(prefix, '-', k, p_ends, pair->one->data,
+ c_old, c_reset);
+ for (; t_cur < diff->target.ranges[j].end && t_cur < t_end; t_cur++)
+ print_line(prefix, '+', t_cur, t_ends, pair->two->data,
+ c_new, c_reset);
+ j++;
+ }
+ for (; t_cur < t_end; t_cur++)
+ print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
+ c_plain, c_reset);
+ }
+
+ free(p_ends);
+ free(t_ends);
+}
+
+/*
+ * NEEDSWORK: manually building a diff here is not the Right
+ * Thing(tm). log -L should be built into the diff pipeline.
+ */
+static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
+{
+ puts(output_prefix(&rev->diffopt));
+ while (range) {
+ dump_diff_hacky_one(rev, range);
+ range = range->next;
+ }
+}
+
+/*
+ * Unlike most other functions, this destructively operates on
+ * 'range'.
+ */
+static int process_diff_filepair(struct rev_info *rev,
+ struct diff_filepair *pair,
+ struct line_log_data *range,
+ struct diff_ranges **diff_out)
+{
+ struct line_log_data *rg = range;
+ struct range_set tmp;
+ struct diff_ranges diff;
+ mmfile_t file_parent, file_target;
+
+ assert(pair->two->path);
+ while (rg) {
+ assert(rg->path);
+ if (!strcmp(rg->path, pair->two->path))
+ break;
+ rg = rg->next;
+ }
+
+ if (!rg)
+ return 0;
+ if (rg->ranges.nr == 0)
+ return 0;
+
+ assert(pair->two->sha1_valid);
+ diff_populate_filespec(pair->two, 0);
+ file_target.ptr = pair->two->data;
+ file_target.size = pair->two->size;
+
+ if (pair->one->sha1_valid) {
+ diff_populate_filespec(pair->one, 0);
+ file_parent.ptr = pair->one->data;
+ file_parent.size = pair->one->size;
+ } else {
+ file_parent.ptr = "";
+ file_parent.size = 0;
+ }
+
+ diff_ranges_init(&diff);
+ collect_diff(&file_parent, &file_target, &diff);
+
+ /* NEEDSWORK should apply some heuristics to prevent mismatches */
+ free(rg->path);
+ rg->path = xstrdup(pair->one->path);
+
+ range_set_init(&tmp, 0);
+ range_set_map_across_diff(&tmp, &rg->ranges, &diff, diff_out);
+ range_set_release(&rg->ranges);
+ range_set_move(&rg->ranges, &tmp);
+
+ diff_ranges_release(&diff);
+
+ return ((*diff_out)->parent.nr > 0);
+}
+
+static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair)
+{
+ struct diff_filepair *new = xmalloc(sizeof(struct diff_filepair));
+ new->one = pair->one;
+ new->two = pair->two;
+ new->one->count++;
+ new->two->count++;
+ return new;
+}
+
+static void free_diffqueues(int n, struct diff_queue_struct *dq)
+{
+ int i, j;
+ for (i = 0; i < n; i++)
+ for (j = 0; j < dq[i].nr; j++)
+ diff_free_filepair(dq[i].queue[j]);
+ free(dq);
+}
+
+static int process_all_files(struct line_log_data **range_out,
+ struct rev_info *rev,
+ struct diff_queue_struct *queue,
+ struct line_log_data *range)
+{
+ int i, changed = 0;
+
+ *range_out = line_log_data_copy(range);
+
+ for (i = 0; i < queue->nr; i++) {
+ struct diff_ranges *pairdiff = NULL;
+ struct diff_filepair *pair = queue->queue[i];
+ if (process_diff_filepair(rev, pair, *range_out, &pairdiff)) {
+ /*
+ * Store away the diff for later output. We
+ * tuck it in the ranges we got as _input_,
+ * since that's the commit that caused the
+ * diff.
+ *
+ * NEEDSWORK not enough when we get around to
+ * doing something interesting with merges;
+ * currently each invocation on a merge parent
+ * trashes the previous one's diff.
+ *
+ * NEEDSWORK tramples over data structures not owned here
+ */
+ struct line_log_data *rg = range;
+ changed++;
+ while (rg && strcmp(rg->path, pair->two->path))
+ rg = rg->next;
+ assert(rg);
+ rg->pair = diff_filepair_dup(queue->queue[i]);
+ memcpy(&rg->diff, pairdiff, sizeof(struct diff_ranges));
+ }
+ }
+
+ return changed;
+}
+
+int line_log_print(struct rev_info *rev, struct commit *commit)
+{
+ struct line_log_data *range = lookup_line_range(rev, commit);
+
+ show_log(rev);
+ dump_diff_hacky(rev, range);
+ return 1;
+}
+
+static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *commit,
+ struct line_log_data *range)
+{
+ struct commit *parent = NULL;
+ struct diff_queue_struct queue;
+ struct line_log_data *parent_range;
+ int changed;
+
+ if (commit->parents)
+ parent = commit->parents->item;
+
+ queue_diffs(range, &rev->diffopt, &queue, commit, parent);
+ changed = process_all_files(&parent_range, rev, &queue, range);
+ if (parent)
+ add_line_range(rev, parent, parent_range);
+ return changed;
+}
+
+static int process_ranges_merge_commit(struct rev_info *rev, struct commit *commit,
+ struct line_log_data *range)
+{
+ struct diff_queue_struct *diffqueues;
+ struct line_log_data **cand;
+ struct commit **parents;
+ struct commit_list *p;
+ int i;
+ int nparents = count_parents(commit);
+
+ diffqueues = xmalloc(nparents * sizeof(*diffqueues));
+ cand = xmalloc(nparents * sizeof(*cand));
+ parents = xmalloc(nparents * sizeof(*parents));
+
+ p = commit->parents;
+ for (i = 0; i < nparents; i++) {
+ parents[i] = p->item;
+ p = p->next;
+ queue_diffs(range, &rev->diffopt, &diffqueues[i], commit, parents[i]);
+ }
+
+ for (i = 0; i < nparents; i++) {
+ int changed;
+ cand[i] = NULL;
+ changed = process_all_files(&cand[i], rev, &diffqueues[i], range);
+ if (!changed) {
+ /*
+ * This parent can take all the blame, so we
+ * don't follow any other path in history
+ */
+ add_line_range(rev, parents[i], cand[i]);
+ clear_commit_line_range(rev, commit);
+ commit->parents = xmalloc(sizeof(struct commit_list));
+ commit->parents->item = parents[i];
+ commit->parents->next = NULL;
+ free(parents);
+ free(cand);
+ free_diffqueues(nparents, diffqueues);
+ /* NEEDSWORK leaking like a sieve */
+ return 0;
+ }
+ }
+
+ /*
+ * No single parent took the blame. We add the candidates
+ * from the above loop to the parents.
+ */
+ for (i = 0; i < nparents; i++) {
+ add_line_range(rev, parents[i], cand[i]);
+ }
+
+ clear_commit_line_range(rev, commit);
+ free(parents);
+ free(cand);
+ free_diffqueues(nparents, diffqueues);
+ return 1;
+
+ /* NEEDSWORK evil merge detection stuff */
+ /* NEEDSWORK leaking like a sieve */
+}
+
+static int process_ranges_arbitrary_commit(struct rev_info *rev, struct commit *commit)
+{
+ struct line_log_data *range = lookup_line_range(rev, commit);
+ int changed = 0;
+
+ if (range) {
+ if (!commit->parents || !commit->parents->next)
+ changed = process_ranges_ordinary_commit(rev, commit, range);
+ else
+ changed = process_ranges_merge_commit(rev, commit, range);
+ }
+
+ if (!changed)
+ commit->object.flags |= TREESAME;
+
+ return changed;
+}
+
+static enum rewrite_result line_log_rewrite_one(struct rev_info *rev, struct commit **pp)
+{
+ for (;;) {
+ struct commit *p = *pp;
+ if (p->parents && p->parents->next)
+ return rewrite_one_ok;
+ if (p->object.flags & UNINTERESTING)
+ return rewrite_one_ok;
+ if (!(p->object.flags & TREESAME))
+ return rewrite_one_ok;
+ if (!p->parents)
+ return rewrite_one_noparents;
+ *pp = p->parents->item;
+ }
+}
+
+int line_log_filter(struct rev_info *rev)
+{
+ struct commit *commit;
+ struct commit_list *list = rev->commits;
+ struct commit_list *out = NULL, **pp = &out;
+
+ while (list) {
+ struct commit_list *to_free = NULL;
+ commit = list->item;
+ if (process_ranges_arbitrary_commit(rev, commit)) {
+ *pp = list;
+ pp = &list->next;
+ } else
+ to_free = list;
+ list = list->next;
+ free(to_free);
+ }
+ *pp = NULL;
+
+ for (list = out; list; list = list->next)
+ rewrite_parents(rev, list->item, line_log_rewrite_one);
+
+ rev->commits = out;
+
+ return 0;
+}
--- /dev/null
+#ifndef LINE_LOG_H
+#define LINE_LOG_H
+
+#include "diffcore.h"
+
+struct rev_info;
+struct commit;
+
+/* A range [start,end]. Lines are numbered starting at 0, and the
+ * ranges include start but exclude end. */
+struct range {
+ long start, end;
+};
+
+/* A set of ranges. The ranges must always be disjoint and sorted. */
+struct range_set {
+ int alloc, nr;
+ struct range *ranges;
+};
+
+/* A diff, encoded as the set of pre- and post-image ranges where the
+ * files differ. A pair of ranges corresponds to a hunk. */
+struct diff_ranges {
+ struct range_set parent;
+ struct range_set target;
+};
+
+/* Linked list of interesting files and their associated ranges. The
+ * list must be kept sorted by path.
+ *
+ * For simplicity, even though this is highly redundant, each
+ * line_log_data owns its 'path'.
+ */
+struct line_log_data {
+ struct line_log_data *next;
+ char *path;
+ char status;
+ struct range_set ranges;
+ int arg_alloc, arg_nr;
+ const char **args;
+ struct diff_filepair *pair;
+ struct diff_ranges diff;
+};
+
+extern void line_log_data_init(struct line_log_data *r);
+
+extern void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args);
+
+extern int line_log_filter(struct rev_info *rev);
+
+extern int line_log_print(struct rev_info *rev, struct commit *commit);
+
+#endif /* LINE_LOG_H */
--- /dev/null
+#include "git-compat-util.h"
+#include "line-range.h"
+#include "xdiff-interface.h"
+#include "strbuf.h"
+#include "userdiff.h"
+
+/*
+ * Parse one item in the -L option
+ */
+static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
+ void *data, long lines, long begin, long *ret)
+{
+ char *term;
+ const char *line;
+ long num;
+ int reg_error;
+ regex_t regexp;
+ regmatch_t match[1];
+
+ /* Allow "-L <something>,+20" to mean starting at <something>
+ * for 20 lines, or "-L <something>,-5" for 5 lines ending at
+ * <something>.
+ */
+ if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
+ num = strtol(spec + 1, &term, 10);
+ if (term != spec + 1) {
+ if (!ret)
+ return term;
+ if (spec[0] == '-')
+ num = 0 - num;
+ if (0 < num)
+ *ret = begin + num - 2;
+ else if (!num)
+ *ret = begin;
+ else
+ *ret = begin + num;
+ return term;
+ }
+ return spec;
+ }
+ num = strtol(spec, &term, 10);
+ if (term != spec) {
+ if (ret)
+ *ret = num;
+ return term;
+ }
+ if (spec[0] != '/')
+ return spec;
+
+ /* it could be a regexp of form /.../ */
+ for (term = (char *) spec + 1; *term && *term != '/'; term++) {
+ if (*term == '\\')
+ term++;
+ }
+ if (*term != '/')
+ return spec;
+
+ /* in the scan-only case we are not interested in the regex */
+ if (!ret)
+ return term+1;
+
+ /* try [spec+1 .. term-1] as regexp */
+ *term = 0;
+ begin--; /* input is in human terms */
+ line = nth_line(data, begin);
+
+ if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) &&
+ !(reg_error = regexec(®exp, line, 1, match, 0))) {
+ const char *cp = line + match[0].rm_so;
+ const char *nline;
+
+ while (begin++ < lines) {
+ nline = nth_line(data, begin);
+ if (line <= cp && cp < nline)
+ break;
+ line = nline;
+ }
+ *ret = begin;
+ regfree(®exp);
+ *term++ = '/';
+ return term;
+ }
+ else {
+ char errbuf[1024];
+ regerror(reg_error, ®exp, errbuf, 1024);
+ die("-L parameter '%s': %s", spec + 1, errbuf);
+ }
+}
+
+static int match_funcname(xdemitconf_t *xecfg, const char *bol, const char *eol)
+{
+ if (xecfg) {
+ char buf[1];
+ return xecfg->find_func(bol, eol - bol, buf, 1,
+ xecfg->find_func_priv) >= 0;
+ }
+
+ if (bol == eol)
+ return 0;
+ if (isalpha(*bol) || *bol == '_' || *bol == '$')
+ return 1;
+ return 0;
+}
+
+static const char *find_funcname_matching_regexp(xdemitconf_t *xecfg, const char *start,
+ regex_t *regexp)
+{
+ int reg_error;
+ regmatch_t match[1];
+ while (1) {
+ const char *bol, *eol;
+ reg_error = regexec(regexp, start, 1, match, 0);
+ if (reg_error == REG_NOMATCH)
+ return NULL;
+ else if (reg_error) {
+ char errbuf[1024];
+ regerror(reg_error, regexp, errbuf, 1024);
+ die("-L parameter: regexec() failed: %s", errbuf);
+ }
+ /* determine extent of line matched */
+ bol = start+match[0].rm_so;
+ eol = start+match[0].rm_eo;
+ while (bol > start && *bol != '\n')
+ bol--;
+ if (*bol == '\n')
+ bol++;
+ while (*eol && *eol != '\n')
+ eol++;
+ if (*eol == '\n')
+ eol++;
+ /* is it a funcname line? */
+ if (match_funcname(xecfg, (char*) bol, (char*) eol))
+ return bol;
+ start = eol;
+ }
+}
+
+static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_cb,
+ void *cb_data, long lines, long *begin, long *end,
+ const char *path)
+{
+ char *pattern;
+ const char *term;
+ struct userdiff_driver *drv;
+ xdemitconf_t *xecfg = NULL;
+ const char *start;
+ const char *p;
+ int reg_error;
+ regex_t regexp;
+
+ assert(*arg == ':');
+ term = arg+1;
+ while (*term && *term != ':') {
+ if (*term == '\\' && *(term+1))
+ term++;
+ term++;
+ }
+ if (term == arg+1)
+ return NULL;
+ if (!begin) /* skip_range_arg case */
+ return term;
+
+ pattern = xstrndup(arg+1, term-(arg+1));
+
+ start = nth_line_cb(cb_data, 0);
+
+ drv = userdiff_find_by_path(path);
+ if (drv && drv->funcname.pattern) {
+ const struct userdiff_funcname *pe = &drv->funcname;
+ xecfg = xcalloc(1, sizeof(*xecfg));
+ xdiff_set_find_func(xecfg, pe->pattern, pe->cflags);
+ }
+
+ reg_error = regcomp(®exp, pattern, REG_NEWLINE);
+ if (reg_error) {
+ char errbuf[1024];
+ regerror(reg_error, ®exp, errbuf, 1024);
+ die("-L parameter '%s': %s", pattern, errbuf);
+ }
+
+ p = find_funcname_matching_regexp(xecfg, (char*) start, ®exp);
+ if (!p)
+ die("-L parameter '%s': no match", pattern);
+ *begin = 0;
+ while (p > nth_line_cb(cb_data, *begin))
+ (*begin)++;
+
+ if (*begin >= lines)
+ die("-L parameter '%s' matches at EOF", pattern);
+
+ *end = *begin+1;
+ while (*end < lines) {
+ const char *bol = nth_line_cb(cb_data, *end);
+ const char *eol = nth_line_cb(cb_data, *end+1);
+ if (match_funcname(xecfg, bol, eol))
+ break;
+ (*end)++;
+ }
+
+ regfree(®exp);
+ free(xecfg);
+ free(pattern);
+
+ /* compensate for 1-based numbering */
+ (*begin)++;
+
+ return term;
+}
+
+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)
+ return -1;
+ return 0;
+ }
+
+ arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);
+
+ if (*arg == ',')
+ arg = parse_loc(arg + 1, nth_line_cb, cb_data, lines, *begin + 1, end);
+
+ if (*arg)
+ return -1;
+
+ if (*begin && *end && *end < *begin) {
+ long tmp;
+ tmp = *end; *end = *begin; *begin = tmp;
+ }
+
+ return 0;
+}
+
+const char *skip_range_arg(const char *arg)
+{
+ if (*arg == ':')
+ return parse_range_funcname(arg, NULL, NULL, 0, NULL, NULL, NULL);
+
+ arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
+
+ if (*arg == ',')
+ arg = parse_loc(arg+1, NULL, NULL, 0, 0, NULL);
+
+ return arg;
+}
--- /dev/null
+#ifndef LINE_RANGE_H
+#define LINE_RANGE_H
+
+/*
+ * Parse one item in an -L begin,end option w.r.t. the notional file
+ * object 'cb_data' consisting of 'lines' lines.
+ *
+ * The 'nth_line_cb' callback is used to determine the start of the
+ * line 'lno' inside the 'cb_data'. The caller is expected to already
+ * have a suitable map at hand to make this a constant-time lookup.
+ *
+ * Returns 0 in case of success and -1 if there was an error. The
+ * actual range is stored in *begin and *end. The counting starts
+ * at 1! In case of error, the caller should show usage message.
+ */
+
+typedef const char *(*nth_line_fn_t)(void *data, long lno);
+
+extern 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);
+
+/*
+ * Scan past a range argument that could be parsed by
+ * 'parse_range_arg', to help the caller determine the start of the
+ * filename in '-L n,m:file' syntax.
+ *
+ * Returns a pointer to the first character after the 'n,m' part, or
+ * NULL in case the argument is obviously malformed.
+ */
+
+extern const char *skip_range_arg(const char *arg);
+
+#endif /* LINE_RANGE_H */
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) {
#include "color.h"
#include "gpg-interface.h"
#include "sequencer.h"
+#include "line-log.h"
struct decoration name_decoration = { "object names" };
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)
log.parent = NULL;
opt->loginfo = &log;
+ if (opt->line_level_traverse)
+ return line_log_print(opt, commit);
+
shown = log_tree_diff(opt, commit, &log);
if (!shown && opt->loginfo && opt->always_show_header) {
log.parent = NULL;
#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;
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);
}
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);
}
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;
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);
}
} 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)
}
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;
while (nend > nstart && isspace(*nend))
--nend;
- *name = (nstart < nend ? nstart : NULL);
+ *name = (nstart <= nend ? nstart : NULL);
*email = left+1;
*(nend+1) = '\0';
*right++ = '\0';
}
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)
*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");
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)
{
{
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);
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;
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);
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);
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_cmd () {
- "$merge_tool_path" --auto \
+ "$merge_tool_path" \
--L1 "$MERGED (A)" --L2 "$MERGED (B)" \
"$LOCAL" "$REMOTE" >/dev/null 2>&1
}
#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];
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)
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
*
--- /dev/null
+#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);
+}
--- /dev/null
+#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
return ret;
}
-static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
+static int string_list_add_one_ref(const char *refname, const unsigned char *sha1,
int flag, void *cb)
{
struct string_list *refs = cb;
- if (!unsorted_string_list_has_string(refs, path))
- string_list_append(refs, path);
+ if (!unsorted_string_list_has_string(refs, refname))
+ string_list_append(refs, refname);
return 0;
}
+/*
+ * The list argument must have strdup_strings set on it.
+ */
void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
{
+ assert(list->strdup_strings);
if (has_glob_specials(glob)) {
for_each_glob_ref(string_list_add_one_ref, glob, list);
} else {
struct object *lookup_object(const unsigned char *sha1)
{
- unsigned int i;
+ unsigned int i, first;
struct object *obj;
if (!obj_hash)
return NULL;
- i = hashtable_index(sha1);
+ first = i = hashtable_index(sha1);
while ((obj = obj_hash[i]) != NULL) {
if (!hashcmp(sha1, obj->sha1))
break;
if (i == obj_hash_size)
i = 0;
}
+ if (obj && i != first) {
+ /*
+ * Move object to where we started to look for it so
+ * that we do not need to walk the hash table the next
+ * time we look for it.
+ */
+ struct object *tmp = obj_hash[i];
+ obj_hash[i] = obj_hash[first];
+ obj_hash[first] = tmp;
+ }
return obj;
}
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) {
if (!tree->object.parsed) {
if (parse_tree_buffer(tree, buffer, size))
return NULL;
- eaten = 1;
+ *eaten_p = 1;
}
}
} else if (type == OBJ_COMMIT) {
return NULL;
if (!commit->buffer) {
commit->buffer = buffer;
- eaten = 1;
+ *eaten_p = 1;
}
obj = &commit->object;
}
}
if (obj && obj->type == OBJ_NONE)
obj->type = type;
- *eaten_p = eaten;
return obj;
}
add_object_array_with_mode(obj, name, array, S_IFINVALID);
}
+/*
+ * A zero-length string to which object_array_entry::name can be
+ * initialized without requiring a malloc/free.
+ */
+static char object_array_slopbuf[1];
+
void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode)
{
unsigned nr = array->nr;
unsigned alloc = array->alloc;
struct object_array_entry *objects = array->objects;
+ struct object_array_entry *entry;
if (nr >= alloc) {
alloc = (alloc + 32) * 2;
array->alloc = alloc;
array->objects = objects;
}
- objects[nr].item = obj;
- objects[nr].name = name;
- objects[nr].mode = mode;
+ entry = &objects[nr];
+ entry->item = obj;
+ if (!name)
+ entry->name = NULL;
+ else if (!*name)
+ /* Use our own empty string instead of allocating one: */
+ entry->name = object_array_slopbuf;
+ else
+ entry->name = xstrdup(name);
+ entry->mode = mode;
array->nr = ++nr;
}
-void object_array_remove_duplicates(struct object_array *array)
+void object_array_filter(struct object_array *array,
+ object_array_each_func_t want, void *cb_data)
{
- unsigned int ref, src, dst;
+ unsigned nr = array->nr, src, dst;
struct object_array_entry *objects = array->objects;
- for (ref = 0; ref + 1 < array->nr; ref++) {
- for (src = ref + 1, dst = src;
- src < array->nr;
- src++) {
- if (!strcmp(objects[ref].name, objects[src].name))
- continue;
+ for (src = dst = 0; src < nr; src++) {
+ if (want(&objects[src], cb_data)) {
if (src != dst)
objects[dst] = objects[src];
dst++;
+ } else {
+ if (objects[src].name != object_array_slopbuf)
+ free(objects[src].name);
+ }
+ }
+ array->nr = dst;
+}
+
+/*
+ * Return true iff array already contains an entry with name.
+ */
+static int contains_name(struct object_array *array, const char *name)
+{
+ unsigned nr = array->nr, i;
+ struct object_array_entry *object = array->objects;
+
+ for (i = 0; i < nr; i++, object++)
+ if (!strcmp(object->name, name))
+ return 1;
+ return 0;
+}
+
+void object_array_remove_duplicates(struct object_array *array)
+{
+ unsigned nr = array->nr, src;
+ struct object_array_entry *objects = array->objects;
+
+ array->nr = 0;
+ for (src = 0; src < nr; src++) {
+ if (!contains_name(array, objects[src].name)) {
+ if (src != array->nr)
+ objects[array->nr] = objects[src];
+ array->nr++;
+ } else {
+ if (objects[src].name != object_array_slopbuf)
+ free(objects[src].name);
}
- array->nr = dst;
}
}
unsigned int alloc;
struct object_array_entry {
struct object *item;
- const char *name;
+ /*
+ * name or NULL. If non-NULL, the memory pointed to
+ * is owned by this object *except* if it points at
+ * object_array_slopbuf, which is a static copy of the
+ * empty string.
+ */
+ char *name;
unsigned mode;
} *objects;
};
/* Object array handling .. */
void add_object_array(struct object *obj, const char *name, struct object_array *array);
void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);
-void object_array_remove_duplicates(struct object_array *);
+
+typedef int (*object_array_each_func_t)(struct object_array_entry *, void *);
+
+/*
+ * Apply want to each entry in array, retaining only the entries for
+ * which the function returns true. Preserve the order of the entries
+ * that are retained.
+ */
+void object_array_filter(struct object_array *array,
+ object_array_each_func_t want, void *cb_data);
+
+/*
+ * Remove from array all but the first entry with a given name.
+ * Warning: this function uses an O(N^2) algorithm.
+ */
+void object_array_remove_duplicates(struct object_array *array);
void clear_object_flags(unsigned flags);
+++ /dev/null
-#include "cache.h"
-#include "refs.h"
-#include "tag.h"
-#include "pack-refs.h"
-
-struct ref_to_prune {
- struct ref_to_prune *next;
- unsigned char sha1[20];
- char name[FLEX_ARRAY];
-};
-
-struct pack_refs_cb_data {
- unsigned int flags;
- struct ref_to_prune *ref_to_prune;
- FILE *refs_file;
-};
-
-static int do_not_prune(int flags)
-{
- /* If it is already packed or if it is a symref,
- * do not prune it.
- */
- return (flags & (REF_ISSYMREF|REF_ISPACKED));
-}
-
-static int handle_one_ref(const char *path, const unsigned char *sha1,
- int flags, void *cb_data)
-{
- struct pack_refs_cb_data *cb = cb_data;
- struct object *o;
- int is_tag_ref;
-
- /* Do not pack the symbolic refs */
- if ((flags & REF_ISSYMREF))
- return 0;
- is_tag_ref = !prefixcmp(path, "refs/tags/");
-
- /* ALWAYS pack refs that were already packed or are tags */
- if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref && !(flags & REF_ISPACKED))
- return 0;
-
- fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
-
- o = parse_object_or_die(sha1, path);
- if (o->type == OBJ_TAG) {
- o = deref_tag(o, path, 0);
- if (o)
- fprintf(cb->refs_file, "^%s\n",
- sha1_to_hex(o->sha1));
- }
-
- if ((cb->flags & PACK_REFS_PRUNE) && !do_not_prune(flags)) {
- int namelen = strlen(path) + 1;
- struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
- hashcpy(n->sha1, sha1);
- strcpy(n->name, path);
- n->next = cb->ref_to_prune;
- cb->ref_to_prune = n;
- }
- return 0;
-}
-
-/*
- * Remove empty parents, but spare refs/ and immediate subdirs.
- * Note: munges *name.
- */
-static void try_remove_empty_parents(char *name)
-{
- char *p, *q;
- int i;
- p = name;
- for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
- while (*p && *p != '/')
- p++;
- /* tolerate duplicate slashes; see check_refname_format() */
- while (*p == '/')
- p++;
- }
- for (q = p; *q; q++)
- ;
- while (1) {
- while (q > p && *q != '/')
- q--;
- while (q > p && *(q-1) == '/')
- q--;
- if (q == p)
- break;
- *q = '\0';
- if (rmdir(git_path("%s", name)))
- break;
- }
-}
-
-/* make sure nobody touched the ref, and unlink */
-static void prune_ref(struct ref_to_prune *r)
-{
- struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
-
- if (lock) {
- unlink_or_warn(git_path("%s", r->name));
- unlock_ref(lock);
- try_remove_empty_parents(r->name);
- }
-}
-
-static void prune_refs(struct ref_to_prune *r)
-{
- while (r) {
- prune_ref(r);
- r = r->next;
- }
-}
-
-static struct lock_file packed;
-
-int pack_refs(unsigned int flags)
-{
- int fd;
- struct pack_refs_cb_data cbdata;
-
- memset(&cbdata, 0, sizeof(cbdata));
- cbdata.flags = flags;
-
- fd = hold_lock_file_for_update(&packed, git_path("packed-refs"),
- LOCK_DIE_ON_ERROR);
- cbdata.refs_file = fdopen(fd, "w");
- if (!cbdata.refs_file)
- die_errno("unable to create ref-pack file structure");
-
- /* perhaps other traits later as well */
- fprintf(cbdata.refs_file, "# pack-refs with: peeled fully-peeled \n");
-
- for_each_ref(handle_one_ref, &cbdata);
- if (ferror(cbdata.refs_file))
- die("failed to write ref-pack file");
- if (fflush(cbdata.refs_file) || fsync(fd) || fclose(cbdata.refs_file))
- die_errno("failed to write ref-pack file");
- /*
- * Since the lock file was fdopen()'ed and then fclose()'ed above,
- * assign -1 to the lock file descriptor so that commit_lock_file()
- * won't try to close() it.
- */
- packed.fd = -1;
- if (commit_lock_file(&packed) < 0)
- die_errno("unable to overwrite old ref-pack file");
- prune_refs(cbdata.ref_to_prune);
- return 0;
-}
+++ /dev/null
-#ifndef PACK_REFS_H
-#define PACK_REFS_H
-
-/*
- * Flags for controlling behaviour of pack_refs()
- * PACK_REFS_PRUNE: Prune loose refs after packing
- * PACK_REFS_ALL: Pack _all_ refs, not just tags and already packed refs
- */
-#define PACK_REFS_PRUNE 0x0001
-#define PACK_REFS_ALL 0x0002
-
-/*
- * Write a packed-refs file for the current repository.
- * flags: Combination of the above PACK_REFS_* flags.
- */
-int pack_refs(unsigned int flags);
-
-#endif /* PACK_REFS_H */
/* 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
}
/*
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));
*/
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;
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)
return 0;
}
+int parse_opt_expiry_date_cb(const struct option *opt, const char *arg,
+ int unset)
+{
+ return parse_expiry_date(arg, (unsigned long *)opt->value);
+}
+
int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
int unset)
{
#define OPT_DATE(s, l, v, h) \
{ OPTION_CALLBACK, (s), (l), (v), N_("time"),(h), 0, \
parse_opt_approxidate_cb }
+#define OPT_EXPIRY_DATE(s, l, v, h) \
+ { OPTION_CALLBACK, (s), (l), (v), N_("expiry date"),(h), 0, \
+ parse_opt_expiry_date_cb }
#define OPT_CALLBACK(s, l, v, a, h, f) \
{ OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }
#define OPT_NUMBER_CALLBACK(v, h, f) \
/*----- some often used options -----*/
extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
+extern int parse_opt_expiry_date_cb(const struct option *, const char *, int);
extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
extern int parse_opt_with_commit(const struct option *, const char *, int);
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;
}
/*
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);
}
}
{
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] != '/' ||
1;
__END__
+=head1 NAME
+
Git::SVN::Editor - commit driver for "git svn set-tree" and dcommit
=head1 SYNOPSIS
package Git::SVN::Fetcher;
-use vars qw/@ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename
- @deleted_gpath %added_placeholder $repo_id/;
+use vars qw/@ISA $_ignore_regex $_include_regex $_preserve_empty_dirs
+ $_placeholder_filename @deleted_gpath %added_placeholder
+ $repo_id/;
use strict;
use warnings;
use SVN::Delta;
my $v = eval { command_oneline('config', '--get', $k) };
$self->{ignore_regex} = $v;
+ $k = "svn-remote.$repo_id.include-paths";
+ $v = eval { command_oneline('config', '--get', $k) };
+ $self->{include_regex} = $v;
+
$k = "svn-remote.$repo_id.preserve-empty-dirs";
$v = eval { command_oneline('config', '--get', '--bool', $k) };
if ($v && $v eq 'true') {
}
# return value: 0 -- don't ignore, 1 -- ignore
+# This will also check whether the path is explicitly included
sub is_path_ignored {
my ($self, $path) = @_;
return 1 if in_dot_git($path);
return 1 if defined($self->{ignore_regex}) &&
$path =~ m!$self->{ignore_regex}!;
+ return 0 if defined($self->{include_regex}) &&
+ $path =~ m!$self->{include_regex}!;
+ return 0 if defined($_include_regex) &&
+ $path =~ m!$_include_regex!;
+ return 1 if defined($self->{include_regex});
+ return 1 if defined($_include_regex);
return 0 unless defined($_ignore_regex);
return 1 if $path =~ m!$_ignore_regex!o;
return 0;
1;
__END__
+=head1 NAME
+
Git::SVN::Fetcher - tree delta consumer for "git svn fetch"
=head1 SYNOPSIS
1;
__END__
+=head1 NAME
+
Git::SVN::Prompt - authentication callbacks for git-svn
=head1 SYNOPSIS
1;
__END__
+=head1 NAME
+
Git::SVN::Ra - Subversion remote access functions for git-svn
=head1 SYNOPSIS
#
msgid ""
msgstr ""
-"Project-Id-Version: git 1.8.2\n"
+"Project-Id-Version: git 1.8.3\n"
"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2013-04-10 15:16+0800\n"
+"POT-Creation-Date: 2013-04-30 08:25+0800\n"
"PO-Revision-Date: 2012-10-02 19:35+0200\n"
"Last-Translator: Ralf Thielow <ralf.thielow@gmail.com>\n"
"Language-Team: German <>\n"
msgid "archive format"
msgstr "Ausgabeformat"
-#: archive.c:324 builtin/log.c:1117
+#: archive.c:324 builtin/log.c:1126
msgid "prefix"
msgstr "Prefix"
msgid "prepend prefix to each pathname in the archive"
msgstr "stellt einen Präfix vor jeden Pfadnamen in der Ausgabe"
-#: archive.c:326 builtin/archive.c:88 builtin/blame.c:2366
-#: builtin/blame.c:2367 builtin/config.c:55 builtin/fast-export.c:659
-#: builtin/fast-export.c:661 builtin/grep.c:715 builtin/hash-object.c:77
-#: builtin/ls-files.c:497 builtin/ls-files.c:500 builtin/notes.c:536
+#: archive.c:326 builtin/archive.c:88 builtin/blame.c:2371
+#: builtin/blame.c:2372 builtin/config.c:55 builtin/fast-export.c:665
+#: builtin/fast-export.c:667 builtin/grep.c:715 builtin/hash-object.c:77
+#: builtin/ls-files.c:490 builtin/ls-files.c:493 builtin/notes.c:536
#: builtin/notes.c:693 builtin/read-tree.c:107 parse-options.h:149
msgid "file"
msgstr "Datei"
"Verneinende Muster werden in Git-Attributen ignoriert.\n"
"Benutzen Sie '\\!' für führende Ausrufezeichen."
+#: branch.c:60
+#, c-format
+msgid "Not setting branch %s as its own upstream."
+msgstr "Zweig %s kann nicht sein eigener Übernahmezweig sein."
+
+#: branch.c:82
+#, c-format
+msgid "Branch %s set up to track remote branch %s from %s by rebasing."
+msgstr "Zweig %s konfiguriert zum Folgen von externem Zweig %s von %s durch "
+"Neuaufbau."
+
+#: branch.c:83
+#, c-format
+msgid "Branch %s set up to track remote branch %s from %s."
+msgstr "Zweig %s konfiguriert zum Folgen von externem Zweig %s von %s."
+
+#: branch.c:87
+#, c-format
+msgid "Branch %s set up to track local branch %s by rebasing."
+msgstr "Zweig %s konfiguriert zum Folgen von lokalem Zweig %s durch Neuaufbau."
+
+#: branch.c:88
+#, c-format
+msgid "Branch %s set up to track local branch %s."
+msgstr "Zweig %s konfiguriert zum Folgen von lokalem Zweig %s."
+
+#: branch.c:92
+#, c-format
+msgid "Branch %s set up to track remote ref %s by rebasing."
+msgstr "Zweig %s konfiguriert zum Folgen von externer Referenz %s durch "
+"Neuaufbau."
+
+#: branch.c:93
+#, c-format
+msgid "Branch %s set up to track remote ref %s."
+msgstr "Zweig %s konfiguriert zum Folgen von externer Referenz %s."
+
+#: branch.c:97
+#, c-format
+msgid "Branch %s set up to track local ref %s by rebasing."
+msgstr "Zweig %s konfiguriert zum Folgen von lokaler Referenz %s durch "
+"Neuaufbau."
+
+#: branch.c:98
+#, c-format
+msgid "Branch %s set up to track local ref %s."
+msgstr "Zweig %s konfiguriert zum Folgen von lokaler Referenz %s."
+
+#: branch.c:118
+#, c-format
+msgid "Tracking not set up: name too long: %s"
+msgstr "Konfiguration zum Folgen von Zweig nicht eingerichtet. Name zu lang: %s"
+
+#: branch.c:137
+#, c-format
+msgid "Not tracking: ambiguous information for ref %s"
+msgstr "Konfiguration zum Folgen von Zweig nicht eingerichtet. Referenz %s ist mehrdeutig."
+
+#: branch.c:182
+#, c-format
+msgid "'%s' is not a valid branch name."
+msgstr "'%s' ist kein gültiger Zweigname."
+
+#: branch.c:187
+#, c-format
+msgid "A branch named '%s' already exists."
+msgstr "Zweig '%s' existiert bereits."
+
+#: branch.c:195
+msgid "Cannot force update the current branch."
+msgstr "Kann Aktualisierung des aktuellen Zweiges nicht erzwingen."
+
#: branch.c:201
#, c-format
msgid "Cannot setup tracking information; starting point '%s' is not a branch."
-msgstr "Kann Informationen zum Übernahmezweig nicht einrichten; "
-"Startpunkt '%s' ist kein Zweig."
+msgstr ""
+"Kann Informationen zum Übernahmezweig nicht einrichten; Startpunkt '%s' ist "
+"kein Zweig."
#: branch.c:203
#, c-format
"\"git push -u\" verwenden, um den externen Übernahmezweig\n"
"beim Versand zu konfigurieren."
+#: branch.c:250
+#, c-format
+msgid "Not a valid object name: '%s'."
+msgstr "Ungültiger Objekt-Name: '%s'"
+
+#: branch.c:270
+#, c-format
+msgid "Ambiguous object name: '%s'."
+msgstr "mehrdeutiger Objekt-Name: '%s'"
+
+#: branch.c:275
+#, c-format
+msgid "Not a valid branch point: '%s'."
+msgstr "Ungültiger Verzweigungspunkt: '%s'"
+
+#: branch.c:281
+msgid "Failed to lock ref for update"
+msgstr "Fehler beim Sperren der Referenz zur Aktualisierung."
+
+#: branch.c:299
+msgid "Failed to write ref"
+msgstr "Fehler beim Schreiben der Referenz."
+
#: bundle.c:36
#, c-format
msgid "'%s' does not look like a v2 bundle file"
msgid "Repository lacks these prerequisite commits:"
msgstr "Dem Projektarchiv fehlen folgende vorausgesetzte Versionen:"
-#: bundle.c:164 sequencer.c:651 sequencer.c:1083 builtin/log.c:300
-#: builtin/log.c:754 builtin/log.c:1350 builtin/log.c:1566 builtin/merge.c:349
-#: builtin/shortlog.c:157
+#: bundle.c:164 sequencer.c:651 sequencer.c:1101 builtin/log.c:300
+#: builtin/log.c:770 builtin/log.c:1344 builtin/log.c:1570 builtin/merge.c:349
+#: builtin/shortlog.c:155
msgid "revision walk setup failed"
msgstr "Einrichtung des Revisionsgangs fehlgeschlagen"
msgid "rev-list died"
msgstr "\"rev-list\" abgebrochen"
-#: bundle.c:300 builtin/log.c:1246 builtin/shortlog.c:260
+#: bundle.c:300 builtin/log.c:1255 builtin/shortlog.c:258
#, c-format
msgid "unrecognized argument: %s"
msgstr "nicht erkanntes Argument: %s"
"Fehler in 'diff.dirstat' Konfigurationsvariable gefunden:\n"
"%s"
-#: diff.c:3480
+#: diff.c:3481
#, c-format
msgid ""
"Failed to parse --dirstat/-X option parameter:\n"
"Fehler beim Parsen des --dirstat/-X Optionsparameters:\n"
"%s"
-#: diff.c:3494
+#: diff.c:3495
#, c-format
msgid "Failed to parse --submodule option parameter: '%s'"
msgstr "Fehler beim Parsen des --submodule Optionsparameters: '%s'"
msgid "failed to read the cache"
msgstr "Lesen des Zwischenspeichers fehlgeschlagen"
-#: merge.c:110 builtin/checkout.c:362 builtin/checkout.c:563
-#: builtin/clone.c:635
+#: merge.c:110 builtin/checkout.c:365 builtin/checkout.c:566
+#: builtin/clone.c:645
msgid "unable to write new index file"
msgstr "Konnte neue Bereitstellungsdatei nicht schreiben."
msgid "blob expected for %s '%s'"
msgstr "Blob erwartet für %s '%s'"
-#: merge-recursive.c:773 builtin/clone.c:303
+#: merge-recursive.c:773 builtin/clone.c:313
#, c-format
msgid "failed to open '%s'"
msgstr "Fehler beim Öffnen von '%s'"
msgid "Could not format %s."
msgstr "Konnte %s nicht formatieren."
-#: sequencer.c:1101
+#: sequencer.c:1083
+#, c-format
+msgid "%s: can't cherry-pick a %s"
+msgstr "%s: %s kann nicht in \"cherry-pick\" benutzt werden"
+
+#: sequencer.c:1085
+#, c-format
+msgid "%s: bad revision"
+msgstr "%s: ungültige Revision"
+
+#: sequencer.c:1119
msgid "Can't revert as initial commit"
msgstr "Rücknahme-Version kann nicht initial sein."
-#: sequencer.c:1102
+#: sequencer.c:1120
msgid "Can't cherry-pick into empty head"
msgstr "Kann \"cherry-pick\" nicht in einem leeren Zweig ausführen."
msgid "bug: unhandled diff status %c"
msgstr "Fehler: unbehandelter Differenz-Status %c"
-#: wt-status.c:805
+#: wt-status.c:803
msgid "You have unmerged paths."
msgstr "Sie haben nicht zusammengeführte Pfade."
-#: wt-status.c:808 wt-status.c:960
+#: wt-status.c:806 wt-status.c:958
msgid " (fix conflicts and run \"git commit\")"
msgstr " (beheben Sie die Konflikte und führen Sie \"git commit\" aus)"
-#: wt-status.c:811
+#: wt-status.c:809
msgid "All conflicts fixed but you are still merging."
msgstr ""
"Alle Konflikte sind behoben, aber Sie sind immer noch beim Zusammenführen."
-#: wt-status.c:814
+#: wt-status.c:812
msgid " (use \"git commit\" to conclude merge)"
msgstr " (benutzen Sie \"git commit\" um die Zusammenführung abzuschließen)"
-#: wt-status.c:824
+#: wt-status.c:822
msgid "You are in the middle of an am session."
msgstr "Eine \"am\"-Sitzung ist im Gange."
-#: wt-status.c:827
+#: wt-status.c:825
msgid "The current patch is empty."
msgstr "Der aktuelle Patch ist leer."
-#: wt-status.c:831
+#: wt-status.c:829
msgid " (fix conflicts and then run \"git am --resolved\")"
msgstr ""
" (beheben Sie die Konflikte und führen Sie dann \"git am --resolved\" aus)"
-#: wt-status.c:833
+#: wt-status.c:831
msgid " (use \"git am --skip\" to skip this patch)"
msgstr " (benutzen Sie \"git am --skip\" um diesen Patch auszulassen)"
-#: wt-status.c:835
+#: wt-status.c:833
msgid " (use \"git am --abort\" to restore the original branch)"
msgstr ""
" (benutzen Sie \"git am --abort\" um den ursprünglichen Zweig "
"wiederherzustellen)"
-#: wt-status.c:895 wt-status.c:912
+#: wt-status.c:893 wt-status.c:910
#, c-format
msgid "You are currently rebasing branch '%s' on '%s'."
msgstr "Sie sind gerade beim Neuaufbau von Zweig '%s' auf '%s'."
-#: wt-status.c:900 wt-status.c:917
+#: wt-status.c:898 wt-status.c:915
msgid "You are currently rebasing."
msgstr "Sie sind gerade beim Neuaufbau."
-#: wt-status.c:903
+#: wt-status.c:901
msgid " (fix conflicts and then run \"git rebase --continue\")"
msgstr ""
" (beheben Sie die Konflikte und führen Sie dann \"git rebase --continue\" "
"aus)"
-#: wt-status.c:905
+#: wt-status.c:903
msgid " (use \"git rebase --skip\" to skip this patch)"
msgstr " (benutzen Sie \"git rebase --skip\" um diesen Patch auszulassen)"
-#: wt-status.c:907
+#: wt-status.c:905
msgid " (use \"git rebase --abort\" to check out the original branch)"
msgstr ""
" (benutzen Sie \"git rebase --abort\" um den ursprünglichen Zweig "
"auszuchecken)"
-#: wt-status.c:920
+#: wt-status.c:918
msgid " (all conflicts fixed: run \"git rebase --continue\")"
msgstr " (alle Konflikte behoben: führen Sie \"git rebase --continue\" aus)"
-#: wt-status.c:924
+#: wt-status.c:922
#, c-format
msgid ""
"You are currently splitting a commit while rebasing branch '%s' on '%s'."
"Sie teilen gerade eine Version auf, während ein Neuaufbau von Zweig '%s' auf "
"'%s' im Gange ist."
-#: wt-status.c:929
+#: wt-status.c:927
msgid "You are currently splitting a commit during a rebase."
msgstr "Sie teilen gerade eine Version während eines Neuaufbaus auf."
-#: wt-status.c:932
+#: wt-status.c:930
msgid " (Once your working directory is clean, run \"git rebase --continue\")"
msgstr ""
" (Sobald Ihr Arbeitsverzeichnis sauber ist, führen Sie \"git rebase --"
"continue\" aus)"
-#: wt-status.c:936
+#: wt-status.c:934
#, c-format
msgid "You are currently editing a commit while rebasing branch '%s' on '%s'."
msgstr ""
"Sie editieren gerade eine Version während eines Neuaufbaus von Zweig '%s' "
"auf '%s'."
-#: wt-status.c:941
+#: wt-status.c:939
msgid "You are currently editing a commit during a rebase."
msgstr "Sie editieren gerade eine Version während eines Neuaufbaus."
-#: wt-status.c:944
+#: wt-status.c:942
msgid " (use \"git commit --amend\" to amend the current commit)"
msgstr ""
" (benutzen Sie \"git commit --amend\" um die aktuelle Version nachzubessern)"
-#: wt-status.c:946
+#: wt-status.c:944
msgid ""
" (use \"git rebase --continue\" once you are satisfied with your changes)"
msgstr ""
" (benutzen Sie \"git rebase --continue\" sobald Ihre Änderungen "
"abgeschlossen sind)"
-#: wt-status.c:956
+#: wt-status.c:954
msgid "You are currently cherry-picking."
msgstr "Sie führen gerade \"cherry-pick\" aus."
-#: wt-status.c:963
+#: wt-status.c:961
msgid " (all conflicts fixed: run \"git commit\")"
msgstr " (alle Konflikte behoben: führen Sie \"git commit\" aus)"
-#: wt-status.c:972
+#: wt-status.c:970
#, c-format
msgid "You are currently reverting commit %s."
msgstr "Sie nehmen gerade Version '%s' zurück."
-#: wt-status.c:977
+#: wt-status.c:975
msgid " (fix conflicts and run \"git revert --continue\")"
msgstr ""
" (beheben Sie die Konflikte und führen Sie dann \"git revert --continue\" "
"aus)"
-#: wt-status.c:980
+#: wt-status.c:978
msgid " (all conflicts fixed: run \"git revert --continue\")"
msgstr " (alle Konflikte behoben: führen Sie \"git revert --continue\" aus)"
-#: wt-status.c:982
+#: wt-status.c:980
msgid " (use \"git revert --abort\" to cancel the revert operation)"
msgstr ""
" (benutzen Sie \"git revert --abort\" um die Umkehroperation abzubrechen)"
-#: wt-status.c:993
+#: wt-status.c:991
#, c-format
msgid "You are currently bisecting, started from branch '%s'."
msgstr "Sie sind gerade bei einer binären Suche, gestartet von Zweig '%s'."
-#: wt-status.c:997
+#: wt-status.c:995
msgid "You are currently bisecting."
msgstr "Sie sind gerade bei einer binären Suche."
-#: wt-status.c:1000
+#: wt-status.c:998
msgid " (use \"git bisect reset\" to get back to the original branch)"
msgstr ""
" (benutzen Sie \"git bisect reset\" um zum ursprünglichen Zweig "
"zurückzukehren)"
-#: wt-status.c:1175
+#: wt-status.c:1173
msgid "On branch "
msgstr "Auf Zweig "
-#: wt-status.c:1186
+#: wt-status.c:1184
msgid "HEAD detached at "
msgstr "Zweigspitze (HEAD) losgelöst bei "
-#: wt-status.c:1188
+#: wt-status.c:1186
msgid "HEAD detached from "
msgstr "Zweigspitze (HEAD) losgelöst von "
-#: wt-status.c:1191
+#: wt-status.c:1189
msgid "Not currently on any branch."
msgstr "Im Moment auf keinem Zweig."
-#: wt-status.c:1208
+#: wt-status.c:1206
msgid "Initial commit"
msgstr "Initiale Version"
-#: wt-status.c:1222
+#: wt-status.c:1220
msgid "Untracked files"
msgstr "Unbeobachtete Dateien"
-#: wt-status.c:1224
+#: wt-status.c:1222
msgid "Ignored files"
msgstr "Ignorierte Dateien"
-#: wt-status.c:1228
+#: wt-status.c:1226
#, c-format
-msgid "It took %.2f seconds to enumerate untracked files. 'status -uno'"
-msgstr "Es dauerte %.2f Sekunden die unbeobachteten Dateien zu bestimmen."
-"'status -uno'"
+msgid ""
+"It took %.2f seconds to enumerate untracked files. 'status -uno'\n"
+"may speed it up, but you have to be careful not to forget to add\n"
+"new files yourself (see 'git help status')."
+msgstr ""
+"Es dauerte %.2f Sekunden die unbeobachteten Dateien zu bestimmen.\n"
+"'status -uno' könnte das beschleunigen, aber Sie müssen darauf achten,\n"
+"neue Dateien selbstständig hinzuzufügen (siehe 'git help status')."
#: wt-status.c:1232
-msgid "may speed it up, but you have to be careful not to forget to add"
-msgstr "könnte das beschleunigen, aber Sie müssen darauf achten, neue"
-
-#: wt-status.c:1235
-msgid "new files yourself (see 'git help status')."
-msgstr "Dateien selbstständig hinzuzufügen (siehe 'git help status')."
-
-#: wt-status.c:1238
#, c-format
msgid "Untracked files not listed%s"
msgstr "Unbeobachtete Dateien nicht aufgelistet%s"
-#: wt-status.c:1240
+#: wt-status.c:1234
msgid " (use -u option to show untracked files)"
msgstr " (benutzen Sie die Option -u um unbeobachteten Dateien anzuzeigen)"
-#: wt-status.c:1246
+#: wt-status.c:1240
msgid "No changes"
msgstr "Keine Änderungen"
-#: wt-status.c:1251
+#: wt-status.c:1245
#, c-format
msgid "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"
msgstr ""
"keine Änderungen zum Eintragen hinzugefügt (benutzen Sie \"git add\" und/"
"oder \"git commit -a\")\n"
-#: wt-status.c:1254
+#: wt-status.c:1248
#, c-format
msgid "no changes added to commit\n"
msgstr "keine Änderungen zum Eintragen hinzugefügt\n"
-#: wt-status.c:1257
+#: wt-status.c:1251
#, c-format
msgid ""
"nothing added to commit but untracked files present (use \"git add\" to "
"nichts zum Eintragen hinzugefügt, aber es gibt unbeobachtete Dateien "
"(benutzen Sie \"git add\" zum Beobachten)\n"
-#: wt-status.c:1260
+#: wt-status.c:1254
#, c-format
msgid "nothing added to commit but untracked files present\n"
msgstr "nichts zum Eintragen hinzugefügt, aber es gibt unbeobachtete Dateien\n"
-#: wt-status.c:1263
+#: wt-status.c:1257
#, c-format
msgid "nothing to commit (create/copy files and use \"git add\" to track)\n"
msgstr ""
"nichts einzutragen (Erstellen/Kopieren Sie Dateien und benutzen Sie \"git add"
"\" zum Beobachten)\n"
-#: wt-status.c:1266 wt-status.c:1271
+#: wt-status.c:1260 wt-status.c:1265
#, c-format
msgid "nothing to commit\n"
msgstr "nichts einzutragen\n"
-#: wt-status.c:1269
+#: wt-status.c:1263
#, c-format
msgid "nothing to commit (use -u to show untracked files)\n"
msgstr ""
"nichts einzutragen (benutzen Sie die Option -u, um unbeobachtete Dateien "
"anzuzeigen)\n"
-#: wt-status.c:1273
+#: wt-status.c:1267
#, c-format
msgid "nothing to commit, working directory clean\n"
msgstr "nichts einzutragen, Arbeitsverzeichnis sauber\n"
-#: wt-status.c:1381
+#: wt-status.c:1375
msgid "HEAD (no branch)"
msgstr "HEAD (kein Zweig)"
-#: wt-status.c:1387
+#: wt-status.c:1381
msgid "Initial commit on "
msgstr "Initiale Version auf "
-#: wt-status.c:1402
+#: wt-status.c:1396
msgid "behind "
msgstr "hinterher "
-#: wt-status.c:1405 wt-status.c:1408
+#: wt-status.c:1399 wt-status.c:1402
msgid "ahead "
msgstr "voraus "
-#: wt-status.c:1410
+#: wt-status.c:1404
msgid ", behind "
msgstr ", hinterher "
-#: compat/precompose_utf8.c:58 builtin/clone.c:342
+#: compat/precompose_utf8.c:58 builtin/clone.c:352
#, c-format
msgid "failed to unlink '%s'"
msgstr "Konnte '%s' nicht entfernen."
msgid "git add [options] [--] <pathspec>..."
msgstr "git add [Optionen] [--] [<Pfadspezifikation>...]"
-#: builtin/add.c:63
+#.
+#. * To be consistent with "git add -p" and most Git
+#. * commands, we should default to being tree-wide, but
+#. * this is not the original behavior and can't be
+#. * changed until users trained themselves not to type
+#. * "git add -u" or "git add -A". For now, we warn and
+#. * keep the old behavior. Later, the behavior can be changed
+#. * to tree-wide, keeping the warning for a while, and
+#. * eventually we can drop the warning.
+#.
+#: builtin/add.c:58
+#, c-format
+msgid ""
+"The behavior of 'git add %s (or %s)' with no path argument from a\n"
+"subdirectory of the tree will change in Git 2.0 and should not be used "
+"anymore.\n"
+"To add content for the whole tree, run:\n"
+"\n"
+" git add %s :/\n"
+" (or git add %s :/)\n"
+"\n"
+"To restrict the command to the current directory, run:\n"
+"\n"
+" git add %s .\n"
+" (or git add %s .)\n"
+"\n"
+"With the current Git version, the command is restricted to the current "
+"directory.\n"
+msgstr ""
+"Das Verhalten von 'git add %s (oder %s)' ohne ein Pfad-Argument von\n"
+"einem Unterverzeichnis aus wird in Git 2.0 geändert und sollte nicht\n"
+"mehr verwendet werden.\n"
+"Um Dateien des gesamten Projektverzeichnisses hinzuzufügen, führen Sie aus:\n"
+"\n"
+" git add %s :/\n"
+" (oder git add %s :/)\n"
+"\n"
+"Zur Einschränkung auf das aktuelle Verzeichnis führen Sie aus:\n"
+"\n"
+" git add %s .\n"
+" (oder git add %s .)\n"
+"\n"
+"Mit der aktuellen Version von Git ist das Kommando auf das aktuelle\n"
+"Verzeichnis beschränkt.\n"
+
+#: builtin/add.c:100
+#, c-format
+msgid ""
+"You ran 'git add' with neither '-A (--all)' or '--ignore-removal',\n"
+"whose behaviour will change in Git 2.0 with respect to paths you removed.\n"
+"Paths like '%s' that are\n"
+"removed from your working tree are ignored with this version of Git.\n"
+"\n"
+"* 'git add --ignore-removal <pathspec>', which is the current default,\n"
+" ignores paths you removed from your working tree.\n"
+"\n"
+"* 'git add --all <pathspec>' will let you also record the removals.\n"
+"\n"
+"Run 'git status' to check the paths you removed from your working tree.\n"
+msgstr ""
+"Sie haben 'git add' weder mit '-A (--all)' noch mit '--ignore-removal'\n"
+"ausgeführt. Das Verhalten des Kommandos ändert sich in Git 2.0 durch\n"
+"Berücksichtigung der gelöschten Pfade.\n"
+"Pfade wie '%s', die im Arbeitsverzeichnis gelöscht wurden, werden in\n"
+"dieser Version von Git ignoriert.\n"
+"\n"
+"* 'git add --ignore-removal <Pfadspezifikation>', was der aktuelle\n"
+" Standardwert ist, ignoriert gelöschte Pfade im Arbeitsverzeichnis."
+"\n"
+"* 'git add --all <Pfadspezifikation>' berücksichtigt ebenfalls gelöschte\n"
+" Pfade."
+"\n"
+"Führen Sie 'git status' aus, um die gelöschten Pfade zu überprüfen.\n"
+
+#: builtin/add.c:144
#, c-format
msgid "unexpected diff status %c"
msgstr "unerwarteter Differenz-Status %c"
-#: builtin/add.c:68 builtin/commit.c:233
+#: builtin/add.c:149 builtin/commit.c:233
msgid "updating files failed"
msgstr "Aktualisierung der Dateien fehlgeschlagen"
-#: builtin/add.c:78
+#: builtin/add.c:163
#, c-format
msgid "remove '%s'\n"
msgstr "lösche '%s'\n"
-#: builtin/add.c:148
+#: builtin/add.c:253
msgid "Unstaged changes after refreshing the index:"
msgstr ""
"Nicht bereitgestellte Änderungen nach Aktualisierung der Bereitstellung:"
-#: builtin/add.c:151 builtin/add.c:460 builtin/rm.c:275
+#: builtin/add.c:256 builtin/add.c:572 builtin/rm.c:275
#, c-format
msgid "pathspec '%s' did not match any files"
msgstr "Pfadspezifikation '%s' stimmt mit keinen Dateien überein"
-#: builtin/add.c:234
+#: builtin/add.c:339
msgid "Could not read the index"
msgstr "Konnte die Bereitstellung nicht lesen"
-#: builtin/add.c:244
+#: builtin/add.c:349
#, c-format
msgid "Could not open '%s' for writing."
msgstr "Konnte '%s' nicht zum Schreiben öffnen."
-#: builtin/add.c:248
+#: builtin/add.c:353
msgid "Could not write patch"
msgstr "Konnte Patch nicht schreiben"
-#: builtin/add.c:253
+#: builtin/add.c:358
#, c-format
msgid "Could not stat '%s'"
msgstr "Konnte Verzeichnis '%s' nicht lesen"
-#: builtin/add.c:255
+#: builtin/add.c:360
msgid "Empty patch. Aborted."
msgstr "Leerer Patch. Abgebrochen."
-#: builtin/add.c:261
+#: builtin/add.c:366
#, c-format
msgid "Could not apply '%s'"
msgstr "Konnte '%s' nicht anwenden."
-#: builtin/add.c:271
+#: builtin/add.c:376
msgid "The following paths are ignored by one of your .gitignore files:\n"
msgstr ""
"Die folgenden Pfade werden durch eine Ihrer \".gitignore\" Dateien "
"ignoriert:\n"
-#: builtin/add.c:277 builtin/clean.c:161 builtin/fetch.c:78 builtin/mv.c:63
+#: builtin/add.c:393 builtin/clean.c:161 builtin/fetch.c:78 builtin/mv.c:63
#: builtin/prune-packed.c:76 builtin/push.c:425 builtin/remote.c:1253
#: builtin/rm.c:206
msgid "dry run"
msgstr "Probelauf"
-#: builtin/add.c:278 builtin/apply.c:4409 builtin/check-ignore.c:19
+#: builtin/add.c:394 builtin/apply.c:4409 builtin/check-ignore.c:19
#: builtin/commit.c:1152 builtin/count-objects.c:95 builtin/fsck.c:613
-#: builtin/log.c:1514 builtin/mv.c:62 builtin/read-tree.c:112
+#: builtin/log.c:1518 builtin/mv.c:62 builtin/read-tree.c:112
msgid "be verbose"
msgstr "erweiterte Ausgaben"
-#: builtin/add.c:280
+#: builtin/add.c:396
msgid "interactive picking"
msgstr "interaktives Auswählen"
-#: builtin/add.c:281 builtin/checkout.c:1060 builtin/reset.c:258
+#: builtin/add.c:397 builtin/checkout.c:1063 builtin/reset.c:258
msgid "select hunks interactively"
msgstr "interaktiv Bereiche auswählen"
-#: builtin/add.c:282
+#: builtin/add.c:398
msgid "edit current diff and apply"
msgstr "aktuelle Unterschiede editieren und anwenden"
-#: builtin/add.c:283
+#: builtin/add.c:399
msgid "allow adding otherwise ignored files"
msgstr "erlaubt das Hinzufügen andernfalls ignorierter Dateien"
-#: builtin/add.c:284
+#: builtin/add.c:400
msgid "update tracked files"
msgstr "aktualisiert beobachtete Dateien"
-#: builtin/add.c:285
+#: builtin/add.c:401
msgid "record only the fact that the path will be added later"
msgstr "speichert nur, dass der Pfad später hinzugefügt werden soll"
-#: builtin/add.c:286
+#: builtin/add.c:402
msgid "add changes from all tracked and untracked files"
msgstr ""
"fügt Änderungen von allen beobachteten und unbeobachteten Dateien hinzu"
-#: builtin/add.c:287
+#. takes no arguments
+#: builtin/add.c:405
+msgid "ignore paths removed in the working tree (same as --no-all)"
+msgstr "ignoriert gelöschte Pfade im Arbeitsverzeichnis (genau wie "
+"--no-all)"
+
+#: builtin/add.c:407
msgid "don't add, only refresh the index"
msgstr "fügt nichts hinzu, aktualisiert nur die Bereitstellung"
-#: builtin/add.c:288
+#: builtin/add.c:408
msgid "just skip files which cannot be added because of errors"
msgstr ""
"überspringt Dateien, die aufgrund von Fehlern nicht hinzugefügt werden "
"konnten"
-#: builtin/add.c:289
+#: builtin/add.c:409
msgid "check if - even missing - files are ignored in dry run"
msgstr "prüft ob - auch fehlende - Dateien im Probelauf ignoriert werden"
-#: builtin/add.c:311
+#: builtin/add.c:431
#, c-format
msgid "Use -f if you really want to add them.\n"
msgstr "Verwenden Sie -f wenn Sie diese wirklich hinzufügen möchten.\n"
-#: builtin/add.c:312
+#: builtin/add.c:432
msgid "no files added"
msgstr "keine Dateien hinzugefügt"
-#: builtin/add.c:318
+#: builtin/add.c:438
msgid "adding files failed"
msgstr "Hinzufügen von Dateien fehlgeschlagen"
-#.
-#. * To be consistent with "git add -p" and most Git
-#. * commands, we should default to being tree-wide, but
-#. * this is not the original behavior and can't be
-#. * changed until users trained themselves not to type
-#. * "git add -u" or "git add -A". For now, we warn and
-#. * keep the old behavior. Later, the behavior can be changed
-#. * to tree-wide, keeping the warning for a while, and
-#. * eventually we can drop the warning.
-#.
-#: builtin/add.c:335
-#, c-format
-msgid ""
-"The behavior of 'git add %s (or %s)' with no path argument from a\n"
-"subdirectory of the tree will change in Git 2.0 and should not be used "
-"anymore.\n"
-"To add content for the whole tree, run:\n"
-"\n"
-" git add %s :/\n"
-" (or git add %s :/)\n"
-"\n"
-"To restrict the command to the current directory, run:\n"
-"\n"
-" git add %s .\n"
-" (or git add %s .)\n"
-"\n"
-"With the current Git version, the command is restricted to the current "
-"directory."
-msgstr ""
-"Das Verhalten von 'git add %s (oder %s)' ohne ein Pfad-Argument von\n"
-"einem Unterverzeichnis aus wird in Git 2.0 geändert und sollte nicht\n"
-"mehr verwendet werden.\n"
-"Um Dateien des gesamten Projektverzeichnisses hinzuzufügen, führen Sie aus:\n"
-"\n"
-" git add %s :/\n"
-" (oder git add %s :/)\n"
-"\n"
-"Zur Einschränkung auf das aktuelle Verzeichnis führen Sie aus:\n"
-"\n"
-" git add %s .\n"
-" (oder git add %s .)\n"
-"\n"
-"Mit der aktuellen Version von Git ist das Kommando auf das aktuelle\n"
-"Verzeichnis beschränkt."
-
-#: builtin/add.c:381
+#: builtin/add.c:477
msgid "-A and -u are mutually incompatible"
msgstr "Die Optionen -A und -u sind zueinander inkompatibel."
-#: builtin/add.c:383
+#: builtin/add.c:495
msgid "Option --ignore-missing can only be used together with --dry-run"
msgstr ""
"Die Option --ignore-missing kann nur zusammen mit --dry-run verwendet werden."
-#: builtin/add.c:414
+#: builtin/add.c:525
#, c-format
msgid "Nothing specified, nothing added.\n"
msgstr "Nichts spezifiziert, nichts hinzugefügt.\n"
-#: builtin/add.c:415
+#: builtin/add.c:526
#, c-format
msgid "Maybe you wanted to say 'git add .'?\n"
msgstr "Wollten Sie vielleicht 'git add .' sagen?\n"
-#: builtin/add.c:421 builtin/check-ignore.c:67 builtin/clean.c:204
+#: builtin/add.c:532 builtin/check-ignore.c:66 builtin/clean.c:204
#: builtin/commit.c:293 builtin/mv.c:82 builtin/rm.c:235
msgid "index file corrupt"
msgstr "Bereitstellungsdatei beschädigt"
-#: builtin/add.c:481 builtin/apply.c:4505 builtin/mv.c:229 builtin/rm.c:370
+#: builtin/add.c:604 builtin/apply.c:4505 builtin/mv.c:229 builtin/rm.c:370
msgid "Unable to write new index file"
msgstr "Konnte neue Bereitstellungsdatei nicht schreiben."
msgid "Checking patch %s..."
msgstr "Prüfe Patch %s..."
-#: builtin/apply.c:3679 builtin/checkout.c:215 builtin/reset.c:124
+#: builtin/apply.c:3679 builtin/checkout.c:216 builtin/reset.c:124
#, c-format
msgid "make_cache_entry failed for path '%s'"
msgstr "make_cache_entry für Pfad '%s' fehlgeschlagen"
"erstellt eine temporäre Bereitstellung basierend auf den integrierten "
"Bereitstellungsinformationen"
-#: builtin/apply.c:4388 builtin/checkout-index.c:197 builtin/ls-files.c:463
+#: builtin/apply.c:4388 builtin/checkout-index.c:197 builtin/ls-files.c:456
msgid "paths are separated with NUL character"
msgstr "Pfade sind getrennt durch NUL Zeichen"
msgid "git blame [options] [rev-opts] [rev] [--] file"
msgstr "git blame [Optionen] [rev-opts] [rev] [--] Datei"
-#: builtin/blame.c:30 builtin/shortlog.c:15
+#: builtin/blame.c:30
msgid "[rev-opts] are documented in git-rev-list(1)"
msgstr "[rev-opts] sind dokumentiert in git-rev-list(1)"
-#: builtin/blame.c:2350
+#: builtin/blame.c:2355
msgid "Show blame entries as we find them, incrementally"
msgstr "Zeigt \"blame\"-Einträge schrittweise, während wir sie generieren"
-#: builtin/blame.c:2351
+#: builtin/blame.c:2356
msgid "Show blank SHA-1 for boundary commits (Default: off)"
msgstr "Zeigt leere SHA-1 für Grenzversionen (Standard: aus)"
-#: builtin/blame.c:2352
+#: builtin/blame.c:2357
msgid "Do not treat root commits as boundaries (Default: off)"
msgstr "Behandelt Ursprungsversionen nicht als Grenzen (Standard: aus)"
-#: builtin/blame.c:2353
+#: builtin/blame.c:2358
msgid "Show work cost statistics"
msgstr "Zeigt Statistiken zum Arbeitsaufwand"
-#: builtin/blame.c:2354
+#: builtin/blame.c:2359
msgid "Show output score for blame entries"
msgstr "Zeigt Ausgabebewertung für \"blame\"-Einträge"
-#: builtin/blame.c:2355
+#: builtin/blame.c:2360
msgid "Show original filename (Default: auto)"
msgstr "Zeigt ursprünglichen Dateinamen (Standard: auto)"
-#: builtin/blame.c:2356
+#: builtin/blame.c:2361
msgid "Show original linenumber (Default: off)"
msgstr "Zeigt ursprüngliche Zeilennummer (Standard: aus)"
-#: builtin/blame.c:2357
+#: builtin/blame.c:2362
msgid "Show in a format designed for machine consumption"
msgstr "Anzeige in einem Format für maschinelle Auswertung"
-#: builtin/blame.c:2358
+#: builtin/blame.c:2363
msgid "Show porcelain format with per-line commit information"
msgstr ""
"Anzeige in Format für Fremdprogramme mit Versionsinformationen pro Zeile"
-#: builtin/blame.c:2359
+#: builtin/blame.c:2364
msgid "Use the same output mode as git-annotate (Default: off)"
msgstr "Benutzt den gleichen Ausgabemodus wie \"git-annotate\" (Standard: aus)"
-#: builtin/blame.c:2360
+#: builtin/blame.c:2365
msgid "Show raw timestamp (Default: off)"
msgstr "Zeigt unbearbeiteten Zeitstempel (Standard: aus)"
-#: builtin/blame.c:2361
+#: builtin/blame.c:2366
msgid "Show long commit SHA1 (Default: off)"
msgstr "Zeigt langen Versions-SHA1 (Standard: aus)"
-#: builtin/blame.c:2362
+#: builtin/blame.c:2367
msgid "Suppress author name and timestamp (Default: off)"
msgstr "Unterdrückt den Namen des Autors und den Zeitstempel (Standard: aus)"
-#: builtin/blame.c:2363
+#: builtin/blame.c:2368
msgid "Show author email instead of name (Default: off)"
msgstr "Zeigt anstatt des Namens die Email-Adresse des Autors (Standard: aus)"
-#: builtin/blame.c:2364
+#: builtin/blame.c:2369
msgid "Ignore whitespace differences"
msgstr "Ignoriert Unterschiede in Leerzeichen"
-#: builtin/blame.c:2365
+#: builtin/blame.c:2370
msgid "Spend extra cycles to find better match"
msgstr "arbeite länger, um bessere Übereinstimmungen zu finden"
-#: builtin/blame.c:2366
+#: builtin/blame.c:2371
msgid "Use revisions from <file> instead of calling git-rev-list"
msgstr "Benutzt Revisionen von <Datei> anstatt \"git-rev-list\" aufzurufen"
-#: builtin/blame.c:2367
+#: builtin/blame.c:2372
msgid "Use <file>'s contents as the final image"
msgstr "Benutzt Inhalte der <Datei>en als entgültiges Abbild"
-#: builtin/blame.c:2368 builtin/blame.c:2369
+#: builtin/blame.c:2373 builtin/blame.c:2374
msgid "score"
msgstr "Bewertung"
-#: builtin/blame.c:2368
+#: builtin/blame.c:2373
msgid "Find line copies within and across files"
msgstr "Findet kopierte Zeilen innerhalb oder zwischen Dateien"
-#: builtin/blame.c:2369
+#: builtin/blame.c:2374
msgid "Find line movements within and across files"
msgstr "Findet verschobene Zeilen innerhalb oder zwischen Dateien"
-#: builtin/blame.c:2370
+#: builtin/blame.c:2375
msgid "n,m"
msgstr "n,m"
-#: builtin/blame.c:2370
+#: builtin/blame.c:2375
msgid "Process only line range n,m, counting from 1"
msgstr "Verarbeitet nur Zeilen im Bereich n,m, gezählt von 1"
msgid "git branch [options] (-m | -M) [<oldbranch>] <newbranch>"
msgstr "git branch [Optionen] (-m | -M) [<alterZweig>] <neuerZweig>"
-#: builtin/branch.c:146
+#: builtin/branch.c:150
#, c-format
msgid ""
"deleting branch '%s' that has been merged to\n"
" '%s', aber noch nicht mit der Zweigspitze (HEAD) zusammengeführt "
"wurde."
-#: builtin/branch.c:150
+#: builtin/branch.c:154
#, c-format
msgid ""
"not deleting branch '%s' that is not yet merged to\n"
"entferne Zweig '%s' nicht, der noch nicht zusammengeführt wurde mit\n"
" '%s', obwohl er mit der Zweigspitze (HEAD) zusammengeführt wurde."
-#: builtin/branch.c:164
+#: builtin/branch.c:168
#, c-format
msgid "Couldn't look up commit object for '%s'"
msgstr "Konnte Versionsobjekt für '%s' nicht nachschlagen."
-#: builtin/branch.c:168
+#: builtin/branch.c:172
#, c-format
msgid ""
"The branch '%s' is not fully merged.\n"
"Wenn Sie sicher sind diesen Zweig zu entfernen, führen Sie 'git branch -D "
"%s' aus."
-#: builtin/branch.c:181
+#: builtin/branch.c:185
msgid "Update of config-file failed"
msgstr "Aktualisierung der Konfigurationsdatei fehlgeschlagen."
-#: builtin/branch.c:209
+#: builtin/branch.c:213
msgid "cannot use -a with -d"
msgstr "kann -a nicht mit -d benutzen"
-#: builtin/branch.c:215
+#: builtin/branch.c:219
msgid "Couldn't look up commit object for HEAD"
msgstr "Konnte Versionsobjekt für Zweigspitze (HEAD) nicht nachschlagen."
-#: builtin/branch.c:223
+#: builtin/branch.c:227
#, c-format
msgid "Cannot delete the branch '%s' which you are currently on."
msgstr ""
"Kann Zweig '%s' nicht entfernen, da Sie sich gerade auf diesem befinden."
-#: builtin/branch.c:236
+#: builtin/branch.c:240
#, c-format
msgid "remote branch '%s' not found."
msgstr "externer Zweig '%s' nicht gefunden"
-#: builtin/branch.c:237
+#: builtin/branch.c:241
#, c-format
msgid "branch '%s' not found."
msgstr "Zweig '%s' nicht gefunden."
-#: builtin/branch.c:251
+#: builtin/branch.c:255
#, c-format
msgid "Error deleting remote branch '%s'"
msgstr "Fehler beim Entfernen des externen Zweiges '%s'"
-#: builtin/branch.c:252
+#: builtin/branch.c:256
#, c-format
msgid "Error deleting branch '%s'"
msgstr "Fehler beim Entfernen des Zweiges '%s'"
-#: builtin/branch.c:259
+#: builtin/branch.c:263
#, c-format
msgid "Deleted remote branch %s (was %s).\n"
msgstr "Externer Zweig %s entfernt (war %s).\n"
-#: builtin/branch.c:260
+#: builtin/branch.c:264
#, c-format
msgid "Deleted branch %s (was %s).\n"
msgstr "Zweig %s entfernt (war %s).\n"
-#: builtin/branch.c:362
+#: builtin/branch.c:366
#, c-format
msgid "branch '%s' does not point at a commit"
msgstr "Zweig '%s' zeigt auf keine Version"
-#: builtin/branch.c:434
+#: builtin/branch.c:453
#, c-format
msgid "[%s: behind %d]"
msgstr "[%s: %d hinterher]"
-#: builtin/branch.c:436
+#: builtin/branch.c:455
#, c-format
msgid "[behind %d]"
msgstr "[%d hinterher]"
-#: builtin/branch.c:440
+#: builtin/branch.c:459
#, c-format
msgid "[%s: ahead %d]"
msgstr "[%s: %d voraus]"
-#: builtin/branch.c:442
+#: builtin/branch.c:461
#, c-format
msgid "[ahead %d]"
msgstr "[%d voraus]"
-#: builtin/branch.c:445
+#: builtin/branch.c:464
#, c-format
msgid "[%s: ahead %d, behind %d]"
msgstr "[%s: %d voraus, %d hinterher]"
-#: builtin/branch.c:448
+#: builtin/branch.c:467
#, c-format
msgid "[ahead %d, behind %d]"
msgstr "[%d voraus, %d hinterher]"
-#: builtin/branch.c:470
+#: builtin/branch.c:490
msgid " **** invalid ref ****"
msgstr " **** ungültige Referenz ****"
-#: builtin/branch.c:562
+#: builtin/branch.c:582
#, c-format
msgid "(no branch, rebasing %s)"
msgstr "(kein Zweig, Neuaufbau von Zweig %s im Gange)"
-#: builtin/branch.c:565
+#: builtin/branch.c:585
#, c-format
msgid "(no branch, bisect started on %s)"
msgstr "(kein Zweig, Neuaufbau begonnen bei %s)"
-#: builtin/branch.c:568
+#: builtin/branch.c:588
#, c-format
msgid "(detached from %s)"
msgstr "(losgelöst von %s)"
-#: builtin/branch.c:571
+#: builtin/branch.c:591
msgid "(no branch)"
msgstr "(kein Zweig)"
-#: builtin/branch.c:617
+#: builtin/branch.c:637
#, c-format
msgid "object '%s' does not point to a commit"
msgstr "Objekt '%s' zeigt auf keine Version"
-#: builtin/branch.c:649
+#: builtin/branch.c:669
msgid "some refs could not be read"
msgstr "Konnte einige Referenzen nicht lesen"
-#: builtin/branch.c:662
+#: builtin/branch.c:682
msgid "cannot rename the current branch while not on any."
msgstr ""
"Kann aktuellen Zweig nicht umbenennen, solange Sie sich auf keinem befinden."
-#: builtin/branch.c:672
+#: builtin/branch.c:692
#, c-format
msgid "Invalid branch name: '%s'"
msgstr "Ungültiger Zweig-Name: '%s'"
-#: builtin/branch.c:687
+#: builtin/branch.c:707
msgid "Branch rename failed"
msgstr "Umbenennung des Zweiges fehlgeschlagen"
-#: builtin/branch.c:691
+#: builtin/branch.c:711
#, c-format
msgid "Renamed a misnamed branch '%s' away"
msgstr "falsch benannten Zweig '%s' umbenannt"
-#: builtin/branch.c:695
+#: builtin/branch.c:715
#, c-format
msgid "Branch renamed to %s, but HEAD is not updated!"
msgstr "Zweig umbenannt zu %s, aber Zweigspitze (HEAD) ist nicht aktualisiert!"
-#: builtin/branch.c:702
+#: builtin/branch.c:722
msgid "Branch is renamed, but update of config-file failed"
msgstr ""
"Zweig ist umbenannt, aber die Aktualisierung der Konfigurationsdatei ist "
"fehlgeschlagen."
-#: builtin/branch.c:717
+#: builtin/branch.c:737
#, c-format
msgid "malformed object name %s"
msgstr "Missgebildeter Objektname %s"
-#: builtin/branch.c:741
+#: builtin/branch.c:761
#, c-format
msgid "could not write branch description template: %s"
msgstr "Konnte Beschreibungsvorlage für Zweig nicht schreiben: %s"
-#: builtin/branch.c:771
+#: builtin/branch.c:791
msgid "Generic options"
msgstr "Allgemeine Optionen"
-#: builtin/branch.c:773
+#: builtin/branch.c:793
msgid "show hash and subject, give twice for upstream branch"
msgstr "Zeigt Hash und Betreff; -vv: zusätzlich externen Übernahmezweig"
-#: builtin/branch.c:774
+#: builtin/branch.c:794
msgid "suppress informational messages"
msgstr "unterdrückt Informationsmeldungen"
-#: builtin/branch.c:775
+#: builtin/branch.c:795
msgid "set up tracking mode (see git-pull(1))"
msgstr "stellt den Übernahmemodus ein (siehe git-pull(1))"
-#: builtin/branch.c:777
+#: builtin/branch.c:797
msgid "change upstream info"
msgstr "ändert Informationen zum externen Übernahmezweig"
-#: builtin/branch.c:781
+#: builtin/branch.c:801
msgid "use colored output"
msgstr "verwendet farbliche Ausgaben"
-#: builtin/branch.c:782
+#: builtin/branch.c:802
msgid "act on remote-tracking branches"
msgstr "wirkt auf externe Übernahmezweige"
-#: builtin/branch.c:785 builtin/branch.c:791 builtin/branch.c:812
-#: builtin/branch.c:818 builtin/commit.c:1368 builtin/commit.c:1369
+#: builtin/branch.c:805 builtin/branch.c:811 builtin/branch.c:832
+#: builtin/branch.c:838 builtin/commit.c:1368 builtin/commit.c:1369
#: builtin/commit.c:1370 builtin/commit.c:1371 builtin/tag.c:468
msgid "commit"
msgstr "Version"
-#: builtin/branch.c:786 builtin/branch.c:792
+#: builtin/branch.c:806 builtin/branch.c:812
msgid "print only branches that contain the commit"
msgstr "gibt nur Zweige aus, welche diese Version beinhalten"
-#: builtin/branch.c:798
+#: builtin/branch.c:818
msgid "Specific git-branch actions:"
msgstr "spezifische Aktionen für \"git-branch\":"
-#: builtin/branch.c:799
+#: builtin/branch.c:819
msgid "list both remote-tracking and local branches"
msgstr "listet externe Übernahmezweige und lokale Zweige auf"
-#: builtin/branch.c:801
+#: builtin/branch.c:821
msgid "delete fully merged branch"
msgstr "entfernt vollständig zusammengeführten Zweig"
-#: builtin/branch.c:802
+#: builtin/branch.c:822
msgid "delete branch (even if not merged)"
msgstr "löscht Zweig (auch wenn nicht zusammengeführt)"
-#: builtin/branch.c:803
+#: builtin/branch.c:823
msgid "move/rename a branch and its reflog"
msgstr "verschiebt/benennt einen Zweig und dessen Referenzprotokoll um"
-#: builtin/branch.c:804
+#: builtin/branch.c:824
msgid "move/rename a branch, even if target exists"
msgstr ""
"verschiebt/benennt einen Zweig um, auch wenn das Ziel bereits existiert"
-#: builtin/branch.c:805
+#: builtin/branch.c:825
msgid "list branch names"
msgstr "listet Zweignamen auf"
-#: builtin/branch.c:806
+#: builtin/branch.c:826
msgid "create the branch's reflog"
msgstr "erzeugt das Referenzprotokoll des Zweiges"
-#: builtin/branch.c:808
+#: builtin/branch.c:828
msgid "edit the description for the branch"
msgstr "bearbeitet die Beschreibung für den Zweig"
-#: builtin/branch.c:809
+#: builtin/branch.c:829
msgid "force creation (when already exists)"
msgstr "erzeuge auch, wenn der Zweig bereits existiert"
-#: builtin/branch.c:812
+#: builtin/branch.c:832
msgid "print only not merged branches"
msgstr "gibt nur Zweige aus, die nicht zusammengeführt sind"
-#: builtin/branch.c:818
+#: builtin/branch.c:838
msgid "print only merged branches"
msgstr "gibt nur Zweige aus, die zusammengeführt sind"
-#: builtin/branch.c:822
+#: builtin/branch.c:842
msgid "list branches in columns"
msgstr "listet Zweige in Spalten auf"
-#: builtin/branch.c:835
+#: builtin/branch.c:855
msgid "Failed to resolve HEAD as a valid ref."
msgstr "Konnte Zweigspitze (HEAD) nicht als gültige Referenz auflösen."
-#: builtin/branch.c:840 builtin/clone.c:609
+#: builtin/branch.c:860 builtin/clone.c:619
msgid "HEAD not found below refs/heads!"
msgstr "Zweigspitze (HEAD) wurde nicht unter \"refs/heads\" gefunden!"
-#: builtin/branch.c:863
+#: builtin/branch.c:883
msgid "--column and --verbose are incompatible"
msgstr "Die Optionen --column und --verbose sind inkompatibel."
-#: builtin/branch.c:869 builtin/branch.c:908
+#: builtin/branch.c:889 builtin/branch.c:928
msgid "branch name required"
msgstr "Zweigname erforderlich"
-#: builtin/branch.c:884
+#: builtin/branch.c:904
msgid "Cannot give description to detached HEAD"
msgstr ""
"zu losgelöster Zweigspitze (HEAD) kann keine Beschreibung hinterlegt werden"
-#: builtin/branch.c:889
+#: builtin/branch.c:909
msgid "cannot edit description of more than one branch"
msgstr "Beschreibung von mehr als einem Zweig kann nicht bearbeitet werden"
-#: builtin/branch.c:896
+#: builtin/branch.c:916
#, c-format
msgid "No commit on branch '%s' yet."
msgstr "Noch keine Version in Zweig '%s'."
-#: builtin/branch.c:899
+#: builtin/branch.c:919
#, c-format
msgid "No branch named '%s'."
msgstr "Zweig '%s' nicht vorhanden."
-#: builtin/branch.c:914
+#: builtin/branch.c:934
msgid "too many branches for a rename operation"
msgstr "zu viele Zweige für eine Umbenennen-Operation angegeben"
-#: builtin/branch.c:919
+#: builtin/branch.c:939
msgid "too many branches to set new upstream"
msgstr "zu viele Zweige angegeben um neuen Übernahmezweig zu setzen"
-#: builtin/branch.c:923
+#: builtin/branch.c:943
#, c-format
msgid ""
"could not set upstream of HEAD to %s when it does not point to any branch."
"Konnte keinen neuen Übernahmezweig von Zweigspitze (HEAD) zu %s setzen,\n"
"da diese auf keinen Zweig zeigt."
-#: builtin/branch.c:926 builtin/branch.c:948 builtin/branch.c:970
+#: builtin/branch.c:946 builtin/branch.c:968 builtin/branch.c:990
#, c-format
msgid "no such branch '%s'"
msgstr "Kein solcher Zweig '%s'"
-#: builtin/branch.c:930
+#: builtin/branch.c:950
#, c-format
msgid "branch '%s' does not exist"
msgstr "Zweig '%s' existiert nicht"
-#: builtin/branch.c:942
+#: builtin/branch.c:962
msgid "too many branches to unset upstream"
-msgstr "zu viele Zweige angegeben um Konfiguration zu Übernahmezweig zu entfernen"
+msgstr ""
+"zu viele Zweige angegeben um Konfiguration zu Übernahmezweig zu entfernen"
-#: builtin/branch.c:946
+#: builtin/branch.c:966
msgid "could not unset upstream of HEAD when it does not point to any branch."
-msgstr "Konnte Konfiguration zum Übernahmezweig von Zweigspitze (HEAD) nicht\n"
+msgstr ""
+"Konnte Konfiguration zum Übernahmezweig von Zweigspitze (HEAD) nicht\n"
"entfernen, da diese auf keinen Zweig zeigt."
-#: builtin/branch.c:952
+#: builtin/branch.c:972
#, c-format
msgid "Branch '%s' has no upstream information"
msgstr "Zweig '%s' hat keinen externen Übernahmezweig gesetzt"
-#: builtin/branch.c:967
+#: builtin/branch.c:987
msgid "it does not make sense to create 'HEAD' manually"
msgstr "'HEAD' darf nicht manuell erstellt werden"
-#: builtin/branch.c:973
+#: builtin/branch.c:993
msgid "-a and -r options to 'git branch' do not make sense with a branch name"
msgstr ""
"Die Optionen -a und -r bei 'git branch' können nicht gemeimsam mit einem "
"Zweignamen verwendet werden."
-#: builtin/branch.c:976
+#: builtin/branch.c:996
#, c-format
msgid ""
"The --set-upstream flag is deprecated and will be removed. Consider using --"
"Die --set-upstream Option ist veraltet und wird entfernt. Benutzen Sie --"
"track oder --set-upstream-to\n"
-#: builtin/branch.c:993
+#: builtin/branch.c:1013
#, c-format
msgid ""
"\n"
"Wenn Sie wollten, dass '%s' den Zweig '%s' als externen Übernahmezweig hat, "
"führen Sie aus:\n"
-#: builtin/branch.c:994
+#: builtin/branch.c:1014
#, c-format
msgid " git branch -d %s\n"
msgstr " git branch -d %s\n"
-#: builtin/branch.c:995
+#: builtin/branch.c:1015
#, c-format
msgid " git branch --set-upstream-to %s\n"
msgstr " git branch --set-upstream-to %s\n"
msgid "Need a repository to unbundle."
msgstr "Zum Entpacken wird ein Projektarchiv benötigt."
-#: builtin/cat-file.c:247
+#: builtin/cat-file.c:176
msgid "git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>"
msgstr "git cat-file (-t|-s|-e|-p|<Art>|--textconv) <Objekt>"
-#: builtin/cat-file.c:248
+#: builtin/cat-file.c:177
msgid "git cat-file (--batch|--batch-check) < <list_of_objects>"
msgstr "git cat-file (--batch|--batch-check) < <Liste_von_Objekten>"
-#: builtin/cat-file.c:266
+#: builtin/cat-file.c:195
msgid "<type> can be one of: blob, tree, commit, tag"
msgstr "<Art> kann sein: blob, tree, commit, tag"
-#: builtin/cat-file.c:267
+#: builtin/cat-file.c:196
msgid "show object type"
msgstr "zeigt Objektart"
-#: builtin/cat-file.c:268
+#: builtin/cat-file.c:197
msgid "show object size"
msgstr "zeigt Objektgröße"
-#: builtin/cat-file.c:270
+#: builtin/cat-file.c:199
msgid "exit with zero when there's no error"
msgstr "beendet mit Rückgabewert 0, wenn kein Fehler aufgetreten ist"
-#: builtin/cat-file.c:271
+#: builtin/cat-file.c:200
msgid "pretty-print object's content"
msgstr "ansprechende Anzeige des Objektinhaltes"
-#: builtin/cat-file.c:273
+#: builtin/cat-file.c:202
msgid "for blob objects, run textconv on object's content"
msgstr "führt eine Textkonvertierung auf den Inhalt von Blob-Objekten aus"
-#: builtin/cat-file.c:275
+#: builtin/cat-file.c:204
msgid "show info and content of objects fed from the standard input"
msgstr ""
"Anzeige von Informationen und Inhalt von Objekten, gelesen von der Standard-"
"Eingabe"
-#: builtin/cat-file.c:278
+#: builtin/cat-file.c:207
msgid "show info about objects fed from the standard input"
msgstr ""
"Anzeige von Informationen über Objekte, gelesen von der Standard-Eingabe"
msgid "input paths are terminated by a null character"
msgstr "Eingabepfade sind durch ein NUL Zeichen abgeschlossen"
-#: builtin/check-ignore.c:18 builtin/checkout.c:1041 builtin/gc.c:177
+#: builtin/check-ignore.c:18 builtin/checkout.c:1044 builtin/gc.c:177
msgid "suppress progress reporting"
msgstr "unterdrückt Fortschrittsanzeige"
-#: builtin/check-ignore.c:151
+#: builtin/check-ignore.c:146
msgid "cannot specify pathnames with --stdin"
msgstr "Angabe von Pfadnamen kann nicht gemeinsam mit --stdin verwendet werden"
-#: builtin/check-ignore.c:154
+#: builtin/check-ignore.c:149
msgid "-z only makes sense with --stdin"
msgstr "Die Option -z kann nur mit --stdin verwendet werden."
-#: builtin/check-ignore.c:156
+#: builtin/check-ignore.c:151
msgid "no path specified"
msgstr "kein Pfad angegeben"
-#: builtin/check-ignore.c:160
+#: builtin/check-ignore.c:155
msgid "--quiet is only valid with a single pathname"
msgstr "Die Option --quiet ist nur mit einem einzelnen Pfadnamen gültig."
-#: builtin/check-ignore.c:162
+#: builtin/check-ignore.c:157
msgid "cannot have both --quiet and --verbose"
msgstr ""
"Die Optionen --quiet und --verbose können nicht gemeinsam verwendet werden."
msgid "git checkout [options] [<branch>] -- <file>..."
msgstr "git checkout [Optionen] [Zweig>] -- <Datei>..."
-#: builtin/checkout.c:116 builtin/checkout.c:149
+#: builtin/checkout.c:117 builtin/checkout.c:150
#, c-format
msgid "path '%s' does not have our version"
msgstr "Pfad '%s' hat nicht unsere Version."
-#: builtin/checkout.c:118 builtin/checkout.c:151
+#: builtin/checkout.c:119 builtin/checkout.c:152
#, c-format
msgid "path '%s' does not have their version"
msgstr "Pfad '%s' hat nicht deren Version."
-#: builtin/checkout.c:134
+#: builtin/checkout.c:135
#, c-format
msgid "path '%s' does not have all necessary versions"
msgstr "Pfad '%s' hat nicht alle notwendigen Versionen."
-#: builtin/checkout.c:178
+#: builtin/checkout.c:179
#, c-format
msgid "path '%s' does not have necessary versions"
msgstr "Pfad '%s' hat nicht die notwendigen Versionen."
-#: builtin/checkout.c:195
+#: builtin/checkout.c:196
#, c-format
msgid "path '%s': cannot merge"
msgstr "Pfad '%s': kann nicht zusammenführen"
-#: builtin/checkout.c:212
+#: builtin/checkout.c:213
#, c-format
msgid "Unable to add merge result for '%s'"
msgstr "Konnte Ergebnis der Zusammenführung von '%s' nicht hinzufügen."
-#: builtin/checkout.c:236 builtin/checkout.c:239 builtin/checkout.c:242
-#: builtin/checkout.c:245
+#: builtin/checkout.c:237 builtin/checkout.c:240 builtin/checkout.c:243
+#: builtin/checkout.c:246
#, c-format
msgid "'%s' cannot be used with updating paths"
msgstr "'%s' kann nicht mit Pfaden verwendet werden"
-#: builtin/checkout.c:248 builtin/checkout.c:251
+#: builtin/checkout.c:249 builtin/checkout.c:252
#, c-format
msgid "'%s' cannot be used with %s"
msgstr "'%s' kann nicht mit '%s' verwendet werden"
-#: builtin/checkout.c:254
+#: builtin/checkout.c:255
#, c-format
msgid "Cannot update paths and switch to branch '%s' at the same time."
msgstr "Kann nicht gleichzeitig Pfade aktualisieren und zu Zweig '%s' wechseln"
-#: builtin/checkout.c:265 builtin/checkout.c:455
+#: builtin/checkout.c:266 builtin/checkout.c:458
msgid "corrupt index file"
msgstr "beschädigte Bereitstellungsdatei"
-#: builtin/checkout.c:326 builtin/checkout.c:333
+#: builtin/checkout.c:329 builtin/checkout.c:336
#, c-format
msgid "path '%s' is unmerged"
msgstr "Pfad '%s' ist nicht zusammengeführt."
-#: builtin/checkout.c:477
+#: builtin/checkout.c:480
msgid "you need to resolve your current index first"
msgstr "Sie müssen zuerst Ihre aktuelle Bereitstellung auflösen."
-#: builtin/checkout.c:598
+#: builtin/checkout.c:601
#, c-format
msgid "Can not do reflog for '%s'\n"
msgstr "Konnte \"reflog\" für '%s' nicht durchführen\n"
-#: builtin/checkout.c:631
+#: builtin/checkout.c:634
msgid "HEAD is now at"
msgstr "Zweigspitze (HEAD) ist jetzt bei"
-#: builtin/checkout.c:638
+#: builtin/checkout.c:641
#, c-format
msgid "Reset branch '%s'\n"
msgstr "Setze Zweig '%s' neu\n"
-#: builtin/checkout.c:641
+#: builtin/checkout.c:644
#, c-format
msgid "Already on '%s'\n"
msgstr "Bereits auf '%s'\n"
-#: builtin/checkout.c:645
+#: builtin/checkout.c:648
#, c-format
msgid "Switched to and reset branch '%s'\n"
msgstr "Gewechselt zu neu gesetztem Zweig '%s'\n"
-#: builtin/checkout.c:647 builtin/checkout.c:984
+#: builtin/checkout.c:650 builtin/checkout.c:987
#, c-format
msgid "Switched to a new branch '%s'\n"
msgstr "Gewechselt zu einem neuen Zweig '%s'\n"
-#: builtin/checkout.c:649
+#: builtin/checkout.c:652
#, c-format
msgid "Switched to branch '%s'\n"
msgstr "Gewechselt zu Zweig '%s'\n"
-#: builtin/checkout.c:705
+#: builtin/checkout.c:708
#, c-format
msgid " ... and %d more.\n"
msgstr " ... und %d weitere.\n"
#. The singular version
-#: builtin/checkout.c:711
+#: builtin/checkout.c:714
#, c-format
msgid ""
"Warning: you are leaving %d commit behind, not connected to\n"
"\n"
"%s\n"
-#: builtin/checkout.c:729
+#: builtin/checkout.c:732
#, c-format
msgid ""
"If you want to keep them by creating a new branch, this may be a good time\n"
" git branch neuer_zweig_name %s\n"
"\n"
-#: builtin/checkout.c:759
+#: builtin/checkout.c:762
msgid "internal error in revision walk"
msgstr "interner Fehler im Revisionsgang"
-#: builtin/checkout.c:763
+#: builtin/checkout.c:766
msgid "Previous HEAD position was"
msgstr "Vorherige Position der Zweigspitze (HEAD) war"
-#: builtin/checkout.c:790 builtin/checkout.c:979
+#: builtin/checkout.c:793 builtin/checkout.c:982
msgid "You are on a branch yet to be born"
msgstr "Sie sind auf einem Zweig, der noch geboren wird"
#. case (1)
-#: builtin/checkout.c:915
+#: builtin/checkout.c:918
#, c-format
msgid "invalid reference: %s"
msgstr "Ungültige Referenz: %s"
#. case (1): want a tree
-#: builtin/checkout.c:954
+#: builtin/checkout.c:957
#, c-format
msgid "reference is not a tree: %s"
msgstr "Referenz ist kein Baum: %s"
-#: builtin/checkout.c:993
+#: builtin/checkout.c:996
msgid "paths cannot be used with switching branches"
msgstr "Pfade können nicht beim Wechseln von Zweigen verwendet werden"
-#: builtin/checkout.c:996 builtin/checkout.c:1000
+#: builtin/checkout.c:999 builtin/checkout.c:1003
#, c-format
msgid "'%s' cannot be used with switching branches"
msgstr "'%s' kann nicht beim Wechseln von Zweigen verwendet werden"
-#: builtin/checkout.c:1004 builtin/checkout.c:1007 builtin/checkout.c:1012
-#: builtin/checkout.c:1015
+#: builtin/checkout.c:1007 builtin/checkout.c:1010 builtin/checkout.c:1015
+#: builtin/checkout.c:1018
#, c-format
msgid "'%s' cannot be used with '%s'"
msgstr "'%s' kann nicht mit '%s' verwendet werden"
-#: builtin/checkout.c:1020
+#: builtin/checkout.c:1023
#, c-format
msgid "Cannot switch branch to a non-commit '%s'"
msgstr "Kann Zweig nicht zu Nicht-Version '%s' wechseln"
-#: builtin/checkout.c:1042 builtin/checkout.c:1044 builtin/clone.c:90
+#: builtin/checkout.c:1045 builtin/checkout.c:1047 builtin/clone.c:90
#: builtin/remote.c:169 builtin/remote.c:171
msgid "branch"
msgstr "Zweig"
-#: builtin/checkout.c:1043
+#: builtin/checkout.c:1046
msgid "create and checkout a new branch"
msgstr "erzeugt und checkt einen neuen Zweig aus"
-#: builtin/checkout.c:1045
+#: builtin/checkout.c:1048
msgid "create/reset and checkout a branch"
msgstr "erzeugt/setzt neu und checkt einen Zweig aus"
-#: builtin/checkout.c:1046
+#: builtin/checkout.c:1049
msgid "create reflog for new branch"
msgstr "erzeugt Referenzprotokoll für den neuen Zweig"
-#: builtin/checkout.c:1047
+#: builtin/checkout.c:1050
msgid "detach the HEAD at named commit"
msgstr "setzt die Zweigspitze (HEAD) zu benannter Version"
-#: builtin/checkout.c:1048
+#: builtin/checkout.c:1051
msgid "set upstream info for new branch"
msgstr "setzt Informationen zum externen Übernahmezweig für den neuen Zweig"
-#: builtin/checkout.c:1050
+#: builtin/checkout.c:1053
msgid "new branch"
msgstr "neuer Zweig"
-#: builtin/checkout.c:1050
+#: builtin/checkout.c:1053
msgid "new unparented branch"
msgstr "neuer Zweig ohne Elternversion"
-#: builtin/checkout.c:1051
+#: builtin/checkout.c:1054
msgid "checkout our version for unmerged files"
msgstr "checkt unsere Variante für nicht zusammengeführte Dateien aus"
-#: builtin/checkout.c:1053
+#: builtin/checkout.c:1056
msgid "checkout their version for unmerged files"
msgstr "checkt ihre Variante für nicht zusammengeführte Dateien aus"
-#: builtin/checkout.c:1055
+#: builtin/checkout.c:1058
msgid "force checkout (throw away local modifications)"
msgstr "erzwingt Auschecken (verwirft lokale Änderungen)"
-#: builtin/checkout.c:1056
+#: builtin/checkout.c:1059
msgid "perform a 3-way merge with the new branch"
msgstr "führt eine 3-Wege-Zusammenführung mit dem neuen Zweig aus"
-#: builtin/checkout.c:1057 builtin/merge.c:217
+#: builtin/checkout.c:1060 builtin/merge.c:217
msgid "update ignored files (default)"
msgstr "aktualisiert ignorierte Dateien (Standard)"
-#: builtin/checkout.c:1058 builtin/log.c:1149 parse-options.h:245
+#: builtin/checkout.c:1061 builtin/log.c:1158 parse-options.h:245
msgid "style"
msgstr "Stil"
-#: builtin/checkout.c:1059
+#: builtin/checkout.c:1062
msgid "conflict style (merge or diff3)"
msgstr "Konfliktstil (merge oder diff3)"
-#: builtin/checkout.c:1062
+#: builtin/checkout.c:1065
+msgid "do not limit pathspecs to sparse entries only"
+msgstr "Pfadspezifikationen ignorieren Einstellungen zum partiellen Auschecken"
+
+#: builtin/checkout.c:1067
msgid "second guess 'git checkout no-such-branch'"
msgstr "second guess 'git checkout no-such-branch'"
-#: builtin/checkout.c:1086
+#: builtin/checkout.c:1091
msgid "-b, -B and --orphan are mutually exclusive"
msgstr "Die Optionen -b, -B und --orphan schließen sich gegenseitig aus."
-#: builtin/checkout.c:1103
+#: builtin/checkout.c:1108
msgid "--track needs a branch name"
msgstr "Bei der Option --track muss ein Zweigname angegeben werden."
-#: builtin/checkout.c:1110
+#: builtin/checkout.c:1115
msgid "Missing branch name; try -b"
msgstr "Vermisse Zweignamen; versuchen Sie -b"
-#: builtin/checkout.c:1145
+#: builtin/checkout.c:1150
msgid "invalid path specification"
msgstr "ungültige Pfadspezifikation"
-#: builtin/checkout.c:1152
+#: builtin/checkout.c:1157
#, c-format
msgid ""
"Cannot update paths and switch to branch '%s' at the same time.\n"
"Haben Sie beabsichtigt '%s' auszuchecken, welcher nicht als Version "
"aufgelöst werden kann?"
-#: builtin/checkout.c:1157
+#: builtin/checkout.c:1162
#, c-format
msgid "git checkout: --detach does not take a path argument '%s'"
msgstr "git checkout: --detach nimmt kein Pfad-Argument '%s'"
-#: builtin/checkout.c:1161
+#: builtin/checkout.c:1166
msgid ""
"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
"checking out of the index."
msgstr "löscht ganze Verzeichnisse"
#: builtin/clean.c:165 builtin/describe.c:412 builtin/grep.c:717
-#: builtin/ls-files.c:494 builtin/name-rev.c:231 builtin/show-ref.c:182
+#: builtin/ls-files.c:487 builtin/name-rev.c:231 builtin/show-ref.c:182
msgid "pattern"
msgstr "Muster"
msgid "set config inside the new repository"
msgstr "setzt Konfiguration innerhalb des neuen Projektarchivs"
-#: builtin/clone.c:244
+#: builtin/clone.c:254
#, c-format
-msgid "reference repository '%s' is not a local directory."
-msgstr "Referenziertes Projektarchiv '%s' ist kein lokales Verzeichnis."
+msgid "reference repository '%s' is not a local repository."
+msgstr "Referenziertes Projektarchiv '%s' ist kein lokales Projektarchiv."
-#: builtin/clone.c:307
+#: builtin/clone.c:317
#, c-format
msgid "failed to create directory '%s'"
msgstr "Fehler beim Erstellen von Verzeichnis '%s'"
-#: builtin/clone.c:309 builtin/diff.c:77
+#: builtin/clone.c:319 builtin/diff.c:77
#, c-format
msgid "failed to stat '%s'"
msgstr "Konnte '%s' nicht lesen"
-#: builtin/clone.c:311
+#: builtin/clone.c:321
#, c-format
msgid "%s exists and is not a directory"
msgstr "%s existiert und ist kein Verzeichnis"
-#: builtin/clone.c:325
+#: builtin/clone.c:335
#, c-format
msgid "failed to stat %s\n"
msgstr "Konnte %s nicht lesen\n"
-#: builtin/clone.c:347
+#: builtin/clone.c:357
#, c-format
msgid "failed to create link '%s'"
msgstr "Konnte Verknüpfung '%s' nicht erstellen"
-#: builtin/clone.c:351
+#: builtin/clone.c:361
#, c-format
msgid "failed to copy file to '%s'"
msgstr "Konnte Datei nicht nach '%s' kopieren"
-#: builtin/clone.c:374
+#: builtin/clone.c:384
#, c-format
msgid "done.\n"
msgstr "Fertig.\n"
-#: builtin/clone.c:387
+#: builtin/clone.c:397
msgid ""
"Clone succeeded, but checkout failed.\n"
"You can inspect what was checked out with 'git status'\n"
"Sie können mit 'git status' prüfen, was ausgecheckt worden ist\n"
"und das Auschecken mit 'git checkout -f HEAD' erneut versuchen.\n"
-#: builtin/clone.c:466
+#: builtin/clone.c:476
#, c-format
msgid "Could not find remote branch %s to clone."
msgstr "Konnte zu klonenden externer Zweig %s nicht finden."
-#: builtin/clone.c:540
+#: builtin/clone.c:550
msgid "remote did not send all necessary objects"
msgstr "Fernarchiv hat nicht alle erforderlichen Objekte gesendet."
-#: builtin/clone.c:600
+#: builtin/clone.c:610
msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
msgstr ""
"Externe Zweigspitze (HEAD) bezieht sich auf eine nicht existierende Referenz "
"und kann nicht ausgecheckt werden.\n"
-#: builtin/clone.c:631
+#: builtin/clone.c:641
msgid "unable to checkout working tree"
msgstr "Arbeitsbaum konnte nicht ausgecheckt werden"
-#: builtin/clone.c:739
+#: builtin/clone.c:749
msgid "Too many arguments."
msgstr "Zu viele Argumente."
-#: builtin/clone.c:743
+#: builtin/clone.c:753
msgid "You must specify a repository to clone."
msgstr "Sie müssen ein Projektarchiv zum Klonen angeben."
-#: builtin/clone.c:754
+#: builtin/clone.c:764
#, c-format
msgid "--bare and --origin %s options are incompatible."
msgstr "Die Optionen --bare und --origin %s sind inkompatibel."
-#: builtin/clone.c:757
+#: builtin/clone.c:767
msgid "--bare and --separate-git-dir are incompatible."
msgstr "Die Optionen --bare und --separate-git-dir sind inkompatibel."
-#: builtin/clone.c:770
+#: builtin/clone.c:780
#, c-format
msgid "repository '%s' does not exist"
msgstr "Projektarchiv '%s' existiert nicht."
-#: builtin/clone.c:775
+#: builtin/clone.c:785
msgid "--depth is ignored in local clones; use file:// instead."
msgstr ""
"Die Option --depth wird in lokalen Klonen ignoriert; benutzen Sie "
"stattdessen file://"
-#: builtin/clone.c:785
+#: builtin/clone.c:795
#, c-format
msgid "destination path '%s' already exists and is not an empty directory."
msgstr "Zielpfad '%s' existiert bereits und ist kein leeres Verzeichnis."
-#: builtin/clone.c:795
+#: builtin/clone.c:805
#, c-format
msgid "working tree '%s' already exists."
msgstr "Arbeitsbaum '%s' existiert bereits."
-#: builtin/clone.c:808 builtin/clone.c:820
+#: builtin/clone.c:818 builtin/clone.c:830
#, c-format
msgid "could not create leading directories of '%s'"
msgstr "Konnte führende Verzeichnisse von '%s' nicht erstellen."
-#: builtin/clone.c:811
+#: builtin/clone.c:821
#, c-format
msgid "could not create work tree dir '%s'."
msgstr "Konnte Arbeitsverzeichnis '%s' nicht erstellen."
-#: builtin/clone.c:830
+#: builtin/clone.c:840
#, c-format
msgid "Cloning into bare repository '%s'...\n"
msgstr "Klone in bloßes Projektarchiv '%s'...\n"
-#: builtin/clone.c:832
+#: builtin/clone.c:842
#, c-format
msgid "Cloning into '%s'...\n"
msgstr "Klone nach '%s'...\n"
-#: builtin/clone.c:867
+#: builtin/clone.c:877
#, c-format
msgid "Don't know how to clone %s"
msgstr "Weiß nicht wie %s zu klonen ist."
-#: builtin/clone.c:916
+#: builtin/clone.c:926
#, c-format
msgid "Remote branch %s not found in upstream %s"
msgstr "externer Zweig %s nicht im anderen Projektarchiv %s gefunden"
-#: builtin/clone.c:923
+#: builtin/clone.c:933
msgid "You appear to have cloned an empty repository."
msgstr "Sie scheinen ein leeres Projektarchiv geklont zu haben."
msgid "could not lookup commit %s"
msgstr "Konnte Version %s nicht nachschlagen"
-#: builtin/commit.c:612 builtin/shortlog.c:272
+#: builtin/commit.c:612 builtin/shortlog.c:270
#, c-format
msgid "(reading log message from standard input)\n"
msgstr "(lese Log-Nachricht von Standard-Eingabe)\n"
msgid "terminate entries with NUL"
msgstr "schließt Einträge mit NUL-Zeichen ab"
-#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/fast-export.c:653
-#: builtin/fast-export.c:656 builtin/tag.c:459
+#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/fast-export.c:659
+#: builtin/fast-export.c:662 builtin/tag.c:459
msgid "mode"
msgstr "Modus"
msgid "the commit is authored by me now (used with -C/-c/--amend)"
msgstr "Setzt Sie als Autor der Version (verwendet mit -C/-c/--amend)"
-#: builtin/commit.c:1373 builtin/log.c:1104 builtin/revert.c:109
+#: builtin/commit.c:1373 builtin/log.c:1113 builtin/revert.c:109
msgid "add Signed-off-by:"
msgstr "fügt 'Signed-off-by:'-Zeile hinzu"
msgstr "beachtet \"include\"-Direktiven beim Nachschlagen"
#: builtin/count-objects.c:82
-msgid "git count-objects [-v]"
-msgstr "git count-objects [-v]"
+msgid "git count-objects [-v] [-H | --human-readable]"
+msgstr "git count-objects [-v] [-H | --human-readable]"
+
+#: builtin/count-objects.c:97
+msgid "print sizes in human readable format"
+msgstr "gibt Größenangaben in menschenlesbaren Format aus"
#: builtin/describe.c:15
msgid "git describe [options] <committish>*"
msgid "git fast-export [rev-list-opts]"
msgstr "git fast-export [rev-list-opts]"
-#: builtin/fast-export.c:652
+#: builtin/fast-export.c:658
msgid "show progress after <n> objects"
msgstr "zeigt Fortschritt nach <n> Objekten an"
-#: builtin/fast-export.c:654
+#: builtin/fast-export.c:660
msgid "select handling of signed tags"
msgstr "wählt Behandlung von signierten Markierungen"
-#: builtin/fast-export.c:657
+#: builtin/fast-export.c:663
msgid "select handling of tags that tag filtered objects"
msgstr "wählt Behandlung von Markierungen, die gefilterte Objekte markieren"
-#: builtin/fast-export.c:660
+#: builtin/fast-export.c:666
msgid "Dump marks to this file"
msgstr "Schreibt Kennzeichen in diese Datei"
-#: builtin/fast-export.c:662
+#: builtin/fast-export.c:668
msgid "Import marks from this file"
msgstr "Importiert Kennzeichen von dieser Datei"
-#: builtin/fast-export.c:664
+#: builtin/fast-export.c:670
msgid "Fake a tagger when tags lack one"
msgstr ""
"erzeugt künstlich einen Markierungsersteller, wenn die Markierung keinen hat"
-#: builtin/fast-export.c:666
+#: builtin/fast-export.c:672
msgid "Output full tree for each commit"
msgstr "gibt für jede Version den gesamten Baum aus"
-#: builtin/fast-export.c:668
+#: builtin/fast-export.c:674
msgid "Use the done feature to terminate the stream"
msgstr "Benutzt die \"done\"-Funktion um den Strom abzuschließen"
-#: builtin/fast-export.c:669
+#: builtin/fast-export.c:675
msgid "Skip output of blob data"
msgstr "Überspringt Ausgabe von Blob-Daten"
msgid "convert to a complete repository"
msgstr "konvertiert zu einem vollständigen Projektarchiv"
-#: builtin/fetch.c:88 builtin/log.c:1121
+#: builtin/fetch.c:88 builtin/log.c:1130
msgid "dir"
msgstr "Verzeichnis"
msgstr ""
"git fmt-merge-msg [-m <Beschreibung>] [--log[=<n>]|--no-log] [--file <Datei>]"
-#: builtin/fmt-merge-msg.c:659 builtin/fmt-merge-msg.c:662 builtin/grep.c:701
-#: builtin/merge.c:188 builtin/show-branch.c:656 builtin/show-ref.c:175
+#: builtin/fmt-merge-msg.c:663 builtin/fmt-merge-msg.c:666 builtin/grep.c:701
+#: builtin/merge.c:188 builtin/show-branch.c:655 builtin/show-ref.c:175
#: builtin/tag.c:446 parse-options.h:133 parse-options.h:239
msgid "n"
msgstr "Anzahl"
-#: builtin/fmt-merge-msg.c:660
+#: builtin/fmt-merge-msg.c:664
msgid "populate log with at most <n> entries from shortlog"
msgstr "fügt Historie mit höchstens <n> Einträgen von \"shortlog\" hinzu"
-#: builtin/fmt-merge-msg.c:663
+#: builtin/fmt-merge-msg.c:667
msgid "alias for --log (deprecated)"
msgstr "Alias für --log (veraltet)"
-#: builtin/fmt-merge-msg.c:666
+#: builtin/fmt-merge-msg.c:670
msgid "text"
msgstr "Text"
-#: builtin/fmt-merge-msg.c:667
+#: builtin/fmt-merge-msg.c:671
msgid "use <text> as start of message"
msgstr "verwendet <Text> als Beschreibungsanfang"
-#: builtin/fmt-merge-msg.c:668
+#: builtin/fmt-merge-msg.c:672
msgid "file to read from"
msgstr "Datei zum Einlesen"
msgid "process file as it were from this path"
msgstr "verarbeitet Datei, als ob sie von diesem Pfad wäre"
-#: builtin/help.c:42
+#: builtin/help.c:43
msgid "print all available commands"
msgstr "Anzeige aller vorhandenen Kommandos"
-#: builtin/help.c:43
+#: builtin/help.c:44
+msgid "print list of useful guides"
+msgstr "zeigt Liste von allgemein verwendeten Anleitungen"
+
+#: builtin/help.c:45
msgid "show man page"
msgstr "zeigt Handbuch"
-#: builtin/help.c:44
+#: builtin/help.c:46
msgid "show manual in web browser"
msgstr "zeigt Handbuch in einem Webbrowser"
-#: builtin/help.c:46
+#: builtin/help.c:48
msgid "show info page"
msgstr "zeigt Info-Seite"
-#: builtin/help.c:52
-msgid "git help [--all] [--man|--web|--info] [command]"
-msgstr "git help [--all] [--man|--web|--info] [Kommando]"
+#: builtin/help.c:54
+msgid "git help [--all] [--guides] [--man|--web|--info] [command]"
+msgstr "git help [--all] [--guides] [--man|--web|--info] [Kommando]"
-#: builtin/help.c:64
+#: builtin/help.c:66
#, c-format
msgid "unrecognized help format '%s'"
msgstr "nicht erkanntes Hilfeformat: %s"
-#: builtin/help.c:92
+#: builtin/help.c:94
msgid "Failed to start emacsclient."
msgstr "Konnte emacsclient nicht starten."
-#: builtin/help.c:105
+#: builtin/help.c:107
msgid "Failed to parse emacsclient version."
msgstr "Konnte Version des emacsclient nicht parsen."
-#: builtin/help.c:113
+#: builtin/help.c:115
#, c-format
msgid "emacsclient version '%d' too old (< 22)."
msgstr "Version des emacsclient '%d' ist zu alt (< 22)."
-#: builtin/help.c:131 builtin/help.c:159 builtin/help.c:168 builtin/help.c:176
+#: builtin/help.c:133 builtin/help.c:161 builtin/help.c:170 builtin/help.c:178
#, c-format
msgid "failed to exec '%s': %s"
msgstr "Fehler beim Ausführen von '%s': %s"
-#: builtin/help.c:216
+#: builtin/help.c:218
#, c-format
msgid ""
"'%s': path for unsupported man viewer.\n"
"'%s': Pfad für nicht unterstützten Handbuchbetrachter.\n"
"Sie könnten stattdessen 'man.<Werkzeug>.cmd' benutzen."
-#: builtin/help.c:228
+#: builtin/help.c:230
#, c-format
msgid ""
"'%s': cmd for supported man viewer.\n"
"'%s': Kommando für unterstützten Handbuchbetrachter.\n"
"Sie könnten stattdessen 'man.<Werkzeug>.path' benutzen."
-#: builtin/help.c:349
+#: builtin/help.c:351
#, c-format
msgid "'%s': unknown man viewer."
msgstr "'%s': unbekannter Handbuch-Betrachter."
-#: builtin/help.c:366
+#: builtin/help.c:368
msgid "no man viewer handled the request"
msgstr "kein Handbuch-Betrachter konnte mit dieser Anfrage umgehen"
-#: builtin/help.c:374
+#: builtin/help.c:376
msgid "no info viewer handled the request"
msgstr "kein Informations-Betrachter konnte mit dieser Anfrage umgehen"
-#: builtin/help.c:429 builtin/help.c:436
+#: builtin/help.c:422
+msgid "Defining attributes per path"
+msgstr "Definition von Attributen pro Pfad"
+
+#: builtin/help.c:423
+msgid "A Git glossary"
+msgstr "Ein Git-Glossar"
+
+#: builtin/help.c:424
+msgid "Specifies intentionally untracked files to ignore"
+msgstr "Spezifikation von bewusst ignorierten, unbeobachteten Dateien"
+
+#: builtin/help.c:425
+msgid "Defining submodule properties"
+msgstr "Definition von Unterprojekt-Eigenschaften"
+
+#: builtin/help.c:426
+msgid "Specifying revisions and ranges for Git"
+msgstr "Spezifikation von Revisionen und Bereichen für Git"
+
+#: builtin/help.c:427
+msgid "A tutorial introduction to Git (for version 1.5.1 or newer)"
+msgstr "Eine einführende Anleitung zu Git (für Version 1.5.1 oder neuer)"
+
+#: builtin/help.c:428
+msgid "An overview of recommended workflows with Git"
+msgstr "Eine Übersicht über empfohlene Arbeitsabläufe mit Git"
+
+#: builtin/help.c:440
+msgid "The common Git guides are:\n"
+msgstr "Die allgemein verwendeten Git-Anleitungen sind:\n"
+
+#: builtin/help.c:462 builtin/help.c:478
#, c-format
msgid "usage: %s%s"
msgstr "Verwendung: %s%s"
-#: builtin/help.c:452
+#: builtin/help.c:494
#, c-format
msgid "`git %s' is aliased to `%s'"
msgstr "für `git %s' wurde der Alias `%s' angelegt"
msgstr "Kann nicht auf Arbeitsbaum '%s' zugreifen."
#: builtin/log.c:40
-msgid "git log [<options>] [<since>..<until>] [[--] <path>...]\n"
-msgstr "git log [<Optionen>] [<seit>..<bis>] [[--] <Pfad>...]\n"
+msgid "git log [<options>] [<revision range>] [[--] <path>...]\n"
+msgstr "git log [<Optionen>] [Revisionsbereich>] [[--] <Pfad>...]\n"
#: builtin/log.c:41
msgid " or: git show [options] <object>..."
msgid "Unknown type: %d"
msgstr "Unbekannter Typ: %d"
-#: builtin/log.c:630
+#: builtin/log.c:638
msgid "format.headers without value"
msgstr "format.headers ohne Wert"
-#: builtin/log.c:704
+#: builtin/log.c:720
msgid "name of output directory is too long"
msgstr "Name des Ausgabeverzeichnisses ist zu lang."
-#: builtin/log.c:720
+#: builtin/log.c:736
#, c-format
msgid "Cannot open patch file %s"
msgstr "Kann Patch-Datei %s nicht öffnen"
-#: builtin/log.c:734
+#: builtin/log.c:750
msgid "Need exactly one range."
msgstr "Brauche genau einen Versionsbereich."
-#: builtin/log.c:742
+#: builtin/log.c:758
msgid "Not a range."
msgstr "Kein Versionsbereich."
-#: builtin/log.c:815
+#: builtin/log.c:860
msgid "Cover letter needs email format"
msgstr "Anschreiben benötigt E-Mail-Format"
-#: builtin/log.c:888
+#: builtin/log.c:936
#, c-format
msgid "insane in-reply-to: %s"
msgstr "ungültiges in-reply-to: %s"
-#: builtin/log.c:916
+#: builtin/log.c:964
msgid "git format-patch [options] [<since> | <revision range>]"
msgstr "git format-patch [Optionen] [<seit> | <Revisionsbereich>]"
-#: builtin/log.c:961
+#: builtin/log.c:1009
msgid "Two output directories?"
msgstr "Zwei Ausgabeverzeichnisse?"
-#: builtin/log.c:1099
+#: builtin/log.c:1108
msgid "use [PATCH n/m] even with a single patch"
msgstr "verwendet [PATCH n/m] auch mit einzelnem Patch"
-#: builtin/log.c:1102
+#: builtin/log.c:1111
msgid "use [PATCH] even with multiple patches"
msgstr "verwendet [PATCH] auch mit mehreren Patches"
-#: builtin/log.c:1106
+#: builtin/log.c:1115
msgid "print patches to standard out"
msgstr "Ausgabe der Patches in Standard-Ausgabe"
-#: builtin/log.c:1108
+#: builtin/log.c:1117
msgid "generate a cover letter"
msgstr "erzeugt ein Deckblatt"
-#: builtin/log.c:1110
+#: builtin/log.c:1119
msgid "use simple number sequence for output file names"
msgstr "verwendet einfache Nummernfolge für die Namen der Ausgabedateien"
-#: builtin/log.c:1111
+#: builtin/log.c:1120
msgid "sfx"
msgstr "Dateiendung"
-#: builtin/log.c:1112
+#: builtin/log.c:1121
msgid "use <sfx> instead of '.patch'"
msgstr "verwendet <Dateiendung> anstatt '.patch'"
-#: builtin/log.c:1114
+#: builtin/log.c:1123
msgid "start numbering patches at <n> instead of 1"
msgstr "beginnt die Nummerierung der Patches bei <n> anstatt bei 1"
-#: builtin/log.c:1116
+#: builtin/log.c:1125
msgid "mark the series as Nth re-roll"
msgstr "kennzeichnet die Serie als n-te Fassung"
-#: builtin/log.c:1118
+#: builtin/log.c:1127
msgid "Use [<prefix>] instead of [PATCH]"
msgstr "verwendet [<Prefix>] anstatt [PATCH]"
-#: builtin/log.c:1121
+#: builtin/log.c:1130
msgid "store resulting files in <dir>"
msgstr "speichert erzeugte Dateien in <Verzeichnis>"
-#: builtin/log.c:1124
+#: builtin/log.c:1133
msgid "don't strip/add [PATCH]"
msgstr "[PATCH] wird nicht entfernt/hinzugefügt"
-#: builtin/log.c:1127
+#: builtin/log.c:1136
msgid "don't output binary diffs"
msgstr "gibt keine binären Unterschiede aus"
-#: builtin/log.c:1129
+#: builtin/log.c:1138
msgid "don't include a patch matching a commit upstream"
msgstr ""
"schließt keine Patches ein, die einer Version im Übernahmezweig entsprechen"
-#: builtin/log.c:1131
+#: builtin/log.c:1140
msgid "show patch format instead of default (patch + stat)"
msgstr "zeigt Patchformat anstatt des Standards (Patch + Zusammenfassung)"
-#: builtin/log.c:1133
+#: builtin/log.c:1142
msgid "Messaging"
msgstr "Email-Einstellungen"
-#: builtin/log.c:1134
+#: builtin/log.c:1143
msgid "header"
msgstr "Header"
-#: builtin/log.c:1135
+#: builtin/log.c:1144
msgid "add email header"
msgstr "fügt Email-Header hinzu"
-#: builtin/log.c:1136 builtin/log.c:1138
+#: builtin/log.c:1145 builtin/log.c:1147
msgid "email"
msgstr "Email"
-#: builtin/log.c:1136
+#: builtin/log.c:1145
msgid "add To: header"
msgstr "fügt \"To:\"-Header hinzu"
-#: builtin/log.c:1138
+#: builtin/log.c:1147
msgid "add Cc: header"
msgstr "fügt \"Cc:\"-Header hinzu"
-#: builtin/log.c:1140
+#: builtin/log.c:1149
msgid "message-id"
msgstr "message-id"
-#: builtin/log.c:1141
+#: builtin/log.c:1150
msgid "make first mail a reply to <message-id>"
msgstr "macht aus erster Email eine Antwort zu <message-id>"
-#: builtin/log.c:1142 builtin/log.c:1145
+#: builtin/log.c:1151 builtin/log.c:1154
msgid "boundary"
msgstr "Grenze"
-#: builtin/log.c:1143
+#: builtin/log.c:1152
msgid "attach the patch"
msgstr "hängt einen Patch an"
-#: builtin/log.c:1146
+#: builtin/log.c:1155
msgid "inline the patch"
msgstr "fügt den Patch direkt in die Nachricht ein"
-#: builtin/log.c:1150
+#: builtin/log.c:1159
msgid "enable message threading, styles: shallow, deep"
msgstr "aktiviert Nachrichtenverkettung, Stile: shallow, deep"
-#: builtin/log.c:1152
+#: builtin/log.c:1161
msgid "signature"
msgstr "Signatur"
-#: builtin/log.c:1153
+#: builtin/log.c:1162
msgid "add a signature"
msgstr "fügt eine Signatur hinzu"
-#: builtin/log.c:1155
+#: builtin/log.c:1164
msgid "don't print the patch filenames"
msgstr "zeigt keine Dateinamen der Patches"
-#: builtin/log.c:1239
+#: builtin/log.c:1248
msgid "-n and -k are mutually exclusive."
msgstr "Die Optionen -n und -k schließen sich gegenseitig aus."
-#: builtin/log.c:1241
+#: builtin/log.c:1250
msgid "--subject-prefix and -k are mutually exclusive."
msgstr "Die Optionen --subject-prefix und -k schließen sich gegenseitig aus."
-#: builtin/log.c:1249
+#: builtin/log.c:1258
msgid "--name-only does not make sense"
msgstr "Die Option --name-only kann nicht verwendet werden."
-#: builtin/log.c:1251
+#: builtin/log.c:1260
msgid "--name-status does not make sense"
msgstr "Die Option --name-status kann nicht verwendet werden."
-#: builtin/log.c:1253
+#: builtin/log.c:1262
msgid "--check does not make sense"
msgstr "Die Option --check kann nicht verwendet werden."
-#: builtin/log.c:1276
+#: builtin/log.c:1285
msgid "standard output, or directory, which one?"
msgstr "Standard-Ausgabe oder Verzeichnis, welches von beidem?"
-#: builtin/log.c:1278
+#: builtin/log.c:1287
#, c-format
msgid "Could not create directory '%s'"
msgstr "Konnte Verzeichnis '%s' nicht erstellen."
-#: builtin/log.c:1431
+#: builtin/log.c:1435
msgid "Failed to create output files"
msgstr "Fehler beim Erstellen der Ausgabedateien."
-#: builtin/log.c:1480
+#: builtin/log.c:1484
msgid "git cherry [-v] [<upstream> [<head> [<limit>]]]"
msgstr "git cherry [-v] [<Übernahmezweig> [<Arbeitszweig> [<Limit>]]]"
-#: builtin/log.c:1535
+#: builtin/log.c:1539
#, c-format
msgid ""
"Could not find a tracked remote branch, please specify <upstream> manually.\n"
"Konnte gefolgten, externen Zweig nicht finden, bitte geben Sie <upstream> "
"manuell an.\n"
-#: builtin/log.c:1548 builtin/log.c:1550 builtin/log.c:1562
+#: builtin/log.c:1552 builtin/log.c:1554 builtin/log.c:1566
#, c-format
msgid "Unknown commit %s"
msgstr "Unbekannte Version %s"
-#: builtin/ls-files.c:409
+#: builtin/ls-files.c:402
msgid "git ls-files [options] [<file>...]"
msgstr "git ls-files [Optionen] [<Datei>...]"
-#: builtin/ls-files.c:466
+#: builtin/ls-files.c:459
msgid "identify the file status with tags"
msgstr "zeigt den Dateistatus mit Markierungen"
-#: builtin/ls-files.c:468
+#: builtin/ls-files.c:461
msgid "use lowercase letters for 'assume unchanged' files"
msgstr ""
"verwendet Kleinbuchstaben für Dateien mit 'assume unchanged' Markierung"
-#: builtin/ls-files.c:470
+#: builtin/ls-files.c:463
msgid "show cached files in the output (default)"
msgstr "zeigt zwischengespeicherten Dateien in der Ausgabe an (Standard)"
-#: builtin/ls-files.c:472
+#: builtin/ls-files.c:465
msgid "show deleted files in the output"
msgstr "zeigt entfernte Dateien in der Ausgabe an"
-#: builtin/ls-files.c:474
+#: builtin/ls-files.c:467
msgid "show modified files in the output"
msgstr "zeigt geänderte Dateien in der Ausgabe an"
-#: builtin/ls-files.c:476
+#: builtin/ls-files.c:469
msgid "show other files in the output"
msgstr "zeigt sonstige Dateien in der Ausgabe an"
-#: builtin/ls-files.c:478
+#: builtin/ls-files.c:471
msgid "show ignored files in the output"
msgstr "zeigt ignorierte Dateien in der Ausgabe an"
-#: builtin/ls-files.c:481
+#: builtin/ls-files.c:474
msgid "show staged contents' object name in the output"
msgstr "zeigt Objektnamen von Inhalten in der Bereitstellung in der Ausgabe an"
-#: builtin/ls-files.c:483
+#: builtin/ls-files.c:476
msgid "show files on the filesystem that need to be removed"
msgstr "zeigt Dateien im Dateisystem, die gelöscht werden müssen, an"
-#: builtin/ls-files.c:485
+#: builtin/ls-files.c:478
msgid "show 'other' directories' name only"
msgstr "zeigt nur Namen von 'sonstigen' Verzeichnissen an"
-#: builtin/ls-files.c:488
+#: builtin/ls-files.c:481
msgid "don't show empty directories"
msgstr "zeigt keine leeren Verzeichnisse an"
-#: builtin/ls-files.c:491
+#: builtin/ls-files.c:484
msgid "show unmerged files in the output"
msgstr "zeigt nicht zusammengeführte Dateien in der Ausgabe an"
-#: builtin/ls-files.c:493
+#: builtin/ls-files.c:486
msgid "show resolve-undo information"
msgstr "zeigt 'resolve-undo' Informationen an"
-#: builtin/ls-files.c:495
+#: builtin/ls-files.c:488
msgid "skip files matching pattern"
msgstr "lässt Dateien aus, die einem Muster entsprechen"
-#: builtin/ls-files.c:498
+#: builtin/ls-files.c:491
msgid "exclude patterns are read from <file>"
msgstr "schließt Muster, gelesen von <Datei>, aus"
-#: builtin/ls-files.c:501
+#: builtin/ls-files.c:494
msgid "read additional per-directory exclude patterns in <file>"
msgstr "liest zusätzliche pro-Verzeichnis Auschlussmuster aus <Datei>"
-#: builtin/ls-files.c:503
+#: builtin/ls-files.c:496
msgid "add the standard git exclusions"
msgstr "fügt die standardmäßigen Git-Ausschlüsse hinzu"
-#: builtin/ls-files.c:506
+#: builtin/ls-files.c:499
msgid "make the output relative to the project top directory"
msgstr "Ausgabe relativ zum Projektverzeichnis"
-#: builtin/ls-files.c:509
+#: builtin/ls-files.c:502
msgid "if any <file> is not in the index, treat this as an error"
msgstr ""
"behandle es als Fehler, wenn sich eine <Datei> nicht in der Bereitstellung "
"befindet"
-#: builtin/ls-files.c:510
+#: builtin/ls-files.c:503
msgid "tree-ish"
msgstr "Versionsreferenz"
-#: builtin/ls-files.c:511
+#: builtin/ls-files.c:504
msgid "pretend that paths removed since <tree-ish> are still present"
msgstr ""
"gibt vor, dass Pfade, die seit <Versionsreferenz> gelöscht wurden, immer "
"noch vorhanden sind"
-#: builtin/ls-files.c:513
+#: builtin/ls-files.c:506
msgid "show debugging data"
msgstr "zeigt Ausgaben zur Fehlersuche an"
#: builtin/merge.c:1265
#, c-format
msgid "Commit %s has an untrusted GPG signature, allegedly by %s."
-msgstr "Version %s hat eine nicht vertrauenswürdige GPG-Signatur, "
-"angeblich von %s."
+msgstr ""
+"Version %s hat eine nicht vertrauenswürdige GPG-Signatur, angeblich von %s."
#: builtin/merge.c:1268
#, c-format
"erster Verwendung aus."
#: builtin/revert.c:22
-msgid "git revert [options] <commit-ish>"
-msgstr "git revert [options] <Versionsangabe>"
+msgid "git revert [options] <commit-ish>..."
+msgstr "git revert [Optionen] <Versionsangabe>..."
#: builtin/revert.c:23
msgid "git revert <subcommand>"
msgstr "git revert <Unterkommando>"
#: builtin/revert.c:28
-msgid "git cherry-pick [options] <commit-ish>"
-msgstr "git cherry-pick [Optionen] <Versionsangabe>"
+msgid "git cherry-pick [options] <commit-ish>..."
+msgstr "git cherry-pick [Optionen] <Versionsangabe>..."
#: builtin/revert.c:29
msgid "git cherry-pick <subcommand>"
msgstr "git rm: konnte %s nicht löschen"
#: builtin/shortlog.c:13
-msgid "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
-msgstr ""
-"git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] "
-"[<Versionsidentifikation>... ]"
+msgid "git shortlog [<options>] [<revision range>] [[--] [<path>...]]"
+msgstr "git shortlog [<Optionen>] [Revisionsbereich] [[--] <Pfad>...]"
-#: builtin/shortlog.c:133
+#: builtin/shortlog.c:131
#, c-format
msgid "Missing author: %s"
msgstr "fehlender Autor: %s"
-#: builtin/shortlog.c:229
+#: builtin/shortlog.c:227
msgid "sort output according to the number of commits per author"
msgstr "sortiert die Ausgabe entsprechend der Anzahl von Versionen pro Autor"
-#: builtin/shortlog.c:231
+#: builtin/shortlog.c:229
msgid "Suppress commit descriptions, only provides commit count"
msgstr "Unterdrückt Versionsbeschreibungen, liefert nur Anzahl der Versionen"
-#: builtin/shortlog.c:233
+#: builtin/shortlog.c:231
msgid "Show the email address of each author"
msgstr "Zeigt die Email-Adresse von jedem Autor"
-#: builtin/shortlog.c:234
+#: builtin/shortlog.c:232
msgid "w[,i1[,i2]]"
msgstr "w[,i1[,i2]]"
-#: builtin/shortlog.c:235
+#: builtin/shortlog.c:233
msgid "Linewrap output"
msgstr "Ausgabe mit Zeilenumbrüchen"
msgid "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]"
msgstr "git show-branch (-g|--reflog)[=<n>[,<Basis>]] [--list] [<Referenz>]"
-#: builtin/show-branch.c:651
+#: builtin/show-branch.c:650
msgid "show remote-tracking and local branches"
msgstr "zeigt externe Übernahmezweige und lokale Zweige an"
-#: builtin/show-branch.c:653
+#: builtin/show-branch.c:652
msgid "show remote-tracking branches"
msgstr "zeigt externe Übernahmezweige an"
-#: builtin/show-branch.c:655
+#: builtin/show-branch.c:654
msgid "color '*!+-' corresponding to the branch"
msgstr "färbt '*!+-' entsprechend des Zweiges ein"
-#: builtin/show-branch.c:657
+#: builtin/show-branch.c:656
msgid "show <n> more commits after the common ancestor"
msgstr "zeigt <n> weitere Versionen nach dem gemeinsamen Vorfahren"
-#: builtin/show-branch.c:659
+#: builtin/show-branch.c:658
msgid "synonym to more=-1"
msgstr "Synonym für more=-1"
-#: builtin/show-branch.c:660
+#: builtin/show-branch.c:659
msgid "suppress naming strings"
msgstr "unterdrückt Namen"
-#: builtin/show-branch.c:662
+#: builtin/show-branch.c:661
msgid "include the current branch"
msgstr "bezieht den aktuellen Zweig ein"
-#: builtin/show-branch.c:664
+#: builtin/show-branch.c:663
msgid "name commits with their object names"
msgstr "benennt Versionen nach ihren Objektnamen"
-#: builtin/show-branch.c:666
+#: builtin/show-branch.c:665
msgid "show possible merge bases"
msgstr "zeigt mögliche Basen für Zusammenführung"
-#: builtin/show-branch.c:668
+#: builtin/show-branch.c:667
msgid "show refs unreachable from any other ref"
msgstr "zeigt Referenzen die unerreichbar von allen anderen Referenzen sind"
-#: builtin/show-branch.c:670
+#: builtin/show-branch.c:669
msgid "show commits in topological order"
msgstr "zeigt Versionen in topologischer Ordnung"
-#: builtin/show-branch.c:672
+#: builtin/show-branch.c:671
msgid "show only commits not on the first branch"
msgstr "zeigt nur Versionen, die sich nicht im ersten Zweig befinden"
-#: builtin/show-branch.c:674
+#: builtin/show-branch.c:673
msgid "show merges reachable from only one tip"
msgstr ""
"zeigt Zusammenführungen, die nur von einer Zweigspitze aus erreichbar sind"
-#: builtin/show-branch.c:676
+#: builtin/show-branch.c:675
msgid "show commits where no parent comes before its children"
msgstr "zeigt Versionen, wo kein Elternteil vor seinem Kind kommt"
-#: builtin/show-branch.c:678
+#: builtin/show-branch.c:677
msgid "<n>[,<base>]"
msgstr "<n>[,<Basis>]"
-#: builtin/show-branch.c:679
+#: builtin/show-branch.c:678
msgid "show <n> most recent ref-log entries starting at base"
msgstr ""
"zeigt die <n> jüngsten Einträge im Referenzprotokoll beginnend an der Basis"
msgstr "nur nützlich für Fehlersuche"
#: git.c:16
-msgid "See 'git help <command>' for more information on a specific command."
+msgid ""
+"'git help -a' and 'git help -g' lists available subcommands and some\n"
+"concept guides. See 'git help <command>' or 'git help <concept>'\n"
+"to read about a specific subcommand or concept."
msgstr ""
-"Siehe 'git help <Kommando>' für weitere Informationen zu einem spezifischen "
-"Kommando"
+"'git help -a' und 'git help -g' listet verfügbare Unterkommandos und\n"
+"einige Anleitungen zu Git-Konzepten auf. Benutzen Sie 'git help <Kommando>'\n"
+"oder 'git help <Konzept>' um mehr über ein spezifisches Kommando oder\n"
+"Konzept zu erfahren."
#: parse-options.h:156
msgid "no-op (backward compatibility)"
"angeben.\n"
"(Sie können dafür \"git bisect bad\" und \"git bisect good\" benutzen.)"
-#: git-bisect.sh:347 git-bisect.sh:474
+#: git-bisect.sh:363 git-bisect.sh:490
msgid "We are not bisecting."
msgstr "keine binäre Suche im Gange"
-#: git-bisect.sh:354
+#: git-bisect.sh:370
#, sh-format
msgid "'$invalid' is not a valid commit"
msgstr "'$invalid' ist keine gültige Version"
-#: git-bisect.sh:363
+#: git-bisect.sh:379
#, sh-format
msgid ""
"Could not check out original HEAD '$branch'.\n"
"Konnte die ursprüngliche Zweigspitze (HEAD) '$branch' nicht auschecken.\n"
"Versuchen Sie 'git bisect reset <Version>'."
-#: git-bisect.sh:390
+#: git-bisect.sh:406
msgid "No logfile given"
msgstr "Keine Log-Datei gegeben"
-#: git-bisect.sh:391
+#: git-bisect.sh:407
#, sh-format
msgid "cannot read $file for replaying"
msgstr "kann $file nicht für das Abspielen lesen"
-#: git-bisect.sh:408
+#: git-bisect.sh:424
msgid "?? what are you talking about?"
msgstr "?? Was reden Sie da?"
-#: git-bisect.sh:420
+#: git-bisect.sh:436
#, sh-format
msgid "running $command"
msgstr "führe $command aus"
-#: git-bisect.sh:427
+#: git-bisect.sh:443
#, sh-format
msgid ""
"bisect run failed:\n"
"'bisect run' fehlgeschlagen:\n"
"Rückkehrwert $res von '$command' ist < 0 oder >= 128"
-#: git-bisect.sh:453
+#: git-bisect.sh:469
msgid "bisect run cannot continue any more"
msgstr "'bisect run' kann nicht mehr fortgesetzt werden"
-#: git-bisect.sh:459
+#: git-bisect.sh:475
#, sh-format
msgid ""
"bisect run failed:\n"
"'bisect run' fehlgeschlagen:\n"
"'bisect_state $state' wurde mit Fehlerwert $res beendet"
-#: git-bisect.sh:466
+#: git-bisect.sh:482
msgid "bisect run success"
msgstr "'bisect run' erfolgreich ausgeführt"
#: git-submodule.sh:431
#, sh-format
msgid "Failed to register submodule '$sm_path'"
-msgstr "Fehler beim Eintragen von Unterprojekt '$sm_path' in die Konfiguration."
+msgstr ""
+"Fehler beim Eintragen von Unterprojekt '$sm_path' in die Konfiguration."
#: git-submodule.sh:474
#, sh-format
#: git-submodule.sh:541
#, sh-format
msgid "Failed to register url for submodule path '$sm_path'"
-msgstr "Fehler beim Eintragen der URL für Unterprojekt-Pfad '$sm_path' in die"
-" Konfiguration."
+msgstr ""
+"Fehler beim Eintragen der URL für Unterprojekt-Pfad '$sm_path' in die "
+"Konfiguration."
#: git-submodule.sh:543
#, sh-format
msgid "Submodule '$name' ($url) registered for path '$sm_path'"
-msgstr "Unterprojekt '$name' ($url) für Pfad '$sm_path' in die Konfiguration "
+msgstr ""
+"Unterprojekt '$name' ($url) für Pfad '$sm_path' in die Konfiguration "
"eingetragen"
#: git-submodule.sh:551
#, sh-format
msgid "Failed to register update mode for submodule path '$sm_path'"
-msgstr "Fehler bei Änderung des Aktualisierungsmodus für Unterprojekt-Pfad "
+msgstr ""
+"Fehler bei Änderung des Aktualisierungsmodus für Unterprojekt-Pfad "
"'$sm_path' in der Konfiguration."
#: git-submodule.sh:588
#, sh-format
msgid "Use '.' if you really want to deinitialize all submodules"
-msgstr "Verwenden Sie '.' wenn Sie wirklich alle Unterprojekte\n"
+msgstr ""
+"Verwenden Sie '.' wenn Sie wirklich alle Unterprojekte\n"
"deinitialisieren möchten."
#: git-submodule.sh:603
#, sh-format
msgid "Submodule work tree '$sm_path' contains a .git directory"
-msgstr "Arbeitsbaum des Unterprojekts in '$sm_path' enthält ein .git-Verzeichnis"
+msgstr ""
+"Arbeitsbaum des Unterprojekts in '$sm_path' enthält ein .git-Verzeichnis"
#: git-submodule.sh:604
#, sh-format
#: git-submodule.sh:617
#, sh-format
msgid "Could not create empty submodule directory '$sm_path'"
-msgstr "Konnte kein leeres Verzeichnis für Unterprojekt in '$sm_path' erstellen."
+msgstr ""
+"Konnte kein leeres Verzeichnis für Unterprojekt in '$sm_path' erstellen."
#: git-submodule.sh:626
#, sh-format
msgid "Submodule '$name' ($url) unregistered for path '$sm_path'"
-msgstr "Unterprojekt '$name' ($url) für Pfad '$sm_path' wurde aus der "
-"Konfiguration entfernt."
+msgstr ""
+"Unterprojekt '$name' ($url) für Pfad '$sm_path' wurde aus der Konfiguration "
+"entfernt."
#: git-submodule.sh:731
#, sh-format
#: git-submodule.sh:744
#, sh-format
msgid "Unable to find current revision in submodule path '$prefix$sm_path'"
-msgstr "Konnte aktuelle Revision in Unterprojekt-Pfad '$prefix$sm_path' nicht finden."
+msgstr ""
+"Konnte aktuelle Revision in Unterprojekt-Pfad '$prefix$sm_path' nicht finden."
#: git-submodule.sh:753
#, sh-format
#: git-submodule.sh:791
#, sh-format
msgid "Unable to rebase '$sha1' in submodule path '$prefix$sm_path'"
-msgstr "Neuaufbau von '$sha1' in Unterprojekt-Pfad '$prefix$sm_path' nicht möglich"
+msgstr ""
+"Neuaufbau von '$sha1' in Unterprojekt-Pfad '$prefix$sm_path' nicht möglich"
#: git-submodule.sh:792
#, sh-format
#, sh-format
msgid "Unable to merge '$sha1' in submodule path '$prefix$sm_path'"
msgstr ""
-"Zusammenführung von '$sha1' in Unterprojekt-Pfad '$prefix$sm_path' fehlgeschlagen"
+"Zusammenführung von '$sha1' in Unterprojekt-Pfad '$prefix$sm_path' "
+"fehlgeschlagen"
#: git-submodule.sh:798
#, sh-format
#: git-submodule.sh:803
#, sh-format
msgid "Unable to checkout '$sha1' in submodule path '$prefix$sm_path'"
-msgstr "Konnte '$sha1' in Unterprojekt-Pfad '$prefix$sm_path' nicht auschecken."
+msgstr ""
+"Konnte '$sha1' in Unterprojekt-Pfad '$prefix$sm_path' nicht auschecken."
#: git-submodule.sh:804
#, sh-format
msgid "Synchronizing submodule url for '$prefix$sm_path'"
msgstr "Synchronisiere Unterprojekt-URL für '$prefix$sm_path'"
+#~ msgid "It took %.2f seconds to enumerate untracked files. 'status -uno'"
+#~ msgstr ""
+#~ "Es dauerte %.2f Sekunden die unbeobachteten Dateien zu bestimmen.'status -"
+#~ "uno'"
+
+#~ msgid "may speed it up, but you have to be careful not to forget to add"
+#~ msgstr "könnte das beschleunigen, aber Sie müssen darauf achten, neue"
+
+#~ msgid "new files yourself (see 'git help status')."
+#~ msgstr "Dateien selbstständig hinzuzufügen (siehe 'git help status')."
+
+#~ msgid "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
+#~ msgstr ""
+#~ "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] "
+#~ "[<Versionsidentifikation>... ]"
+
+#~ msgid "See 'git help <command>' for more information on a specific command."
+#~ msgstr ""
+#~ "Siehe 'git help <Kommando>' für weitere Informationen zu einem "
+#~ "spezifischen Kommando"
+
#~ msgid "use any ref in .git/refs"
#~ msgstr "verwendet alle Referenzen in .git/refs"
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2013-04-10 15:16+0800\n"
+"POT-Creation-Date: 2013-04-30 08:25+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
msgid "archive format"
msgstr ""
-#: archive.c:324 builtin/log.c:1117
+#: archive.c:324 builtin/log.c:1126
msgid "prefix"
msgstr ""
msgid "prepend prefix to each pathname in the archive"
msgstr ""
-#: archive.c:326 builtin/archive.c:88 builtin/blame.c:2366
-#: builtin/blame.c:2367 builtin/config.c:55 builtin/fast-export.c:659
-#: builtin/fast-export.c:661 builtin/grep.c:715 builtin/hash-object.c:77
-#: builtin/ls-files.c:497 builtin/ls-files.c:500 builtin/notes.c:536
+#: archive.c:326 builtin/archive.c:88 builtin/blame.c:2371
+#: builtin/blame.c:2372 builtin/config.c:55 builtin/fast-export.c:665
+#: builtin/fast-export.c:667 builtin/grep.c:715 builtin/hash-object.c:77
+#: builtin/ls-files.c:490 builtin/ls-files.c:493 builtin/notes.c:536
#: builtin/notes.c:693 builtin/read-tree.c:107 parse-options.h:149
msgid "file"
msgstr ""
"Use '\\!' for literal leading exclamation."
msgstr ""
+#: branch.c:60
+#, c-format
+msgid "Not setting branch %s as its own upstream."
+msgstr ""
+
+#: branch.c:82
+#, c-format
+msgid "Branch %s set up to track remote branch %s from %s by rebasing."
+msgstr ""
+
+#: branch.c:83
+#, c-format
+msgid "Branch %s set up to track remote branch %s from %s."
+msgstr ""
+
+#: branch.c:87
+#, c-format
+msgid "Branch %s set up to track local branch %s by rebasing."
+msgstr ""
+
+#: branch.c:88
+#, c-format
+msgid "Branch %s set up to track local branch %s."
+msgstr ""
+
+#: branch.c:92
+#, c-format
+msgid "Branch %s set up to track remote ref %s by rebasing."
+msgstr ""
+
+#: branch.c:93
+#, c-format
+msgid "Branch %s set up to track remote ref %s."
+msgstr ""
+
+#: branch.c:97
+#, c-format
+msgid "Branch %s set up to track local ref %s by rebasing."
+msgstr ""
+
+#: branch.c:98
+#, c-format
+msgid "Branch %s set up to track local ref %s."
+msgstr ""
+
+#: branch.c:118
+#, c-format
+msgid "Tracking not set up: name too long: %s"
+msgstr ""
+
+#: branch.c:137
+#, c-format
+msgid "Not tracking: ambiguous information for ref %s"
+msgstr ""
+
+#: branch.c:182
+#, c-format
+msgid "'%s' is not a valid branch name."
+msgstr ""
+
+#: branch.c:187
+#, c-format
+msgid "A branch named '%s' already exists."
+msgstr ""
+
+#: branch.c:195
+msgid "Cannot force update the current branch."
+msgstr ""
+
#: branch.c:201
#, c-format
msgid "Cannot setup tracking information; starting point '%s' is not a branch."
"\"git push -u\" to set the upstream config as you push."
msgstr ""
+#: branch.c:250
+#, c-format
+msgid "Not a valid object name: '%s'."
+msgstr ""
+
+#: branch.c:270
+#, c-format
+msgid "Ambiguous object name: '%s'."
+msgstr ""
+
+#: branch.c:275
+#, c-format
+msgid "Not a valid branch point: '%s'."
+msgstr ""
+
+#: branch.c:281
+msgid "Failed to lock ref for update"
+msgstr ""
+
+#: branch.c:299
+msgid "Failed to write ref"
+msgstr ""
+
#: bundle.c:36
#, c-format
msgid "'%s' does not look like a v2 bundle file"
msgid "Repository lacks these prerequisite commits:"
msgstr ""
-#: bundle.c:164 sequencer.c:651 sequencer.c:1083 builtin/log.c:300
-#: builtin/log.c:754 builtin/log.c:1350 builtin/log.c:1566 builtin/merge.c:349
-#: builtin/shortlog.c:157
+#: bundle.c:164 sequencer.c:651 sequencer.c:1101 builtin/log.c:300
+#: builtin/log.c:770 builtin/log.c:1344 builtin/log.c:1570 builtin/merge.c:349
+#: builtin/shortlog.c:155
msgid "revision walk setup failed"
msgstr ""
msgid "rev-list died"
msgstr ""
-#: bundle.c:300 builtin/log.c:1246 builtin/shortlog.c:260
+#: bundle.c:300 builtin/log.c:1255 builtin/shortlog.c:258
#, c-format
msgid "unrecognized argument: %s"
msgstr ""
"%s"
msgstr ""
-#: diff.c:3480
+#: diff.c:3481
#, c-format
msgid ""
"Failed to parse --dirstat/-X option parameter:\n"
"%s"
msgstr ""
-#: diff.c:3494
+#: diff.c:3495
#, c-format
msgid "Failed to parse --submodule option parameter: '%s'"
msgstr ""
msgid "failed to read the cache"
msgstr ""
-#: merge.c:110 builtin/checkout.c:362 builtin/checkout.c:563
-#: builtin/clone.c:635
+#: merge.c:110 builtin/checkout.c:365 builtin/checkout.c:566
+#: builtin/clone.c:645
msgid "unable to write new index file"
msgstr ""
msgid "blob expected for %s '%s'"
msgstr ""
-#: merge-recursive.c:773 builtin/clone.c:303
+#: merge-recursive.c:773 builtin/clone.c:313
#, c-format
msgid "failed to open '%s'"
msgstr ""
msgid "Could not format %s."
msgstr ""
-#: sequencer.c:1101
+#: sequencer.c:1083
+#, c-format
+msgid "%s: can't cherry-pick a %s"
+msgstr ""
+
+#: sequencer.c:1085
+#, c-format
+msgid "%s: bad revision"
+msgstr ""
+
+#: sequencer.c:1119
msgid "Can't revert as initial commit"
msgstr ""
-#: sequencer.c:1102
+#: sequencer.c:1120
msgid "Can't cherry-pick into empty head"
msgstr ""
msgid "bug: unhandled diff status %c"
msgstr ""
-#: wt-status.c:805
+#: wt-status.c:803
msgid "You have unmerged paths."
msgstr ""
-#: wt-status.c:808 wt-status.c:960
+#: wt-status.c:806 wt-status.c:958
msgid " (fix conflicts and run \"git commit\")"
msgstr ""
-#: wt-status.c:811
+#: wt-status.c:809
msgid "All conflicts fixed but you are still merging."
msgstr ""
-#: wt-status.c:814
+#: wt-status.c:812
msgid " (use \"git commit\" to conclude merge)"
msgstr ""
-#: wt-status.c:824
+#: wt-status.c:822
msgid "You are in the middle of an am session."
msgstr ""
-#: wt-status.c:827
+#: wt-status.c:825
msgid "The current patch is empty."
msgstr ""
-#: wt-status.c:831
+#: wt-status.c:829
msgid " (fix conflicts and then run \"git am --resolved\")"
msgstr ""
-#: wt-status.c:833
+#: wt-status.c:831
msgid " (use \"git am --skip\" to skip this patch)"
msgstr ""
-#: wt-status.c:835
+#: wt-status.c:833
msgid " (use \"git am --abort\" to restore the original branch)"
msgstr ""
-#: wt-status.c:895 wt-status.c:912
+#: wt-status.c:893 wt-status.c:910
#, c-format
msgid "You are currently rebasing branch '%s' on '%s'."
msgstr ""
-#: wt-status.c:900 wt-status.c:917
+#: wt-status.c:898 wt-status.c:915
msgid "You are currently rebasing."
msgstr ""
-#: wt-status.c:903
+#: wt-status.c:901
msgid " (fix conflicts and then run \"git rebase --continue\")"
msgstr ""
-#: wt-status.c:905
+#: wt-status.c:903
msgid " (use \"git rebase --skip\" to skip this patch)"
msgstr ""
-#: wt-status.c:907
+#: wt-status.c:905
msgid " (use \"git rebase --abort\" to check out the original branch)"
msgstr ""
-#: wt-status.c:920
+#: wt-status.c:918
msgid " (all conflicts fixed: run \"git rebase --continue\")"
msgstr ""
-#: wt-status.c:924
+#: wt-status.c:922
#, c-format
msgid ""
"You are currently splitting a commit while rebasing branch '%s' on '%s'."
msgstr ""
-#: wt-status.c:929
+#: wt-status.c:927
msgid "You are currently splitting a commit during a rebase."
msgstr ""
-#: wt-status.c:932
+#: wt-status.c:930
msgid " (Once your working directory is clean, run \"git rebase --continue\")"
msgstr ""
-#: wt-status.c:936
+#: wt-status.c:934
#, c-format
msgid "You are currently editing a commit while rebasing branch '%s' on '%s'."
msgstr ""
-#: wt-status.c:941
+#: wt-status.c:939
msgid "You are currently editing a commit during a rebase."
msgstr ""
-#: wt-status.c:944
+#: wt-status.c:942
msgid " (use \"git commit --amend\" to amend the current commit)"
msgstr ""
-#: wt-status.c:946
+#: wt-status.c:944
msgid ""
" (use \"git rebase --continue\" once you are satisfied with your changes)"
msgstr ""
-#: wt-status.c:956
+#: wt-status.c:954
msgid "You are currently cherry-picking."
msgstr ""
-#: wt-status.c:963
+#: wt-status.c:961
msgid " (all conflicts fixed: run \"git commit\")"
msgstr ""
-#: wt-status.c:972
+#: wt-status.c:970
#, c-format
msgid "You are currently reverting commit %s."
msgstr ""
-#: wt-status.c:977
+#: wt-status.c:975
msgid " (fix conflicts and run \"git revert --continue\")"
msgstr ""
-#: wt-status.c:980
+#: wt-status.c:978
msgid " (all conflicts fixed: run \"git revert --continue\")"
msgstr ""
-#: wt-status.c:982
+#: wt-status.c:980
msgid " (use \"git revert --abort\" to cancel the revert operation)"
msgstr ""
-#: wt-status.c:993
+#: wt-status.c:991
#, c-format
msgid "You are currently bisecting, started from branch '%s'."
msgstr ""
-#: wt-status.c:997
+#: wt-status.c:995
msgid "You are currently bisecting."
msgstr ""
-#: wt-status.c:1000
+#: wt-status.c:998
msgid " (use \"git bisect reset\" to get back to the original branch)"
msgstr ""
-#: wt-status.c:1175
+#: wt-status.c:1173
msgid "On branch "
msgstr ""
-#: wt-status.c:1186
+#: wt-status.c:1184
msgid "HEAD detached at "
msgstr ""
-#: wt-status.c:1188
+#: wt-status.c:1186
msgid "HEAD detached from "
msgstr ""
-#: wt-status.c:1191
+#: wt-status.c:1189
msgid "Not currently on any branch."
msgstr ""
-#: wt-status.c:1208
+#: wt-status.c:1206
msgid "Initial commit"
msgstr ""
-#: wt-status.c:1222
+#: wt-status.c:1220
msgid "Untracked files"
msgstr ""
-#: wt-status.c:1224
+#: wt-status.c:1222
msgid "Ignored files"
msgstr ""
-#: wt-status.c:1228
+#: wt-status.c:1226
#, c-format
-msgid "It took %.2f seconds to enumerate untracked files. 'status -uno'"
+msgid ""
+"It took %.2f seconds to enumerate untracked files. 'status -uno'\n"
+"may speed it up, but you have to be careful not to forget to add\n"
+"new files yourself (see 'git help status')."
msgstr ""
#: wt-status.c:1232
-msgid "may speed it up, but you have to be careful not to forget to add"
-msgstr ""
-
-#: wt-status.c:1235
-msgid "new files yourself (see 'git help status')."
-msgstr ""
-
-#: wt-status.c:1238
#, c-format
msgid "Untracked files not listed%s"
msgstr ""
-#: wt-status.c:1240
+#: wt-status.c:1234
msgid " (use -u option to show untracked files)"
msgstr ""
-#: wt-status.c:1246
+#: wt-status.c:1240
msgid "No changes"
msgstr ""
-#: wt-status.c:1251
+#: wt-status.c:1245
#, c-format
msgid "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"
msgstr ""
-#: wt-status.c:1254
+#: wt-status.c:1248
#, c-format
msgid "no changes added to commit\n"
msgstr ""
-#: wt-status.c:1257
+#: wt-status.c:1251
#, c-format
msgid ""
"nothing added to commit but untracked files present (use \"git add\" to "
"track)\n"
msgstr ""
-#: wt-status.c:1260
+#: wt-status.c:1254
#, c-format
msgid "nothing added to commit but untracked files present\n"
msgstr ""
-#: wt-status.c:1263
+#: wt-status.c:1257
#, c-format
msgid "nothing to commit (create/copy files and use \"git add\" to track)\n"
msgstr ""
-#: wt-status.c:1266 wt-status.c:1271
+#: wt-status.c:1260 wt-status.c:1265
#, c-format
msgid "nothing to commit\n"
msgstr ""
-#: wt-status.c:1269
+#: wt-status.c:1263
#, c-format
msgid "nothing to commit (use -u to show untracked files)\n"
msgstr ""
-#: wt-status.c:1273
+#: wt-status.c:1267
#, c-format
msgid "nothing to commit, working directory clean\n"
msgstr ""
-#: wt-status.c:1381
+#: wt-status.c:1375
msgid "HEAD (no branch)"
msgstr ""
-#: wt-status.c:1387
+#: wt-status.c:1381
msgid "Initial commit on "
msgstr ""
-#: wt-status.c:1402
+#: wt-status.c:1396
msgid "behind "
msgstr ""
-#: wt-status.c:1405 wt-status.c:1408
+#: wt-status.c:1399 wt-status.c:1402
msgid "ahead "
msgstr ""
-#: wt-status.c:1410
+#: wt-status.c:1404
msgid ", behind "
msgstr ""
-#: compat/precompose_utf8.c:58 builtin/clone.c:342
+#: compat/precompose_utf8.c:58 builtin/clone.c:352
#, c-format
msgid "failed to unlink '%s'"
msgstr ""
msgid "git add [options] [--] <pathspec>..."
msgstr ""
-#: builtin/add.c:63
+#.
+#. * To be consistent with "git add -p" and most Git
+#. * commands, we should default to being tree-wide, but
+#. * this is not the original behavior and can't be
+#. * changed until users trained themselves not to type
+#. * "git add -u" or "git add -A". For now, we warn and
+#. * keep the old behavior. Later, the behavior can be changed
+#. * to tree-wide, keeping the warning for a while, and
+#. * eventually we can drop the warning.
+#.
+#: builtin/add.c:58
+#, c-format
+msgid ""
+"The behavior of 'git add %s (or %s)' with no path argument from a\n"
+"subdirectory of the tree will change in Git 2.0 and should not be used "
+"anymore.\n"
+"To add content for the whole tree, run:\n"
+"\n"
+" git add %s :/\n"
+" (or git add %s :/)\n"
+"\n"
+"To restrict the command to the current directory, run:\n"
+"\n"
+" git add %s .\n"
+" (or git add %s .)\n"
+"\n"
+"With the current Git version, the command is restricted to the current "
+"directory.\n"
+msgstr ""
+
+#: builtin/add.c:100
+#, c-format
+msgid ""
+"You ran 'git add' with neither '-A (--all)' or '--ignore-removal',\n"
+"whose behaviour will change in Git 2.0 with respect to paths you removed.\n"
+"Paths like '%s' that are\n"
+"removed from your working tree are ignored with this version of Git.\n"
+"\n"
+"* 'git add --ignore-removal <pathspec>', which is the current default,\n"
+" ignores paths you removed from your working tree.\n"
+"\n"
+"* 'git add --all <pathspec>' will let you also record the removals.\n"
+"\n"
+"Run 'git status' to check the paths you removed from your working tree.\n"
+msgstr ""
+
+#: builtin/add.c:144
#, c-format
msgid "unexpected diff status %c"
msgstr ""
-#: builtin/add.c:68 builtin/commit.c:233
+#: builtin/add.c:149 builtin/commit.c:233
msgid "updating files failed"
msgstr ""
-#: builtin/add.c:78
+#: builtin/add.c:163
#, c-format
msgid "remove '%s'\n"
msgstr ""
-#: builtin/add.c:148
+#: builtin/add.c:253
msgid "Unstaged changes after refreshing the index:"
msgstr ""
-#: builtin/add.c:151 builtin/add.c:460 builtin/rm.c:275
+#: builtin/add.c:256 builtin/add.c:572 builtin/rm.c:275
#, c-format
msgid "pathspec '%s' did not match any files"
msgstr ""
-#: builtin/add.c:234
+#: builtin/add.c:339
msgid "Could not read the index"
msgstr ""
-#: builtin/add.c:244
+#: builtin/add.c:349
#, c-format
msgid "Could not open '%s' for writing."
msgstr ""
-#: builtin/add.c:248
+#: builtin/add.c:353
msgid "Could not write patch"
msgstr ""
-#: builtin/add.c:253
+#: builtin/add.c:358
#, c-format
msgid "Could not stat '%s'"
msgstr ""
-#: builtin/add.c:255
+#: builtin/add.c:360
msgid "Empty patch. Aborted."
msgstr ""
-#: builtin/add.c:261
+#: builtin/add.c:366
#, c-format
msgid "Could not apply '%s'"
msgstr ""
-#: builtin/add.c:271
+#: builtin/add.c:376
msgid "The following paths are ignored by one of your .gitignore files:\n"
msgstr ""
-#: builtin/add.c:277 builtin/clean.c:161 builtin/fetch.c:78 builtin/mv.c:63
+#: builtin/add.c:393 builtin/clean.c:161 builtin/fetch.c:78 builtin/mv.c:63
#: builtin/prune-packed.c:76 builtin/push.c:425 builtin/remote.c:1253
#: builtin/rm.c:206
msgid "dry run"
msgstr ""
-#: builtin/add.c:278 builtin/apply.c:4409 builtin/check-ignore.c:19
+#: builtin/add.c:394 builtin/apply.c:4409 builtin/check-ignore.c:19
#: builtin/commit.c:1152 builtin/count-objects.c:95 builtin/fsck.c:613
-#: builtin/log.c:1514 builtin/mv.c:62 builtin/read-tree.c:112
+#: builtin/log.c:1518 builtin/mv.c:62 builtin/read-tree.c:112
msgid "be verbose"
msgstr ""
-#: builtin/add.c:280
+#: builtin/add.c:396
msgid "interactive picking"
msgstr ""
-#: builtin/add.c:281 builtin/checkout.c:1060 builtin/reset.c:258
+#: builtin/add.c:397 builtin/checkout.c:1063 builtin/reset.c:258
msgid "select hunks interactively"
msgstr ""
-#: builtin/add.c:282
+#: builtin/add.c:398
msgid "edit current diff and apply"
msgstr ""
-#: builtin/add.c:283
+#: builtin/add.c:399
msgid "allow adding otherwise ignored files"
msgstr ""
-#: builtin/add.c:284
+#: builtin/add.c:400
msgid "update tracked files"
msgstr ""
-#: builtin/add.c:285
+#: builtin/add.c:401
msgid "record only the fact that the path will be added later"
msgstr ""
-#: builtin/add.c:286
+#: builtin/add.c:402
msgid "add changes from all tracked and untracked files"
msgstr ""
-#: builtin/add.c:287
+#. takes no arguments
+#: builtin/add.c:405
+msgid "ignore paths removed in the working tree (same as --no-all)"
+msgstr ""
+
+#: builtin/add.c:407
msgid "don't add, only refresh the index"
msgstr ""
-#: builtin/add.c:288
+#: builtin/add.c:408
msgid "just skip files which cannot be added because of errors"
msgstr ""
-#: builtin/add.c:289
+#: builtin/add.c:409
msgid "check if - even missing - files are ignored in dry run"
msgstr ""
-#: builtin/add.c:311
+#: builtin/add.c:431
#, c-format
msgid "Use -f if you really want to add them.\n"
msgstr ""
-#: builtin/add.c:312
+#: builtin/add.c:432
msgid "no files added"
msgstr ""
-#: builtin/add.c:318
+#: builtin/add.c:438
msgid "adding files failed"
msgstr ""
-#.
-#. * To be consistent with "git add -p" and most Git
-#. * commands, we should default to being tree-wide, but
-#. * this is not the original behavior and can't be
-#. * changed until users trained themselves not to type
-#. * "git add -u" or "git add -A". For now, we warn and
-#. * keep the old behavior. Later, the behavior can be changed
-#. * to tree-wide, keeping the warning for a while, and
-#. * eventually we can drop the warning.
-#.
-#: builtin/add.c:335
-#, c-format
-msgid ""
-"The behavior of 'git add %s (or %s)' with no path argument from a\n"
-"subdirectory of the tree will change in Git 2.0 and should not be used "
-"anymore.\n"
-"To add content for the whole tree, run:\n"
-"\n"
-" git add %s :/\n"
-" (or git add %s :/)\n"
-"\n"
-"To restrict the command to the current directory, run:\n"
-"\n"
-" git add %s .\n"
-" (or git add %s .)\n"
-"\n"
-"With the current Git version, the command is restricted to the current "
-"directory."
-msgstr ""
-
-#: builtin/add.c:381
+#: builtin/add.c:477
msgid "-A and -u are mutually incompatible"
msgstr ""
-#: builtin/add.c:383
+#: builtin/add.c:495
msgid "Option --ignore-missing can only be used together with --dry-run"
msgstr ""
-#: builtin/add.c:414
+#: builtin/add.c:525
#, c-format
msgid "Nothing specified, nothing added.\n"
msgstr ""
-#: builtin/add.c:415
+#: builtin/add.c:526
#, c-format
msgid "Maybe you wanted to say 'git add .'?\n"
msgstr ""
-#: builtin/add.c:421 builtin/check-ignore.c:67 builtin/clean.c:204
+#: builtin/add.c:532 builtin/check-ignore.c:66 builtin/clean.c:204
#: builtin/commit.c:293 builtin/mv.c:82 builtin/rm.c:235
msgid "index file corrupt"
msgstr ""
-#: builtin/add.c:481 builtin/apply.c:4505 builtin/mv.c:229 builtin/rm.c:370
+#: builtin/add.c:604 builtin/apply.c:4505 builtin/mv.c:229 builtin/rm.c:370
msgid "Unable to write new index file"
msgstr ""
msgid "Checking patch %s..."
msgstr ""
-#: builtin/apply.c:3679 builtin/checkout.c:215 builtin/reset.c:124
+#: builtin/apply.c:3679 builtin/checkout.c:216 builtin/reset.c:124
#, c-format
msgid "make_cache_entry failed for path '%s'"
msgstr ""
msgid "build a temporary index based on embedded index information"
msgstr ""
-#: builtin/apply.c:4388 builtin/checkout-index.c:197 builtin/ls-files.c:463
+#: builtin/apply.c:4388 builtin/checkout-index.c:197 builtin/ls-files.c:456
msgid "paths are separated with NUL character"
msgstr ""
msgid "git blame [options] [rev-opts] [rev] [--] file"
msgstr ""
-#: builtin/blame.c:30 builtin/shortlog.c:15
+#: builtin/blame.c:30
msgid "[rev-opts] are documented in git-rev-list(1)"
msgstr ""
-#: builtin/blame.c:2350
+#: builtin/blame.c:2355
msgid "Show blame entries as we find them, incrementally"
msgstr ""
-#: builtin/blame.c:2351
+#: builtin/blame.c:2356
msgid "Show blank SHA-1 for boundary commits (Default: off)"
msgstr ""
-#: builtin/blame.c:2352
+#: builtin/blame.c:2357
msgid "Do not treat root commits as boundaries (Default: off)"
msgstr ""
-#: builtin/blame.c:2353
+#: builtin/blame.c:2358
msgid "Show work cost statistics"
msgstr ""
-#: builtin/blame.c:2354
+#: builtin/blame.c:2359
msgid "Show output score for blame entries"
msgstr ""
-#: builtin/blame.c:2355
+#: builtin/blame.c:2360
msgid "Show original filename (Default: auto)"
msgstr ""
-#: builtin/blame.c:2356
+#: builtin/blame.c:2361
msgid "Show original linenumber (Default: off)"
msgstr ""
-#: builtin/blame.c:2357
+#: builtin/blame.c:2362
msgid "Show in a format designed for machine consumption"
msgstr ""
-#: builtin/blame.c:2358
+#: builtin/blame.c:2363
msgid "Show porcelain format with per-line commit information"
msgstr ""
-#: builtin/blame.c:2359
+#: builtin/blame.c:2364
msgid "Use the same output mode as git-annotate (Default: off)"
msgstr ""
-#: builtin/blame.c:2360
+#: builtin/blame.c:2365
msgid "Show raw timestamp (Default: off)"
msgstr ""
-#: builtin/blame.c:2361
+#: builtin/blame.c:2366
msgid "Show long commit SHA1 (Default: off)"
msgstr ""
-#: builtin/blame.c:2362
+#: builtin/blame.c:2367
msgid "Suppress author name and timestamp (Default: off)"
msgstr ""
-#: builtin/blame.c:2363
+#: builtin/blame.c:2368
msgid "Show author email instead of name (Default: off)"
msgstr ""
-#: builtin/blame.c:2364
+#: builtin/blame.c:2369
msgid "Ignore whitespace differences"
msgstr ""
-#: builtin/blame.c:2365
+#: builtin/blame.c:2370
msgid "Spend extra cycles to find better match"
msgstr ""
-#: builtin/blame.c:2366
+#: builtin/blame.c:2371
msgid "Use revisions from <file> instead of calling git-rev-list"
msgstr ""
-#: builtin/blame.c:2367
+#: builtin/blame.c:2372
msgid "Use <file>'s contents as the final image"
msgstr ""
-#: builtin/blame.c:2368 builtin/blame.c:2369
+#: builtin/blame.c:2373 builtin/blame.c:2374
msgid "score"
msgstr ""
-#: builtin/blame.c:2368
+#: builtin/blame.c:2373
msgid "Find line copies within and across files"
msgstr ""
-#: builtin/blame.c:2369
+#: builtin/blame.c:2374
msgid "Find line movements within and across files"
msgstr ""
-#: builtin/blame.c:2370
+#: builtin/blame.c:2375
msgid "n,m"
msgstr ""
-#: builtin/blame.c:2370
+#: builtin/blame.c:2375
msgid "Process only line range n,m, counting from 1"
msgstr ""
msgid "git branch [options] (-m | -M) [<oldbranch>] <newbranch>"
msgstr ""
-#: builtin/branch.c:146
+#: builtin/branch.c:150
#, c-format
msgid ""
"deleting branch '%s' that has been merged to\n"
" '%s', but not yet merged to HEAD."
msgstr ""
-#: builtin/branch.c:150
+#: builtin/branch.c:154
#, c-format
msgid ""
"not deleting branch '%s' that is not yet merged to\n"
" '%s', even though it is merged to HEAD."
msgstr ""
-#: builtin/branch.c:164
+#: builtin/branch.c:168
#, c-format
msgid "Couldn't look up commit object for '%s'"
msgstr ""
-#: builtin/branch.c:168
+#: builtin/branch.c:172
#, c-format
msgid ""
"The branch '%s' is not fully merged.\n"
"If you are sure you want to delete it, run 'git branch -D %s'."
msgstr ""
-#: builtin/branch.c:181
+#: builtin/branch.c:185
msgid "Update of config-file failed"
msgstr ""
-#: builtin/branch.c:209
+#: builtin/branch.c:213
msgid "cannot use -a with -d"
msgstr ""
-#: builtin/branch.c:215
+#: builtin/branch.c:219
msgid "Couldn't look up commit object for HEAD"
msgstr ""
-#: builtin/branch.c:223
+#: builtin/branch.c:227
#, c-format
msgid "Cannot delete the branch '%s' which you are currently on."
msgstr ""
-#: builtin/branch.c:236
+#: builtin/branch.c:240
#, c-format
msgid "remote branch '%s' not found."
msgstr ""
-#: builtin/branch.c:237
+#: builtin/branch.c:241
#, c-format
msgid "branch '%s' not found."
msgstr ""
-#: builtin/branch.c:251
+#: builtin/branch.c:255
#, c-format
msgid "Error deleting remote branch '%s'"
msgstr ""
-#: builtin/branch.c:252
+#: builtin/branch.c:256
#, c-format
msgid "Error deleting branch '%s'"
msgstr ""
-#: builtin/branch.c:259
+#: builtin/branch.c:263
#, c-format
msgid "Deleted remote branch %s (was %s).\n"
msgstr ""
-#: builtin/branch.c:260
+#: builtin/branch.c:264
#, c-format
msgid "Deleted branch %s (was %s).\n"
msgstr ""
-#: builtin/branch.c:362
+#: builtin/branch.c:366
#, c-format
msgid "branch '%s' does not point at a commit"
msgstr ""
-#: builtin/branch.c:434
+#: builtin/branch.c:453
#, c-format
msgid "[%s: behind %d]"
msgstr ""
-#: builtin/branch.c:436
+#: builtin/branch.c:455
#, c-format
msgid "[behind %d]"
msgstr ""
-#: builtin/branch.c:440
+#: builtin/branch.c:459
#, c-format
msgid "[%s: ahead %d]"
msgstr ""
-#: builtin/branch.c:442
+#: builtin/branch.c:461
#, c-format
msgid "[ahead %d]"
msgstr ""
-#: builtin/branch.c:445
+#: builtin/branch.c:464
#, c-format
msgid "[%s: ahead %d, behind %d]"
msgstr ""
-#: builtin/branch.c:448
+#: builtin/branch.c:467
#, c-format
msgid "[ahead %d, behind %d]"
msgstr ""
-#: builtin/branch.c:470
+#: builtin/branch.c:490
msgid " **** invalid ref ****"
msgstr ""
-#: builtin/branch.c:562
+#: builtin/branch.c:582
#, c-format
msgid "(no branch, rebasing %s)"
msgstr ""
-#: builtin/branch.c:565
+#: builtin/branch.c:585
#, c-format
msgid "(no branch, bisect started on %s)"
msgstr ""
-#: builtin/branch.c:568
+#: builtin/branch.c:588
#, c-format
msgid "(detached from %s)"
msgstr ""
-#: builtin/branch.c:571
+#: builtin/branch.c:591
msgid "(no branch)"
msgstr ""
-#: builtin/branch.c:617
+#: builtin/branch.c:637
#, c-format
msgid "object '%s' does not point to a commit"
msgstr ""
-#: builtin/branch.c:649
+#: builtin/branch.c:669
msgid "some refs could not be read"
msgstr ""
-#: builtin/branch.c:662
+#: builtin/branch.c:682
msgid "cannot rename the current branch while not on any."
msgstr ""
-#: builtin/branch.c:672
+#: builtin/branch.c:692
#, c-format
msgid "Invalid branch name: '%s'"
msgstr ""
-#: builtin/branch.c:687
+#: builtin/branch.c:707
msgid "Branch rename failed"
msgstr ""
-#: builtin/branch.c:691
+#: builtin/branch.c:711
#, c-format
msgid "Renamed a misnamed branch '%s' away"
msgstr ""
-#: builtin/branch.c:695
+#: builtin/branch.c:715
#, c-format
msgid "Branch renamed to %s, but HEAD is not updated!"
msgstr ""
-#: builtin/branch.c:702
+#: builtin/branch.c:722
msgid "Branch is renamed, but update of config-file failed"
msgstr ""
-#: builtin/branch.c:717
+#: builtin/branch.c:737
#, c-format
msgid "malformed object name %s"
msgstr ""
-#: builtin/branch.c:741
+#: builtin/branch.c:761
#, c-format
msgid "could not write branch description template: %s"
msgstr ""
-#: builtin/branch.c:771
+#: builtin/branch.c:791
msgid "Generic options"
msgstr ""
-#: builtin/branch.c:773
+#: builtin/branch.c:793
msgid "show hash and subject, give twice for upstream branch"
msgstr ""
-#: builtin/branch.c:774
+#: builtin/branch.c:794
msgid "suppress informational messages"
msgstr ""
-#: builtin/branch.c:775
+#: builtin/branch.c:795
msgid "set up tracking mode (see git-pull(1))"
msgstr ""
-#: builtin/branch.c:777
+#: builtin/branch.c:797
msgid "change upstream info"
msgstr ""
-#: builtin/branch.c:781
+#: builtin/branch.c:801
msgid "use colored output"
msgstr ""
-#: builtin/branch.c:782
+#: builtin/branch.c:802
msgid "act on remote-tracking branches"
msgstr ""
-#: builtin/branch.c:785 builtin/branch.c:791 builtin/branch.c:812
-#: builtin/branch.c:818 builtin/commit.c:1368 builtin/commit.c:1369
+#: builtin/branch.c:805 builtin/branch.c:811 builtin/branch.c:832
+#: builtin/branch.c:838 builtin/commit.c:1368 builtin/commit.c:1369
#: builtin/commit.c:1370 builtin/commit.c:1371 builtin/tag.c:468
msgid "commit"
msgstr ""
-#: builtin/branch.c:786 builtin/branch.c:792
+#: builtin/branch.c:806 builtin/branch.c:812
msgid "print only branches that contain the commit"
msgstr ""
-#: builtin/branch.c:798
+#: builtin/branch.c:818
msgid "Specific git-branch actions:"
msgstr ""
-#: builtin/branch.c:799
+#: builtin/branch.c:819
msgid "list both remote-tracking and local branches"
msgstr ""
-#: builtin/branch.c:801
+#: builtin/branch.c:821
msgid "delete fully merged branch"
msgstr ""
-#: builtin/branch.c:802
+#: builtin/branch.c:822
msgid "delete branch (even if not merged)"
msgstr ""
-#: builtin/branch.c:803
+#: builtin/branch.c:823
msgid "move/rename a branch and its reflog"
msgstr ""
-#: builtin/branch.c:804
+#: builtin/branch.c:824
msgid "move/rename a branch, even if target exists"
msgstr ""
-#: builtin/branch.c:805
+#: builtin/branch.c:825
msgid "list branch names"
msgstr ""
-#: builtin/branch.c:806
+#: builtin/branch.c:826
msgid "create the branch's reflog"
msgstr ""
-#: builtin/branch.c:808
+#: builtin/branch.c:828
msgid "edit the description for the branch"
msgstr ""
-#: builtin/branch.c:809
+#: builtin/branch.c:829
msgid "force creation (when already exists)"
msgstr ""
-#: builtin/branch.c:812
+#: builtin/branch.c:832
msgid "print only not merged branches"
msgstr ""
-#: builtin/branch.c:818
+#: builtin/branch.c:838
msgid "print only merged branches"
msgstr ""
-#: builtin/branch.c:822
+#: builtin/branch.c:842
msgid "list branches in columns"
msgstr ""
-#: builtin/branch.c:835
+#: builtin/branch.c:855
msgid "Failed to resolve HEAD as a valid ref."
msgstr ""
-#: builtin/branch.c:840 builtin/clone.c:609
+#: builtin/branch.c:860 builtin/clone.c:619
msgid "HEAD not found below refs/heads!"
msgstr ""
-#: builtin/branch.c:863
+#: builtin/branch.c:883
msgid "--column and --verbose are incompatible"
msgstr ""
-#: builtin/branch.c:869 builtin/branch.c:908
+#: builtin/branch.c:889 builtin/branch.c:928
msgid "branch name required"
msgstr ""
-#: builtin/branch.c:884
+#: builtin/branch.c:904
msgid "Cannot give description to detached HEAD"
msgstr ""
-#: builtin/branch.c:889
+#: builtin/branch.c:909
msgid "cannot edit description of more than one branch"
msgstr ""
-#: builtin/branch.c:896
+#: builtin/branch.c:916
#, c-format
msgid "No commit on branch '%s' yet."
msgstr ""
-#: builtin/branch.c:899
+#: builtin/branch.c:919
#, c-format
msgid "No branch named '%s'."
msgstr ""
-#: builtin/branch.c:914
+#: builtin/branch.c:934
msgid "too many branches for a rename operation"
msgstr ""
-#: builtin/branch.c:919
+#: builtin/branch.c:939
msgid "too many branches to set new upstream"
msgstr ""
-#: builtin/branch.c:923
+#: builtin/branch.c:943
#, c-format
msgid ""
"could not set upstream of HEAD to %s when it does not point to any branch."
msgstr ""
-#: builtin/branch.c:926 builtin/branch.c:948 builtin/branch.c:970
+#: builtin/branch.c:946 builtin/branch.c:968 builtin/branch.c:990
#, c-format
msgid "no such branch '%s'"
msgstr ""
-#: builtin/branch.c:930
+#: builtin/branch.c:950
#, c-format
msgid "branch '%s' does not exist"
msgstr ""
-#: builtin/branch.c:942
+#: builtin/branch.c:962
msgid "too many branches to unset upstream"
msgstr ""
-#: builtin/branch.c:946
+#: builtin/branch.c:966
msgid "could not unset upstream of HEAD when it does not point to any branch."
msgstr ""
-#: builtin/branch.c:952
+#: builtin/branch.c:972
#, c-format
msgid "Branch '%s' has no upstream information"
msgstr ""
-#: builtin/branch.c:967
+#: builtin/branch.c:987
msgid "it does not make sense to create 'HEAD' manually"
msgstr ""
-#: builtin/branch.c:973
+#: builtin/branch.c:993
msgid "-a and -r options to 'git branch' do not make sense with a branch name"
msgstr ""
-#: builtin/branch.c:976
+#: builtin/branch.c:996
#, c-format
msgid ""
"The --set-upstream flag is deprecated and will be removed. Consider using --"
"track or --set-upstream-to\n"
msgstr ""
-#: builtin/branch.c:993
+#: builtin/branch.c:1013
#, c-format
msgid ""
"\n"
"\n"
msgstr ""
-#: builtin/branch.c:994
+#: builtin/branch.c:1014
#, c-format
msgid " git branch -d %s\n"
msgstr ""
-#: builtin/branch.c:995
+#: builtin/branch.c:1015
#, c-format
msgid " git branch --set-upstream-to %s\n"
msgstr ""
msgid "Need a repository to unbundle."
msgstr ""
-#: builtin/cat-file.c:247
+#: builtin/cat-file.c:176
msgid "git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>"
msgstr ""
-#: builtin/cat-file.c:248
+#: builtin/cat-file.c:177
msgid "git cat-file (--batch|--batch-check) < <list_of_objects>"
msgstr ""
-#: builtin/cat-file.c:266
+#: builtin/cat-file.c:195
msgid "<type> can be one of: blob, tree, commit, tag"
msgstr ""
-#: builtin/cat-file.c:267
+#: builtin/cat-file.c:196
msgid "show object type"
msgstr ""
-#: builtin/cat-file.c:268
+#: builtin/cat-file.c:197
msgid "show object size"
msgstr ""
-#: builtin/cat-file.c:270
+#: builtin/cat-file.c:199
msgid "exit with zero when there's no error"
msgstr ""
-#: builtin/cat-file.c:271
+#: builtin/cat-file.c:200
msgid "pretty-print object's content"
msgstr ""
-#: builtin/cat-file.c:273
+#: builtin/cat-file.c:202
msgid "for blob objects, run textconv on object's content"
msgstr ""
-#: builtin/cat-file.c:275
+#: builtin/cat-file.c:204
msgid "show info and content of objects fed from the standard input"
msgstr ""
-#: builtin/cat-file.c:278
+#: builtin/cat-file.c:207
msgid "show info about objects fed from the standard input"
msgstr ""
msgid "input paths are terminated by a null character"
msgstr ""
-#: builtin/check-ignore.c:18 builtin/checkout.c:1041 builtin/gc.c:177
+#: builtin/check-ignore.c:18 builtin/checkout.c:1044 builtin/gc.c:177
msgid "suppress progress reporting"
msgstr ""
-#: builtin/check-ignore.c:151
+#: builtin/check-ignore.c:146
msgid "cannot specify pathnames with --stdin"
msgstr ""
-#: builtin/check-ignore.c:154
+#: builtin/check-ignore.c:149
msgid "-z only makes sense with --stdin"
msgstr ""
-#: builtin/check-ignore.c:156
+#: builtin/check-ignore.c:151
msgid "no path specified"
msgstr ""
-#: builtin/check-ignore.c:160
+#: builtin/check-ignore.c:155
msgid "--quiet is only valid with a single pathname"
msgstr ""
-#: builtin/check-ignore.c:162
+#: builtin/check-ignore.c:157
msgid "cannot have both --quiet and --verbose"
msgstr ""
msgid "git checkout [options] [<branch>] -- <file>..."
msgstr ""
-#: builtin/checkout.c:116 builtin/checkout.c:149
+#: builtin/checkout.c:117 builtin/checkout.c:150
#, c-format
msgid "path '%s' does not have our version"
msgstr ""
-#: builtin/checkout.c:118 builtin/checkout.c:151
+#: builtin/checkout.c:119 builtin/checkout.c:152
#, c-format
msgid "path '%s' does not have their version"
msgstr ""
-#: builtin/checkout.c:134
+#: builtin/checkout.c:135
#, c-format
msgid "path '%s' does not have all necessary versions"
msgstr ""
-#: builtin/checkout.c:178
+#: builtin/checkout.c:179
#, c-format
msgid "path '%s' does not have necessary versions"
msgstr ""
-#: builtin/checkout.c:195
+#: builtin/checkout.c:196
#, c-format
msgid "path '%s': cannot merge"
msgstr ""
-#: builtin/checkout.c:212
+#: builtin/checkout.c:213
#, c-format
msgid "Unable to add merge result for '%s'"
msgstr ""
-#: builtin/checkout.c:236 builtin/checkout.c:239 builtin/checkout.c:242
-#: builtin/checkout.c:245
+#: builtin/checkout.c:237 builtin/checkout.c:240 builtin/checkout.c:243
+#: builtin/checkout.c:246
#, c-format
msgid "'%s' cannot be used with updating paths"
msgstr ""
-#: builtin/checkout.c:248 builtin/checkout.c:251
+#: builtin/checkout.c:249 builtin/checkout.c:252
#, c-format
msgid "'%s' cannot be used with %s"
msgstr ""
-#: builtin/checkout.c:254
+#: builtin/checkout.c:255
#, c-format
msgid "Cannot update paths and switch to branch '%s' at the same time."
msgstr ""
-#: builtin/checkout.c:265 builtin/checkout.c:455
+#: builtin/checkout.c:266 builtin/checkout.c:458
msgid "corrupt index file"
msgstr ""
-#: builtin/checkout.c:326 builtin/checkout.c:333
+#: builtin/checkout.c:329 builtin/checkout.c:336
#, c-format
msgid "path '%s' is unmerged"
msgstr ""
-#: builtin/checkout.c:477
+#: builtin/checkout.c:480
msgid "you need to resolve your current index first"
msgstr ""
-#: builtin/checkout.c:598
+#: builtin/checkout.c:601
#, c-format
msgid "Can not do reflog for '%s'\n"
msgstr ""
-#: builtin/checkout.c:631
+#: builtin/checkout.c:634
msgid "HEAD is now at"
msgstr ""
-#: builtin/checkout.c:638
+#: builtin/checkout.c:641
#, c-format
msgid "Reset branch '%s'\n"
msgstr ""
-#: builtin/checkout.c:641
+#: builtin/checkout.c:644
#, c-format
msgid "Already on '%s'\n"
msgstr ""
-#: builtin/checkout.c:645
+#: builtin/checkout.c:648
#, c-format
msgid "Switched to and reset branch '%s'\n"
msgstr ""
-#: builtin/checkout.c:647 builtin/checkout.c:984
+#: builtin/checkout.c:650 builtin/checkout.c:987
#, c-format
msgid "Switched to a new branch '%s'\n"
msgstr ""
-#: builtin/checkout.c:649
+#: builtin/checkout.c:652
#, c-format
msgid "Switched to branch '%s'\n"
msgstr ""
-#: builtin/checkout.c:705
+#: builtin/checkout.c:708
#, c-format
msgid " ... and %d more.\n"
msgstr ""
#. The singular version
-#: builtin/checkout.c:711
+#: builtin/checkout.c:714
#, c-format
msgid ""
"Warning: you are leaving %d commit behind, not connected to\n"
msgstr[0] ""
msgstr[1] ""
-#: builtin/checkout.c:729
+#: builtin/checkout.c:732
#, c-format
msgid ""
"If you want to keep them by creating a new branch, this may be a good time\n"
"\n"
msgstr ""
-#: builtin/checkout.c:759
+#: builtin/checkout.c:762
msgid "internal error in revision walk"
msgstr ""
-#: builtin/checkout.c:763
+#: builtin/checkout.c:766
msgid "Previous HEAD position was"
msgstr ""
-#: builtin/checkout.c:790 builtin/checkout.c:979
+#: builtin/checkout.c:793 builtin/checkout.c:982
msgid "You are on a branch yet to be born"
msgstr ""
#. case (1)
-#: builtin/checkout.c:915
+#: builtin/checkout.c:918
#, c-format
msgid "invalid reference: %s"
msgstr ""
#. case (1): want a tree
-#: builtin/checkout.c:954
+#: builtin/checkout.c:957
#, c-format
msgid "reference is not a tree: %s"
msgstr ""
-#: builtin/checkout.c:993
+#: builtin/checkout.c:996
msgid "paths cannot be used with switching branches"
msgstr ""
-#: builtin/checkout.c:996 builtin/checkout.c:1000
+#: builtin/checkout.c:999 builtin/checkout.c:1003
#, c-format
msgid "'%s' cannot be used with switching branches"
msgstr ""
-#: builtin/checkout.c:1004 builtin/checkout.c:1007 builtin/checkout.c:1012
-#: builtin/checkout.c:1015
+#: builtin/checkout.c:1007 builtin/checkout.c:1010 builtin/checkout.c:1015
+#: builtin/checkout.c:1018
#, c-format
msgid "'%s' cannot be used with '%s'"
msgstr ""
-#: builtin/checkout.c:1020
+#: builtin/checkout.c:1023
#, c-format
msgid "Cannot switch branch to a non-commit '%s'"
msgstr ""
-#: builtin/checkout.c:1042 builtin/checkout.c:1044 builtin/clone.c:90
+#: builtin/checkout.c:1045 builtin/checkout.c:1047 builtin/clone.c:90
#: builtin/remote.c:169 builtin/remote.c:171
msgid "branch"
msgstr ""
-#: builtin/checkout.c:1043
+#: builtin/checkout.c:1046
msgid "create and checkout a new branch"
msgstr ""
-#: builtin/checkout.c:1045
+#: builtin/checkout.c:1048
msgid "create/reset and checkout a branch"
msgstr ""
-#: builtin/checkout.c:1046
+#: builtin/checkout.c:1049
msgid "create reflog for new branch"
msgstr ""
-#: builtin/checkout.c:1047
+#: builtin/checkout.c:1050
msgid "detach the HEAD at named commit"
msgstr ""
-#: builtin/checkout.c:1048
+#: builtin/checkout.c:1051
msgid "set upstream info for new branch"
msgstr ""
-#: builtin/checkout.c:1050
+#: builtin/checkout.c:1053
msgid "new branch"
msgstr ""
-#: builtin/checkout.c:1050
+#: builtin/checkout.c:1053
msgid "new unparented branch"
msgstr ""
-#: builtin/checkout.c:1051
+#: builtin/checkout.c:1054
msgid "checkout our version for unmerged files"
msgstr ""
-#: builtin/checkout.c:1053
+#: builtin/checkout.c:1056
msgid "checkout their version for unmerged files"
msgstr ""
-#: builtin/checkout.c:1055
+#: builtin/checkout.c:1058
msgid "force checkout (throw away local modifications)"
msgstr ""
-#: builtin/checkout.c:1056
+#: builtin/checkout.c:1059
msgid "perform a 3-way merge with the new branch"
msgstr ""
-#: builtin/checkout.c:1057 builtin/merge.c:217
+#: builtin/checkout.c:1060 builtin/merge.c:217
msgid "update ignored files (default)"
msgstr ""
-#: builtin/checkout.c:1058 builtin/log.c:1149 parse-options.h:245
+#: builtin/checkout.c:1061 builtin/log.c:1158 parse-options.h:245
msgid "style"
msgstr ""
-#: builtin/checkout.c:1059
+#: builtin/checkout.c:1062
msgid "conflict style (merge or diff3)"
msgstr ""
-#: builtin/checkout.c:1062
+#: builtin/checkout.c:1065
+msgid "do not limit pathspecs to sparse entries only"
+msgstr ""
+
+#: builtin/checkout.c:1067
msgid "second guess 'git checkout no-such-branch'"
msgstr ""
-#: builtin/checkout.c:1086
+#: builtin/checkout.c:1091
msgid "-b, -B and --orphan are mutually exclusive"
msgstr ""
-#: builtin/checkout.c:1103
+#: builtin/checkout.c:1108
msgid "--track needs a branch name"
msgstr ""
-#: builtin/checkout.c:1110
+#: builtin/checkout.c:1115
msgid "Missing branch name; try -b"
msgstr ""
-#: builtin/checkout.c:1145
+#: builtin/checkout.c:1150
msgid "invalid path specification"
msgstr ""
-#: builtin/checkout.c:1152
+#: builtin/checkout.c:1157
#, c-format
msgid ""
"Cannot update paths and switch to branch '%s' at the same time.\n"
"Did you intend to checkout '%s' which can not be resolved as commit?"
msgstr ""
-#: builtin/checkout.c:1157
+#: builtin/checkout.c:1162
#, c-format
msgid "git checkout: --detach does not take a path argument '%s'"
msgstr ""
-#: builtin/checkout.c:1161
+#: builtin/checkout.c:1166
msgid ""
"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
"checking out of the index."
msgstr ""
#: builtin/clean.c:165 builtin/describe.c:412 builtin/grep.c:717
-#: builtin/ls-files.c:494 builtin/name-rev.c:231 builtin/show-ref.c:182
+#: builtin/ls-files.c:487 builtin/name-rev.c:231 builtin/show-ref.c:182
msgid "pattern"
msgstr ""
msgid "set config inside the new repository"
msgstr ""
-#: builtin/clone.c:244
+#: builtin/clone.c:254
#, c-format
-msgid "reference repository '%s' is not a local directory."
+msgid "reference repository '%s' is not a local repository."
msgstr ""
-#: builtin/clone.c:307
+#: builtin/clone.c:317
#, c-format
msgid "failed to create directory '%s'"
msgstr ""
-#: builtin/clone.c:309 builtin/diff.c:77
+#: builtin/clone.c:319 builtin/diff.c:77
#, c-format
msgid "failed to stat '%s'"
msgstr ""
-#: builtin/clone.c:311
+#: builtin/clone.c:321
#, c-format
msgid "%s exists and is not a directory"
msgstr ""
-#: builtin/clone.c:325
+#: builtin/clone.c:335
#, c-format
msgid "failed to stat %s\n"
msgstr ""
-#: builtin/clone.c:347
+#: builtin/clone.c:357
#, c-format
msgid "failed to create link '%s'"
msgstr ""
-#: builtin/clone.c:351
+#: builtin/clone.c:361
#, c-format
msgid "failed to copy file to '%s'"
msgstr ""
-#: builtin/clone.c:374
+#: builtin/clone.c:384
#, c-format
msgid "done.\n"
msgstr ""
-#: builtin/clone.c:387
+#: builtin/clone.c:397
msgid ""
"Clone succeeded, but checkout failed.\n"
"You can inspect what was checked out with 'git status'\n"
"and retry the checkout with 'git checkout -f HEAD'\n"
msgstr ""
-#: builtin/clone.c:466
+#: builtin/clone.c:476
#, c-format
msgid "Could not find remote branch %s to clone."
msgstr ""
-#: builtin/clone.c:540
+#: builtin/clone.c:550
msgid "remote did not send all necessary objects"
msgstr ""
-#: builtin/clone.c:600
+#: builtin/clone.c:610
msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
msgstr ""
-#: builtin/clone.c:631
+#: builtin/clone.c:641
msgid "unable to checkout working tree"
msgstr ""
-#: builtin/clone.c:739
+#: builtin/clone.c:749
msgid "Too many arguments."
msgstr ""
-#: builtin/clone.c:743
+#: builtin/clone.c:753
msgid "You must specify a repository to clone."
msgstr ""
-#: builtin/clone.c:754
+#: builtin/clone.c:764
#, c-format
msgid "--bare and --origin %s options are incompatible."
msgstr ""
-#: builtin/clone.c:757
+#: builtin/clone.c:767
msgid "--bare and --separate-git-dir are incompatible."
msgstr ""
-#: builtin/clone.c:770
+#: builtin/clone.c:780
#, c-format
msgid "repository '%s' does not exist"
msgstr ""
-#: builtin/clone.c:775
+#: builtin/clone.c:785
msgid "--depth is ignored in local clones; use file:// instead."
msgstr ""
-#: builtin/clone.c:785
+#: builtin/clone.c:795
#, c-format
msgid "destination path '%s' already exists and is not an empty directory."
msgstr ""
-#: builtin/clone.c:795
+#: builtin/clone.c:805
#, c-format
msgid "working tree '%s' already exists."
msgstr ""
-#: builtin/clone.c:808 builtin/clone.c:820
+#: builtin/clone.c:818 builtin/clone.c:830
#, c-format
msgid "could not create leading directories of '%s'"
msgstr ""
-#: builtin/clone.c:811
+#: builtin/clone.c:821
#, c-format
msgid "could not create work tree dir '%s'."
msgstr ""
-#: builtin/clone.c:830
+#: builtin/clone.c:840
#, c-format
msgid "Cloning into bare repository '%s'...\n"
msgstr ""
-#: builtin/clone.c:832
+#: builtin/clone.c:842
#, c-format
msgid "Cloning into '%s'...\n"
msgstr ""
-#: builtin/clone.c:867
+#: builtin/clone.c:877
#, c-format
msgid "Don't know how to clone %s"
msgstr ""
-#: builtin/clone.c:916
+#: builtin/clone.c:926
#, c-format
msgid "Remote branch %s not found in upstream %s"
msgstr ""
-#: builtin/clone.c:923
+#: builtin/clone.c:933
msgid "You appear to have cloned an empty repository."
msgstr ""
msgid "could not lookup commit %s"
msgstr ""
-#: builtin/commit.c:612 builtin/shortlog.c:272
+#: builtin/commit.c:612 builtin/shortlog.c:270
#, c-format
msgid "(reading log message from standard input)\n"
msgstr ""
msgid "terminate entries with NUL"
msgstr ""
-#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/fast-export.c:653
-#: builtin/fast-export.c:656 builtin/tag.c:459
+#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/fast-export.c:659
+#: builtin/fast-export.c:662 builtin/tag.c:459
msgid "mode"
msgstr ""
msgid "the commit is authored by me now (used with -C/-c/--amend)"
msgstr ""
-#: builtin/commit.c:1373 builtin/log.c:1104 builtin/revert.c:109
+#: builtin/commit.c:1373 builtin/log.c:1113 builtin/revert.c:109
msgid "add Signed-off-by:"
msgstr ""
msgstr ""
#: builtin/count-objects.c:82
-msgid "git count-objects [-v]"
+msgid "git count-objects [-v] [-H | --human-readable]"
+msgstr ""
+
+#: builtin/count-objects.c:97
+msgid "print sizes in human readable format"
msgstr ""
#: builtin/describe.c:15
msgid "git fast-export [rev-list-opts]"
msgstr ""
-#: builtin/fast-export.c:652
+#: builtin/fast-export.c:658
msgid "show progress after <n> objects"
msgstr ""
-#: builtin/fast-export.c:654
+#: builtin/fast-export.c:660
msgid "select handling of signed tags"
msgstr ""
-#: builtin/fast-export.c:657
+#: builtin/fast-export.c:663
msgid "select handling of tags that tag filtered objects"
msgstr ""
-#: builtin/fast-export.c:660
+#: builtin/fast-export.c:666
msgid "Dump marks to this file"
msgstr ""
-#: builtin/fast-export.c:662
+#: builtin/fast-export.c:668
msgid "Import marks from this file"
msgstr ""
-#: builtin/fast-export.c:664
+#: builtin/fast-export.c:670
msgid "Fake a tagger when tags lack one"
msgstr ""
-#: builtin/fast-export.c:666
+#: builtin/fast-export.c:672
msgid "Output full tree for each commit"
msgstr ""
-#: builtin/fast-export.c:668
+#: builtin/fast-export.c:674
msgid "Use the done feature to terminate the stream"
msgstr ""
-#: builtin/fast-export.c:669
+#: builtin/fast-export.c:675
msgid "Skip output of blob data"
msgstr ""
msgid "convert to a complete repository"
msgstr ""
-#: builtin/fetch.c:88 builtin/log.c:1121
+#: builtin/fetch.c:88 builtin/log.c:1130
msgid "dir"
msgstr ""
msgid "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]"
msgstr ""
-#: builtin/fmt-merge-msg.c:659 builtin/fmt-merge-msg.c:662 builtin/grep.c:701
-#: builtin/merge.c:188 builtin/show-branch.c:656 builtin/show-ref.c:175
+#: builtin/fmt-merge-msg.c:663 builtin/fmt-merge-msg.c:666 builtin/grep.c:701
+#: builtin/merge.c:188 builtin/show-branch.c:655 builtin/show-ref.c:175
#: builtin/tag.c:446 parse-options.h:133 parse-options.h:239
msgid "n"
msgstr ""
-#: builtin/fmt-merge-msg.c:660
+#: builtin/fmt-merge-msg.c:664
msgid "populate log with at most <n> entries from shortlog"
msgstr ""
-#: builtin/fmt-merge-msg.c:663
+#: builtin/fmt-merge-msg.c:667
msgid "alias for --log (deprecated)"
msgstr ""
-#: builtin/fmt-merge-msg.c:666
+#: builtin/fmt-merge-msg.c:670
msgid "text"
msgstr ""
-#: builtin/fmt-merge-msg.c:667
+#: builtin/fmt-merge-msg.c:671
msgid "use <text> as start of message"
msgstr ""
-#: builtin/fmt-merge-msg.c:668
+#: builtin/fmt-merge-msg.c:672
msgid "file to read from"
msgstr ""
msgid "process file as it were from this path"
msgstr ""
-#: builtin/help.c:42
+#: builtin/help.c:43
msgid "print all available commands"
msgstr ""
-#: builtin/help.c:43
+#: builtin/help.c:44
+msgid "print list of useful guides"
+msgstr ""
+
+#: builtin/help.c:45
msgid "show man page"
msgstr ""
-#: builtin/help.c:44
+#: builtin/help.c:46
msgid "show manual in web browser"
msgstr ""
-#: builtin/help.c:46
+#: builtin/help.c:48
msgid "show info page"
msgstr ""
-#: builtin/help.c:52
-msgid "git help [--all] [--man|--web|--info] [command]"
+#: builtin/help.c:54
+msgid "git help [--all] [--guides] [--man|--web|--info] [command]"
msgstr ""
-#: builtin/help.c:64
+#: builtin/help.c:66
#, c-format
msgid "unrecognized help format '%s'"
msgstr ""
-#: builtin/help.c:92
+#: builtin/help.c:94
msgid "Failed to start emacsclient."
msgstr ""
-#: builtin/help.c:105
+#: builtin/help.c:107
msgid "Failed to parse emacsclient version."
msgstr ""
-#: builtin/help.c:113
+#: builtin/help.c:115
#, c-format
msgid "emacsclient version '%d' too old (< 22)."
msgstr ""
-#: builtin/help.c:131 builtin/help.c:159 builtin/help.c:168 builtin/help.c:176
+#: builtin/help.c:133 builtin/help.c:161 builtin/help.c:170 builtin/help.c:178
#, c-format
msgid "failed to exec '%s': %s"
msgstr ""
-#: builtin/help.c:216
+#: builtin/help.c:218
#, c-format
msgid ""
"'%s': path for unsupported man viewer.\n"
"Please consider using 'man.<tool>.cmd' instead."
msgstr ""
-#: builtin/help.c:228
+#: builtin/help.c:230
#, c-format
msgid ""
"'%s': cmd for supported man viewer.\n"
"Please consider using 'man.<tool>.path' instead."
msgstr ""
-#: builtin/help.c:349
+#: builtin/help.c:351
#, c-format
msgid "'%s': unknown man viewer."
msgstr ""
-#: builtin/help.c:366
+#: builtin/help.c:368
msgid "no man viewer handled the request"
msgstr ""
-#: builtin/help.c:374
+#: builtin/help.c:376
msgid "no info viewer handled the request"
msgstr ""
-#: builtin/help.c:429 builtin/help.c:436
+#: builtin/help.c:422
+msgid "Defining attributes per path"
+msgstr ""
+
+#: builtin/help.c:423
+msgid "A Git glossary"
+msgstr ""
+
+#: builtin/help.c:424
+msgid "Specifies intentionally untracked files to ignore"
+msgstr ""
+
+#: builtin/help.c:425
+msgid "Defining submodule properties"
+msgstr ""
+
+#: builtin/help.c:426
+msgid "Specifying revisions and ranges for Git"
+msgstr ""
+
+#: builtin/help.c:427
+msgid "A tutorial introduction to Git (for version 1.5.1 or newer)"
+msgstr ""
+
+#: builtin/help.c:428
+msgid "An overview of recommended workflows with Git"
+msgstr ""
+
+#: builtin/help.c:440
+msgid "The common Git guides are:\n"
+msgstr ""
+
+#: builtin/help.c:462 builtin/help.c:478
#, c-format
msgid "usage: %s%s"
msgstr ""
-#: builtin/help.c:452
+#: builtin/help.c:494
#, c-format
msgid "`git %s' is aliased to `%s'"
msgstr ""
msgstr ""
#: builtin/log.c:40
-msgid "git log [<options>] [<since>..<until>] [[--] <path>...]\n"
+msgid "git log [<options>] [<revision range>] [[--] <path>...]\n"
msgstr ""
#: builtin/log.c:41
msgid "Unknown type: %d"
msgstr ""
-#: builtin/log.c:630
+#: builtin/log.c:638
msgid "format.headers without value"
msgstr ""
-#: builtin/log.c:704
+#: builtin/log.c:720
msgid "name of output directory is too long"
msgstr ""
-#: builtin/log.c:720
+#: builtin/log.c:736
#, c-format
msgid "Cannot open patch file %s"
msgstr ""
-#: builtin/log.c:734
+#: builtin/log.c:750
msgid "Need exactly one range."
msgstr ""
-#: builtin/log.c:742
+#: builtin/log.c:758
msgid "Not a range."
msgstr ""
-#: builtin/log.c:815
+#: builtin/log.c:860
msgid "Cover letter needs email format"
msgstr ""
-#: builtin/log.c:888
+#: builtin/log.c:936
#, c-format
msgid "insane in-reply-to: %s"
msgstr ""
-#: builtin/log.c:916
+#: builtin/log.c:964
msgid "git format-patch [options] [<since> | <revision range>]"
msgstr ""
-#: builtin/log.c:961
+#: builtin/log.c:1009
msgid "Two output directories?"
msgstr ""
-#: builtin/log.c:1099
+#: builtin/log.c:1108
msgid "use [PATCH n/m] even with a single patch"
msgstr ""
-#: builtin/log.c:1102
+#: builtin/log.c:1111
msgid "use [PATCH] even with multiple patches"
msgstr ""
-#: builtin/log.c:1106
+#: builtin/log.c:1115
msgid "print patches to standard out"
msgstr ""
-#: builtin/log.c:1108
+#: builtin/log.c:1117
msgid "generate a cover letter"
msgstr ""
-#: builtin/log.c:1110
+#: builtin/log.c:1119
msgid "use simple number sequence for output file names"
msgstr ""
-#: builtin/log.c:1111
+#: builtin/log.c:1120
msgid "sfx"
msgstr ""
-#: builtin/log.c:1112
+#: builtin/log.c:1121
msgid "use <sfx> instead of '.patch'"
msgstr ""
-#: builtin/log.c:1114
+#: builtin/log.c:1123
msgid "start numbering patches at <n> instead of 1"
msgstr ""
-#: builtin/log.c:1116
+#: builtin/log.c:1125
msgid "mark the series as Nth re-roll"
msgstr ""
-#: builtin/log.c:1118
+#: builtin/log.c:1127
msgid "Use [<prefix>] instead of [PATCH]"
msgstr ""
-#: builtin/log.c:1121
+#: builtin/log.c:1130
msgid "store resulting files in <dir>"
msgstr ""
-#: builtin/log.c:1124
+#: builtin/log.c:1133
msgid "don't strip/add [PATCH]"
msgstr ""
-#: builtin/log.c:1127
+#: builtin/log.c:1136
msgid "don't output binary diffs"
msgstr ""
-#: builtin/log.c:1129
+#: builtin/log.c:1138
msgid "don't include a patch matching a commit upstream"
msgstr ""
-#: builtin/log.c:1131
+#: builtin/log.c:1140
msgid "show patch format instead of default (patch + stat)"
msgstr ""
-#: builtin/log.c:1133
+#: builtin/log.c:1142
msgid "Messaging"
msgstr ""
-#: builtin/log.c:1134
+#: builtin/log.c:1143
msgid "header"
msgstr ""
-#: builtin/log.c:1135
+#: builtin/log.c:1144
msgid "add email header"
msgstr ""
-#: builtin/log.c:1136 builtin/log.c:1138
+#: builtin/log.c:1145 builtin/log.c:1147
msgid "email"
msgstr ""
-#: builtin/log.c:1136
+#: builtin/log.c:1145
msgid "add To: header"
msgstr ""
-#: builtin/log.c:1138
+#: builtin/log.c:1147
msgid "add Cc: header"
msgstr ""
-#: builtin/log.c:1140
+#: builtin/log.c:1149
msgid "message-id"
msgstr ""
-#: builtin/log.c:1141
+#: builtin/log.c:1150
msgid "make first mail a reply to <message-id>"
msgstr ""
-#: builtin/log.c:1142 builtin/log.c:1145
+#: builtin/log.c:1151 builtin/log.c:1154
msgid "boundary"
msgstr ""
-#: builtin/log.c:1143
+#: builtin/log.c:1152
msgid "attach the patch"
msgstr ""
-#: builtin/log.c:1146
+#: builtin/log.c:1155
msgid "inline the patch"
msgstr ""
-#: builtin/log.c:1150
+#: builtin/log.c:1159
msgid "enable message threading, styles: shallow, deep"
msgstr ""
-#: builtin/log.c:1152
+#: builtin/log.c:1161
msgid "signature"
msgstr ""
-#: builtin/log.c:1153
+#: builtin/log.c:1162
msgid "add a signature"
msgstr ""
-#: builtin/log.c:1155
+#: builtin/log.c:1164
msgid "don't print the patch filenames"
msgstr ""
-#: builtin/log.c:1239
+#: builtin/log.c:1248
msgid "-n and -k are mutually exclusive."
msgstr ""
-#: builtin/log.c:1241
+#: builtin/log.c:1250
msgid "--subject-prefix and -k are mutually exclusive."
msgstr ""
-#: builtin/log.c:1249
+#: builtin/log.c:1258
msgid "--name-only does not make sense"
msgstr ""
-#: builtin/log.c:1251
+#: builtin/log.c:1260
msgid "--name-status does not make sense"
msgstr ""
-#: builtin/log.c:1253
+#: builtin/log.c:1262
msgid "--check does not make sense"
msgstr ""
-#: builtin/log.c:1276
+#: builtin/log.c:1285
msgid "standard output, or directory, which one?"
msgstr ""
-#: builtin/log.c:1278
+#: builtin/log.c:1287
#, c-format
msgid "Could not create directory '%s'"
msgstr ""
-#: builtin/log.c:1431
+#: builtin/log.c:1435
msgid "Failed to create output files"
msgstr ""
-#: builtin/log.c:1480
+#: builtin/log.c:1484
msgid "git cherry [-v] [<upstream> [<head> [<limit>]]]"
msgstr ""
-#: builtin/log.c:1535
+#: builtin/log.c:1539
#, c-format
msgid ""
"Could not find a tracked remote branch, please specify <upstream> manually.\n"
msgstr ""
-#: builtin/log.c:1548 builtin/log.c:1550 builtin/log.c:1562
+#: builtin/log.c:1552 builtin/log.c:1554 builtin/log.c:1566
#, c-format
msgid "Unknown commit %s"
msgstr ""
-#: builtin/ls-files.c:409
+#: builtin/ls-files.c:402
msgid "git ls-files [options] [<file>...]"
msgstr ""
-#: builtin/ls-files.c:466
+#: builtin/ls-files.c:459
msgid "identify the file status with tags"
msgstr ""
-#: builtin/ls-files.c:468
+#: builtin/ls-files.c:461
msgid "use lowercase letters for 'assume unchanged' files"
msgstr ""
-#: builtin/ls-files.c:470
+#: builtin/ls-files.c:463
msgid "show cached files in the output (default)"
msgstr ""
-#: builtin/ls-files.c:472
+#: builtin/ls-files.c:465
msgid "show deleted files in the output"
msgstr ""
-#: builtin/ls-files.c:474
+#: builtin/ls-files.c:467
msgid "show modified files in the output"
msgstr ""
-#: builtin/ls-files.c:476
+#: builtin/ls-files.c:469
msgid "show other files in the output"
msgstr ""
-#: builtin/ls-files.c:478
+#: builtin/ls-files.c:471
msgid "show ignored files in the output"
msgstr ""
-#: builtin/ls-files.c:481
+#: builtin/ls-files.c:474
msgid "show staged contents' object name in the output"
msgstr ""
-#: builtin/ls-files.c:483
+#: builtin/ls-files.c:476
msgid "show files on the filesystem that need to be removed"
msgstr ""
-#: builtin/ls-files.c:485
+#: builtin/ls-files.c:478
msgid "show 'other' directories' name only"
msgstr ""
-#: builtin/ls-files.c:488
+#: builtin/ls-files.c:481
msgid "don't show empty directories"
msgstr ""
-#: builtin/ls-files.c:491
+#: builtin/ls-files.c:484
msgid "show unmerged files in the output"
msgstr ""
-#: builtin/ls-files.c:493
+#: builtin/ls-files.c:486
msgid "show resolve-undo information"
msgstr ""
-#: builtin/ls-files.c:495
+#: builtin/ls-files.c:488
msgid "skip files matching pattern"
msgstr ""
-#: builtin/ls-files.c:498
+#: builtin/ls-files.c:491
msgid "exclude patterns are read from <file>"
msgstr ""
-#: builtin/ls-files.c:501
+#: builtin/ls-files.c:494
msgid "read additional per-directory exclude patterns in <file>"
msgstr ""
-#: builtin/ls-files.c:503
+#: builtin/ls-files.c:496
msgid "add the standard git exclusions"
msgstr ""
-#: builtin/ls-files.c:506
+#: builtin/ls-files.c:499
msgid "make the output relative to the project top directory"
msgstr ""
-#: builtin/ls-files.c:509
+#: builtin/ls-files.c:502
msgid "if any <file> is not in the index, treat this as an error"
msgstr ""
-#: builtin/ls-files.c:510
+#: builtin/ls-files.c:503
msgid "tree-ish"
msgstr ""
-#: builtin/ls-files.c:511
+#: builtin/ls-files.c:504
msgid "pretend that paths removed since <tree-ish> are still present"
msgstr ""
-#: builtin/ls-files.c:513
+#: builtin/ls-files.c:506
msgid "show debugging data"
msgstr ""
msgstr ""
#: builtin/revert.c:22
-msgid "git revert [options] <commit-ish>"
+msgid "git revert [options] <commit-ish>..."
msgstr ""
#: builtin/revert.c:23
msgstr ""
#: builtin/revert.c:28
-msgid "git cherry-pick [options] <commit-ish>"
+msgid "git cherry-pick [options] <commit-ish>..."
msgstr ""
#: builtin/revert.c:29
msgstr ""
#: builtin/shortlog.c:13
-msgid "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
+msgid "git shortlog [<options>] [<revision range>] [[--] [<path>...]]"
msgstr ""
-#: builtin/shortlog.c:133
+#: builtin/shortlog.c:131
#, c-format
msgid "Missing author: %s"
msgstr ""
-#: builtin/shortlog.c:229
+#: builtin/shortlog.c:227
msgid "sort output according to the number of commits per author"
msgstr ""
-#: builtin/shortlog.c:231
+#: builtin/shortlog.c:229
msgid "Suppress commit descriptions, only provides commit count"
msgstr ""
-#: builtin/shortlog.c:233
+#: builtin/shortlog.c:231
msgid "Show the email address of each author"
msgstr ""
-#: builtin/shortlog.c:234
+#: builtin/shortlog.c:232
msgid "w[,i1[,i2]]"
msgstr ""
-#: builtin/shortlog.c:235
+#: builtin/shortlog.c:233
msgid "Linewrap output"
msgstr ""
msgid "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]"
msgstr ""
-#: builtin/show-branch.c:651
+#: builtin/show-branch.c:650
msgid "show remote-tracking and local branches"
msgstr ""
-#: builtin/show-branch.c:653
+#: builtin/show-branch.c:652
msgid "show remote-tracking branches"
msgstr ""
-#: builtin/show-branch.c:655
+#: builtin/show-branch.c:654
msgid "color '*!+-' corresponding to the branch"
msgstr ""
-#: builtin/show-branch.c:657
+#: builtin/show-branch.c:656
msgid "show <n> more commits after the common ancestor"
msgstr ""
-#: builtin/show-branch.c:659
+#: builtin/show-branch.c:658
msgid "synonym to more=-1"
msgstr ""
-#: builtin/show-branch.c:660
+#: builtin/show-branch.c:659
msgid "suppress naming strings"
msgstr ""
-#: builtin/show-branch.c:662
+#: builtin/show-branch.c:661
msgid "include the current branch"
msgstr ""
-#: builtin/show-branch.c:664
+#: builtin/show-branch.c:663
msgid "name commits with their object names"
msgstr ""
-#: builtin/show-branch.c:666
+#: builtin/show-branch.c:665
msgid "show possible merge bases"
msgstr ""
-#: builtin/show-branch.c:668
+#: builtin/show-branch.c:667
msgid "show refs unreachable from any other ref"
msgstr ""
-#: builtin/show-branch.c:670
+#: builtin/show-branch.c:669
msgid "show commits in topological order"
msgstr ""
-#: builtin/show-branch.c:672
+#: builtin/show-branch.c:671
msgid "show only commits not on the first branch"
msgstr ""
-#: builtin/show-branch.c:674
+#: builtin/show-branch.c:673
msgid "show merges reachable from only one tip"
msgstr ""
-#: builtin/show-branch.c:676
+#: builtin/show-branch.c:675
msgid "show commits where no parent comes before its children"
msgstr ""
-#: builtin/show-branch.c:678
+#: builtin/show-branch.c:677
msgid "<n>[,<base>]"
msgstr ""
-#: builtin/show-branch.c:679
+#: builtin/show-branch.c:678
msgid "show <n> most recent ref-log entries starting at base"
msgstr ""
msgstr ""
#: git.c:16
-msgid "See 'git help <command>' for more information on a specific command."
+msgid ""
+"'git help -a' and 'git help -g' lists available subcommands and some\n"
+"concept guides. See 'git help <command>' or 'git help <concept>'\n"
+"to read about a specific subcommand or concept."
msgstr ""
#: parse-options.h:156
"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
msgstr ""
-#: git-bisect.sh:347 git-bisect.sh:474
+#: git-bisect.sh:363 git-bisect.sh:490
msgid "We are not bisecting."
msgstr ""
-#: git-bisect.sh:354
+#: git-bisect.sh:370
#, sh-format
msgid "'$invalid' is not a valid commit"
msgstr ""
-#: git-bisect.sh:363
+#: git-bisect.sh:379
#, sh-format
msgid ""
"Could not check out original HEAD '$branch'.\n"
"Try 'git bisect reset <commit>'."
msgstr ""
-#: git-bisect.sh:390
+#: git-bisect.sh:406
msgid "No logfile given"
msgstr ""
-#: git-bisect.sh:391
+#: git-bisect.sh:407
#, sh-format
msgid "cannot read $file for replaying"
msgstr ""
-#: git-bisect.sh:408
+#: git-bisect.sh:424
msgid "?? what are you talking about?"
msgstr ""
-#: git-bisect.sh:420
+#: git-bisect.sh:436
#, sh-format
msgid "running $command"
msgstr ""
-#: git-bisect.sh:427
+#: git-bisect.sh:443
#, sh-format
msgid ""
"bisect run failed:\n"
"exit code $res from '$command' is < 0 or >= 128"
msgstr ""
-#: git-bisect.sh:453
+#: git-bisect.sh:469
msgid "bisect run cannot continue any more"
msgstr ""
-#: git-bisect.sh:459
+#: git-bisect.sh:475
#, sh-format
msgid ""
"bisect run failed:\n"
"'bisect_state $state' exited with error code $res"
msgstr ""
-#: git-bisect.sh:466
+#: git-bisect.sh:482
msgid "bisect run success"
msgstr ""
msgstr ""
"Project-Id-Version: git 1.8.3\n"
"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2013-04-10 15:16+0800\n"
-"PO-Revision-Date: 2013-04-11 11:56+0100\n"
+"POT-Creation-Date: 2013-04-30 08:25+0800\n"
+"PO-Revision-Date: 2013-04-30 12:22+0100\n"
"Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
"Language: sv\n"
msgid "archive format"
msgstr "arkivformat"
-#: archive.c:324 builtin/log.c:1117
+#: archive.c:324 builtin/log.c:1126
msgid "prefix"
msgstr "prefix"
msgid "prepend prefix to each pathname in the archive"
msgstr "lägg till prefix till varje sökväg i arkivet"
-#: archive.c:326 builtin/archive.c:88 builtin/blame.c:2366
-#: builtin/blame.c:2367 builtin/config.c:55 builtin/fast-export.c:659
-#: builtin/fast-export.c:661 builtin/grep.c:715 builtin/hash-object.c:77
-#: builtin/ls-files.c:497 builtin/ls-files.c:500 builtin/notes.c:536
+#: archive.c:326 builtin/archive.c:88 builtin/blame.c:2371
+#: builtin/blame.c:2372 builtin/config.c:55 builtin/fast-export.c:665
+#: builtin/fast-export.c:667 builtin/grep.c:715 builtin/hash-object.c:77
+#: builtin/ls-files.c:490 builtin/ls-files.c:493 builtin/notes.c:536
#: builtin/notes.c:693 builtin/read-tree.c:107 parse-options.h:149
msgid "file"
msgstr "fil"
"Negativa mönster ignoreras i git-attribut\n"
"Använd '\\!' för att inleda med ett utropstecken."
+#: branch.c:60
+#, c-format
+msgid "Not setting branch %s as its own upstream."
+msgstr "Ställer inte in grenen %s som sin egen uppströmsgren."
+
+#: branch.c:82
+#, c-format
+msgid "Branch %s set up to track remote branch %s from %s by rebasing."
+msgstr ""
+"Grenen %s ställdes in att spåra fjärrgrenen %s från %s genom ombasering."
+
+#: branch.c:83
+#, c-format
+msgid "Branch %s set up to track remote branch %s from %s."
+msgstr "Grenen %s ställdes in att spåra fjärrgrenen %s från %s."
+
+#: branch.c:87
+#, c-format
+msgid "Branch %s set up to track local branch %s by rebasing."
+msgstr "Grenen %s ställdes in att spåra den lokala grenen %s genom ombasering."
+
+#: branch.c:88
+#, c-format
+msgid "Branch %s set up to track local branch %s."
+msgstr "Grenen %s ställdes in att spåra den lokala grenen %s."
+
+#: branch.c:92
+#, c-format
+msgid "Branch %s set up to track remote ref %s by rebasing."
+msgstr "Grenen %s ställdes in att spåra fjärreferensen %s genom ombasering."
+
+#: branch.c:93
+#, c-format
+msgid "Branch %s set up to track remote ref %s."
+msgstr "Grenen %s ställdes in att spåra fjärreferensen %s."
+
+#: branch.c:97
+#, c-format
+msgid "Branch %s set up to track local ref %s by rebasing."
+msgstr ""
+"Grenen %s ställdes in att spåra den lokala referensen %s genom ombasering."
+
+#: branch.c:98
+#, c-format
+msgid "Branch %s set up to track local ref %s."
+msgstr "Grenen %s ställdes in att spåra den lokala referensen %s."
+
+#: branch.c:118
+#, c-format
+msgid "Tracking not set up: name too long: %s"
+msgstr "Spårning har inte ställts in: namnet för långt: %s"
+
+#: branch.c:137
+#, c-format
+msgid "Not tracking: ambiguous information for ref %s"
+msgstr "Spårar inte: tvetydig information för referensen %s"
+
+#: branch.c:182
+#, c-format
+msgid "'%s' is not a valid branch name."
+msgstr "\"%s\" är inte ett giltigt grennamn."
+
+#: branch.c:187
+#, c-format
+msgid "A branch named '%s' already exists."
+msgstr "Det finns redan en gren som heter \"%s\""
+
+#: branch.c:195
+msgid "Cannot force update the current branch."
+msgstr "Kan inte tvinga uppdatering av aktuell gren."
+
#: branch.c:201
#, c-format
msgid "Cannot setup tracking information; starting point '%s' is not a branch."
"spåra dess fjärrmotsvarighet kan du använda \"git push -u\"\n"
"för att ställa in uppströmskonfigurationen när du sänder in."
+#: branch.c:250
+#, c-format
+msgid "Not a valid object name: '%s'."
+msgstr "Objektnamnet är inte giltigt: \"%s\"."
+
+#: branch.c:270
+#, c-format
+msgid "Ambiguous object name: '%s'."
+msgstr "Objektnamnet är tvetydigt: \"%s\"."
+
+#: branch.c:275
+#, c-format
+msgid "Not a valid branch point: '%s'."
+msgstr "Avgreningspunkten är inte giltig: \"%s\""
+
+#: branch.c:281
+msgid "Failed to lock ref for update"
+msgstr "Misslyckades låsa referens för uppdatering"
+
+#: branch.c:299
+msgid "Failed to write ref"
+msgstr "Misslyckades skriva referens"
+
#: bundle.c:36
#, c-format
msgid "'%s' does not look like a v2 bundle file"
msgid "Repository lacks these prerequisite commits:"
msgstr "Arkivet saknar dessa nödvändiga incheckningar:"
-#: bundle.c:164 sequencer.c:651 sequencer.c:1083 builtin/log.c:300
-#: builtin/log.c:754 builtin/log.c:1350 builtin/log.c:1566 builtin/merge.c:349
-#: builtin/shortlog.c:157
+#: bundle.c:164 sequencer.c:651 sequencer.c:1101 builtin/log.c:300
+#: builtin/log.c:770 builtin/log.c:1344 builtin/log.c:1570 builtin/merge.c:349
+#: builtin/shortlog.c:155
msgid "revision walk setup failed"
msgstr "misslyckades skapa revisionstraversering"
msgid "rev-list died"
msgstr "rev-list dog"
-#: bundle.c:300 builtin/log.c:1246 builtin/shortlog.c:260
+#: bundle.c:300 builtin/log.c:1255 builtin/shortlog.c:258
#, c-format
msgid "unrecognized argument: %s"
msgstr "okänt argument: %s"
"Hittade fel i konfigurationsvariabeln \"diff.dirstat\":\n"
"%s"
-#: diff.c:3480
+#: diff.c:3481
#, c-format
msgid ""
"Failed to parse --dirstat/-X option parameter:\n"
"Misslyckades tolka argument till flaggan --dirstat/-X;\n"
"%s"
-#: diff.c:3494
+#: diff.c:3495
#, c-format
msgid "Failed to parse --submodule option parameter: '%s'"
msgstr "Misslyckades tolka argument till flaggan --submodule: \"%s\""
msgid "failed to read the cache"
msgstr "misslyckads läsa cachen"
-#: merge.c:110 builtin/checkout.c:362 builtin/checkout.c:563
-#: builtin/clone.c:635
+#: merge.c:110 builtin/checkout.c:365 builtin/checkout.c:566
+#: builtin/clone.c:645
msgid "unable to write new index file"
msgstr "kunde inte skriva ny indexfil"
msgid "blob expected for %s '%s'"
msgstr "blob förväntades för %s \"%s\""
-#: merge-recursive.c:773 builtin/clone.c:303
+#: merge-recursive.c:773 builtin/clone.c:313
#, c-format
msgid "failed to open '%s'"
msgstr "misslyckades öppna \"%s\""
msgid "Could not format %s."
msgstr "Kunde inte formatera %s."
-#: sequencer.c:1101
+#: sequencer.c:1083
+#, c-format
+msgid "%s: can't cherry-pick a %s"
+msgstr "%s: kan inte göra \"cherry-pick\" på typen \"%s\""
+
+#: sequencer.c:1085
+#, c-format
+msgid "%s: bad revision"
+msgstr "%s: felaktig revision"
+
+#: sequencer.c:1119
msgid "Can't revert as initial commit"
msgstr "Kan inte ångra som första incheckning"
-#: sequencer.c:1102
+#: sequencer.c:1120
msgid "Can't cherry-pick into empty head"
msgstr "Kan inte göra \"cherry-pick\" i ett tomt huvud"
msgid "bug: unhandled diff status %c"
msgstr "programfel: diff-status %c ej hanterad"
-#: wt-status.c:805
+#: wt-status.c:803
msgid "You have unmerged paths."
msgstr "Du har ej sammanslagna sökvägar."
-#: wt-status.c:808 wt-status.c:960
+#: wt-status.c:806 wt-status.c:958
msgid " (fix conflicts and run \"git commit\")"
msgstr " (rätta konflikter och kör \"git commit\")"
-#: wt-status.c:811
+#: wt-status.c:809
msgid "All conflicts fixed but you are still merging."
msgstr "Alla konflikter har rättats men du är fortfarande i en sammanslagning."
-#: wt-status.c:814
+#: wt-status.c:812
msgid " (use \"git commit\" to conclude merge)"
msgstr " (använd \"git commit\" för att slutföra sammanslagningen)"
-#: wt-status.c:824
+#: wt-status.c:822
msgid "You are in the middle of an am session."
msgstr "Du är i mitten av en körning av \"git am\"."
-#: wt-status.c:827
+#: wt-status.c:825
msgid "The current patch is empty."
msgstr "Aktuell patch är tom."
-#: wt-status.c:831
+#: wt-status.c:829
msgid " (fix conflicts and then run \"git am --resolved\")"
msgstr " (rätta konflikter och kör sedan \"git am --resolved\")"
-#: wt-status.c:833
+#: wt-status.c:831
msgid " (use \"git am --skip\" to skip this patch)"
msgstr " (använd \"git am --skip\" för att hoppa över patchen)"
-#: wt-status.c:835
+#: wt-status.c:833
msgid " (use \"git am --abort\" to restore the original branch)"
msgstr " (använd \"git am --abort\" för att återställa ursprungsgrenen)"
-#: wt-status.c:895 wt-status.c:912
+#: wt-status.c:893 wt-status.c:910
#, c-format
msgid "You are currently rebasing branch '%s' on '%s'."
msgstr "Du håller på att ombasera grenen \"%s\" ovanpå \"%s\"."
-#: wt-status.c:900 wt-status.c:917
+#: wt-status.c:898 wt-status.c:915
msgid "You are currently rebasing."
msgstr "Du håller på med en ombasering."
-#: wt-status.c:903
+#: wt-status.c:901
msgid " (fix conflicts and then run \"git rebase --continue\")"
msgstr " (rätta konflikter och kör sedan \"git rebase --continue\")"
-#: wt-status.c:905
+#: wt-status.c:903
msgid " (use \"git rebase --skip\" to skip this patch)"
msgstr " (använd \"git rebase --skip\" för att hoppa över patchen)"
-#: wt-status.c:907
+#: wt-status.c:905
msgid " (use \"git rebase --abort\" to check out the original branch)"
msgstr " (använd \"git rebase --abort\" för att checka ut ursprungsgrenen)"
-#: wt-status.c:920
+#: wt-status.c:918
msgid " (all conflicts fixed: run \"git rebase --continue\")"
msgstr " (alla konflikter rättade: kör \"git rebase --continue\")"
-#: wt-status.c:924
+#: wt-status.c:922
#, c-format
msgid ""
"You are currently splitting a commit while rebasing branch '%s' on '%s'."
"Du håller på att dela upp en incheckning medan du ombaserar grenen \"%s\" "
"ovanpå \"%s\"."
-#: wt-status.c:929
+#: wt-status.c:927
msgid "You are currently splitting a commit during a rebase."
msgstr "Du håller på att dela upp en incheckning i en ombasering."
-#: wt-status.c:932
+#: wt-status.c:930
msgid " (Once your working directory is clean, run \"git rebase --continue\")"
msgstr " (Så fort din arbetskatalog är ren, kör \"git rebase --continue\")"
-#: wt-status.c:936
+#: wt-status.c:934
#, c-format
msgid "You are currently editing a commit while rebasing branch '%s' on '%s'."
msgstr ""
"Du håller på att redigera en incheckning medan du ombaserar grenen \"%s\" "
"ovanpå \"%s\"."
-#: wt-status.c:941
+#: wt-status.c:939
msgid "You are currently editing a commit during a rebase."
msgstr "Du håller på att redigera en incheckning under en ombasering."
-#: wt-status.c:944
+#: wt-status.c:942
msgid " (use \"git commit --amend\" to amend the current commit)"
msgstr ""
" (använd \"git commit --amend\" för att lägga till på aktuell incheckning)"
-#: wt-status.c:946
+#: wt-status.c:944
msgid ""
" (use \"git rebase --continue\" once you are satisfied with your changes)"
msgstr " (använd \"git rebase --continue\" när du är nöjd med dina ändringar)"
-#: wt-status.c:956
+#: wt-status.c:954
msgid "You are currently cherry-picking."
msgstr "Du håller på med en \"cherry-pick\"."
-#: wt-status.c:963
+#: wt-status.c:961
msgid " (all conflicts fixed: run \"git commit\")"
msgstr " (alla konflikter har rättats: kör \"git commit\")"
-#: wt-status.c:972
+#: wt-status.c:970
#, c-format
msgid "You are currently reverting commit %s."
msgstr "Du håller på med att ångra incheckningen %s."
-#: wt-status.c:977
+#: wt-status.c:975
msgid " (fix conflicts and run \"git revert --continue\")"
msgstr " (rätta konflikter och kör sedan \"git revert --continue\")"
-#: wt-status.c:980
+#: wt-status.c:978
msgid " (all conflicts fixed: run \"git revert --continue\")"
msgstr " (alla konflikter rättade: kör \"git revert --continue\")"
-#: wt-status.c:982
+#: wt-status.c:980
msgid " (use \"git revert --abort\" to cancel the revert operation)"
msgstr " (använd \"git revert --abort\" för att avbryta ångrandet)"
-#: wt-status.c:993
+#: wt-status.c:991
#, c-format
msgid "You are currently bisecting, started from branch '%s'."
msgstr "Du håller på med en \"bisect\", startad från grenen \"%s\"."
-#: wt-status.c:997
+#: wt-status.c:995
msgid "You are currently bisecting."
msgstr "Du håller på med en \"bisect\"."
-#: wt-status.c:1000
+#: wt-status.c:998
msgid " (use \"git bisect reset\" to get back to the original branch)"
msgstr ""
" (använd \"git bisect reset\" för att komma tillbaka till ursprungsgrenen)"
-#: wt-status.c:1175
+#: wt-status.c:1173
msgid "On branch "
msgstr "På grenen "
-#: wt-status.c:1186
+#: wt-status.c:1184
msgid "HEAD detached at "
msgstr "HEAD frånkopplad vid "
-#: wt-status.c:1188
+#: wt-status.c:1186
msgid "HEAD detached from "
msgstr "HEAD frånkopplad från "
-#: wt-status.c:1191
+#: wt-status.c:1189
msgid "Not currently on any branch."
msgstr "Inte på någon gren för närvarande."
-#: wt-status.c:1208
+#: wt-status.c:1206
msgid "Initial commit"
msgstr "Första incheckning"
-#: wt-status.c:1222
+#: wt-status.c:1220
msgid "Untracked files"
msgstr "Ospårade filer"
-#: wt-status.c:1224
+#: wt-status.c:1222
msgid "Ignored files"
msgstr "Ignorerade filer"
-#: wt-status.c:1228
+#: wt-status.c:1226
#, c-format
-msgid "It took %.2f seconds to enumerate untracked files. 'status -uno'"
-msgstr "Det tog %.2f sekunder att räkna ospårade filer. \"status -uno\""
-
-#: wt-status.c:1232
-msgid "may speed it up, but you have to be careful not to forget to add"
-msgstr "kanske gör det snabbare, men du måste vara försiktig så att du"
-
-#: wt-status.c:1235
-msgid "new files yourself (see 'git help status')."
-msgstr "inte glömmer lägga till filer själv (se \"git help status\")"
+msgid ""
+"It took %.2f seconds to enumerate untracked files. 'status -uno'\n"
+"may speed it up, but you have to be careful not to forget to add\n"
+"new files yourself (see 'git help status')."
+msgstr ""
+"Det tog %.2f sekunder att räkna upp ospårade filer. \"status -uno\"\n"
+"kan gå snabbare, men du måste vara försiktig så du inte glömmer\n"
+"lägga till nya filer själv (se \"git help status\")."
# %s är nästa sträng eller tom.
-#: wt-status.c:1238
+#: wt-status.c:1232
#, c-format
msgid "Untracked files not listed%s"
msgstr "Ospårade filer visas ej%s"
-#: wt-status.c:1240
+#: wt-status.c:1234
msgid " (use -u option to show untracked files)"
msgstr " (använd flaggan -u för att visa ospårade filer)"
-#: wt-status.c:1246
+#: wt-status.c:1240
msgid "No changes"
msgstr "Inga ändringar"
-#: wt-status.c:1251
+#: wt-status.c:1245
#, c-format
msgid "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"
msgstr ""
"inga ändringar att checka in (använd \"git add\" och/eller \"git commit -a"
"\")\n"
-#: wt-status.c:1254
+#: wt-status.c:1248
#, c-format
msgid "no changes added to commit\n"
msgstr "inga ändringar att checka in\n"
-#: wt-status.c:1257
+#: wt-status.c:1251
#, c-format
msgid ""
"nothing added to commit but untracked files present (use \"git add\" to "
"inget köat för incheckning, men ospårade filer finns (spåra med \"git add"
"\")\n"
-#: wt-status.c:1260
+#: wt-status.c:1254
#, c-format
msgid "nothing added to commit but untracked files present\n"
msgstr "inget köat för incheckning, men ospårade filer finns\n"
-#: wt-status.c:1263
+#: wt-status.c:1257
#, c-format
msgid "nothing to commit (create/copy files and use \"git add\" to track)\n"
msgstr "inget att checka in (skapa/kopiera filer och spåra med \"git add\")\n"
-#: wt-status.c:1266 wt-status.c:1271
+#: wt-status.c:1260 wt-status.c:1265
#, c-format
msgid "nothing to commit\n"
msgstr "inget att checka in\n"
-#: wt-status.c:1269
+#: wt-status.c:1263
#, c-format
msgid "nothing to commit (use -u to show untracked files)\n"
msgstr "inget att checka in (använd -u för att visa ospårade filer)\n"
-#: wt-status.c:1273
+#: wt-status.c:1267
#, c-format
msgid "nothing to commit, working directory clean\n"
msgstr "inget att checka in, arbetskatalogen ren\n"
-#: wt-status.c:1381
+#: wt-status.c:1375
msgid "HEAD (no branch)"
msgstr "HEAD (ingen gren)"
-#: wt-status.c:1387
+#: wt-status.c:1381
msgid "Initial commit on "
msgstr "Första incheckning på "
-#: wt-status.c:1402
+#: wt-status.c:1396
msgid "behind "
msgstr "efter "
-#: wt-status.c:1405 wt-status.c:1408
+#: wt-status.c:1399 wt-status.c:1402
msgid "ahead "
msgstr "före "
-#: wt-status.c:1410
+#: wt-status.c:1404
msgid ", behind "
msgstr ", efter "
-#: compat/precompose_utf8.c:58 builtin/clone.c:342
+#: compat/precompose_utf8.c:58 builtin/clone.c:352
#, c-format
msgid "failed to unlink '%s'"
msgstr "misslyckades ta bort länken \"%s\""
msgid "git add [options] [--] <pathspec>..."
msgstr "git add [flaggor] [--] <sökväg>..."
-#: builtin/add.c:63
+#.
+#. * To be consistent with "git add -p" and most Git
+#. * commands, we should default to being tree-wide, but
+#. * this is not the original behavior and can't be
+#. * changed until users trained themselves not to type
+#. * "git add -u" or "git add -A". For now, we warn and
+#. * keep the old behavior. Later, the behavior can be changed
+#. * to tree-wide, keeping the warning for a while, and
+#. * eventually we can drop the warning.
+#.
+#: builtin/add.c:58
+#, c-format
+msgid ""
+"The behavior of 'git add %s (or %s)' with no path argument from a\n"
+"subdirectory of the tree will change in Git 2.0 and should not be used "
+"anymore.\n"
+"To add content for the whole tree, run:\n"
+"\n"
+" git add %s :/\n"
+" (or git add %s :/)\n"
+"\n"
+"To restrict the command to the current directory, run:\n"
+"\n"
+" git add %s .\n"
+" (or git add %s .)\n"
+"\n"
+"With the current Git version, the command is restricted to the current "
+"directory.\n"
+msgstr ""
+"Beteendet för \"git add %s (eller %s)\" utan sökvägsargument från en\n"
+"underkatalog i ett träd kommer ändras i Git 2.0 och bör inte längre "
+"användas.\n"
+"För att lägga till innehållet för hela trädet, använd:\n"
+"\n"
+" git add %s :/\n"
+" (eller git add %s :/)\n"
+"\n"
+"För att begränsa kommandot till aktuell katalog, använd:\n"
+"\n"
+" git add %s .\n"
+" (eller git add %s .)\n"
+"\n"
+"I nuvarande version av Git begränsas kommandot till aktuell katalog.\n"
+
+#: builtin/add.c:100
+#, c-format
+msgid ""
+"You ran 'git add' with neither '-A (--all)' or '--ignore-removal',\n"
+"whose behaviour will change in Git 2.0 with respect to paths you removed.\n"
+"Paths like '%s' that are\n"
+"removed from your working tree are ignored with this version of Git.\n"
+"\n"
+"* 'git add --ignore-removal <pathspec>', which is the current default,\n"
+" ignores paths you removed from your working tree.\n"
+"\n"
+"* 'git add --all <pathspec>' will let you also record the removals.\n"
+"\n"
+"Run 'git status' to check the paths you removed from your working tree.\n"
+msgstr ""
+"Du körde \"git add\" utan varken \"-A (--all)\" eller \"--ignore-removal\".\n"
+"Beteendet kommer ändras i Git 2.0 vad gäller sökvägar du tagit bort.\n"
+"Sökvägar som \"%s\", som har\n"
+"tagits bort från din arbetskatalog ignoreras i den här versionen av Git.\n"
+"\n"
+"* \"git add --ignore-removal <sökväg>\", som är förvalet just nu,\n"
+" ignorerar sökvägar du har tagit bort från arbetskatalogen.\n"
+"\n"
+"* \"git add --all <sökväg>\" låter dig även registrera borttagningarna.\n"
+"\n"
+"Kör \"git status\" för att kontrollera sökvägarna du tagit bort från\n"
+"arbetskatalogen.\n"
+
+#: builtin/add.c:144
#, c-format
msgid "unexpected diff status %c"
msgstr "diff-status %c förväntades inte"
-#: builtin/add.c:68 builtin/commit.c:233
+#: builtin/add.c:149 builtin/commit.c:233
msgid "updating files failed"
msgstr "misslyckades uppdatera filer"
-#: builtin/add.c:78
+#: builtin/add.c:163
#, c-format
msgid "remove '%s'\n"
msgstr "ta bort \"%s\"\n"
-#: builtin/add.c:148
+#: builtin/add.c:253
msgid "Unstaged changes after refreshing the index:"
msgstr "Ospårade ändringar efter att ha uppdaterat indexet:"
-#: builtin/add.c:151 builtin/add.c:460 builtin/rm.c:275
+#: builtin/add.c:256 builtin/add.c:572 builtin/rm.c:275
#, c-format
msgid "pathspec '%s' did not match any files"
msgstr "sökvägsangivelsen \"%s\" motsvarade inte några filer"
-#: builtin/add.c:234
+#: builtin/add.c:339
msgid "Could not read the index"
msgstr "Kunde inte läsa indexet"
-#: builtin/add.c:244
+#: builtin/add.c:349
#, c-format
msgid "Could not open '%s' for writing."
msgstr "Kunde inte öppna \"%s\" för skrivning"
-#: builtin/add.c:248
+#: builtin/add.c:353
msgid "Could not write patch"
msgstr "Kunde inte skriva patch"
-#: builtin/add.c:253
+#: builtin/add.c:358
#, c-format
msgid "Could not stat '%s'"
msgstr "Kunde inte ta status på \"%s\""
-#: builtin/add.c:255
+#: builtin/add.c:360
msgid "Empty patch. Aborted."
msgstr "Tom patch. Avbryter."
-#: builtin/add.c:261
+#: builtin/add.c:366
#, c-format
msgid "Could not apply '%s'"
msgstr "Kunde inte tillämpa \"%s\""
-#: builtin/add.c:271
+#: builtin/add.c:376
msgid "The following paths are ignored by one of your .gitignore files:\n"
msgstr "Följande sökvägar ignoreras av en av dina .gitignore-filer:\n"
-#: builtin/add.c:277 builtin/clean.c:161 builtin/fetch.c:78 builtin/mv.c:63
+#: builtin/add.c:393 builtin/clean.c:161 builtin/fetch.c:78 builtin/mv.c:63
#: builtin/prune-packed.c:76 builtin/push.c:425 builtin/remote.c:1253
#: builtin/rm.c:206
msgid "dry run"
msgstr "testkörning"
-#: builtin/add.c:278 builtin/apply.c:4409 builtin/check-ignore.c:19
+#: builtin/add.c:394 builtin/apply.c:4409 builtin/check-ignore.c:19
#: builtin/commit.c:1152 builtin/count-objects.c:95 builtin/fsck.c:613
-#: builtin/log.c:1514 builtin/mv.c:62 builtin/read-tree.c:112
+#: builtin/log.c:1518 builtin/mv.c:62 builtin/read-tree.c:112
msgid "be verbose"
msgstr "var pratsam"
-#: builtin/add.c:280
+#: builtin/add.c:396
msgid "interactive picking"
msgstr "plocka interaktivt"
-#: builtin/add.c:281 builtin/checkout.c:1060 builtin/reset.c:258
+#: builtin/add.c:397 builtin/checkout.c:1063 builtin/reset.c:258
msgid "select hunks interactively"
msgstr "välj stycken interaktivt"
-#: builtin/add.c:282
+#: builtin/add.c:398
msgid "edit current diff and apply"
msgstr "redigera aktuell diff och applicera"
-#: builtin/add.c:283
+#: builtin/add.c:399
msgid "allow adding otherwise ignored files"
msgstr "tillåt lägga till annars ignorerade filer"
-#: builtin/add.c:284
+#: builtin/add.c:400
msgid "update tracked files"
msgstr "uppdatera spårade filer"
-#: builtin/add.c:285
+#: builtin/add.c:401
msgid "record only the fact that the path will be added later"
msgstr "registrera endast att sökvägen kommer läggas till senare"
-#: builtin/add.c:286
+#: builtin/add.c:402
msgid "add changes from all tracked and untracked files"
msgstr "lägg till ändringar från alla spårade och ospårade filer"
-#: builtin/add.c:287
+#. takes no arguments
+#: builtin/add.c:405
+msgid "ignore paths removed in the working tree (same as --no-all)"
+msgstr "ignorera sökvägar borttagna i arbetskatalogen (samma som --no-all)"
+
+#: builtin/add.c:407
msgid "don't add, only refresh the index"
msgstr "lägg inte till, uppdatera endast indexet"
-#: builtin/add.c:288
+#: builtin/add.c:408
msgid "just skip files which cannot be added because of errors"
msgstr "hoppa bara över filer som inte kan läggas till på grund av fel"
-#: builtin/add.c:289
+#: builtin/add.c:409
msgid "check if - even missing - files are ignored in dry run"
msgstr "se om - även saknade - filer ignoreras i testkörning"
-#: builtin/add.c:311
+#: builtin/add.c:431
#, c-format
msgid "Use -f if you really want to add them.\n"
msgstr "Använd -f om du verkligen vill lägga till dem.\n"
-#: builtin/add.c:312
+#: builtin/add.c:432
msgid "no files added"
msgstr "inga filer har lagts till"
-#: builtin/add.c:318
+#: builtin/add.c:438
msgid "adding files failed"
msgstr "misslyckades lägga till filer"
-#.
-#. * To be consistent with "git add -p" and most Git
-#. * commands, we should default to being tree-wide, but
-#. * this is not the original behavior and can't be
-#. * changed until users trained themselves not to type
-#. * "git add -u" or "git add -A". For now, we warn and
-#. * keep the old behavior. Later, the behavior can be changed
-#. * to tree-wide, keeping the warning for a while, and
-#. * eventually we can drop the warning.
-#.
-#: builtin/add.c:335
-#, c-format
-msgid ""
-"The behavior of 'git add %s (or %s)' with no path argument from a\n"
-"subdirectory of the tree will change in Git 2.0 and should not be used "
-"anymore.\n"
-"To add content for the whole tree, run:\n"
-"\n"
-" git add %s :/\n"
-" (or git add %s :/)\n"
-"\n"
-"To restrict the command to the current directory, run:\n"
-"\n"
-" git add %s .\n"
-" (or git add %s .)\n"
-"\n"
-"With the current Git version, the command is restricted to the current "
-"directory."
-msgstr ""
-"Beteendet för \"git add %s (eller %s)\" utan sökvägsargument från en\n"
-"underkatalog i ett träd kommer ändras i Git 2.0 och bör inte längre "
-"användas.\n"
-"För att lägga till innehållet för hela trädet, använd:\n"
-"\n"
-" git add %s :/\n"
-" (eller git add %s :/)\n"
-"\n"
-"För att begränsa kommandot till aktuell katalog, använd:\n"
-"\n"
-" git add %s .\n"
-" (eller git add %s .)\n"
-"\n"
-"I nuvarande version av Git begränsas kommandot till aktuell katalog."
-
-#: builtin/add.c:381
+#: builtin/add.c:477
msgid "-A and -u are mutually incompatible"
msgstr "-A och -u är ömsesidigt inkompatibla"
-#: builtin/add.c:383
+#: builtin/add.c:495
msgid "Option --ignore-missing can only be used together with --dry-run"
msgstr "Flaggan --ignore-missing kan endast användas tillsammans med --dry-run"
-#: builtin/add.c:414
+#: builtin/add.c:525
#, c-format
msgid "Nothing specified, nothing added.\n"
msgstr "Inget angivet, inget tillagt.\n"
-#: builtin/add.c:415
+#: builtin/add.c:526
#, c-format
msgid "Maybe you wanted to say 'git add .'?\n"
msgstr "Kanske menade du att skriva \"git add .\"?\n"
-#: builtin/add.c:421 builtin/check-ignore.c:67 builtin/clean.c:204
+#: builtin/add.c:532 builtin/check-ignore.c:66 builtin/clean.c:204
#: builtin/commit.c:293 builtin/mv.c:82 builtin/rm.c:235
msgid "index file corrupt"
msgstr "indexfilen trasig"
-#: builtin/add.c:481 builtin/apply.c:4505 builtin/mv.c:229 builtin/rm.c:370
+#: builtin/add.c:604 builtin/apply.c:4505 builtin/mv.c:229 builtin/rm.c:370
msgid "Unable to write new index file"
msgstr "Kunde inte skriva ny indexfil"
msgid "Checking patch %s..."
msgstr "Kontrollerar patchen %s..."
-#: builtin/apply.c:3679 builtin/checkout.c:215 builtin/reset.c:124
+#: builtin/apply.c:3679 builtin/checkout.c:216 builtin/reset.c:124
#, c-format
msgid "make_cache_entry failed for path '%s'"
msgstr "make_cache_entry misslyckades för sökvägen \"%s\""
msgid "build a temporary index based on embedded index information"
msgstr "bygg ett temporärt index baserat på inbyggd indexinformation"
-#: builtin/apply.c:4388 builtin/checkout-index.c:197 builtin/ls-files.c:463
+#: builtin/apply.c:4388 builtin/checkout-index.c:197 builtin/ls-files.c:456
msgid "paths are separated with NUL character"
msgstr "sökvägar avdelas med NUL-tecken"
msgid "git blame [options] [rev-opts] [rev] [--] file"
msgstr "git blame [flaggor] [rev-flaggor] [rev] [--] fil"
-#: builtin/blame.c:30 builtin/shortlog.c:15
+#: builtin/blame.c:30
msgid "[rev-opts] are documented in git-rev-list(1)"
msgstr "[rev-flaggor] dokumenteras i git-rev-list(1)"
-#: builtin/blame.c:2350
+#: builtin/blame.c:2355
msgid "Show blame entries as we find them, incrementally"
msgstr "Visa klandringsposter när vi hittar dem, interaktivt"
-#: builtin/blame.c:2351
+#: builtin/blame.c:2356
msgid "Show blank SHA-1 for boundary commits (Default: off)"
msgstr "Visa blank SHA-1 för gränsincheckningar (Standard: av)"
-#: builtin/blame.c:2352
+#: builtin/blame.c:2357
msgid "Do not treat root commits as boundaries (Default: off)"
msgstr "Behandla inte rotincheckningar som gränser (Standard: av)"
-#: builtin/blame.c:2353
+#: builtin/blame.c:2358
msgid "Show work cost statistics"
msgstr "Visa statistik över arbetskostnad"
-#: builtin/blame.c:2354
+#: builtin/blame.c:2359
msgid "Show output score for blame entries"
msgstr "Visa utdatapoäng för klandringsposter"
-#: builtin/blame.c:2355
+#: builtin/blame.c:2360
msgid "Show original filename (Default: auto)"
msgstr "Visa originalfilnamn (Standard: auto)"
-#: builtin/blame.c:2356
+#: builtin/blame.c:2361
msgid "Show original linenumber (Default: off)"
msgstr "Visa ursprungligt radnummer (Standard: av)"
-#: builtin/blame.c:2357
+#: builtin/blame.c:2362
msgid "Show in a format designed for machine consumption"
msgstr "Visa i ett format avsett för maskinkonsumtion"
-#: builtin/blame.c:2358
+#: builtin/blame.c:2363
msgid "Show porcelain format with per-line commit information"
msgstr "Visa porslinsformat med per-rad-incheckningsinformation"
-#: builtin/blame.c:2359
+#: builtin/blame.c:2364
msgid "Use the same output mode as git-annotate (Default: off)"
msgstr "Använd samma utdataläge som git-annotate (Standard: av)"
-#: builtin/blame.c:2360
+#: builtin/blame.c:2365
msgid "Show raw timestamp (Default: off)"
msgstr "Visa rå tidsstämpel (Standard: av)"
-#: builtin/blame.c:2361
+#: builtin/blame.c:2366
msgid "Show long commit SHA1 (Default: off)"
msgstr "Visa lång inchecknings-SHA1 (Standard: av)"
-#: builtin/blame.c:2362
+#: builtin/blame.c:2367
msgid "Suppress author name and timestamp (Default: off)"
msgstr "Undertryck författarnamn och tidsstämpel (Standard: av)"
-#: builtin/blame.c:2363
+#: builtin/blame.c:2368
msgid "Show author email instead of name (Default: off)"
msgstr "Visa författarens e-post istället för namn (Standard: av)"
-#: builtin/blame.c:2364
+#: builtin/blame.c:2369
msgid "Ignore whitespace differences"
msgstr "Ignorera ändringar i blanksteg"
-#: builtin/blame.c:2365
+#: builtin/blame.c:2370
msgid "Spend extra cycles to find better match"
msgstr "Slösa extra cykler med att hitta bättre träff"
-#: builtin/blame.c:2366
+#: builtin/blame.c:2371
msgid "Use revisions from <file> instead of calling git-rev-list"
msgstr "Använd revisioner från <fil> istället för att anropa git-rev-list"
-#: builtin/blame.c:2367
+#: builtin/blame.c:2372
msgid "Use <file>'s contents as the final image"
msgstr "Använd <fil>s innehåll som slutgiltig bild"
-#: builtin/blame.c:2368 builtin/blame.c:2369
+#: builtin/blame.c:2373 builtin/blame.c:2374
msgid "score"
msgstr "poäng"
-#: builtin/blame.c:2368
+#: builtin/blame.c:2373
msgid "Find line copies within and across files"
msgstr "Hitta kopierade rader inuti och mellan filer"
-#: builtin/blame.c:2369
+#: builtin/blame.c:2374
msgid "Find line movements within and across files"
msgstr "Hitta flyttade rader inuti och mellan filer"
-#: builtin/blame.c:2370
+#: builtin/blame.c:2375
msgid "n,m"
msgstr "n,m"
-#: builtin/blame.c:2370
+#: builtin/blame.c:2375
msgid "Process only line range n,m, counting from 1"
msgstr "Behandla endast radintervallet n,m, med början på 1"
msgid "git branch [options] (-m | -M) [<oldbranch>] <newbranch>"
msgstr "git branch [flaggor] (-m | -M) [<gammal_gren>] <ny_gren>"
-#: builtin/branch.c:146
+#: builtin/branch.c:150
#, c-format
msgid ""
"deleting branch '%s' that has been merged to\n"
"tar bort grenen \"%s\" som har slagits ihop med\n"
" \"%s\", men ännu inte slagits ihop med HEAD."
-#: builtin/branch.c:150
+#: builtin/branch.c:154
#, c-format
msgid ""
"not deleting branch '%s' that is not yet merged to\n"
"tar inte bort grenen \"%s\" som inte har slagits ihop med\n"
" \"%s\", trots att den har slagits ihop med HEAD."
-#: builtin/branch.c:164
+#: builtin/branch.c:168
#, c-format
msgid "Couldn't look up commit object for '%s'"
msgstr "Kunde inte slå upp incheckningsobjekt för \"%s\""
-#: builtin/branch.c:168
+#: builtin/branch.c:172
#, c-format
msgid ""
"The branch '%s' is not fully merged.\n"
"Grenen \"%s\" har inte slagits samman i sin helhet.\n"
"Om du är säker på att du vill ta bort den, kör \"git branch -D %s\"."
-#: builtin/branch.c:181
+#: builtin/branch.c:185
msgid "Update of config-file failed"
msgstr "Misslyckades uppdatera konfigurationsfil"
-#: builtin/branch.c:209
+#: builtin/branch.c:213
msgid "cannot use -a with -d"
msgstr "kan inte ange -a med -d"
-#: builtin/branch.c:215
+#: builtin/branch.c:219
msgid "Couldn't look up commit object for HEAD"
msgstr "Kunde inte slå upp incheckningsobjekt för HEAD"
-#: builtin/branch.c:223
+#: builtin/branch.c:227
#, c-format
msgid "Cannot delete the branch '%s' which you are currently on."
msgstr "Kan inte ta bort grenen \"%s\" som du befinner dig på för närvarande."
-#: builtin/branch.c:236
+#: builtin/branch.c:240
#, c-format
msgid "remote branch '%s' not found."
msgstr "fjärrgrenen \"%s\" hittades inte."
-#: builtin/branch.c:237
+#: builtin/branch.c:241
#, c-format
msgid "branch '%s' not found."
msgstr "grenen \"%s\" hittades inte."
-#: builtin/branch.c:251
+#: builtin/branch.c:255
#, c-format
msgid "Error deleting remote branch '%s'"
msgstr "Fel vid borttagning av fjärrgrenen \"%s\""
-#: builtin/branch.c:252
+#: builtin/branch.c:256
#, c-format
msgid "Error deleting branch '%s'"
msgstr "Fel vid borttagning av grenen \"%s\""
-#: builtin/branch.c:259
+#: builtin/branch.c:263
#, c-format
msgid "Deleted remote branch %s (was %s).\n"
msgstr "Tog bort fjärrgrenen %s (var %s).\n"
-#: builtin/branch.c:260
+#: builtin/branch.c:264
#, c-format
msgid "Deleted branch %s (was %s).\n"
msgstr "Tog bort grenen %s (var %s).\n"
-#: builtin/branch.c:362
+#: builtin/branch.c:366
#, c-format
msgid "branch '%s' does not point at a commit"
msgstr "grenen \"%s\" pekar inte på en incheckning"
-#: builtin/branch.c:434
+#: builtin/branch.c:453
#, c-format
msgid "[%s: behind %d]"
msgstr "[%s: bakom %d] "
-#: builtin/branch.c:436
+#: builtin/branch.c:455
#, c-format
msgid "[behind %d]"
msgstr "[bakom %d] "
-#: builtin/branch.c:440
+#: builtin/branch.c:459
#, c-format
msgid "[%s: ahead %d]"
msgstr "[%s: före %d] "
-#: builtin/branch.c:442
+#: builtin/branch.c:461
#, c-format
msgid "[ahead %d]"
msgstr "[före %d] "
-#: builtin/branch.c:445
+#: builtin/branch.c:464
#, c-format
msgid "[%s: ahead %d, behind %d]"
msgstr "[%s: före %d, bakom %d] "
-#: builtin/branch.c:448
+#: builtin/branch.c:467
#, c-format
msgid "[ahead %d, behind %d]"
msgstr "[före %d, bakom %d] "
-#: builtin/branch.c:470
+#: builtin/branch.c:490
msgid " **** invalid ref ****"
msgstr " **** ogiltig ref ****"
-#: builtin/branch.c:562
+#: builtin/branch.c:582
#, c-format
msgid "(no branch, rebasing %s)"
msgstr "(ingen gren, ombaserar %s)"
-#: builtin/branch.c:565
+#: builtin/branch.c:585
#, c-format
msgid "(no branch, bisect started on %s)"
msgstr "(ingen gren, \"bisect\" startad på %s)"
-#: builtin/branch.c:568
+#: builtin/branch.c:588
#, c-format
msgid "(detached from %s)"
msgstr "(frånkopplad från %s)"
-#: builtin/branch.c:571
+#: builtin/branch.c:591
msgid "(no branch)"
msgstr "(ingen gren)"
-#: builtin/branch.c:617
+#: builtin/branch.c:637
#, c-format
msgid "object '%s' does not point to a commit"
msgstr "objektet \"%s\" pekar på en incheckning"
-#: builtin/branch.c:649
+#: builtin/branch.c:669
msgid "some refs could not be read"
msgstr "vissa referenser kunde inte läsas"
-#: builtin/branch.c:662
+#: builtin/branch.c:682
msgid "cannot rename the current branch while not on any."
msgstr ""
"kunde inte byta namn på aktuell gren när du inte befinner dig på någon."
-#: builtin/branch.c:672
+#: builtin/branch.c:692
#, c-format
msgid "Invalid branch name: '%s'"
msgstr "Felaktigt namn på gren: \"%s\""
-#: builtin/branch.c:687
+#: builtin/branch.c:707
msgid "Branch rename failed"
msgstr "Misslyckades byta namn på gren"
-#: builtin/branch.c:691
+#: builtin/branch.c:711
#, c-format
msgid "Renamed a misnamed branch '%s' away"
msgstr "Bytte bort namn på en felaktigt namngiven gren \"%s\""
-#: builtin/branch.c:695
+#: builtin/branch.c:715
#, c-format
msgid "Branch renamed to %s, but HEAD is not updated!"
msgstr "Grenen namnbytt till %s, men HEAD har inte uppdaterats!"
-#: builtin/branch.c:702
+#: builtin/branch.c:722
msgid "Branch is renamed, but update of config-file failed"
msgstr "Grenen namnbytt, men misslyckades uppdatera konfigurationsfilen"
-#: builtin/branch.c:717
+#: builtin/branch.c:737
#, c-format
msgid "malformed object name %s"
msgstr "felformat objektnamn %s"
-#: builtin/branch.c:741
+#: builtin/branch.c:761
#, c-format
msgid "could not write branch description template: %s"
msgstr "kunde inte skriva grenbeskrivningsmall: %s"
-#: builtin/branch.c:771
+#: builtin/branch.c:791
msgid "Generic options"
msgstr "Allmänna flaggor"
-#: builtin/branch.c:773
+#: builtin/branch.c:793
msgid "show hash and subject, give twice for upstream branch"
msgstr "visa hash och ärenderad, ange två gånger för uppströmsgren"
-#: builtin/branch.c:774
+#: builtin/branch.c:794
msgid "suppress informational messages"
msgstr "undertryck informationsmeddelanden"
-#: builtin/branch.c:775
+#: builtin/branch.c:795
msgid "set up tracking mode (see git-pull(1))"
msgstr "ställ in spårningsläge (se git-pull(1))"
-#: builtin/branch.c:777
+#: builtin/branch.c:797
msgid "change upstream info"
msgstr "ändra uppströmsinformation"
-#: builtin/branch.c:781
+#: builtin/branch.c:801
msgid "use colored output"
msgstr "använd färgad utdata"
-#: builtin/branch.c:782
+#: builtin/branch.c:802
msgid "act on remote-tracking branches"
msgstr "arbeta på fjärrspårande grenar"
-#: builtin/branch.c:785 builtin/branch.c:791 builtin/branch.c:812
-#: builtin/branch.c:818 builtin/commit.c:1368 builtin/commit.c:1369
+#: builtin/branch.c:805 builtin/branch.c:811 builtin/branch.c:832
+#: builtin/branch.c:838 builtin/commit.c:1368 builtin/commit.c:1369
#: builtin/commit.c:1370 builtin/commit.c:1371 builtin/tag.c:468
msgid "commit"
msgstr "incheckning"
-#: builtin/branch.c:786 builtin/branch.c:792
+#: builtin/branch.c:806 builtin/branch.c:812
msgid "print only branches that contain the commit"
msgstr "visa endast grenar som innehåller incheckningen"
-#: builtin/branch.c:798
+#: builtin/branch.c:818
msgid "Specific git-branch actions:"
msgstr "Specifika git-branch-åtgärder:"
-#: builtin/branch.c:799
+#: builtin/branch.c:819
msgid "list both remote-tracking and local branches"
msgstr "visa både fjärrspårande och lokala grenar"
-#: builtin/branch.c:801
+#: builtin/branch.c:821
msgid "delete fully merged branch"
msgstr "ta bort helt sammanslagen gren"
-#: builtin/branch.c:802
+#: builtin/branch.c:822
msgid "delete branch (even if not merged)"
msgstr "ta bort gren (även om inte helt sammanslagen)"
-#: builtin/branch.c:803
+#: builtin/branch.c:823
msgid "move/rename a branch and its reflog"
msgstr "flytta/ta bort en gren och dess reflogg"
-#: builtin/branch.c:804
+#: builtin/branch.c:824
msgid "move/rename a branch, even if target exists"
msgstr "flytta/ta bort en gren, även om målet finns"
-#: builtin/branch.c:805
+#: builtin/branch.c:825
msgid "list branch names"
msgstr "lista namn på grenar"
-#: builtin/branch.c:806
+#: builtin/branch.c:826
msgid "create the branch's reflog"
msgstr "skapa grenens reflogg"
-#: builtin/branch.c:808
+#: builtin/branch.c:828
msgid "edit the description for the branch"
msgstr "redigera beskrivning för grenen"
-#: builtin/branch.c:809
+#: builtin/branch.c:829
msgid "force creation (when already exists)"
msgstr "tvinga skapande (när den redan finns)"
-#: builtin/branch.c:812
+#: builtin/branch.c:832
msgid "print only not merged branches"
msgstr "visa endast ej sammanslagna grenar"
-#: builtin/branch.c:818
+#: builtin/branch.c:838
msgid "print only merged branches"
msgstr "visa endast sammanslagna grenar"
-#: builtin/branch.c:822
+#: builtin/branch.c:842
msgid "list branches in columns"
msgstr "visa grenar i spalter"
-#: builtin/branch.c:835
+#: builtin/branch.c:855
msgid "Failed to resolve HEAD as a valid ref."
msgstr "Misslyckades slå upp HEAD som giltig referens"
-#: builtin/branch.c:840 builtin/clone.c:609
+#: builtin/branch.c:860 builtin/clone.c:619
msgid "HEAD not found below refs/heads!"
msgstr "HEAD hittades inte under refs/heads!"
-#: builtin/branch.c:863
+#: builtin/branch.c:883
msgid "--column and --verbose are incompatible"
msgstr "--column och --verbose är inkompatibla"
-#: builtin/branch.c:869 builtin/branch.c:908
+#: builtin/branch.c:889 builtin/branch.c:928
msgid "branch name required"
msgstr "grennamn krävs"
-#: builtin/branch.c:884
+#: builtin/branch.c:904
msgid "Cannot give description to detached HEAD"
msgstr "Kan inte beskriva frånkopplad HEAD"
-#: builtin/branch.c:889
+#: builtin/branch.c:909
msgid "cannot edit description of more than one branch"
msgstr "kan inte redigera beskrivning för mer än en gren"
-#: builtin/branch.c:896
+#: builtin/branch.c:916
#, c-format
msgid "No commit on branch '%s' yet."
msgstr "Inga incheckningar på grenen \"%s\" ännu"
-#: builtin/branch.c:899
+#: builtin/branch.c:919
#, c-format
msgid "No branch named '%s'."
msgstr "Ingen gren vid namnet \"%s\"."
-#: builtin/branch.c:914
+#: builtin/branch.c:934
msgid "too many branches for a rename operation"
msgstr "för många grenar för namnbyte"
-#: builtin/branch.c:919
+#: builtin/branch.c:939
msgid "too many branches to set new upstream"
msgstr "för många grenar för att byta uppström"
-#: builtin/branch.c:923
+#: builtin/branch.c:943
#, c-format
msgid ""
"could not set upstream of HEAD to %s when it does not point to any branch."
msgstr ""
"kunde inte sätta uppström för HEAD till %s när det inte pekar mot någon gren."
-#: builtin/branch.c:926 builtin/branch.c:948 builtin/branch.c:970
+#: builtin/branch.c:946 builtin/branch.c:968 builtin/branch.c:990
#, c-format
msgid "no such branch '%s'"
msgstr "okänd gren \"%s\""
-#: builtin/branch.c:930
+#: builtin/branch.c:950
#, c-format
msgid "branch '%s' does not exist"
msgstr "grenen \"%s\" finns inte"
-#: builtin/branch.c:942
+#: builtin/branch.c:962
msgid "too many branches to unset upstream"
msgstr "för många grenar för att ta bort uppström"
-#: builtin/branch.c:946
+#: builtin/branch.c:966
msgid "could not unset upstream of HEAD when it does not point to any branch."
msgstr ""
"kunde inte ta bort uppström för HEAD när det inte pekar mot någon gren."
-#: builtin/branch.c:952
+#: builtin/branch.c:972
#, c-format
msgid "Branch '%s' has no upstream information"
msgstr "Grenen \"%s\" har ingen uppströmsinformation"
-#: builtin/branch.c:967
+#: builtin/branch.c:987
msgid "it does not make sense to create 'HEAD' manually"
msgstr "kan inte skapa \"HEAD\" manuellt"
-#: builtin/branch.c:973
+#: builtin/branch.c:993
msgid "-a and -r options to 'git branch' do not make sense with a branch name"
msgstr ""
"flaggorna -a och -r på \"git branch\" kan inte anges tillsammans med ett "
"grennamn"
-#: builtin/branch.c:976
+#: builtin/branch.c:996
#, c-format
msgid ""
"The --set-upstream flag is deprecated and will be removed. Consider using --"
"Flaggan --set-upstream rekommenderas ej och kommer tas bort. Använd --track "
"eller --set-upstream-to\n"
-#: builtin/branch.c:993
+#: builtin/branch.c:1013
#, c-format
msgid ""
"\n"
"Om du vill göra så att \"%s\" spårar \"%s\" gör du så här:\n"
"\n"
-#: builtin/branch.c:994
+#: builtin/branch.c:1014
#, c-format
msgid " git branch -d %s\n"
msgstr " git branch -d %s\n"
-#: builtin/branch.c:995
+#: builtin/branch.c:1015
#, c-format
msgid " git branch --set-upstream-to %s\n"
msgstr " git branch --set-upstream-to %s\n"
msgid "Need a repository to unbundle."
msgstr "Behöver ett arkiv för att packa upp ett paket (bundle)."
-#: builtin/cat-file.c:247
+#: builtin/cat-file.c:176
msgid "git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>"
msgstr "git cat-file (-t|-s|-e|-p|<typ>|--textconv) <objekt>"
-#: builtin/cat-file.c:248
+#: builtin/cat-file.c:177
msgid "git cat-file (--batch|--batch-check) < <list_of_objects>"
msgstr "git cat-file (--batch|--batch-check) < <objektlista>"
-#: builtin/cat-file.c:266
+#: builtin/cat-file.c:195
msgid "<type> can be one of: blob, tree, commit, tag"
msgstr "<typ> kan vara en av: blob, tree, commit, tag"
-#: builtin/cat-file.c:267
+#: builtin/cat-file.c:196
msgid "show object type"
msgstr "visa objekttyp"
-#: builtin/cat-file.c:268
+#: builtin/cat-file.c:197
msgid "show object size"
msgstr "visa objektstorlek"
-#: builtin/cat-file.c:270
+#: builtin/cat-file.c:199
msgid "exit with zero when there's no error"
msgstr "avsluta med noll när det inte uppstått något fel"
-#: builtin/cat-file.c:271
+#: builtin/cat-file.c:200
msgid "pretty-print object's content"
msgstr "visa objektets innehåll snyggt"
-#: builtin/cat-file.c:273
+#: builtin/cat-file.c:202
msgid "for blob objects, run textconv on object's content"
msgstr "för blob-objekt, kör textconv på objektets innehåll"
-#: builtin/cat-file.c:275
+#: builtin/cat-file.c:204
msgid "show info and content of objects fed from the standard input"
msgstr "visa information och innehåll för objekt som listas på standard in"
-#: builtin/cat-file.c:278
+#: builtin/cat-file.c:207
msgid "show info about objects fed from the standard input"
msgstr "visa information för objekt som listas på standard in"
msgid "input paths are terminated by a null character"
msgstr "sökvägar avdelas med null-tecken"
-#: builtin/check-ignore.c:18 builtin/checkout.c:1041 builtin/gc.c:177
+#: builtin/check-ignore.c:18 builtin/checkout.c:1044 builtin/gc.c:177
msgid "suppress progress reporting"
msgstr "undertryck förloppsrapportering"
-#: builtin/check-ignore.c:151
+#: builtin/check-ignore.c:146
msgid "cannot specify pathnames with --stdin"
msgstr "kan inte ange sökvägsnamn med --stdin"
-#: builtin/check-ignore.c:154
+#: builtin/check-ignore.c:149
msgid "-z only makes sense with --stdin"
msgstr "-z kan endast användas tillsammans med --stdin"
-#: builtin/check-ignore.c:156
+#: builtin/check-ignore.c:151
msgid "no path specified"
msgstr "ingen sökväg angavs"
-#: builtin/check-ignore.c:160
+#: builtin/check-ignore.c:155
msgid "--quiet is only valid with a single pathname"
msgstr "--quiet kan endast användas med ett enkelt sökvägsnamn"
-#: builtin/check-ignore.c:162
+#: builtin/check-ignore.c:157
msgid "cannot have both --quiet and --verbose"
msgstr "kan inte använda både --quiet och --verbose"
msgid "git checkout [options] [<branch>] -- <file>..."
msgstr "git checkout [flaggor] [<gren>] -- <fil>..."
-#: builtin/checkout.c:116 builtin/checkout.c:149
+#: builtin/checkout.c:117 builtin/checkout.c:150
#, c-format
msgid "path '%s' does not have our version"
msgstr "sökvägen \"%s\" har inte vår version"
-#: builtin/checkout.c:118 builtin/checkout.c:151
+#: builtin/checkout.c:119 builtin/checkout.c:152
#, c-format
msgid "path '%s' does not have their version"
msgstr "sökvägen \"%s\" har inte deras version"
-#: builtin/checkout.c:134
+#: builtin/checkout.c:135
#, c-format
msgid "path '%s' does not have all necessary versions"
msgstr "sökvägen \"%s\" innehåller inte alla nödvändiga versioner"
-#: builtin/checkout.c:178
+#: builtin/checkout.c:179
#, c-format
msgid "path '%s' does not have necessary versions"
msgstr "sökvägen \"%s\" innehåller inte nödvändiga versioner"
-#: builtin/checkout.c:195
+#: builtin/checkout.c:196
#, c-format
msgid "path '%s': cannot merge"
msgstr "sökväg \"%s\": kan inte slå ihop"
-#: builtin/checkout.c:212
+#: builtin/checkout.c:213
#, c-format
msgid "Unable to add merge result for '%s'"
msgstr "Kunde inte lägga till sammanslagningsresultat för \"%s\""
-#: builtin/checkout.c:236 builtin/checkout.c:239 builtin/checkout.c:242
-#: builtin/checkout.c:245
+#: builtin/checkout.c:237 builtin/checkout.c:240 builtin/checkout.c:243
+#: builtin/checkout.c:246
#, c-format
msgid "'%s' cannot be used with updating paths"
msgstr "\"%s\" kan inte användas vid uppdatering av sökvägar"
-#: builtin/checkout.c:248 builtin/checkout.c:251
+#: builtin/checkout.c:249 builtin/checkout.c:252
#, c-format
msgid "'%s' cannot be used with %s"
msgstr "\"%s\" kan inte användas med %s"
-#: builtin/checkout.c:254
+#: builtin/checkout.c:255
#, c-format
msgid "Cannot update paths and switch to branch '%s' at the same time."
msgstr "Kan inte uppdatera sökvägar och växla till grenen \"%s\" samtidigt."
-#: builtin/checkout.c:265 builtin/checkout.c:455
+#: builtin/checkout.c:266 builtin/checkout.c:458
msgid "corrupt index file"
msgstr "indexfilen är trasig"
-#: builtin/checkout.c:326 builtin/checkout.c:333
+#: builtin/checkout.c:329 builtin/checkout.c:336
#, c-format
msgid "path '%s' is unmerged"
msgstr "sökvägen \"%s\" har inte slagits ihop"
-#: builtin/checkout.c:477
+#: builtin/checkout.c:480
msgid "you need to resolve your current index first"
msgstr "du måste lösa ditt befintliga index först"
-#: builtin/checkout.c:598
+#: builtin/checkout.c:601
#, c-format
msgid "Can not do reflog for '%s'\n"
msgstr "Kan inte skapa referenslog för \"%s\"\n"
-#: builtin/checkout.c:631
+#: builtin/checkout.c:634
msgid "HEAD is now at"
msgstr "HEAD är nu på"
-#: builtin/checkout.c:638
+#: builtin/checkout.c:641
#, c-format
msgid "Reset branch '%s'\n"
msgstr "Återställ gren \"%s\"\n"
-#: builtin/checkout.c:641
+#: builtin/checkout.c:644
#, c-format
msgid "Already on '%s'\n"
msgstr "Redan på \"%s\"\n"
-#: builtin/checkout.c:645
+#: builtin/checkout.c:648
#, c-format
msgid "Switched to and reset branch '%s'\n"
msgstr "Växlade till och nollställde grenen \"%s\"\n"
-#: builtin/checkout.c:647 builtin/checkout.c:984
+#: builtin/checkout.c:650 builtin/checkout.c:987
#, c-format
msgid "Switched to a new branch '%s'\n"
msgstr "Växlade till en ny gren \"%s\"\n"
-#: builtin/checkout.c:649
+#: builtin/checkout.c:652
#, c-format
msgid "Switched to branch '%s'\n"
msgstr "Växlade till grenen \"%s\"\n"
-#: builtin/checkout.c:705
+#: builtin/checkout.c:708
#, c-format
msgid " ... and %d more.\n"
msgstr " ... och %d till.\n"
#. The singular version
-#: builtin/checkout.c:711
+#: builtin/checkout.c:714
#, c-format
msgid ""
"Warning: you are leaving %d commit behind, not connected to\n"
"\n"
"%s\n"
-#: builtin/checkout.c:729
+#: builtin/checkout.c:732
#, c-format
msgid ""
"If you want to keep them by creating a new branch, this may be a good time\n"
" git branch nytt_grennamn %s\n"
"\n"
-#: builtin/checkout.c:759
+#: builtin/checkout.c:762
msgid "internal error in revision walk"
msgstr "internt fel vid genomgång av revisioner (revision walk)"
-#: builtin/checkout.c:763
+#: builtin/checkout.c:766
msgid "Previous HEAD position was"
msgstr "Tidigare position för HEAD var"
-#: builtin/checkout.c:790 builtin/checkout.c:979
+#: builtin/checkout.c:793 builtin/checkout.c:982
msgid "You are on a branch yet to be born"
msgstr "Du är på en gren som ännu inte är född"
#. case (1)
-#: builtin/checkout.c:915
+#: builtin/checkout.c:918
#, c-format
msgid "invalid reference: %s"
msgstr "felaktig referens: %s"
#. case (1): want a tree
-#: builtin/checkout.c:954
+#: builtin/checkout.c:957
#, c-format
msgid "reference is not a tree: %s"
msgstr "referensen är inte ett träd: %s"
-#: builtin/checkout.c:993
+#: builtin/checkout.c:996
msgid "paths cannot be used with switching branches"
msgstr "sökvägar kan inte användas vid byte av gren"
-#: builtin/checkout.c:996 builtin/checkout.c:1000
+#: builtin/checkout.c:999 builtin/checkout.c:1003
#, c-format
msgid "'%s' cannot be used with switching branches"
msgstr "\"%s\" kan inte användas vid byte av gren"
-#: builtin/checkout.c:1004 builtin/checkout.c:1007 builtin/checkout.c:1012
-#: builtin/checkout.c:1015
+#: builtin/checkout.c:1007 builtin/checkout.c:1010 builtin/checkout.c:1015
+#: builtin/checkout.c:1018
#, c-format
msgid "'%s' cannot be used with '%s'"
msgstr "\"%s\" kan inte användas med \"%s\""
-#: builtin/checkout.c:1020
+#: builtin/checkout.c:1023
#, c-format
msgid "Cannot switch branch to a non-commit '%s'"
msgstr "Kan inte växla gren till icke-incheckningen \"%s\""
-#: builtin/checkout.c:1042 builtin/checkout.c:1044 builtin/clone.c:90
+#: builtin/checkout.c:1045 builtin/checkout.c:1047 builtin/clone.c:90
#: builtin/remote.c:169 builtin/remote.c:171
msgid "branch"
msgstr "gren"
-#: builtin/checkout.c:1043
+#: builtin/checkout.c:1046
msgid "create and checkout a new branch"
msgstr "skapa och checka ut en ny gren"
-#: builtin/checkout.c:1045
+#: builtin/checkout.c:1048
msgid "create/reset and checkout a branch"
msgstr "skapa/nollställ och checka ut en gren"
-#: builtin/checkout.c:1046
+#: builtin/checkout.c:1049
msgid "create reflog for new branch"
msgstr "skapa reflogg för ny gren"
-#: builtin/checkout.c:1047
+#: builtin/checkout.c:1050
msgid "detach the HEAD at named commit"
msgstr "koppla från HEAD vid namngiven incheckning"
-#: builtin/checkout.c:1048
+#: builtin/checkout.c:1051
msgid "set upstream info for new branch"
msgstr "sätt uppströmsinformation för ny gren"
-#: builtin/checkout.c:1050
+#: builtin/checkout.c:1053
msgid "new branch"
msgstr "ny gren"
-#: builtin/checkout.c:1050
+#: builtin/checkout.c:1053
msgid "new unparented branch"
msgstr "ny gren utan förälder"
-#: builtin/checkout.c:1051
+#: builtin/checkout.c:1054
msgid "checkout our version for unmerged files"
msgstr "checka ut vår version för ej sammanslagna filer"
-#: builtin/checkout.c:1053
+#: builtin/checkout.c:1056
msgid "checkout their version for unmerged files"
msgstr "checka ut deras version för ej sammanslagna filer"
-#: builtin/checkout.c:1055
+#: builtin/checkout.c:1058
msgid "force checkout (throw away local modifications)"
msgstr "tvinga utcheckning (kasta bort lokala ändringar)"
-#: builtin/checkout.c:1056
+#: builtin/checkout.c:1059
msgid "perform a 3-way merge with the new branch"
msgstr "utför en 3-vägssammanslagning för den nya grenen"
-#: builtin/checkout.c:1057 builtin/merge.c:217
+#: builtin/checkout.c:1060 builtin/merge.c:217
msgid "update ignored files (default)"
msgstr "uppdatera ignorerade filer (standard)"
-#: builtin/checkout.c:1058 builtin/log.c:1149 parse-options.h:245
+#: builtin/checkout.c:1061 builtin/log.c:1158 parse-options.h:245
msgid "style"
msgstr "stil"
-#: builtin/checkout.c:1059
+#: builtin/checkout.c:1062
msgid "conflict style (merge or diff3)"
msgstr "konfliktstil (merge eller diff3)"
-#: builtin/checkout.c:1062
+#: builtin/checkout.c:1065
+msgid "do not limit pathspecs to sparse entries only"
+msgstr "begränsa inte sökvägar till endast glesa poster"
+
+#: builtin/checkout.c:1067
msgid "second guess 'git checkout no-such-branch'"
msgstr "förutspå \"git checkout gren-saknas\""
-#: builtin/checkout.c:1086
+#: builtin/checkout.c:1091
msgid "-b, -B and --orphan are mutually exclusive"
msgstr "-b, -B och --orphan är ömsesidigt uteslutande"
-#: builtin/checkout.c:1103
+#: builtin/checkout.c:1108
msgid "--track needs a branch name"
msgstr "--track behöver ett namn på en gren"
-#: builtin/checkout.c:1110
+#: builtin/checkout.c:1115
msgid "Missing branch name; try -b"
msgstr "Grennamn saknas; försök med -b"
-#: builtin/checkout.c:1145
+#: builtin/checkout.c:1150
msgid "invalid path specification"
msgstr "felaktig sökvägsangivelse"
-#: builtin/checkout.c:1152
+#: builtin/checkout.c:1157
#, c-format
msgid ""
"Cannot update paths and switch to branch '%s' at the same time.\n"
"Kan inte uppdatera sökvägar och växla till grenen \"%s\" samtidigt.\n"
"Ville du checka ut \"%s\" som inte kan lösas som en utcheckning?"
-#: builtin/checkout.c:1157
+#: builtin/checkout.c:1162
#, c-format
msgid "git checkout: --detach does not take a path argument '%s'"
msgstr "git checkout: --detach tar inte en sökväg som argument \"%s\""
-#: builtin/checkout.c:1161
+#: builtin/checkout.c:1166
msgid ""
"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
"checking out of the index."
msgstr "ta bort hela kataloger"
#: builtin/clean.c:165 builtin/describe.c:412 builtin/grep.c:717
-#: builtin/ls-files.c:494 builtin/name-rev.c:231 builtin/show-ref.c:182
+#: builtin/ls-files.c:487 builtin/name-rev.c:231 builtin/show-ref.c:182
msgid "pattern"
msgstr "mönster"
msgid "set config inside the new repository"
msgstr "ställ in konfiguration i det nya arkivet"
-#: builtin/clone.c:244
+#: builtin/clone.c:254
#, c-format
-msgid "reference repository '%s' is not a local directory."
-msgstr "referensarkivet \"%s\" är inte en lokal katalog."
+msgid "reference repository '%s' is not a local repository."
+msgstr "referensarkivet \"%s\" är inte ett lokalt arkiv."
-#: builtin/clone.c:307
+#: builtin/clone.c:317
#, c-format
msgid "failed to create directory '%s'"
msgstr "misslyckades skapa katalogen \"%s\""
-#: builtin/clone.c:309 builtin/diff.c:77
+#: builtin/clone.c:319 builtin/diff.c:77
#, c-format
msgid "failed to stat '%s'"
msgstr "misslyckades ta status på \"%s\""
-#: builtin/clone.c:311
+#: builtin/clone.c:321
#, c-format
msgid "%s exists and is not a directory"
msgstr "%s finns och är ingen katalog"
-#: builtin/clone.c:325
+#: builtin/clone.c:335
#, c-format
msgid "failed to stat %s\n"
msgstr "misslyckades ta status på %s\n"
-#: builtin/clone.c:347
+#: builtin/clone.c:357
#, c-format
msgid "failed to create link '%s'"
msgstr "misslyckades skapa länken \"%s\""
-#: builtin/clone.c:351
+#: builtin/clone.c:361
#, c-format
msgid "failed to copy file to '%s'"
msgstr "misslyckades kopiera filen till \"%s\""
-#: builtin/clone.c:374
+#: builtin/clone.c:384
#, c-format
msgid "done.\n"
msgstr "klart.\n"
-#: builtin/clone.c:387
+#: builtin/clone.c:397
msgid ""
"Clone succeeded, but checkout failed.\n"
"You can inspect what was checked out with 'git status'\n"
"Du kan inspektera det som checkades ut med \"git status\"\n"
"och försöka checka ut igen med \"git checkout -f HEAD\"\n"
-#: builtin/clone.c:466
+#: builtin/clone.c:476
#, c-format
msgid "Could not find remote branch %s to clone."
msgstr "Kunde inte hitta fjärrgrenen %s för att klona."
-#: builtin/clone.c:540
+#: builtin/clone.c:550
msgid "remote did not send all necessary objects"
msgstr "fjärren sände inte alla nödvändiga objekt"
-#: builtin/clone.c:600
+#: builtin/clone.c:610
msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
msgstr ""
"HEAD hos fjärren pekar på en obefintlig referens, kan inte checka ut.\n"
-#: builtin/clone.c:631
+#: builtin/clone.c:641
msgid "unable to checkout working tree"
msgstr "kunde inte checka ut arbetskatalogen"
-#: builtin/clone.c:739
+#: builtin/clone.c:749
msgid "Too many arguments."
msgstr "För många argument."
-#: builtin/clone.c:743
+#: builtin/clone.c:753
msgid "You must specify a repository to clone."
msgstr "Du måste ange ett arkiv att klona."
-#: builtin/clone.c:754
+#: builtin/clone.c:764
#, c-format
msgid "--bare and --origin %s options are incompatible."
msgstr "flaggorna --bare och --origin %s är inkompatibla."
-#: builtin/clone.c:757
+#: builtin/clone.c:767
msgid "--bare and --separate-git-dir are incompatible."
msgstr "flaggorna --bare och --separate-git-dir är inkompatibla."
-#: builtin/clone.c:770
+#: builtin/clone.c:780
#, c-format
msgid "repository '%s' does not exist"
msgstr "arkivet \"%s\" finns inte"
-#: builtin/clone.c:775
+#: builtin/clone.c:785
msgid "--depth is ignored in local clones; use file:// instead."
msgstr "--depth ignoreras i lokala kloningar; använd file:// istället"
-#: builtin/clone.c:785
+#: builtin/clone.c:795
#, c-format
msgid "destination path '%s' already exists and is not an empty directory."
msgstr "destinationssökvägen \"%s\" finns redan och är inte en tom katalog."
-#: builtin/clone.c:795
+#: builtin/clone.c:805
#, c-format
msgid "working tree '%s' already exists."
msgstr "arbetsträdet \"%s\" finns redan."
-#: builtin/clone.c:808 builtin/clone.c:820
+#: builtin/clone.c:818 builtin/clone.c:830
#, c-format
msgid "could not create leading directories of '%s'"
msgstr "kunde inte skapa inledande kataloger för \"%s\""
-#: builtin/clone.c:811
+#: builtin/clone.c:821
#, c-format
msgid "could not create work tree dir '%s'."
msgstr "kunde inte skapa arbetskatalogen \"%s\""
-#: builtin/clone.c:830
+#: builtin/clone.c:840
#, c-format
msgid "Cloning into bare repository '%s'...\n"
msgstr "Klonar till ett naket arkiv \"%s\"...\n"
-#: builtin/clone.c:832
+#: builtin/clone.c:842
#, c-format
msgid "Cloning into '%s'...\n"
msgstr "Klonar till \"%s\"...\n"
-#: builtin/clone.c:867
+#: builtin/clone.c:877
#, c-format
msgid "Don't know how to clone %s"
msgstr "Vet inte hur man klonar %s"
-#: builtin/clone.c:916
+#: builtin/clone.c:926
#, c-format
msgid "Remote branch %s not found in upstream %s"
msgstr "Fjärrgrenen %s hittades inte i uppströmsarkivet %s"
-#: builtin/clone.c:923
+#: builtin/clone.c:933
msgid "You appear to have cloned an empty repository."
msgstr "Du verkar ha klonat ett tomt arkiv."
msgid "could not lookup commit %s"
msgstr "kunde inte slå upp incheckningen %s"
-#: builtin/commit.c:612 builtin/shortlog.c:272
+#: builtin/commit.c:612 builtin/shortlog.c:270
#, c-format
msgid "(reading log message from standard input)\n"
msgstr "(läser loggmeddelande från standard in)\n"
msgid "terminate entries with NUL"
msgstr "terminera poster med NUL"
-#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/fast-export.c:653
-#: builtin/fast-export.c:656 builtin/tag.c:459
+#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/fast-export.c:659
+#: builtin/fast-export.c:662 builtin/tag.c:459
msgid "mode"
msgstr "läge"
msgid "the commit is authored by me now (used with -C/-c/--amend)"
msgstr "jag är nu författare av incheckningen (används med -C/-c/--amend)"
-#: builtin/commit.c:1373 builtin/log.c:1104 builtin/revert.c:109
+#: builtin/commit.c:1373 builtin/log.c:1113 builtin/revert.c:109
msgid "add Signed-off-by:"
msgstr "lägg till Signed-off-by:"
msgstr "respektera inkluderingsdirektiv vid uppslag"
#: builtin/count-objects.c:82
-msgid "git count-objects [-v]"
-msgstr "git count-objects [-v]"
+msgid "git count-objects [-v] [-H | --human-readable]"
+msgstr "git count-objects [-v] [-H | --human-readable]"
+
+#: builtin/count-objects.c:97
+msgid "print sizes in human readable format"
+msgstr "skriv storlekar i människoläsbart format"
#: builtin/describe.c:15
msgid "git describe [options] <committish>*"
msgid "git fast-export [rev-list-opts]"
msgstr "git fast-export [rev-list-flaggor]"
-#: builtin/fast-export.c:652
+#: builtin/fast-export.c:658
msgid "show progress after <n> objects"
msgstr "visa förlopp efter <n> objekt"
-#: builtin/fast-export.c:654
+#: builtin/fast-export.c:660
msgid "select handling of signed tags"
msgstr "välj hantering av signerade taggar"
-#: builtin/fast-export.c:657
+#: builtin/fast-export.c:663
msgid "select handling of tags that tag filtered objects"
msgstr "välj hantering av taggar som har taggfiltrerade objekt"
-#: builtin/fast-export.c:660
+#: builtin/fast-export.c:666
msgid "Dump marks to this file"
msgstr "Dump märken till filen"
-#: builtin/fast-export.c:662
+#: builtin/fast-export.c:668
msgid "Import marks from this file"
msgstr "Importera märken från filen"
-#: builtin/fast-export.c:664
+#: builtin/fast-export.c:670
msgid "Fake a tagger when tags lack one"
msgstr "Fejka taggare när taggen saknar en"
-#: builtin/fast-export.c:666
+#: builtin/fast-export.c:672
msgid "Output full tree for each commit"
msgstr "Skriv ut hela trädet för varje incheckning"
-#: builtin/fast-export.c:668
+#: builtin/fast-export.c:674
msgid "Use the done feature to terminate the stream"
msgstr "Använd done-funktionen för att avsluta strömmen"
-#: builtin/fast-export.c:669
+#: builtin/fast-export.c:675
msgid "Skip output of blob data"
msgstr "Hoppa över skrivning av blob-data"
msgid "convert to a complete repository"
msgstr "konvertera till komplett arkiv"
-#: builtin/fetch.c:88 builtin/log.c:1121
+#: builtin/fetch.c:88 builtin/log.c:1130
msgid "dir"
msgstr "kat"
msgstr ""
"git fmt-merge-msg [-m <meddelande>] [--log[=<n>]|--no-log] [--file <fil>]"
-#: builtin/fmt-merge-msg.c:659 builtin/fmt-merge-msg.c:662 builtin/grep.c:701
-#: builtin/merge.c:188 builtin/show-branch.c:656 builtin/show-ref.c:175
+#: builtin/fmt-merge-msg.c:663 builtin/fmt-merge-msg.c:666 builtin/grep.c:701
+#: builtin/merge.c:188 builtin/show-branch.c:655 builtin/show-ref.c:175
#: builtin/tag.c:446 parse-options.h:133 parse-options.h:239
msgid "n"
msgstr "n"
-#: builtin/fmt-merge-msg.c:660
+#: builtin/fmt-merge-msg.c:664
msgid "populate log with at most <n> entries from shortlog"
msgstr "fyll i loggen med som mest <n> poster från shortlog"
-#: builtin/fmt-merge-msg.c:663
+#: builtin/fmt-merge-msg.c:667
msgid "alias for --log (deprecated)"
msgstr "alias för --log (avråds)"
-#: builtin/fmt-merge-msg.c:666
+#: builtin/fmt-merge-msg.c:670
msgid "text"
msgstr "text"
-#: builtin/fmt-merge-msg.c:667
+#: builtin/fmt-merge-msg.c:671
msgid "use <text> as start of message"
msgstr "inled meddelande med <text>"
-#: builtin/fmt-merge-msg.c:668
+#: builtin/fmt-merge-msg.c:672
msgid "file to read from"
msgstr "fil att läsa från"
msgid "process file as it were from this path"
msgstr "hantera filen som om den kom från sökvägen"
-#: builtin/help.c:42
+#: builtin/help.c:43
msgid "print all available commands"
msgstr "visa alla tillgängliga kommandon"
-#: builtin/help.c:43
+#: builtin/help.c:44
+msgid "print list of useful guides"
+msgstr "lista användbara vägledningar"
+
+#: builtin/help.c:45
msgid "show man page"
msgstr "visa manualsida"
-#: builtin/help.c:44
+#: builtin/help.c:46
msgid "show manual in web browser"
msgstr "visa manual i webbläsare"
-#: builtin/help.c:46
+#: builtin/help.c:48
msgid "show info page"
msgstr "visa info-sida"
-#: builtin/help.c:52
-msgid "git help [--all] [--man|--web|--info] [command]"
-msgstr "git help [--all] [--man|--web|--info] [kommando]"
+#: builtin/help.c:54
+msgid "git help [--all] [--guides] [--man|--web|--info] [command]"
+msgstr "git help [--all] [--guides] [--man|--web|--info] [kommando]"
-#: builtin/help.c:64
+#: builtin/help.c:66
#, c-format
msgid "unrecognized help format '%s'"
msgstr "okänt hjälpformat: %s"
-#: builtin/help.c:92
+#: builtin/help.c:94
msgid "Failed to start emacsclient."
msgstr "Misslyckades starta emacsclient."
-#: builtin/help.c:105
+#: builtin/help.c:107
msgid "Failed to parse emacsclient version."
msgstr "Kunde inte tolka emacsclient-version."
-#: builtin/help.c:113
+#: builtin/help.c:115
#, c-format
msgid "emacsclient version '%d' too old (< 22)."
msgstr "emacsclient version \"%d\" för gammal (< 22)."
-#: builtin/help.c:131 builtin/help.c:159 builtin/help.c:168 builtin/help.c:176
+#: builtin/help.c:133 builtin/help.c:161 builtin/help.c:170 builtin/help.c:178
#, c-format
msgid "failed to exec '%s': %s"
msgstr "exec misslyckades för \"%s\": %s"
-#: builtin/help.c:216
+#: builtin/help.c:218
#, c-format
msgid ""
"'%s': path for unsupported man viewer.\n"
"\"%s\": sökväg för man-visare som ej stöds.\n"
"Använd \"man.<verktyg>.cmd\" istället."
-#: builtin/help.c:228
+#: builtin/help.c:230
#, c-format
msgid ""
"'%s': cmd for supported man viewer.\n"
"\"%s\": kommando för man-visare som stöds.\n"
"Använd \"man.<verktyg>.path\" istället."
-#: builtin/help.c:349
+#: builtin/help.c:351
#, c-format
msgid "'%s': unknown man viewer."
msgstr "\"%s\": okänd man-visare."
-#: builtin/help.c:366
+#: builtin/help.c:368
msgid "no man viewer handled the request"
msgstr "ingen man-visare hanterade förfrågan"
-#: builtin/help.c:374
+#: builtin/help.c:376
msgid "no info viewer handled the request"
msgstr "ingen info-visare hanterade förfrågan"
-#: builtin/help.c:429 builtin/help.c:436
+#: builtin/help.c:422
+msgid "Defining attributes per path"
+msgstr "Definierar attribut per sökväg"
+
+#: builtin/help.c:423
+msgid "A Git glossary"
+msgstr "En Git-ordlista"
+
+#: builtin/help.c:424
+msgid "Specifies intentionally untracked files to ignore"
+msgstr "Ange avsiktligen ospårade filer att ignorera"
+
+#: builtin/help.c:425
+msgid "Defining submodule properties"
+msgstr "Ange egenskaper för undermoduler"
+
+#: builtin/help.c:426
+msgid "Specifying revisions and ranges for Git"
+msgstr "Ange versioner och intervall i Git"
+
+#: builtin/help.c:427
+msgid "A tutorial introduction to Git (for version 1.5.1 or newer)"
+msgstr "Introduktion till Git (för version 1.5.1 och senare)"
+
+#: builtin/help.c:428
+msgid "An overview of recommended workflows with Git"
+msgstr "Översikt över rekommenderade arbetsflöden med Git"
+
+#: builtin/help.c:440
+msgid "The common Git guides are:\n"
+msgstr "De vanliga Git-vägledningarna är:\n"
+
+#: builtin/help.c:462 builtin/help.c:478
#, c-format
msgid "usage: %s%s"
msgstr "användning: %s%s"
-#: builtin/help.c:452
+#: builtin/help.c:494
#, c-format
msgid "`git %s' is aliased to `%s'"
msgstr "\"git %s\" är ett alias för \"%s\""
msgstr "Kan inte komma åt arbetskatalogen \"%s\""
#: builtin/log.c:40
-msgid "git log [<options>] [<since>..<until>] [[--] <path>...]\n"
-msgstr "git log [<flaggor>] [<sedan>..<till>] [[--] <sökväg>...]\n"
+msgid "git log [<options>] [<revision range>] [[--] <path>...]\n"
+msgstr "git log [<flaggor>] [<versionsintervall>] [[--] <sökväg>...]\n"
#: builtin/log.c:41
msgid " or: git show [options] <object>..."
msgid "Unknown type: %d"
msgstr "Okänd typ: %d"
-#: builtin/log.c:630
+#: builtin/log.c:638
msgid "format.headers without value"
msgstr "format.headers utan värde"
-#: builtin/log.c:704
+#: builtin/log.c:720
msgid "name of output directory is too long"
msgstr "namnet på utdatakatalogen är för långt"
-#: builtin/log.c:720
+#: builtin/log.c:736
#, c-format
msgid "Cannot open patch file %s"
msgstr "Kan inte öppna patchfilen %s"
-#: builtin/log.c:734
+#: builtin/log.c:750
msgid "Need exactly one range."
msgstr "Behöver precis ett intervall."
-#: builtin/log.c:742
+#: builtin/log.c:758
msgid "Not a range."
msgstr "Inte ett intervall."
-#: builtin/log.c:815
+#: builtin/log.c:860
msgid "Cover letter needs email format"
msgstr "Omslagsbrevet behöver e-postformat"
-#: builtin/log.c:888
+#: builtin/log.c:936
#, c-format
msgid "insane in-reply-to: %s"
msgstr "tokigt in-reply-to: %s"
-#: builtin/log.c:916
+#: builtin/log.c:964
msgid "git format-patch [options] [<since> | <revision range>]"
msgstr "git format-patch [flaggor] [<sedan> | <revisionsintervall>]"
-#: builtin/log.c:961
+#: builtin/log.c:1009
msgid "Two output directories?"
msgstr "Två utdatakataloger?"
-#: builtin/log.c:1099
+#: builtin/log.c:1108
msgid "use [PATCH n/m] even with a single patch"
msgstr "använd [PATCH n/m] även för en ensam patch"
-#: builtin/log.c:1102
+#: builtin/log.c:1111
msgid "use [PATCH] even with multiple patches"
msgstr "använd [PATCH] även för flera patchar"
-#: builtin/log.c:1106
+#: builtin/log.c:1115
msgid "print patches to standard out"
msgstr "skriv patcharna på stnadard ut"
-#: builtin/log.c:1108
+#: builtin/log.c:1117
msgid "generate a cover letter"
msgstr "generera ett följebrev"
-#: builtin/log.c:1110
+#: builtin/log.c:1119
msgid "use simple number sequence for output file names"
msgstr "använd enkel nummersekvens för utdatafilnamn"
-#: builtin/log.c:1111
+#: builtin/log.c:1120
msgid "sfx"
msgstr "sfx"
-#: builtin/log.c:1112
+#: builtin/log.c:1121
msgid "use <sfx> instead of '.patch'"
msgstr "använd <sfx> istället för \".patch\""
-#: builtin/log.c:1114
+#: builtin/log.c:1123
msgid "start numbering patches at <n> instead of 1"
msgstr "börja numrera patchar på <n> istället för 1"
-#: builtin/log.c:1116
+#: builtin/log.c:1125
msgid "mark the series as Nth re-roll"
msgstr "markera serien som N:te försök"
-#: builtin/log.c:1118
+#: builtin/log.c:1127
msgid "Use [<prefix>] instead of [PATCH]"
msgstr "Använd [<prefix>] istället för [PATCH]"
-#: builtin/log.c:1121
+#: builtin/log.c:1130
msgid "store resulting files in <dir>"
msgstr "spara filerna i <katalog>"
-#: builtin/log.c:1124
+#: builtin/log.c:1133
msgid "don't strip/add [PATCH]"
msgstr "ta inte bort eller lägg till [PATCH]"
-#: builtin/log.c:1127
+#: builtin/log.c:1136
msgid "don't output binary diffs"
msgstr "skriv inte binära diffar"
-#: builtin/log.c:1129
+#: builtin/log.c:1138
msgid "don't include a patch matching a commit upstream"
msgstr "ta inte med patchar som motsvarar en uppströmsincheckning"
-#: builtin/log.c:1131
+#: builtin/log.c:1140
msgid "show patch format instead of default (patch + stat)"
msgstr "visa patchformat istället för standard (patch + stat)"
-#: builtin/log.c:1133
+#: builtin/log.c:1142
msgid "Messaging"
msgstr "E-post"
-#: builtin/log.c:1134
+#: builtin/log.c:1143
msgid "header"
msgstr "huvud"
-#: builtin/log.c:1135
+#: builtin/log.c:1144
msgid "add email header"
msgstr "lägg till e-posthuvud"
-#: builtin/log.c:1136 builtin/log.c:1138
+#: builtin/log.c:1145 builtin/log.c:1147
msgid "email"
msgstr "epost"
-#: builtin/log.c:1136
+#: builtin/log.c:1145
msgid "add To: header"
msgstr "Lägg till mottagarhuvud (\"To:\")"
-#: builtin/log.c:1138
+#: builtin/log.c:1147
msgid "add Cc: header"
msgstr "Lägg till kopiehuvud (\"Cc:\")"
-#: builtin/log.c:1140
+#: builtin/log.c:1149
msgid "message-id"
msgstr "meddelande-id"
-#: builtin/log.c:1141
+#: builtin/log.c:1150
msgid "make first mail a reply to <message-id>"
msgstr "Gör det första brevet ett svar till <meddelande-id>"
-#: builtin/log.c:1142 builtin/log.c:1145
+#: builtin/log.c:1151 builtin/log.c:1154
msgid "boundary"
msgstr "gräns"
-#: builtin/log.c:1143
+#: builtin/log.c:1152
msgid "attach the patch"
msgstr "bifoga patchen"
-#: builtin/log.c:1146
+#: builtin/log.c:1155
msgid "inline the patch"
msgstr "gör patchen ett inline-objekt"
-#: builtin/log.c:1150
+#: builtin/log.c:1159
msgid "enable message threading, styles: shallow, deep"
msgstr "aktivera brevtrådning, typer: shallow, deep"
-#: builtin/log.c:1152
+#: builtin/log.c:1161
msgid "signature"
msgstr "signatur"
-#: builtin/log.c:1153
+#: builtin/log.c:1162
msgid "add a signature"
msgstr "lägg till signatur"
-#: builtin/log.c:1155
+#: builtin/log.c:1164
msgid "don't print the patch filenames"
msgstr "visa inte filnamn för patchar"
-#: builtin/log.c:1239
+#: builtin/log.c:1248
msgid "-n and -k are mutually exclusive."
msgstr "-n och -k kan inte användas samtidigt."
-#: builtin/log.c:1241
+#: builtin/log.c:1250
msgid "--subject-prefix and -k are mutually exclusive."
msgstr "--subject-prefix och -k kan inte användas samtidigt."
-#: builtin/log.c:1249
+#: builtin/log.c:1258
msgid "--name-only does not make sense"
msgstr "kan inte använda --name-only"
-#: builtin/log.c:1251
+#: builtin/log.c:1260
msgid "--name-status does not make sense"
msgstr "kan inte använda --name-status"
-#: builtin/log.c:1253
+#: builtin/log.c:1262
msgid "--check does not make sense"
msgstr "kan inte använda --check"
-#: builtin/log.c:1276
+#: builtin/log.c:1285
msgid "standard output, or directory, which one?"
msgstr "standard ut, eller katalog, vilken skall det vara?"
-#: builtin/log.c:1278
+#: builtin/log.c:1287
#, c-format
msgid "Could not create directory '%s'"
msgstr "Kunde inte skapa katalogen \"%s\""
-#: builtin/log.c:1431
+#: builtin/log.c:1435
msgid "Failed to create output files"
msgstr "Misslyckades skapa utdatafiler"
-#: builtin/log.c:1480
+#: builtin/log.c:1484
msgid "git cherry [-v] [<upstream> [<head> [<limit>]]]"
msgstr "git cherry [-v] [<uppström> [<huvud> [<gräns>]]]"
-#: builtin/log.c:1535
+#: builtin/log.c:1539
#, c-format
msgid ""
"Could not find a tracked remote branch, please specify <upstream> manually.\n"
msgstr "Kunde inte hitta en spårad fjärrgren, ange <uppström> manuellt.\n"
-#: builtin/log.c:1548 builtin/log.c:1550 builtin/log.c:1562
+#: builtin/log.c:1552 builtin/log.c:1554 builtin/log.c:1566
#, c-format
msgid "Unknown commit %s"
msgstr "Okänd incheckning %s"
-#: builtin/ls-files.c:409
+#: builtin/ls-files.c:402
msgid "git ls-files [options] [<file>...]"
msgstr "git ls-files [flaggor] [<fil>...]"
-#: builtin/ls-files.c:466
+#: builtin/ls-files.c:459
msgid "identify the file status with tags"
msgstr "identifiera filstatus med taggar"
-#: builtin/ls-files.c:468
+#: builtin/ls-files.c:461
msgid "use lowercase letters for 'assume unchanged' files"
msgstr "använd små bokstäver för \"anta oförändrade\"-filer"
-#: builtin/ls-files.c:470
+#: builtin/ls-files.c:463
msgid "show cached files in the output (default)"
msgstr "visa cachade filer i utdata (standard)"
-#: builtin/ls-files.c:472
+#: builtin/ls-files.c:465
msgid "show deleted files in the output"
msgstr "visa borttagna filer i utdata"
-#: builtin/ls-files.c:474
+#: builtin/ls-files.c:467
msgid "show modified files in the output"
msgstr "visa modifierade filer i utdata"
-#: builtin/ls-files.c:476
+#: builtin/ls-files.c:469
msgid "show other files in the output"
msgstr "visa andra filer i utdata"
-#: builtin/ls-files.c:478
+#: builtin/ls-files.c:471
msgid "show ignored files in the output"
msgstr "visa ignorerade filer i utdata"
-#: builtin/ls-files.c:481
+#: builtin/ls-files.c:474
msgid "show staged contents' object name in the output"
msgstr "visa köat innehålls objektnamn i utdata"
-#: builtin/ls-files.c:483
+#: builtin/ls-files.c:476
msgid "show files on the filesystem that need to be removed"
msgstr "visa filer i filsystemet som behöver tas bort"
-#: builtin/ls-files.c:485
+#: builtin/ls-files.c:478
msgid "show 'other' directories' name only"
msgstr "visa endast namn för \"andra\" kataloger"
-#: builtin/ls-files.c:488
+#: builtin/ls-files.c:481
msgid "don't show empty directories"
msgstr "visa inte tomma kataloger"
-#: builtin/ls-files.c:491
+#: builtin/ls-files.c:484
msgid "show unmerged files in the output"
msgstr "visa ej sammanslagna filer i utdata"
-#: builtin/ls-files.c:493
+#: builtin/ls-files.c:486
msgid "show resolve-undo information"
msgstr "visa \"resolve-undo\"-information"
-#: builtin/ls-files.c:495
+#: builtin/ls-files.c:488
msgid "skip files matching pattern"
msgstr "hoppa över filer som motsvarar mönster"
-#: builtin/ls-files.c:498
+#: builtin/ls-files.c:491
msgid "exclude patterns are read from <file>"
msgstr "exkludera mönster som läses från <fil>"
-#: builtin/ls-files.c:501
+#: builtin/ls-files.c:494
msgid "read additional per-directory exclude patterns in <file>"
msgstr "läs ytterligare per-katalog-exkluderingsmönster från <fil>"
-#: builtin/ls-files.c:503
+#: builtin/ls-files.c:496
msgid "add the standard git exclusions"
msgstr "lägg till git:s standardexkluderingar"
-#: builtin/ls-files.c:506
+#: builtin/ls-files.c:499
msgid "make the output relative to the project top directory"
msgstr "gör utdata relativ till projektets toppkatalog"
-#: builtin/ls-files.c:509
+#: builtin/ls-files.c:502
msgid "if any <file> is not in the index, treat this as an error"
msgstr "om en <fil> inte är indexet, betrakta det som ett fel"
-#: builtin/ls-files.c:510
+#: builtin/ls-files.c:503
msgid "tree-ish"
msgstr "träd-igt"
-#: builtin/ls-files.c:511
+#: builtin/ls-files.c:504
msgid "pretend that paths removed since <tree-ish> are still present"
msgstr "låtsas att sökvägar borttagna sedan <träd-igt> fortfarande finns"
-#: builtin/ls-files.c:513
+#: builtin/ls-files.c:506
msgid "show debugging data"
msgstr "visa felsökningsutdata"
"varianten."
#: builtin/revert.c:22
-msgid "git revert [options] <commit-ish>"
-msgstr "git revert [flaggor] <incheckning-igt>"
+msgid "git revert [options] <commit-ish>..."
+msgstr "git revert [flaggor] <incheckning-igt>..."
#: builtin/revert.c:23
msgid "git revert <subcommand>"
msgstr "git revert <underkommando>"
#: builtin/revert.c:28
-msgid "git cherry-pick [options] <commit-ish>"
-msgstr "git cherry-pick [flaggor] <incheckning-igt>"
+msgid "git cherry-pick [options] <commit-ish>..."
+msgstr "git cherry-pick [flaggor] <incheckning-igt>..."
#: builtin/revert.c:29
msgid "git cherry-pick <subcommand>"
msgstr "git rm: kan inte ta bort %s"
#: builtin/shortlog.c:13
-msgid "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
-msgstr ""
-"git shortlog [-n] [-s] [-e] [-w] [rev-flaggor] [--] [<incheckning-id>... ]"
+msgid "git shortlog [<options>] [<revision range>] [[--] [<path>...]]"
+msgstr "git shortlog [<flaggor>] [<versionsintervall>] [[--] <sökväg>...]"
-#: builtin/shortlog.c:133
+#: builtin/shortlog.c:131
#, c-format
msgid "Missing author: %s"
msgstr "Författare saknas: %s"
-#: builtin/shortlog.c:229
+#: builtin/shortlog.c:227
msgid "sort output according to the number of commits per author"
msgstr "sortera utdata enligt antal incheckningar per författare"
-#: builtin/shortlog.c:231
+#: builtin/shortlog.c:229
msgid "Suppress commit descriptions, only provides commit count"
msgstr "Undertryck beskrivningar, visa bara antal incheckningar"
-#: builtin/shortlog.c:233
+#: builtin/shortlog.c:231
msgid "Show the email address of each author"
msgstr "Visa e-postadress för varje författare"
-#: builtin/shortlog.c:234
+#: builtin/shortlog.c:232
msgid "w[,i1[,i2]]"
msgstr "w[,i1[,i2]]"
-#: builtin/shortlog.c:235
+#: builtin/shortlog.c:233
msgid "Linewrap output"
msgstr "Radbryt utdata"
msgid "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]"
msgstr "git show-branch (-g|--reflog)[=<n>[,<bas>]] [--list] [<ref>]"
-#: builtin/show-branch.c:651
+#: builtin/show-branch.c:650
msgid "show remote-tracking and local branches"
msgstr "visa fjärrspårande och lokala grenar"
-#: builtin/show-branch.c:653
+#: builtin/show-branch.c:652
msgid "show remote-tracking branches"
msgstr "visa fjärrspårande grenar"
-#: builtin/show-branch.c:655
+#: builtin/show-branch.c:654
msgid "color '*!+-' corresponding to the branch"
msgstr "färga \"*!+-\" enligt grenen"
-#: builtin/show-branch.c:657
+#: builtin/show-branch.c:656
msgid "show <n> more commits after the common ancestor"
msgstr "visa <n> ytterligare incheckningar efter gemensam anfader"
-#: builtin/show-branch.c:659
+#: builtin/show-branch.c:658
msgid "synonym to more=-1"
msgstr "synonym till more=-1"
-#: builtin/show-branch.c:660
+#: builtin/show-branch.c:659
msgid "suppress naming strings"
msgstr "undertyck namnsträngar"
-#: builtin/show-branch.c:662
+#: builtin/show-branch.c:661
msgid "include the current branch"
msgstr "inkludera aktuell gren"
-#: builtin/show-branch.c:664
+#: builtin/show-branch.c:663
msgid "name commits with their object names"
msgstr "namnge incheckningar med deras objektnamn"
-#: builtin/show-branch.c:666
+#: builtin/show-branch.c:665
msgid "show possible merge bases"
msgstr "visa möjliga sammanslaningsbaser"
-#: builtin/show-branch.c:668
+#: builtin/show-branch.c:667
msgid "show refs unreachable from any other ref"
msgstr "visa referenser som inte kan nås från någon annan referens"
-#: builtin/show-branch.c:670
+#: builtin/show-branch.c:669
msgid "show commits in topological order"
msgstr "visa incheckningar i topologisk ordning"
-#: builtin/show-branch.c:672
+#: builtin/show-branch.c:671
msgid "show only commits not on the first branch"
msgstr "visa endast incheckningar inte på den första grenen"
-#: builtin/show-branch.c:674
+#: builtin/show-branch.c:673
msgid "show merges reachable from only one tip"
msgstr "visa sammanslagningar som endast kan nås från en spets"
-#: builtin/show-branch.c:676
+#: builtin/show-branch.c:675
msgid "show commits where no parent comes before its children"
msgstr "visa incheckningar där ingen förälder kommer före dess barn"
-#: builtin/show-branch.c:678
+#: builtin/show-branch.c:677
msgid "<n>[,<base>]"
msgstr "<n>[,<bas>]"
-#: builtin/show-branch.c:679
+#: builtin/show-branch.c:678
msgid "show <n> most recent ref-log entries starting at base"
msgstr "visa <n> nyaste refloggposter med början på bas"
msgstr "endast användbart vid felsökning"
#: git.c:16
-msgid "See 'git help <command>' for more information on a specific command."
+msgid ""
+"'git help -a' and 'git help -g' lists available subcommands and some\n"
+"concept guides. See 'git help <command>' or 'git help <concept>'\n"
+"to read about a specific subcommand or concept."
msgstr ""
-"Se \"git help <kommando>\" för mer information om ett specifikt kommando."
+"\"git help -a\" och \"git help -g\" visar tillgängliga underkommandon och\n"
+"några konceptvägledningar. Se \"git help <kommando>\" eller \"git help\n"
+"<koncept>\" för att läsa mer om specifika underkommandon och koncept."
#: parse-options.h:156
msgid "no-op (backward compatibility)"
"Du måste sedan ange åtminstone en bra och en dålig version.\n"
"(Du kan använda \"git bisect bad\" och \"git bisect good\" för detta.)"
-#: git-bisect.sh:347 git-bisect.sh:474
+#: git-bisect.sh:363 git-bisect.sh:490
msgid "We are not bisecting."
msgstr "Vi utför ingen bisect för tillfället."
-#: git-bisect.sh:354
+#: git-bisect.sh:370
#, sh-format
msgid "'$invalid' is not a valid commit"
msgstr "\"$invalid\" är inte en giltig incheckning"
-#: git-bisect.sh:363
+#: git-bisect.sh:379
#, sh-format
msgid ""
"Could not check out original HEAD '$branch'.\n"
"Kunde inte checka ut original-HEAD \"$branch\".\n"
"Försök \"git bisect reset <incheckning>\"."
-#: git-bisect.sh:390
+#: git-bisect.sh:406
msgid "No logfile given"
msgstr "Ingen loggfil angiven"
-#: git-bisect.sh:391
+#: git-bisect.sh:407
#, sh-format
msgid "cannot read $file for replaying"
msgstr "kan inte läsa $file för uppspelning"
-#: git-bisect.sh:408
+#: git-bisect.sh:424
msgid "?? what are you talking about?"
msgstr "?? vad menar du?"
-#: git-bisect.sh:420
+#: git-bisect.sh:436
#, sh-format
msgid "running $command"
msgstr "kör $command"
-#: git-bisect.sh:427
+#: git-bisect.sh:443
#, sh-format
msgid ""
"bisect run failed:\n"
"\"bisect\"-körningen misslyckades:\n"
"felkod $res från \"$command\" är < 0 eller >= 128"
-#: git-bisect.sh:453
+#: git-bisect.sh:469
msgid "bisect run cannot continue any more"
msgstr "\"bisect\"-körningen kan inte fortsätta längre"
-#: git-bisect.sh:459
+#: git-bisect.sh:475
#, sh-format
msgid ""
"bisect run failed:\n"
"\"bisect\"-körningen misslyckades:\n"
"\"bisect_state $state\" avslutades med felkoden $res"
-#: git-bisect.sh:466
+#: git-bisect.sh:482
msgid "bisect run success"
msgstr "\"bisect\"-körningen lyckades"
msgid "Synchronizing submodule url for '$prefix$sm_path'"
msgstr "Synkroniserar undermodul-url för \"$prefix$sm_path\""
+#~ msgid "It took %.2f seconds to enumerate untracked files. 'status -uno'"
+#~ msgstr "Det tog %.2f sekunder att räkna ospårade filer. \"status -uno\""
+
+#~ msgid "may speed it up, but you have to be careful not to forget to add"
+#~ msgstr "kanske gör det snabbare, men du måste vara försiktig så att du"
+
+#~ msgid "new files yourself (see 'git help status')."
+#~ msgstr "inte glömmer lägga till filer själv (se \"git help status\")"
+
+#~ msgid "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
+#~ msgstr ""
+#~ "git shortlog [-n] [-s] [-e] [-w] [rev-flaggor] [--] [<incheckning-id>... ]"
+
+#~ msgid "See 'git help <command>' for more information on a specific command."
+#~ msgstr ""
+#~ "Se \"git help <kommando>\" för mer information om ett specifikt kommando."
+
#~ msgid "use any ref in .git/refs"
#~ msgstr "använd alla referenser i .git/refs"
#~ msgid "# Changed but not updated:"
#~ msgstr "# Ändrade men inte uppdaterade:"
-#~ msgid "A branch named '%s' already exists."
-#~ msgstr "Det finns redan en gren som heter \"%s\""
-
#~ msgid "path '%s' does not have all 3 versions"
#~ msgstr "sökvägen \"%s\" har inte alla 3 versionerna"
#~ msgid "Could not extract author time from %s"
#~ msgstr "Kunde inte hämta författartid från %s"
-#~ msgid "No author information found in %s"
-#~ msgstr "Hittade ingen författarinformation i %s"
-
#~ msgid "cherry-pick --ff cannot be used with --signoff"
#~ msgstr "cherry-pick --ff kan inte användas med --signoff"
#
msgid ""
msgstr ""
-"Project-Id-Version: git-v1.8.2.1-342-gfa728\n"
+"Project-Id-Version: git-v1.8.3-rc0-20-gc6bc7\n"
"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2013-04-10 15:16+0800\n"
-"PO-Revision-Date: 2013-04-11 14:21+0700\n"
+"POT-Creation-Date: 2013-04-30 08:25+0800\n"
+"PO-Revision-Date: 2013-05-01 14:26+0700\n"
"Last-Translator: Trần Ngọc Quân <vnwildman@gmail.com>\n"
"Language-Team: Vietnamese <translation-team-vi@lists.sourceforge.net>\n"
"Language: vi\n"
msgid "archive format"
msgstr "định dạng lưu trữ"
-#: archive.c:324 builtin/log.c:1117
+#: archive.c:324 builtin/log.c:1126
msgid "prefix"
msgstr "tiền tố"
msgid "prepend prefix to each pathname in the archive"
msgstr "nối thêm tiền tố vào từng đường dẫn tập tin trong kho lưu"
-#: archive.c:326 builtin/archive.c:88 builtin/blame.c:2366
-#: builtin/blame.c:2367 builtin/config.c:55 builtin/fast-export.c:659
-#: builtin/fast-export.c:661 builtin/grep.c:715 builtin/hash-object.c:77
-#: builtin/ls-files.c:497 builtin/ls-files.c:500 builtin/notes.c:536
+#: archive.c:326 builtin/archive.c:88 builtin/blame.c:2371
+#: builtin/blame.c:2372 builtin/config.c:55 builtin/fast-export.c:665
+#: builtin/fast-export.c:667 builtin/grep.c:715 builtin/hash-object.c:77
+#: builtin/ls-files.c:490 builtin/ls-files.c:493 builtin/notes.c:536
#: builtin/notes.c:693 builtin/read-tree.c:107 parse-options.h:149
msgid "file"
msgstr "tập-tin"
#: archive.c:330
msgid "report archived files on stderr"
-msgstr "liệt kê các tập tin được lưu trữ vào stderr (đầu ra lỗi chuẩn)"
+msgstr "liệt kê các tập tin được lưu trữ vào stderr (đầu ra lỗi tiêu chuẩn)"
#: archive.c:331
msgid "store only"
"Các mẫu dạng phủ định bị cấm dùng cho các thuộc tính của git\n"
"Dùng “\\!” cho các chuỗi văn bản có dấu chấm than dẫn đầu."
+#: branch.c:60
+#, c-format
+msgid "Not setting branch %s as its own upstream."
+msgstr "Chưa cài đặt nhánh %s như là thượng nguồn (upstream) của nó."
+
+#: branch.c:82
+#, c-format
+msgid "Branch %s set up to track remote branch %s from %s by rebasing."
+msgstr ""
+"Nhánh %s cài đặt để theo dõi vết nhánh máy chủ %s từ %s bằng cách rebase."
+
+#: branch.c:83
+#, c-format
+msgid "Branch %s set up to track remote branch %s from %s."
+msgstr "Nhánh %s cài đặt để theo vết nhánh máy chủ %s từ %s."
+
+#: branch.c:87
+#, c-format
+msgid "Branch %s set up to track local branch %s by rebasing."
+msgstr "Nhánh %s cài đặt để theo vết nhánh nội bộ %s bằng cách rebase."
+
+#: branch.c:88
+#, c-format
+msgid "Branch %s set up to track local branch %s."
+msgstr "Nhánh %s cài đặt để theo vết nhánh nội bộ %s."
+
+#: branch.c:92
+#, c-format
+msgid "Branch %s set up to track remote ref %s by rebasing."
+msgstr "Nhánh %s cài đặt để theo vết nhánh máy chủ %s bằng cách rebase."
+
+#: branch.c:93
+#, c-format
+msgid "Branch %s set up to track remote ref %s."
+msgstr "Nhánh %s cài đặt để theo vết tham chiếu máy chủ %s."
+
+#: branch.c:97
+#, c-format
+msgid "Branch %s set up to track local ref %s by rebasing."
+msgstr ""
+"Nhánh %s cài đặt để theo dõi vết tham chiếu nội bộ %s bằng cách rebase."
+
+#: branch.c:98
+#, c-format
+msgid "Branch %s set up to track local ref %s."
+msgstr "Nhánh %s cài đặt để theo dõi tham chiếu nội bộ %s."
+
+#: branch.c:118
+#, c-format
+msgid "Tracking not set up: name too long: %s"
+msgstr "Theo vết chưa được cài đặt: tên quá dài: %s"
+
+#: branch.c:137
+#, c-format
+msgid "Not tracking: ambiguous information for ref %s"
+msgstr "Không theo vết: thông tin chưa rõ ràng cho tham chiếu %s"
+
+#: branch.c:182
+#, c-format
+msgid "'%s' is not a valid branch name."
+msgstr "“%s” không phải là một tên nhánh hợp lệ."
+
+#: branch.c:187
+#, c-format
+msgid "A branch named '%s' already exists."
+msgstr "Đã có nhánh mang tên “%s”."
+
+#: branch.c:195
+msgid "Cannot force update the current branch."
+msgstr "Không thể ép buộc cập nhật nhánh hiện hành."
+
#: branch.c:201
#, c-format
msgid "Cannot setup tracking information; starting point '%s' is not a branch."
msgstr ""
-"Không thể cài đặt thông tin theo vết; điểm bắt đầu '%s' không phải là một "
+"Không thể cài đặt thông tin theo vết; điểm bắt đầu “%s” không phải là một "
"nhánh."
#: branch.c:203
"sẽ theo dõi bản đối chiếu máy chủ của nó, bạn cần dùng lệnh\n"
"\"git push -u\" để đặt cấu hình dòng ngược bạn muốn push."
+#: branch.c:250
+#, c-format
+msgid "Not a valid object name: '%s'."
+msgstr "Không phải tên đối tượng hợp lệ: “%s”."
+
+#: branch.c:270
+#, c-format
+msgid "Ambiguous object name: '%s'."
+msgstr "Tên đối tượng chưa rõ ràng: “%s”."
+
+#: branch.c:275
+#, c-format
+msgid "Not a valid branch point: '%s'."
+msgstr "Nhánh không hợp lệ: “%s”."
+
+#: branch.c:281
+msgid "Failed to lock ref for update"
+msgstr "Gặp lỗi khi khóa tham chiếu (ref) để cập nhật"
+
+#: branch.c:299
+msgid "Failed to write ref"
+msgstr "Gặp lỗi khi ghi tham chiếu (ref)"
+
#: bundle.c:36
#, c-format
msgid "'%s' does not look like a v2 bundle file"
msgid "Repository lacks these prerequisite commits:"
msgstr "Kho chứa thiếu những lần chuyển giao (commit) cần trước hết này:"
-#: bundle.c:164 sequencer.c:651 sequencer.c:1083 builtin/log.c:300
-#: builtin/log.c:754 builtin/log.c:1350 builtin/log.c:1566 builtin/merge.c:349
-#: builtin/shortlog.c:157
+#: bundle.c:164 sequencer.c:651 sequencer.c:1101 builtin/log.c:300
+#: builtin/log.c:770 builtin/log.c:1344 builtin/log.c:1570 builtin/merge.c:349
+#: builtin/shortlog.c:155
msgid "revision walk setup failed"
msgstr "cài đặt việc di chuyển qua các điểm xét lại gặp lỗi"
msgid "rev-list died"
msgstr "rev-list đã chết"
-#: bundle.c:300 builtin/log.c:1246 builtin/shortlog.c:260
+#: bundle.c:300 builtin/log.c:1255 builtin/shortlog.c:258
#, c-format
msgid "unrecognized argument: %s"
msgstr "đối số không được thừa nhận: %s"
"Tìm thấy các lỗi trong biến cấu hình “diff.dirstat”:\n"
"%s"
-#: diff.c:3480
+#: diff.c:3481
#, c-format
msgid ""
"Failed to parse --dirstat/-X option parameter:\n"
"Gặp lỗi khi phân tích đối số tùy chọn --dirstat/-X:\n"
"%s"
-#: diff.c:3494
+#: diff.c:3495
#, c-format
msgid "Failed to parse --submodule option parameter: '%s'"
msgstr "Gặp lỗi khi phân tích đối số tùy chọn --submodule: “%s”"
msgid "failed to read the cache"
msgstr "gặp lỗi khi đọc bộ nhớ tạm"
-#: merge.c:110 builtin/checkout.c:362 builtin/checkout.c:563
-#: builtin/clone.c:635
+#: merge.c:110 builtin/checkout.c:365 builtin/checkout.c:566
+#: builtin/clone.c:645
msgid "unable to write new index file"
msgstr "không thể ghi tập tin lưu bảng mục lục mới"
msgid "blob expected for %s '%s'"
msgstr "đối tượng blob được mong đợi cho %s “%s”"
-#: merge-recursive.c:773 builtin/clone.c:303
+#: merge-recursive.c:773 builtin/clone.c:313
#, c-format
msgid "failed to open '%s'"
msgstr "gặp lỗi khi mở “%s”"
#, c-format
msgid "Mainline was specified but commit %s is not a merge."
msgstr ""
-"Luồng chính được chỉ ra nhưng lần chuyển giao (commit) %s không phải là một "
-"lần hòa trộn."
+"Luồng chính đã được chỉ ra nhưng lần chuyển giao (commit) %s không phải là "
+"một lần hòa trộn."
#. TRANSLATORS: The first %s will be "revert" or
#. "cherry-pick", the second %s a SHA1
msgid "Could not format %s."
msgstr "Không thể định dạng %s."
-#: sequencer.c:1101
+#: sequencer.c:1083
+#, c-format
+msgid "%s: can't cherry-pick a %s"
+msgstr "%s: không thể cherry-pick một %s"
+
+#: sequencer.c:1085
+#, c-format
+msgid "%s: bad revision"
+msgstr "%s: điểm xét duyệt sai"
+
+#: sequencer.c:1119
msgid "Can't revert as initial commit"
msgstr "Không thể revert một lần chuyển giao (commit) khởi tạo"
-#: sequencer.c:1102
+#: sequencer.c:1120
msgid "Can't cherry-pick into empty head"
msgstr "Không thể cherry-pick vào một đầu (head) trống rỗng"
msgid "bug: unhandled diff status %c"
msgstr "lỗi: không lấy được trạng thái lệnh diff %c"
-#: wt-status.c:805
+#: wt-status.c:803
msgid "You have unmerged paths."
msgstr "Bạn có những đường dẫn chưa được hòa trộn."
-#: wt-status.c:808 wt-status.c:960
+#: wt-status.c:806 wt-status.c:958
msgid " (fix conflicts and run \"git commit\")"
msgstr " (sửa các xung đột sau đó chạy \"git commit\")"
-#: wt-status.c:811
+#: wt-status.c:809
msgid "All conflicts fixed but you are still merging."
msgstr "Tất cả các xung đột đã được giải quyết nhưng bạn vẫn đang hòa trộn."
-#: wt-status.c:814
+#: wt-status.c:812
msgid " (use \"git commit\" to conclude merge)"
msgstr " (dùng \"git commit\" để hoàn tất việc hòa trộn)"
-#: wt-status.c:824
+#: wt-status.c:822
msgid "You are in the middle of an am session."
msgstr "Bạn đang ở giữa của một phiên “am”."
-#: wt-status.c:827
+#: wt-status.c:825
msgid "The current patch is empty."
msgstr "Miếng vá hiện tại bị trống rỗng."
-#: wt-status.c:831
+#: wt-status.c:829
msgid " (fix conflicts and then run \"git am --resolved\")"
msgstr " (sửa các xung đột và sau đó chạy lệnh \"git am --resolved\")"
-#: wt-status.c:833
+#: wt-status.c:831
msgid " (use \"git am --skip\" to skip this patch)"
msgstr " (dùng \"git am --skip\" để bỏ qua miếng vá này)"
-#: wt-status.c:835
+#: wt-status.c:833
msgid " (use \"git am --abort\" to restore the original branch)"
msgstr " (dùng \"git am --abort\" để phục hồi lại nhánh nguyên thủy)"
-#: wt-status.c:895 wt-status.c:912
+#: wt-status.c:893 wt-status.c:910
#, c-format
msgid "You are currently rebasing branch '%s' on '%s'."
msgstr "Bạn hiện nay đang thực hiện việc rebase nhánh “%s” trên “%s”."
-#: wt-status.c:900 wt-status.c:917
+#: wt-status.c:898 wt-status.c:915
msgid "You are currently rebasing."
msgstr "Bạn hiện nay đang thực hiện việc rebase (tái cấu trúc)."
-#: wt-status.c:903
+#: wt-status.c:901
msgid " (fix conflicts and then run \"git rebase --continue\")"
msgstr " (sửa các xung đột và sau đó chạy lệnh \"git rebase --continue\")"
-#: wt-status.c:905
+#: wt-status.c:903
msgid " (use \"git rebase --skip\" to skip this patch)"
msgstr " (dùng \"git rebase --skip\" để bỏ qua lần vá này)"
-#: wt-status.c:907
+#: wt-status.c:905
msgid " (use \"git rebase --abort\" to check out the original branch)"
msgstr " (dùng \"git rebase --abort\" để check-out nhánh nguyên thủy)"
-#: wt-status.c:920
+#: wt-status.c:918
msgid " (all conflicts fixed: run \"git rebase --continue\")"
msgstr ""
" (khi tất cả các xung đột đã sửa xong: chạy lệnh \"git rebase --continue\")"
-#: wt-status.c:924
+#: wt-status.c:922
#, c-format
msgid ""
"You are currently splitting a commit while rebasing branch '%s' on '%s'."
"Bạn hiện nay đang thực hiện việc chia tách một lần chuyển giao (commit) "
"trong khi đang rebase nhánh “%s” trên “%s”."
-#: wt-status.c:929
+#: wt-status.c:927
msgid "You are currently splitting a commit during a rebase."
msgstr ""
"Bạn hiện tại đang cắt đôi một lần chuyển giao trong khi đang thực hiện việc "
"rebase."
-#: wt-status.c:932
+#: wt-status.c:930
msgid " (Once your working directory is clean, run \"git rebase --continue\")"
msgstr ""
" (Một khi thư mục làm việc của bạn đã gọn gàng, chạy \"git rebase --continue"
"\")"
-#: wt-status.c:936
+#: wt-status.c:934
#, c-format
msgid "You are currently editing a commit while rebasing branch '%s' on '%s'."
msgstr ""
"Bạn hiện nay đang thực hiện việc sửa chữa một lần chuyển giao (commit) trong "
"khi đang rebase nhánh “%s” trên “%s”."
-#: wt-status.c:941
+#: wt-status.c:939
msgid "You are currently editing a commit during a rebase."
msgstr "Bạn hiện đang sửa một lần chuyển giao trong khi bạn thực hiện rebase."
-#: wt-status.c:944
+#: wt-status.c:942
msgid " (use \"git commit --amend\" to amend the current commit)"
msgstr ""
" (dùng \"git commit --amend\" để tu bổ lần chuyển giao (commit) hiện tại)"
-#: wt-status.c:946
+#: wt-status.c:944
msgid ""
" (use \"git rebase --continue\" once you are satisfied with your changes)"
msgstr ""
" (dùng \"git rebase --continue\" một khi bạn cảm thấy hài lòng về những "
"thay đổi của mình)"
-#: wt-status.c:956
+#: wt-status.c:954
msgid "You are currently cherry-picking."
msgstr "Bạn hiện nay đang thực hiện việc cherry-pick."
-#: wt-status.c:963
+#: wt-status.c:961
msgid " (all conflicts fixed: run \"git commit\")"
msgstr " (khi tất cả các xung đột đã sửa xong: chạy lệnh \"git commit\")"
-#: wt-status.c:972
+#: wt-status.c:970
#, c-format
msgid "You are currently reverting commit %s."
msgstr "Bạn hiện nay đang thực hiện thao tác revert lần chuyển giao “%s”."
-#: wt-status.c:977
+#: wt-status.c:975
msgid " (fix conflicts and run \"git revert --continue\")"
msgstr " (sửa các xung đột và sau đó chạy lệnh \"git revert --continue\")"
-#: wt-status.c:980
+#: wt-status.c:978
msgid " (all conflicts fixed: run \"git revert --continue\")"
msgstr ""
" (khi tất cả các xung đột đã sửa xong: chạy lệnh \"git revert --continue\")"
-#: wt-status.c:982
+#: wt-status.c:980
msgid " (use \"git revert --abort\" to cancel the revert operation)"
msgstr " (dùng \"git revert --abort\" để hủy bỏ thao tác revert)"
-#: wt-status.c:993
+#: wt-status.c:991
#, c-format
msgid "You are currently bisecting, started from branch '%s'."
msgstr ""
"Bạn hiện nay đang thực hiện thao tác di chuyển nửa bước (bisect), bắt đầu từ "
"nhánh “%s”."
-#: wt-status.c:997
+#: wt-status.c:995
msgid "You are currently bisecting."
msgstr "Bạn hiện tại đang thực hiện việc bisect (chia đôi)."
-#: wt-status.c:1000
+#: wt-status.c:998
msgid " (use \"git bisect reset\" to get back to the original branch)"
msgstr " (dùng \"git bisect reset\" để quay trở lại nhánh nguyên thủy)"
-#: wt-status.c:1175
+#: wt-status.c:1173
msgid "On branch "
msgstr "Trên nhánh "
-#: wt-status.c:1186
+#: wt-status.c:1184
msgid "HEAD detached at "
msgstr "HEAD được tách rời tại"
-#: wt-status.c:1188
+#: wt-status.c:1186
msgid "HEAD detached from "
msgstr "HEAD được tách rời từ"
-#: wt-status.c:1191
+#: wt-status.c:1189
msgid "Not currently on any branch."
msgstr "Hiện tại chẳng ở nhánh nào cả."
-#: wt-status.c:1208
+#: wt-status.c:1206
msgid "Initial commit"
msgstr "Lần chuyển giao (commit) khởi đầu"
-#: wt-status.c:1222
+#: wt-status.c:1220
msgid "Untracked files"
msgstr "Những tập tin chưa được theo dõi"
-#: wt-status.c:1224
+#: wt-status.c:1222
msgid "Ignored files"
msgstr "Những tập tin bị lờ đi"
-#: wt-status.c:1228
+#: wt-status.c:1226
#, c-format
-msgid "It took %.2f seconds to enumerate untracked files. 'status -uno'"
-msgstr "Cần %.2f giây để đếm các tập tin chưa được theo vết. 'status -uno'"
-
-#: wt-status.c:1232
-msgid "may speed it up, but you have to be careful not to forget to add"
+msgid ""
+"It took %.2f seconds to enumerate untracked files. 'status -uno'\n"
+"may speed it up, but you have to be careful not to forget to add\n"
+"new files yourself (see 'git help status')."
msgstr ""
-"có thể làm nó nhanh lên, nhưng bạn phải cẩn trọng đừng quên thêm nó vào"
-
-#: wt-status.c:1235
-msgid "new files yourself (see 'git help status')."
-msgstr "tập tin mới của chính bạn (xem 'git help status')."
+"Cần %.2f giây để liệt kê tất cả các tập tin chưa được theo dõi. “status -"
+"uno”\n"
+"có lẽ làm nó nhanh hơn, nhưng bạn phải cẩn thận đừng quên mình phải\n"
+"tự thêm các tập tin mới (xem “git help status”.."
-#: wt-status.c:1238
+#: wt-status.c:1232
#, c-format
msgid "Untracked files not listed%s"
msgstr "Những tập tin không bị theo vết không được liệt kê ra %s"
-#: wt-status.c:1240
+#: wt-status.c:1234
msgid " (use -u option to show untracked files)"
msgstr " (dùng tùy chọn -u để hiển thị các tập tin chưa được theo dõi)"
-#: wt-status.c:1246
+#: wt-status.c:1240
msgid "No changes"
msgstr "Không có thay đổi nào"
-#: wt-status.c:1251
+#: wt-status.c:1245
#, c-format
msgid "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"
msgstr ""
"không có thay đổi nào được thêm vào commit (dùng \"git add\" và/hoặc \"git "
"commit -a\")\n"
-#: wt-status.c:1254
+#: wt-status.c:1248
#, c-format
msgid "no changes added to commit\n"
msgstr "không có thay đổi nào được thêm vào lần chuyển giao (commit)\n"
-#: wt-status.c:1257
+#: wt-status.c:1251
#, c-format
msgid ""
"nothing added to commit but untracked files present (use \"git add\" to "
"không có gì được thêm vào lần chuyển giao (commit) nhưng có những tập tin "
"không được theo dấu vết hiện diện (dùng \"git add\" để đưa vào theo dõi)\n"
-#: wt-status.c:1260
+#: wt-status.c:1254
#, c-format
msgid "nothing added to commit but untracked files present\n"
msgstr ""
"không có gì được thêm vào lần chuyển giao (commit) nhưng có những tập tin "
"không được theo dấu vết hiện diện\n"
-#: wt-status.c:1263
+#: wt-status.c:1257
#, c-format
msgid "nothing to commit (create/copy files and use \"git add\" to track)\n"
msgstr ""
" không có gì để commit (tạo/sao-chép các tập tin và dùng \"git add\" để theo "
"dõi dấu vết)\n"
-#: wt-status.c:1266 wt-status.c:1271
+#: wt-status.c:1260 wt-status.c:1265
#, c-format
msgid "nothing to commit\n"
msgstr "không có gì để chuyển giao (commit)\n"
-#: wt-status.c:1269
+#: wt-status.c:1263
#, c-format
msgid "nothing to commit (use -u to show untracked files)\n"
msgstr ""
"không có gì để chuyển giao (commit) (dùng -u để bỏ các tập tin cần theo dấu "
"vết)\n"
-#: wt-status.c:1273
+#: wt-status.c:1267
#, c-format
msgid "nothing to commit, working directory clean\n"
msgstr "không có gì để chuyển giao (commit), thư mục làm việc sạch sẽ\n"
-#: wt-status.c:1381
+#: wt-status.c:1375
msgid "HEAD (no branch)"
msgstr "HEAD (không nhánh)"
-#: wt-status.c:1387
+#: wt-status.c:1381
msgid "Initial commit on "
msgstr "Lần chuyển giao (commit) khởi tạo trên "
-#: wt-status.c:1402
+#: wt-status.c:1396
msgid "behind "
msgstr "đằng sau "
-#: wt-status.c:1405 wt-status.c:1408
+#: wt-status.c:1399 wt-status.c:1402
msgid "ahead "
msgstr "phía trước "
-#: wt-status.c:1410
+#: wt-status.c:1404
msgid ", behind "
msgstr ", đằng sau "
-#: compat/precompose_utf8.c:58 builtin/clone.c:342
+#: compat/precompose_utf8.c:58 builtin/clone.c:352
#, c-format
msgid "failed to unlink '%s'"
msgstr "bỏ liên kết (unlink) %s không thành công"
msgid "git add [options] [--] <pathspec>..."
msgstr "git add [các-tùy-chọn] [--] <pathspec>..."
-#: builtin/add.c:63
+#.
+#. * To be consistent with "git add -p" and most Git
+#. * commands, we should default to being tree-wide, but
+#. * this is not the original behavior and can't be
+#. * changed until users trained themselves not to type
+#. * "git add -u" or "git add -A". For now, we warn and
+#. * keep the old behavior. Later, the behavior can be changed
+#. * to tree-wide, keeping the warning for a while, and
+#. * eventually we can drop the warning.
+#.
+#: builtin/add.c:58
+#, c-format
+msgid ""
+"The behavior of 'git add %s (or %s)' with no path argument from a\n"
+"subdirectory of the tree will change in Git 2.0 and should not be used "
+"anymore.\n"
+"To add content for the whole tree, run:\n"
+"\n"
+" git add %s :/\n"
+" (or git add %s :/)\n"
+"\n"
+"To restrict the command to the current directory, run:\n"
+"\n"
+" git add %s .\n"
+" (or git add %s .)\n"
+"\n"
+"With the current Git version, the command is restricted to the current "
+"directory.\n"
+msgstr ""
+"Cách ứng xử của lệnh “git add %s (hay %s)” khi không có tham số đường dẫn "
+"từ\n"
+"thư-mục con của cây sẽ thay đổi kể từ Git 2.0 và không thể sử dụng như thế "
+"nữa.\n"
+"Để thêm nội dung cho toàn bộ cây, chạy:\n"
+"\n"
+" git add %s :/\n"
+" (hay git add %s :/)\n"
+"\n"
+"Để hạn chế lệnh cho thư-mục hiện tại, chạy:\n"
+"\n"
+" git add %s .\n"
+" (hay git add %s .)\n"
+"\n"
+"Với phiên bản hiện tại của Git, lệnh bị hạn chế cho thư-mục hiện tại.\n"
+
+#: builtin/add.c:100
+#, c-format
+msgid ""
+"You ran 'git add' with neither '-A (--all)' or '--ignore-removal',\n"
+"whose behaviour will change in Git 2.0 with respect to paths you removed.\n"
+"Paths like '%s' that are\n"
+"removed from your working tree are ignored with this version of Git.\n"
+"\n"
+"* 'git add --ignore-removal <pathspec>', which is the current default,\n"
+" ignores paths you removed from your working tree.\n"
+"\n"
+"* 'git add --all <pathspec>' will let you also record the removals.\n"
+"\n"
+"Run 'git status' to check the paths you removed from your working tree.\n"
+msgstr ""
+"Bạn chạy “git add” mà không có “-A (--all)” cũng không “--ignore-removal”,\n"
+"cách ứng xử của nó sẽ thay đổi kể từ Git 2.0: nó quan tâm đến các đường dẫn "
+"mà\n"
+"bạn đã gỡ bỏ. Các đường dẫn như là “%s” cái mà\n"
+"bị gỡ bỏ từ cây làm việc của bạn thì bị bỏ qua với phiên bản này của Git.\n"
+"\n"
+"* “git add --ignore-removal <pathspec>”, cái hiện tại là mặc định,\n"
+" bỏ qua các đường dẫn bạn đã gỡ bỏ từ cây làm việc của bạn.\n"
+"\n"
+"* “git add --all <pathspec>” sẽ đồng thời giúp bạn ghi lại việc dời đi.\n"
+"\n"
+"Chạy “git status” để kiểm tra các đường dẫn bạn đã gỡ bỏ từ cây làm việc của "
+"bạn.\n"
+
+#: builtin/add.c:144
#, c-format
msgid "unexpected diff status %c"
msgstr "trạng thái lệnh diff không như mong đợi %c"
-#: builtin/add.c:68 builtin/commit.c:233
+#: builtin/add.c:149 builtin/commit.c:233
msgid "updating files failed"
msgstr "Cập nhật tập tin gặp lỗi"
-#: builtin/add.c:78
+#: builtin/add.c:163
#, c-format
msgid "remove '%s'\n"
msgstr "gỡ bỏ “%s”\n"
-#: builtin/add.c:148
+#: builtin/add.c:253
msgid "Unstaged changes after refreshing the index:"
msgstr ""
"Các thay đổi không được lưu trạng thái sau khi làm tươi mới lại bảng mục lục:"
-#: builtin/add.c:151 builtin/add.c:460 builtin/rm.c:275
+#: builtin/add.c:256 builtin/add.c:572 builtin/rm.c:275
#, c-format
msgid "pathspec '%s' did not match any files"
msgstr "pathspec “%s” không khớp với bất kỳ tập tin nào"
-#: builtin/add.c:234
+#: builtin/add.c:339
msgid "Could not read the index"
msgstr "Không thể đọc bảng mục lục"
-#: builtin/add.c:244
+#: builtin/add.c:349
#, c-format
msgid "Could not open '%s' for writing."
msgstr "Không thể mở “%s” để ghi."
-#: builtin/add.c:248
+#: builtin/add.c:353
msgid "Could not write patch"
msgstr "Không thể ghi ra miếng vá"
-#: builtin/add.c:253
+#: builtin/add.c:358
#, c-format
msgid "Could not stat '%s'"
msgstr "không thể lấy trạng thái về “%s”"
-#: builtin/add.c:255
+#: builtin/add.c:360
msgid "Empty patch. Aborted."
msgstr "Miếng vá trống rỗng. Đã bỏ qua."
-#: builtin/add.c:261
+#: builtin/add.c:366
#, c-format
msgid "Could not apply '%s'"
msgstr "Không thể apply (áp dụng miếng vá) “%s”"
-#: builtin/add.c:271
+#: builtin/add.c:376
msgid "The following paths are ignored by one of your .gitignore files:\n"
msgstr ""
"Các đường dẫn theo sau đây sẽ bị lờ đi bởi một trong các tập tin .gitignore "
"của bạn:\n"
-#: builtin/add.c:277 builtin/clean.c:161 builtin/fetch.c:78 builtin/mv.c:63
+#: builtin/add.c:393 builtin/clean.c:161 builtin/fetch.c:78 builtin/mv.c:63
#: builtin/prune-packed.c:76 builtin/push.c:425 builtin/remote.c:1253
#: builtin/rm.c:206
msgid "dry run"
msgstr "chạy thử"
-#: builtin/add.c:278 builtin/apply.c:4409 builtin/check-ignore.c:19
+#: builtin/add.c:394 builtin/apply.c:4409 builtin/check-ignore.c:19
#: builtin/commit.c:1152 builtin/count-objects.c:95 builtin/fsck.c:613
-#: builtin/log.c:1514 builtin/mv.c:62 builtin/read-tree.c:112
+#: builtin/log.c:1518 builtin/mv.c:62 builtin/read-tree.c:112
msgid "be verbose"
msgstr "chi tiết"
-#: builtin/add.c:280
+#: builtin/add.c:396
msgid "interactive picking"
msgstr "sửa bằng cách tương tác"
-#: builtin/add.c:281 builtin/checkout.c:1060 builtin/reset.c:258
+#: builtin/add.c:397 builtin/checkout.c:1063 builtin/reset.c:258
msgid "select hunks interactively"
msgstr "chọn “hunks” một cách tương tác"
-#: builtin/add.c:282
+#: builtin/add.c:398
msgid "edit current diff and apply"
msgstr "sửa diff hiện nay và áp dụng nó"
-#: builtin/add.c:283
+#: builtin/add.c:399
msgid "allow adding otherwise ignored files"
msgstr "cho phép thêm các tập tin bị bỏ qua khác"
-#: builtin/add.c:284
+#: builtin/add.c:400
msgid "update tracked files"
msgstr "cập nhật các tập tin được theo vết"
-#: builtin/add.c:285
+#: builtin/add.c:401
msgid "record only the fact that the path will be added later"
msgstr "chỉ ghi lại sự việc mà đường dẫn sẽ được thêm vào sau"
-#: builtin/add.c:286
+#: builtin/add.c:402
msgid "add changes from all tracked and untracked files"
msgstr ""
"thêm các thay đổi từ tất cả các tập tin có cũng như không được theo dõi dấu "
"vết"
-#: builtin/add.c:287
+#. takes no arguments
+#: builtin/add.c:405
+msgid "ignore paths removed in the working tree (same as --no-all)"
+msgstr ""
+"lờ đi các đường dẫn bị gỡ bỏ trong cây thư mục làm việc (giống với --no-all)"
+
+#: builtin/add.c:407
msgid "don't add, only refresh the index"
msgstr "không thêm, chỉ làm tươi mới bảng mục lục"
-#: builtin/add.c:288
+#: builtin/add.c:408
msgid "just skip files which cannot be added because of errors"
msgstr "chie bỏ qua những tập tin mà nó không thể được thêm vào bởi vì gặp lỗi"
-#: builtin/add.c:289
+#: builtin/add.c:409
msgid "check if - even missing - files are ignored in dry run"
msgstr ""
"kiểm tra xem - thậm chí thiếu - tập tin bị bỏ qua trong quá trình chạy thử"
-#: builtin/add.c:311
+#: builtin/add.c:431
#, c-format
msgid "Use -f if you really want to add them.\n"
msgstr "Sử dụng tùy chọn -f nếu bạn thực sự muốn thêm chúng vào.\n"
-#: builtin/add.c:312
+#: builtin/add.c:432
msgid "no files added"
msgstr "chưa có tập tin nào được thêm vào"
-#: builtin/add.c:318
+#: builtin/add.c:438
msgid "adding files failed"
msgstr "thêm tập tin gặp lỗi"
-#.
-#. * To be consistent with "git add -p" and most Git
-#. * commands, we should default to being tree-wide, but
-#. * this is not the original behavior and can't be
-#. * changed until users trained themselves not to type
-#. * "git add -u" or "git add -A". For now, we warn and
-#. * keep the old behavior. Later, the behavior can be changed
-#. * to tree-wide, keeping the warning for a while, and
-#. * eventually we can drop the warning.
-#.
-#: builtin/add.c:335
-#, c-format
-msgid ""
-"The behavior of 'git add %s (or %s)' with no path argument from a\n"
-"subdirectory of the tree will change in Git 2.0 and should not be used "
-"anymore.\n"
-"To add content for the whole tree, run:\n"
-"\n"
-" git add %s :/\n"
-" (or git add %s :/)\n"
-"\n"
-"To restrict the command to the current directory, run:\n"
-"\n"
-" git add %s .\n"
-" (or git add %s .)\n"
-"\n"
-"With the current Git version, the command is restricted to the current "
-"directory."
-msgstr ""
-"Cách ứng xử của lệnh “git add %s (hay %s)” khi không có tham số đường dẫn "
-"từ\n"
-"thư-mục con của cây sẽ thay đổi kể từ Git 2.0 và không thể sử dụng như thế "
-"nữa.\n"
-"Để thêm nội dung cho toàn bộ cây, chạy:\n"
-"\n"
-" git add %s :/\n"
-" (hay git add %s :/)\n"
-"\n"
-"Để hạn chế lệnh cho thư-mục hiện tại, chạy:\n"
-"\n"
-" git add %s .\n"
-" (hay git add %s .)\n"
-"\n"
-"Với phiên bản hiện tại của Git, lệnh bị hạn chế cho thư-mục hiện tại."
-
-#: builtin/add.c:381
+#: builtin/add.c:477
msgid "-A and -u are mutually incompatible"
msgstr "-A và -u xung khắc nhau"
-#: builtin/add.c:383
+#: builtin/add.c:495
msgid "Option --ignore-missing can only be used together with --dry-run"
msgstr "Tùy chọn --ignore-missing chỉ có thể được dùng cùng với --dry-run"
-#: builtin/add.c:414
+#: builtin/add.c:525
#, c-format
msgid "Nothing specified, nothing added.\n"
msgstr "Không có gì được chỉ ra, không có gì được thêm vào.\n"
-#: builtin/add.c:415
+#: builtin/add.c:526
#, c-format
msgid "Maybe you wanted to say 'git add .'?\n"
msgstr "Có lẽ bạn muốn là “git add .” phải không?\n"
-#: builtin/add.c:421 builtin/check-ignore.c:67 builtin/clean.c:204
+#: builtin/add.c:532 builtin/check-ignore.c:66 builtin/clean.c:204
#: builtin/commit.c:293 builtin/mv.c:82 builtin/rm.c:235
msgid "index file corrupt"
msgstr "tập tin ghi bảng mục lục bị hỏng"
-#: builtin/add.c:481 builtin/apply.c:4505 builtin/mv.c:229 builtin/rm.c:370
+#: builtin/add.c:604 builtin/apply.c:4505 builtin/mv.c:229 builtin/rm.c:370
msgid "Unable to write new index file"
msgstr "Không thể ghi tập tin lưu bảng mục lục mới"
msgid "Checking patch %s..."
msgstr "Đang kiểm tra miếng vá %s..."
-#: builtin/apply.c:3679 builtin/checkout.c:215 builtin/reset.c:124
+#: builtin/apply.c:3679 builtin/checkout.c:216 builtin/reset.c:124
#, c-format
msgid "make_cache_entry failed for path '%s'"
msgstr "make_cache_entry gặp lỗi đối với đường dẫn “%s”"
msgstr ""
"xây dựng bảng mục lục tạm thời trên cơ sở thông tin bảng mục lục được nhúng"
-#: builtin/apply.c:4388 builtin/checkout-index.c:197 builtin/ls-files.c:463
+#: builtin/apply.c:4388 builtin/checkout-index.c:197 builtin/ls-files.c:456
msgid "paths are separated with NUL character"
msgstr "các đường dẫn bị ngăn cách bởi ký tự NULL"
msgid "git blame [options] [rev-opts] [rev] [--] file"
msgstr "git blame [các-tùy-chọn] [rev-opts] [rev] [--] tập-tin"
-#: builtin/blame.c:30 builtin/shortlog.c:15
+#: builtin/blame.c:30
msgid "[rev-opts] are documented in git-rev-list(1)"
msgstr "[rev-opts] được mô tả trong git-rev-list(1)"
-#: builtin/blame.c:2350
+#: builtin/blame.c:2355
msgid "Show blame entries as we find them, incrementally"
msgstr "Hiển thị các mục “blame” như là chúng ta thấy chúng, tăng dần"
-#: builtin/blame.c:2351
+#: builtin/blame.c:2356
msgid "Show blank SHA-1 for boundary commits (Default: off)"
msgstr ""
"Hiển thị SHA-1 trắng cho những lần chuyển giao biên giới (Mặc định: off)"
-#: builtin/blame.c:2352
+#: builtin/blame.c:2357
msgid "Do not treat root commits as boundaries (Default: off)"
msgstr "Không coi các lần chuyển giao gốc là giới hạn (Mặc định: off)"
-#: builtin/blame.c:2353
+#: builtin/blame.c:2358
msgid "Show work cost statistics"
msgstr "Hiển thị thống kê công sức làm việc"
-#: builtin/blame.c:2354
+#: builtin/blame.c:2359
msgid "Show output score for blame entries"
msgstr "Hiển thị kết xuất điểm số có các mục tin “blame”"
-#: builtin/blame.c:2355
+#: builtin/blame.c:2360
msgid "Show original filename (Default: auto)"
msgstr "Hiển thị tên tập tin gốc (Mặc định: auto)"
-#: builtin/blame.c:2356
+#: builtin/blame.c:2361
msgid "Show original linenumber (Default: off)"
msgstr "Hiển thị số dòng gốc (Mặc định: off)"
-#: builtin/blame.c:2357
+#: builtin/blame.c:2362
msgid "Show in a format designed for machine consumption"
msgstr "Hiển thị ở định dạng đã thiết kế cho sự tiêu dùng bằng máy"
-#: builtin/blame.c:2358
+#: builtin/blame.c:2363
msgid "Show porcelain format with per-line commit information"
msgstr "Hiển thị định dạng “porcelain” với thông tin chuyển giao mỗi dòng"
-#: builtin/blame.c:2359
+#: builtin/blame.c:2364
msgid "Use the same output mode as git-annotate (Default: off)"
msgstr "Dùng cùng chế độ xuất ra vóigit-annotate (Mặc định: off)"
-#: builtin/blame.c:2360
+#: builtin/blame.c:2365
msgid "Show raw timestamp (Default: off)"
msgstr "Hiển thị dấu vết thời gian dạng thô (Mặc định: off)"
-#: builtin/blame.c:2361
+#: builtin/blame.c:2366
msgid "Show long commit SHA1 (Default: off)"
msgstr "Hiển thị SHA1 của lần chuyển giao (commit) dạng dài (Mặc định: off)"
-#: builtin/blame.c:2362
+#: builtin/blame.c:2367
msgid "Suppress author name and timestamp (Default: off)"
msgstr "Không hiển thị tên tác giả và dấu vết thời gian (Mặc định: off)"
-#: builtin/blame.c:2363
+#: builtin/blame.c:2368
msgid "Show author email instead of name (Default: off)"
msgstr "Hiển thị thư điện tử của tác giả thay vì tên (Mặc định: off)"
-#: builtin/blame.c:2364
+#: builtin/blame.c:2369
msgid "Ignore whitespace differences"
msgstr "Bỏ qua các khác biệt do khoảng trắng gây ra"
-#: builtin/blame.c:2365
+#: builtin/blame.c:2370
msgid "Spend extra cycles to find better match"
msgstr "Tiêu thụ thêm năng tài nguyên máy móc để tìm kiếm tốt hơn nữa"
-#: builtin/blame.c:2366
+#: builtin/blame.c:2371
msgid "Use revisions from <file> instead of calling git-rev-list"
msgstr ""
"Sử dụng điểm xét duyệt (revision) từ <tập tin> thay vì gọi “git-rev-list”"
-#: builtin/blame.c:2367
+#: builtin/blame.c:2372
msgid "Use <file>'s contents as the final image"
msgstr "Sử dụng nội dung của <tập tin> như là ảnh cuối cùng"
-#: builtin/blame.c:2368 builtin/blame.c:2369
+#: builtin/blame.c:2373 builtin/blame.c:2374
msgid "score"
msgstr "điểm số"
-#: builtin/blame.c:2368
+#: builtin/blame.c:2373
msgid "Find line copies within and across files"
msgstr "Tìm các bản sao chép dòng trong và ngang qua tập tin"
-#: builtin/blame.c:2369
+#: builtin/blame.c:2374
msgid "Find line movements within and across files"
msgstr "Tìm các di chuyển dòng trong và ngang qua tập tin"
-#: builtin/blame.c:2370
+#: builtin/blame.c:2375
msgid "n,m"
msgstr "n,m"
-#: builtin/blame.c:2370
+#: builtin/blame.c:2375
msgid "Process only line range n,m, counting from 1"
msgstr "Xử lý chỉ dòng vùng n,m, tính từ 1"
msgid "git branch [options] (-m | -M) [<oldbranch>] <newbranch>"
msgstr "git branch [các-tùy-chọn] (-m | -M) [<nhánh-cũ>] <nhánh-mới>"
-#: builtin/branch.c:146
+#: builtin/branch.c:150
#, c-format
msgid ""
"deleting branch '%s' that has been merged to\n"
"đang xóa nhánh “%s” mà nó lại đã được hòa trộn vào\n"
" “%s”, nhưng vẫn chưa được hòa trộn vào HEAD."
-#: builtin/branch.c:150
+#: builtin/branch.c:154
#, c-format
msgid ""
"not deleting branch '%s' that is not yet merged to\n"
"không xóa nhánh “%s” cái mà chưa được hòa trộn vào\n"
" “%s”, cho dù là nó đã được hòa trộn vào HEAD."
-#: builtin/branch.c:164
+#: builtin/branch.c:168
#, c-format
msgid "Couldn't look up commit object for '%s'"
msgstr "Không thể tìm kiếm đối tượng chuyển giao (commit) cho “%s”"
-#: builtin/branch.c:168
+#: builtin/branch.c:172
#, c-format
msgid ""
"The branch '%s' is not fully merged.\n"
"Nhánh “%s” không được trộn một cách đầy đủ.\n"
"Nếu bạn thực sự muốn xóa nó, thì chạy lệnh “git branch -D %s”."
-#: builtin/branch.c:181
+#: builtin/branch.c:185
msgid "Update of config-file failed"
msgstr "Cập nhật tập tin cấu hình gặp lỗi"
-#: builtin/branch.c:209
+#: builtin/branch.c:213
msgid "cannot use -a with -d"
msgstr "không thể dùng tùy chọn -a với -d"
-#: builtin/branch.c:215
+#: builtin/branch.c:219
msgid "Couldn't look up commit object for HEAD"
msgstr "Không thể tìm kiếm đối tượng chuyển giao (commit) cho HEAD"
-#: builtin/branch.c:223
+#: builtin/branch.c:227
#, c-format
msgid "Cannot delete the branch '%s' which you are currently on."
msgstr "Không thể xóa nhánh “%s” cái mà bạn hiện nay đang ở."
-#: builtin/branch.c:236
+#: builtin/branch.c:240
#, c-format
msgid "remote branch '%s' not found."
msgstr "không tìm thấy nhánh máy chủ “%s”."
-#: builtin/branch.c:237
+#: builtin/branch.c:241
#, c-format
msgid "branch '%s' not found."
msgstr "không tìm thấy nhánh “%s”."
-#: builtin/branch.c:251
+#: builtin/branch.c:255
#, c-format
msgid "Error deleting remote branch '%s'"
msgstr "Gặp lỗi khi đang xóa nhánh trên máy chủ “%s”"
-#: builtin/branch.c:252
+#: builtin/branch.c:256
#, c-format
msgid "Error deleting branch '%s'"
msgstr "Lỗi khi xoá bỏ nhánh “%s”"
-#: builtin/branch.c:259
+#: builtin/branch.c:263
#, c-format
msgid "Deleted remote branch %s (was %s).\n"
msgstr "Nhánh trên máy chủ \"%s\" đã bị xóa (từng là %s).\n"
-#: builtin/branch.c:260
+#: builtin/branch.c:264
#, c-format
msgid "Deleted branch %s (was %s).\n"
msgstr "Nhánh “%s” đã bị xóa (từng là %s)\n"
-#: builtin/branch.c:362
+#: builtin/branch.c:366
#, c-format
msgid "branch '%s' does not point at a commit"
msgstr "nhánh “%s” không chỉ đến một lần chuyển giao (commit) nào cả"
-#: builtin/branch.c:434
+#: builtin/branch.c:453
#, c-format
msgid "[%s: behind %d]"
msgstr "[%s: đằng sau %d]"
-#: builtin/branch.c:436
+#: builtin/branch.c:455
#, c-format
msgid "[behind %d]"
msgstr "[đằng sau %d]"
-#: builtin/branch.c:440
+#: builtin/branch.c:459
#, c-format
msgid "[%s: ahead %d]"
msgstr "[%s: phía trước %d]"
-#: builtin/branch.c:442
+#: builtin/branch.c:461
#, c-format
msgid "[ahead %d]"
msgstr "[phía trước %d]"
-#: builtin/branch.c:445
+#: builtin/branch.c:464
#, c-format
msgid "[%s: ahead %d, behind %d]"
msgstr "[%s: trước %d, sau %d]"
-#: builtin/branch.c:448
+#: builtin/branch.c:467
#, c-format
msgid "[ahead %d, behind %d]"
msgstr "[trước %d, sau %d]"
-#: builtin/branch.c:470
+#: builtin/branch.c:490
msgid " **** invalid ref ****"
msgstr " **** tham chiếu sai ****"
-#: builtin/branch.c:562
+#: builtin/branch.c:582
#, c-format
msgid "(no branch, rebasing %s)"
msgstr "(không nhánh, đang rebase %s)"
-#: builtin/branch.c:565
+#: builtin/branch.c:585
#, c-format
msgid "(no branch, bisect started on %s)"
msgstr "(không nhánh, bisect được bắt đầu tại %s)"
-#: builtin/branch.c:568
+#: builtin/branch.c:588
#, c-format
msgid "(detached from %s)"
msgstr "(được tách rời từ %s)"
-#: builtin/branch.c:571
+#: builtin/branch.c:591
msgid "(no branch)"
msgstr "(không nhánh)"
-#: builtin/branch.c:617
+#: builtin/branch.c:637
#, c-format
msgid "object '%s' does not point to a commit"
msgstr "đối tượng “%s” không chỉ đến một lần chuyển giao (commit) nào cả"
-#: builtin/branch.c:649
+#: builtin/branch.c:669
msgid "some refs could not be read"
msgstr "một số tham chiếu đã không thể đọc được"
-#: builtin/branch.c:662
+#: builtin/branch.c:682
msgid "cannot rename the current branch while not on any."
msgstr "không thể đổi tên nhánh hiện hành trong khi nó chẳng ở đâu cả."
-#: builtin/branch.c:672
+#: builtin/branch.c:692
#, c-format
msgid "Invalid branch name: '%s'"
msgstr "Sai tên nhánh: “%s”"
-#: builtin/branch.c:687
+#: builtin/branch.c:707
msgid "Branch rename failed"
msgstr "Đổi tên nhánh gặp lỗi"
-#: builtin/branch.c:691
+#: builtin/branch.c:711
#, c-format
msgid "Renamed a misnamed branch '%s' away"
msgstr "Đã đổi tên nhánh khuyết danh “%s” đi"
-#: builtin/branch.c:695
+#: builtin/branch.c:715
#, c-format
msgid "Branch renamed to %s, but HEAD is not updated!"
msgstr "Nhánh bị đổi tên thành %s, nhưng HEAD lại không được cập nhật!"
-#: builtin/branch.c:702
+#: builtin/branch.c:722
msgid "Branch is renamed, but update of config-file failed"
msgstr "Nhánh bị đổi tên, nhưng cập nhật tập tin cấu hình gặp lỗi"
-#: builtin/branch.c:717
+#: builtin/branch.c:737
#, c-format
msgid "malformed object name %s"
msgstr "tên đối tượng dị hình %s"
-#: builtin/branch.c:741
+#: builtin/branch.c:761
#, c-format
msgid "could not write branch description template: %s"
msgstr "không thể ghi vào mẫu mô tả nhánh: %s"
-#: builtin/branch.c:771
+#: builtin/branch.c:791
msgid "Generic options"
msgstr "Tùy chọn chung"
-#: builtin/branch.c:773
+#: builtin/branch.c:793
msgid "show hash and subject, give twice for upstream branch"
msgstr "hiển thị mã băm và chủ đề, đưa ra hai lần cho nhánh thượng nguồn"
-#: builtin/branch.c:774
+#: builtin/branch.c:794
msgid "suppress informational messages"
msgstr "Thu hồi các thông điệp thông tin"
-#: builtin/branch.c:775
+#: builtin/branch.c:795
msgid "set up tracking mode (see git-pull(1))"
msgstr "cài đặt chế độ theo vết (xem git-pull(1))"
-#: builtin/branch.c:777
+#: builtin/branch.c:797
msgid "change upstream info"
msgstr "thay đổi thông tin thượng nguồn (upstream)"
-#: builtin/branch.c:781
+#: builtin/branch.c:801
msgid "use colored output"
msgstr "sử dụng kết xuất có tô màu"
-#: builtin/branch.c:782
+#: builtin/branch.c:802
msgid "act on remote-tracking branches"
msgstr "thao tác trên nhánh “remote-tracking”"
-#: builtin/branch.c:785 builtin/branch.c:791 builtin/branch.c:812
-#: builtin/branch.c:818 builtin/commit.c:1368 builtin/commit.c:1369
+#: builtin/branch.c:805 builtin/branch.c:811 builtin/branch.c:832
+#: builtin/branch.c:838 builtin/commit.c:1368 builtin/commit.c:1369
#: builtin/commit.c:1370 builtin/commit.c:1371 builtin/tag.c:468
msgid "commit"
msgstr "commit"
-#: builtin/branch.c:786 builtin/branch.c:792
+#: builtin/branch.c:806 builtin/branch.c:812
msgid "print only branches that contain the commit"
msgstr "chỉ hiển thị những nhánh mà nó chứa lần chuyển giao"
-#: builtin/branch.c:798
+#: builtin/branch.c:818
msgid "Specific git-branch actions:"
msgstr "Hành động git-branch:"
-#: builtin/branch.c:799
+#: builtin/branch.c:819
msgid "list both remote-tracking and local branches"
msgstr "liệt kê cả nhánh “remote-tracking” và nội bộ"
-#: builtin/branch.c:801
+#: builtin/branch.c:821
msgid "delete fully merged branch"
msgstr "xóa một cách đầy đủ nhánh đã hòa trộn"
-#: builtin/branch.c:802
+#: builtin/branch.c:822
msgid "delete branch (even if not merged)"
msgstr "xoá nhánh (cho dù là chưa được hòa trộn)"
-#: builtin/branch.c:803
+#: builtin/branch.c:823
msgid "move/rename a branch and its reflog"
msgstr "di chuyển hay đổi tên một nhánh và reflog của nó"
-#: builtin/branch.c:804
+#: builtin/branch.c:824
msgid "move/rename a branch, even if target exists"
msgstr "di chuyển hoặc đổi tên một nhánh, thậm chí cả khi đích đã có sẵn"
-#: builtin/branch.c:805
+#: builtin/branch.c:825
msgid "list branch names"
msgstr "liệt kê các tên nhánh"
-#: builtin/branch.c:806
+#: builtin/branch.c:826
msgid "create the branch's reflog"
msgstr "tạo reflog của nhánh"
-#: builtin/branch.c:808
+#: builtin/branch.c:828
msgid "edit the description for the branch"
msgstr "sửa mô tả cho nhánh"
-#: builtin/branch.c:809
+#: builtin/branch.c:829
msgid "force creation (when already exists)"
msgstr "ép buộc tạo (khi đã sẵn tồn tại rồi)"
-#: builtin/branch.c:812
+#: builtin/branch.c:832
msgid "print only not merged branches"
msgstr "chỉ hiển thị các nhánh chưa được hòa trộn"
-#: builtin/branch.c:818
+#: builtin/branch.c:838
msgid "print only merged branches"
msgstr "chỉ hiển thị các nhánh được hòa trộn"
-#: builtin/branch.c:822
+#: builtin/branch.c:842
msgid "list branches in columns"
msgstr "liệt kê các nhánh trong các cột"
-#: builtin/branch.c:835
+#: builtin/branch.c:855
msgid "Failed to resolve HEAD as a valid ref."
msgstr "Gặp lỗi khi phân giải HEAD như là một tham chiếu (ref) hợp lệ."
-#: builtin/branch.c:840 builtin/clone.c:609
+#: builtin/branch.c:860 builtin/clone.c:619
msgid "HEAD not found below refs/heads!"
msgstr "không tìm thấy HEAD ở dưới refs/heads!"
-#: builtin/branch.c:863
+#: builtin/branch.c:883
msgid "--column and --verbose are incompatible"
msgstr "--column và --verbose xung khắc nhau"
-#: builtin/branch.c:869 builtin/branch.c:908
+#: builtin/branch.c:889 builtin/branch.c:928
msgid "branch name required"
msgstr "cần tên nhánh"
-#: builtin/branch.c:884
+#: builtin/branch.c:904
msgid "Cannot give description to detached HEAD"
msgstr "Không thể đưa ra mô tả HEAD đã tách rời"
-#: builtin/branch.c:889
+#: builtin/branch.c:909
msgid "cannot edit description of more than one branch"
msgstr "không thể sửa mô tả cho nhiều hơn một nhánh"
-#: builtin/branch.c:896
+#: builtin/branch.c:916
#, c-format
msgid "No commit on branch '%s' yet."
msgstr "Vẫn chưa chuyển giao trên nhánh “%s”."
-#: builtin/branch.c:899
+#: builtin/branch.c:919
#, c-format
msgid "No branch named '%s'."
msgstr "Không có nhánh nào có tên “%s”."
-#: builtin/branch.c:914
+#: builtin/branch.c:934
msgid "too many branches for a rename operation"
msgstr "quá nhiều nhánh dành cho thao tác đổi tên"
-#: builtin/branch.c:919
+#: builtin/branch.c:939
msgid "too many branches to set new upstream"
msgstr "quá nhiều nhánh được đặt cho dòng ngược (upstream) mới"
-#: builtin/branch.c:923
+#: builtin/branch.c:943
#, c-format
msgid ""
"could not set upstream of HEAD to %s when it does not point to any branch."
"không thể đặt dòng ngược (upstream) của HEAD thành %s khi mà nó chẳng chỉ "
"đến nhánh nào cả."
-#: builtin/branch.c:926 builtin/branch.c:948 builtin/branch.c:970
+#: builtin/branch.c:946 builtin/branch.c:968 builtin/branch.c:990
#, c-format
msgid "no such branch '%s'"
msgstr "không có nhánh nào như thế “%s”"
-#: builtin/branch.c:930
+#: builtin/branch.c:950
#, c-format
msgid "branch '%s' does not exist"
msgstr "nhánh “%s” chưa sẵn có"
-#: builtin/branch.c:942
+#: builtin/branch.c:962
msgid "too many branches to unset upstream"
msgstr "quá nhiều nhánh để bỏ đặt ngược dòng (upstream)"
-#: builtin/branch.c:946
+#: builtin/branch.c:966
msgid "could not unset upstream of HEAD when it does not point to any branch."
msgstr ""
"không thể bỏ đặt ngược dòng (upstream) của HEAD không chỉ đến một nhánh nào "
"cả."
-#: builtin/branch.c:952
+#: builtin/branch.c:972
#, c-format
msgid "Branch '%s' has no upstream information"
msgstr "Nhánh “%s” không có thông tin thượng nguồn (upstream)"
-#: builtin/branch.c:967
+#: builtin/branch.c:987
msgid "it does not make sense to create 'HEAD' manually"
-msgstr "không hợp lý khi tạo 'HEAD' thủ công "
+msgstr "không hợp lý khi tạo “HEAD” thủ công "
-#: builtin/branch.c:973
+#: builtin/branch.c:993
msgid "-a and -r options to 'git branch' do not make sense with a branch name"
msgstr ""
"hai tùy chọn -a và -r áp dụng cho lệnh “git branch” không hợp lý đối với tên "
"nhánh"
-#: builtin/branch.c:976
+#: builtin/branch.c:996
#, c-format
msgid ""
"The --set-upstream flag is deprecated and will be removed. Consider using --"
"Cờ --set-upstream bị phản đối và sẽ bị xóa bỏ. Nên dùng --track hoặc --set-"
"upstream-to\n"
-#: builtin/branch.c:993
+#: builtin/branch.c:1013
#, c-format
msgid ""
"\n"
"Nếu bạn muốn “%s” theo dõi “%s”, thực hiện lệnh sau:\n"
"\n"
-#: builtin/branch.c:994
+#: builtin/branch.c:1014
#, c-format
msgid " git branch -d %s\n"
msgstr " git branch -d %s\n"
-#: builtin/branch.c:995
+#: builtin/branch.c:1015
#, c-format
msgid " git branch --set-upstream-to %s\n"
msgstr " git branch --set-upstream-to %s\n"
msgid "Need a repository to unbundle."
msgstr "Cần một kho chứa để mà bung một bundle."
-#: builtin/cat-file.c:247
+#: builtin/cat-file.c:176
msgid "git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>"
msgstr "git cat-file (-t|-s|-e|-p|<kiểu>|--textconv) <đối tượng>"
-#: builtin/cat-file.c:248
+#: builtin/cat-file.c:177
msgid "git cat-file (--batch|--batch-check) < <list_of_objects>"
msgstr "git cat-file (--batch|--batch-check) < <danh-sách-đối-tượng>"
-#: builtin/cat-file.c:266
+#: builtin/cat-file.c:195
msgid "<type> can be one of: blob, tree, commit, tag"
msgstr "<kiểu> là một trong số: blob, tree, commit, tag"
-#: builtin/cat-file.c:267
+#: builtin/cat-file.c:196
msgid "show object type"
msgstr "hiển thị kiểu đối tượng"
-#: builtin/cat-file.c:268
+#: builtin/cat-file.c:197
msgid "show object size"
msgstr "hiển thị kích thước đối tượng"
-#: builtin/cat-file.c:270
+#: builtin/cat-file.c:199
msgid "exit with zero when there's no error"
msgstr "thoát với 0 khi không có lỗi"
-#: builtin/cat-file.c:271
+#: builtin/cat-file.c:200
msgid "pretty-print object's content"
msgstr "in nội dung đối tượng dạng dễ đọc"
-#: builtin/cat-file.c:273
+#: builtin/cat-file.c:202
msgid "for blob objects, run textconv on object's content"
msgstr "với đối tượng blob, chạy lệnh textconv trên nội dung của đối tượng"
-#: builtin/cat-file.c:275
+#: builtin/cat-file.c:204
msgid "show info and content of objects fed from the standard input"
msgstr ""
"hiển thị thông tin và nội dung của các đối tượng lấy từ đầu vào tiêu chuẩn"
-#: builtin/cat-file.c:278
+#: builtin/cat-file.c:207
msgid "show info about objects fed from the standard input"
msgstr "hiển thị các thông tin về đối tượng fed từ đầu vào tiêu chuẩn"
msgid "input paths are terminated by a null character"
msgstr "các đường dẫn được ngăn cách bởi ký tự null"
-#: builtin/check-ignore.c:18 builtin/checkout.c:1041 builtin/gc.c:177
+#: builtin/check-ignore.c:18 builtin/checkout.c:1044 builtin/gc.c:177
msgid "suppress progress reporting"
msgstr "chặn các báo cáo tiến trình hoạt động"
-#: builtin/check-ignore.c:151
+#: builtin/check-ignore.c:146
msgid "cannot specify pathnames with --stdin"
msgstr "không thể chỉ định các tên đường dẫn với --stdin"
-#: builtin/check-ignore.c:154
+#: builtin/check-ignore.c:149
msgid "-z only makes sense with --stdin"
msgstr "-z chỉ hợp lý với --stdin"
-#: builtin/check-ignore.c:156
+#: builtin/check-ignore.c:151
msgid "no path specified"
msgstr "chưa ghi rõ đường dẫn"
-#: builtin/check-ignore.c:160
+#: builtin/check-ignore.c:155
msgid "--quiet is only valid with a single pathname"
msgstr "--quiet chỉ hợp lệ với tên đường dẫn đơn"
-#: builtin/check-ignore.c:162
+#: builtin/check-ignore.c:157
msgid "cannot have both --quiet and --verbose"
msgstr "không thể dùng cả hai tùy chọn --quiet và --verbose"
msgid "git checkout [options] [<branch>] -- <file>..."
msgstr "git checkout [các-tùy-chọn] [<nhánh>] -- <tập-tin>..."
-#: builtin/checkout.c:116 builtin/checkout.c:149
+#: builtin/checkout.c:117 builtin/checkout.c:150
#, c-format
msgid "path '%s' does not have our version"
msgstr "đường dẫn “%s” không có các phiên bản của chúng ta"
-#: builtin/checkout.c:118 builtin/checkout.c:151
+#: builtin/checkout.c:119 builtin/checkout.c:152
#, c-format
msgid "path '%s' does not have their version"
msgstr "đường dẫn “%s” không có các phiên bản của chúng"
-#: builtin/checkout.c:134
+#: builtin/checkout.c:135
#, c-format
msgid "path '%s' does not have all necessary versions"
msgstr "đường dẫn “%s” không có tất cả các phiên bản cần thiết"
-#: builtin/checkout.c:178
+#: builtin/checkout.c:179
#, c-format
msgid "path '%s' does not have necessary versions"
msgstr "đường dẫn “%s” không có các phiên bản cần thiết"
-#: builtin/checkout.c:195
+#: builtin/checkout.c:196
#, c-format
msgid "path '%s': cannot merge"
msgstr "đường dẫn “%s”: không thể hòa trộn"
-#: builtin/checkout.c:212
+#: builtin/checkout.c:213
#, c-format
msgid "Unable to add merge result for '%s'"
msgstr "Không thể thêm kết quả hòa trộn cho “%s”"
-#: builtin/checkout.c:236 builtin/checkout.c:239 builtin/checkout.c:242
-#: builtin/checkout.c:245
+#: builtin/checkout.c:237 builtin/checkout.c:240 builtin/checkout.c:243
+#: builtin/checkout.c:246
#, c-format
msgid "'%s' cannot be used with updating paths"
msgstr "“%s” không thể được sử dụng với các đường dẫn cập nhật"
-#: builtin/checkout.c:248 builtin/checkout.c:251
+#: builtin/checkout.c:249 builtin/checkout.c:252
#, c-format
msgid "'%s' cannot be used with %s"
msgstr "“%s” không thể được sử dụng với %s"
-#: builtin/checkout.c:254
+#: builtin/checkout.c:255
#, c-format
msgid "Cannot update paths and switch to branch '%s' at the same time."
msgstr ""
"Không thể cập nhật các đường dẫn và chuyển đến nhánh “%s” cùng một lúc."
-#: builtin/checkout.c:265 builtin/checkout.c:455
+#: builtin/checkout.c:266 builtin/checkout.c:458
msgid "corrupt index file"
msgstr "tập tin ghi bảng mục lục bị hỏng"
-#: builtin/checkout.c:326 builtin/checkout.c:333
+#: builtin/checkout.c:329 builtin/checkout.c:336
#, c-format
msgid "path '%s' is unmerged"
msgstr "đường dẫn “%s” không được hòa trộn"
-#: builtin/checkout.c:477
+#: builtin/checkout.c:480
msgid "you need to resolve your current index first"
msgstr "bạn cần phải phân giải bảng mục lục hiện tại của bạn trước đã"
-#: builtin/checkout.c:598
+#: builtin/checkout.c:601
#, c-format
msgid "Can not do reflog for '%s'\n"
msgstr "Không thể thực hiện reflog cho “%s”\n"
-#: builtin/checkout.c:631
+#: builtin/checkout.c:634
msgid "HEAD is now at"
msgstr "HEAD hiện giờ tại"
-#: builtin/checkout.c:638
+#: builtin/checkout.c:641
#, c-format
msgid "Reset branch '%s'\n"
msgstr "Đặt lại nhánh “%s”\n"
-#: builtin/checkout.c:641
+#: builtin/checkout.c:644
#, c-format
msgid "Already on '%s'\n"
msgstr "Đã sẵn sàng trên “%s”\n"
-#: builtin/checkout.c:645
+#: builtin/checkout.c:648
#, c-format
msgid "Switched to and reset branch '%s'\n"
msgstr "Đã chuyển tới và reset nhánh “%s”\n"
-#: builtin/checkout.c:647 builtin/checkout.c:984
+#: builtin/checkout.c:650 builtin/checkout.c:987
#, c-format
msgid "Switched to a new branch '%s'\n"
msgstr "Đã chuyển đến nhánh mới “%s”\n"
-#: builtin/checkout.c:649
+#: builtin/checkout.c:652
#, c-format
msgid "Switched to branch '%s'\n"
msgstr "Đã chuyển đến nhánh “%s”\n"
-#: builtin/checkout.c:705
+#: builtin/checkout.c:708
#, c-format
msgid " ... and %d more.\n"
msgstr " ... và nhiều hơn %d.\n"
#. The singular version
-#: builtin/checkout.c:711
+#: builtin/checkout.c:714
#, c-format
msgid ""
"Warning: you are leaving %d commit behind, not connected to\n"
"\n"
"%s\n"
-#: builtin/checkout.c:729
+#: builtin/checkout.c:732
#, c-format
msgid ""
"If you want to keep them by creating a new branch, this may be a good time\n"
" git branch tên_nhánh_mới %s\n"
"\n"
-#: builtin/checkout.c:759
+#: builtin/checkout.c:762
msgid "internal error in revision walk"
msgstr "lỗi nội bộ trong khi di chuyển qua các điểm xét lại"
-#: builtin/checkout.c:763
+#: builtin/checkout.c:766
msgid "Previous HEAD position was"
msgstr "Vị trí kế trước của HEAD là"
-#: builtin/checkout.c:790 builtin/checkout.c:979
+#: builtin/checkout.c:793 builtin/checkout.c:982
msgid "You are on a branch yet to be born"
msgstr "Bạn tại nhánh mà nó chưa hề được sinh ra"
#. case (1)
-#: builtin/checkout.c:915
+#: builtin/checkout.c:918
#, c-format
msgid "invalid reference: %s"
msgstr "tham chiếu sai: %s"
#. case (1): want a tree
-#: builtin/checkout.c:954
+#: builtin/checkout.c:957
#, c-format
msgid "reference is not a tree: %s"
msgstr "tham chiếu không phải là một cây (tree):%s"
-#: builtin/checkout.c:993
+#: builtin/checkout.c:996
msgid "paths cannot be used with switching branches"
msgstr "các đường dẫn không thể dùng cùng với các nhánh chuyển"
-#: builtin/checkout.c:996 builtin/checkout.c:1000
+#: builtin/checkout.c:999 builtin/checkout.c:1003
#, c-format
msgid "'%s' cannot be used with switching branches"
msgstr "“%s” không thể được sử dụng với các nhánh chuyển"
-#: builtin/checkout.c:1004 builtin/checkout.c:1007 builtin/checkout.c:1012
-#: builtin/checkout.c:1015
+#: builtin/checkout.c:1007 builtin/checkout.c:1010 builtin/checkout.c:1015
+#: builtin/checkout.c:1018
#, c-format
msgid "'%s' cannot be used with '%s'"
msgstr "“%s” không thể được sử dụng với “%s”"
-#: builtin/checkout.c:1020
+#: builtin/checkout.c:1023
#, c-format
msgid "Cannot switch branch to a non-commit '%s'"
msgstr "Không thể chuyển nhánh đến một non-commit “%s”"
-#: builtin/checkout.c:1042 builtin/checkout.c:1044 builtin/clone.c:90
+#: builtin/checkout.c:1045 builtin/checkout.c:1047 builtin/clone.c:90
#: builtin/remote.c:169 builtin/remote.c:171
msgid "branch"
msgstr "nhánh"
-#: builtin/checkout.c:1043
+#: builtin/checkout.c:1046
msgid "create and checkout a new branch"
msgstr "tạo và checkout một nhánh mới"
-#: builtin/checkout.c:1045
+#: builtin/checkout.c:1048
msgid "create/reset and checkout a branch"
msgstr "create/reset và checkout một nhánh"
-#: builtin/checkout.c:1046
+#: builtin/checkout.c:1049
msgid "create reflog for new branch"
msgstr "tạo reflog cho nhánh mới"
-#: builtin/checkout.c:1047
+#: builtin/checkout.c:1050
msgid "detach the HEAD at named commit"
msgstr "rời bỏ HEAD tại lần chuyển giao danh nghĩa"
-#: builtin/checkout.c:1048
+#: builtin/checkout.c:1051
msgid "set upstream info for new branch"
msgstr "đặt thông tin thượng nguồn (upstream) cho nhánh mới"
-#: builtin/checkout.c:1050
+#: builtin/checkout.c:1053
msgid "new branch"
msgstr "nhánh mới"
-#: builtin/checkout.c:1050
+#: builtin/checkout.c:1053
msgid "new unparented branch"
msgstr "nhánh mồ côi mới"
-#: builtin/checkout.c:1051
+#: builtin/checkout.c:1054
msgid "checkout our version for unmerged files"
msgstr ""
"lấy ra (checkout) phiên bản của chúng ta cho các tập tin chưa được hòa trộn"
-#: builtin/checkout.c:1053
+#: builtin/checkout.c:1056
msgid "checkout their version for unmerged files"
msgstr ""
"lấy ra (checkout) phiên bản của chúng họ cho các tập tin chưa được hòa trộn"
-#: builtin/checkout.c:1055
+#: builtin/checkout.c:1058
msgid "force checkout (throw away local modifications)"
msgstr "ép buộc lấy ra (checkout) (bỏ đi những thay đổi nội bộ)"
-#: builtin/checkout.c:1056
+#: builtin/checkout.c:1059
msgid "perform a 3-way merge with the new branch"
msgstr "thực hiện hòa trộn kiểu 3-way với nhánh mới"
-#: builtin/checkout.c:1057 builtin/merge.c:217
+#: builtin/checkout.c:1060 builtin/merge.c:217
msgid "update ignored files (default)"
msgstr "cập nhật các tập tin bị bỏ qua (mặc định)"
-#: builtin/checkout.c:1058 builtin/log.c:1149 parse-options.h:245
+#: builtin/checkout.c:1061 builtin/log.c:1158 parse-options.h:245
msgid "style"
msgstr "kiểu"
-#: builtin/checkout.c:1059
+#: builtin/checkout.c:1062
msgid "conflict style (merge or diff3)"
msgstr "xung đột kiểu (hòa trộn hay diff3)"
-#: builtin/checkout.c:1062
+#: builtin/checkout.c:1065
+msgid "do not limit pathspecs to sparse entries only"
+msgstr "không giới hạn pathspecs chỉ thành các mục thưa thớt"
+
+#: builtin/checkout.c:1067
msgid "second guess 'git checkout no-such-branch'"
msgstr "gợi ý thứ hai “git checkout không-nhánh-nào-như-vậy”"
-#: builtin/checkout.c:1086
+#: builtin/checkout.c:1091
msgid "-b, -B and --orphan are mutually exclusive"
msgstr "Tùy chọn -b|-B và --orphan loại từ lẫn nhau"
-#: builtin/checkout.c:1103
+#: builtin/checkout.c:1108
msgid "--track needs a branch name"
msgstr "--track cần tên một nhánh"
-#: builtin/checkout.c:1110
+#: builtin/checkout.c:1115
msgid "Missing branch name; try -b"
msgstr "Thiếu tên nhánh; hãy thử -b"
-#: builtin/checkout.c:1145
+#: builtin/checkout.c:1150
msgid "invalid path specification"
msgstr "đường dẫn đã cho không hợp lệ"
-#: builtin/checkout.c:1152
+#: builtin/checkout.c:1157
#, c-format
msgid ""
"Cannot update paths and switch to branch '%s' at the same time.\n"
"Bạn đã có ý định checkout “%s” cái mà không thể được phân giải như là lần "
"chuyển giao (commit)?"
-#: builtin/checkout.c:1157
+#: builtin/checkout.c:1162
#, c-format
msgid "git checkout: --detach does not take a path argument '%s'"
msgstr "git checkout: --detach không nhận một đối số đường dẫn “%s”"
-#: builtin/checkout.c:1161
+#: builtin/checkout.c:1166
msgid ""
"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
"checking out of the index."
msgstr "gỡ bỏ toàn bộ thư mục"
#: builtin/clean.c:165 builtin/describe.c:412 builtin/grep.c:717
-#: builtin/ls-files.c:494 builtin/name-rev.c:231 builtin/show-ref.c:182
+#: builtin/ls-files.c:487 builtin/name-rev.c:231 builtin/show-ref.c:182
msgid "pattern"
msgstr "mẫu"
msgid "set config inside the new repository"
msgstr "đặt cấu hình bên trong một kho chứa mới"
-#: builtin/clone.c:244
+#: builtin/clone.c:254
#, c-format
-msgid "reference repository '%s' is not a local directory."
-msgstr "kho tham chiếu “%s” không phải là một thư mục nội bộ."
+msgid "reference repository '%s' is not a local repository."
+msgstr "kho tham chiếu “%s” không phải là một kho nội bộ."
-#: builtin/clone.c:307
+#: builtin/clone.c:317
#, c-format
msgid "failed to create directory '%s'"
msgstr "tạo thư mục \"%s\" gặp lỗi"
-#: builtin/clone.c:309 builtin/diff.c:77
+#: builtin/clone.c:319 builtin/diff.c:77
#, c-format
msgid "failed to stat '%s'"
msgstr "gặp lỗi stat (lấy trạng thái về) “%s”"
-#: builtin/clone.c:311
+#: builtin/clone.c:321
#, c-format
msgid "%s exists and is not a directory"
msgstr "%s tồn tại nhưng không phải là một thư mục"
-#: builtin/clone.c:325
+#: builtin/clone.c:335
#, c-format
msgid "failed to stat %s\n"
msgstr "lỗi stat (lấy trạng thái về) %s\n"
-#: builtin/clone.c:347
+#: builtin/clone.c:357
#, c-format
msgid "failed to create link '%s'"
msgstr "gặp lỗi khi tạo được liên kết mềm %s"
-#: builtin/clone.c:351
+#: builtin/clone.c:361
#, c-format
msgid "failed to copy file to '%s'"
msgstr "gặp lỗi khi chép tập tin tới “%s”"
-#: builtin/clone.c:374
+#: builtin/clone.c:384
#, c-format
msgid "done.\n"
msgstr "hoàn tất.\n"
-#: builtin/clone.c:387
+#: builtin/clone.c:397
msgid ""
"Clone succeeded, but checkout failed.\n"
"You can inspect what was checked out with 'git status'\n"
"and retry the checkout with 'git checkout -f HEAD'\n"
msgstr ""
"Việc nhân bản thành công, nhưng checkout gặp lỗi.\n"
-"Bạn kiểm tra kỹ xem cái gì được lấy ra bằng lệnh 'git status'\n"
-"và thử checkout với lệnh 'git checkout -f HEAD'\n"
+"Bạn kiểm tra kỹ xem cái gì được lấy ra bằng lệnh “git status”\n"
+"và thử checkout với lệnh “git checkout -f HEAD”\n"
-#: builtin/clone.c:466
+#: builtin/clone.c:476
#, c-format
msgid "Could not find remote branch %s to clone."
msgstr "Không tìm thấy nhánh máy chủ %s để nhân bản (clone)."
-#: builtin/clone.c:540
+#: builtin/clone.c:550
msgid "remote did not send all necessary objects"
msgstr "máy chủ đã không gửi tất cả các đối tượng cần thiết"
-#: builtin/clone.c:600
+#: builtin/clone.c:610
msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
msgstr "refers HEAD máy chủ chỉ đến ref không tồn tại, không thể checkout.\n"
-#: builtin/clone.c:631
+#: builtin/clone.c:641
msgid "unable to checkout working tree"
msgstr "không thể lấy ra (checkout) cây làm việc"
-#: builtin/clone.c:739
+#: builtin/clone.c:749
msgid "Too many arguments."
msgstr "Có quá nhiều đối số."
-#: builtin/clone.c:743
+#: builtin/clone.c:753
msgid "You must specify a repository to clone."
msgstr "Bạn phải chỉ định một kho để mà nhân bản (clone)."
-#: builtin/clone.c:754
+#: builtin/clone.c:764
#, c-format
msgid "--bare and --origin %s options are incompatible."
msgstr "tùy chọn --bare và --origin %s xung khắc nhau."
-#: builtin/clone.c:757
+#: builtin/clone.c:767
msgid "--bare and --separate-git-dir are incompatible."
msgstr "tùy chọn --bare và --separate-git-dir xung khắc nhau."
-#: builtin/clone.c:770
+#: builtin/clone.c:780
#, c-format
msgid "repository '%s' does not exist"
msgstr "kho chứa “%s” chưa tồn tại"
-#: builtin/clone.c:775
+#: builtin/clone.c:785
msgid "--depth is ignored in local clones; use file:// instead."
msgstr "--depth bị lờ đi khi nhân bản nội bộ; hãy sử dụng file:// để thay thế."
-#: builtin/clone.c:785
+#: builtin/clone.c:795
#, c-format
msgid "destination path '%s' already exists and is not an empty directory."
msgstr "đường dẫn đích “%s” đã có từ trước và không phải là một thư mục rỗng."
-#: builtin/clone.c:795
+#: builtin/clone.c:805
#, c-format
msgid "working tree '%s' already exists."
msgstr "cây làm việc “%s” đã sẵn tồn tại rồi."
-#: builtin/clone.c:808 builtin/clone.c:820
+#: builtin/clone.c:818 builtin/clone.c:830
#, c-format
msgid "could not create leading directories of '%s'"
msgstr "không thể tạo các thư mục dẫn đầu của “%s”"
-#: builtin/clone.c:811
+#: builtin/clone.c:821
#, c-format
msgid "could not create work tree dir '%s'."
msgstr "không thể tạo cây thư mục làm việc dir “%s”."
-#: builtin/clone.c:830
+#: builtin/clone.c:840
#, c-format
msgid "Cloning into bare repository '%s'...\n"
msgstr "Đang nhân bản thành kho chứa bare “%s”...\n"
-#: builtin/clone.c:832
+#: builtin/clone.c:842
#, c-format
msgid "Cloning into '%s'...\n"
msgstr "Đang nhân bản thành “%s”...\n"
-#: builtin/clone.c:867
+#: builtin/clone.c:877
#, c-format
msgid "Don't know how to clone %s"
msgstr "Không biết làm cách nào để nhân bản (clone) %s"
-#: builtin/clone.c:916
+#: builtin/clone.c:926
#, c-format
msgid "Remote branch %s not found in upstream %s"
msgstr "Nhánh máy chủ %s không tìm thấy trong thượng nguồn (upstream) %s"
-#: builtin/clone.c:923
+#: builtin/clone.c:933
msgid "You appear to have cloned an empty repository."
msgstr "Bạn hình như là đã nhân bản một kho trống rỗng."
msgid "could not lookup commit %s"
msgstr "không thể tìm kiếm commit (lần chuyển giao) %s"
-#: builtin/commit.c:612 builtin/shortlog.c:272
+#: builtin/commit.c:612 builtin/shortlog.c:270
#, c-format
msgid "(reading log message from standard input)\n"
msgstr "(đang đọc thông điệp nhật ký từ đầu vào tiêu chuẩn)\n"
msgid "terminate entries with NUL"
msgstr "chấm dứt các mục bằng NUL"
-#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/fast-export.c:653
-#: builtin/fast-export.c:656 builtin/tag.c:459
+#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/fast-export.c:659
+#: builtin/fast-export.c:662 builtin/tag.c:459
msgid "mode"
msgstr "chế độ"
msgstr ""
"lần chuyển giao nhận tôi là tác giả (được dùng với tùy chọn -C/-c/--amend)"
-#: builtin/commit.c:1373 builtin/log.c:1104 builtin/revert.c:109
+#: builtin/commit.c:1373 builtin/log.c:1113 builtin/revert.c:109
msgid "add Signed-off-by:"
msgstr "thêm dòng Signed-off-by:"
msgstr "tôn trọng kể cà các hướng trong tìm kiếm"
#: builtin/count-objects.c:82
-msgid "git count-objects [-v]"
-msgstr "git count-objects [-v]"
+msgid "git count-objects [-v] [-H | --human-readable]"
+msgstr "git count-objects [-v] [-H | --human-readable]"
+
+#: builtin/count-objects.c:97
+msgid "print sizes in human readable format"
+msgstr "hiển thị kích cỡ theo định dạng dành cho người đọc"
#: builtin/describe.c:15
msgid "git describe [options] <committish>*"
#: builtin/describe.c:405
msgid "use any tag, even unannotated"
-msgstr "dùng thẻ bất kỳ, cả khi `unannotated'"
+msgstr "dùng thẻ bất kỳ, cả khi “unannotated”"
#: builtin/describe.c:406
msgid "always use long format"
msgid "git fast-export [rev-list-opts]"
msgstr "git fast-export [rev-list-opts]"
-#: builtin/fast-export.c:652
+#: builtin/fast-export.c:658
msgid "show progress after <n> objects"
msgstr "hiển thị tiến triển sau <n> đối tượng"
-#: builtin/fast-export.c:654
+#: builtin/fast-export.c:660
msgid "select handling of signed tags"
msgstr "chọn điều khiển của thẻ đã ký"
-#: builtin/fast-export.c:657
+#: builtin/fast-export.c:663
msgid "select handling of tags that tag filtered objects"
msgstr "chọn sự xử lý của các thẻ, cái mà đánh thẻ các đối tượng được lọc ra"
-#: builtin/fast-export.c:660
+#: builtin/fast-export.c:666
msgid "Dump marks to this file"
msgstr "Đổ các đánh dấu này vào tập-tin"
-#: builtin/fast-export.c:662
+#: builtin/fast-export.c:668
msgid "Import marks from this file"
msgstr "nhập vào đánh dấu từ tập tin này"
-#: builtin/fast-export.c:664
+#: builtin/fast-export.c:670
msgid "Fake a tagger when tags lack one"
msgstr "Làm giả một cái thẻ khi thẻ bị thiếu một cái"
-#: builtin/fast-export.c:666
+#: builtin/fast-export.c:672
msgid "Output full tree for each commit"
msgstr "Xuất ra toàn bộ cây cho mỗi lần chuyển giao"
-#: builtin/fast-export.c:668
+#: builtin/fast-export.c:674
msgid "Use the done feature to terminate the stream"
msgstr "Sử dụng tính năng done để chấm dứt luồng dữ liệu"
-#: builtin/fast-export.c:669
+#: builtin/fast-export.c:675
msgid "Skip output of blob data"
msgstr "Bỏ qua kết xuất của dữ liệu blob"
msgid "convert to a complete repository"
msgstr "chuyển đổi hoàn toàn sang kho git"
-#: builtin/fetch.c:88 builtin/log.c:1121
+#: builtin/fetch.c:88 builtin/log.c:1130
msgid "dir"
msgstr "tmục"
msgstr ""
"git fmt-merge-msg [-m <thông điệp>] [--log[=<n>]|--no-log] [--file <tập-tin>]"
-#: builtin/fmt-merge-msg.c:659 builtin/fmt-merge-msg.c:662 builtin/grep.c:701
-#: builtin/merge.c:188 builtin/show-branch.c:656 builtin/show-ref.c:175
+#: builtin/fmt-merge-msg.c:663 builtin/fmt-merge-msg.c:666 builtin/grep.c:701
+#: builtin/merge.c:188 builtin/show-branch.c:655 builtin/show-ref.c:175
#: builtin/tag.c:446 parse-options.h:133 parse-options.h:239
msgid "n"
msgstr "n"
-#: builtin/fmt-merge-msg.c:660
+#: builtin/fmt-merge-msg.c:664
msgid "populate log with at most <n> entries from shortlog"
msgstr "gắn nhật ký với ít nhất <n> mục từ lệnh “shortlog”"
-#: builtin/fmt-merge-msg.c:663
+#: builtin/fmt-merge-msg.c:667
msgid "alias for --log (deprecated)"
msgstr "bí danh cho --log (không được dùng)"
-#: builtin/fmt-merge-msg.c:666
+#: builtin/fmt-merge-msg.c:670
msgid "text"
msgstr "văn bản"
-#: builtin/fmt-merge-msg.c:667
+#: builtin/fmt-merge-msg.c:671
msgid "use <text> as start of message"
msgstr "dùng <văn bản thường> để bắt đầu ghi chú"
-#: builtin/fmt-merge-msg.c:668
+#: builtin/fmt-merge-msg.c:672
msgid "file to read from"
msgstr "tập tin để đọc dữ liệu từ đó"
msgid "process file as it were from this path"
msgstr "xử lý tập tin như là nó đang ở thư mục này"
-#: builtin/help.c:42
+#: builtin/help.c:43
msgid "print all available commands"
msgstr "hiển thị danh sách các câu lệnh người dùng có thể sử dụng"
-#: builtin/help.c:43
+#: builtin/help.c:44
+msgid "print list of useful guides"
+msgstr "hiển thị danh sách các hướng dẫn hữu dụng"
+
+#: builtin/help.c:45
msgid "show man page"
msgstr "hiển thị trang man"
-#: builtin/help.c:44
+#: builtin/help.c:46
msgid "show manual in web browser"
msgstr "hiển thị hướng dẫn sử dụng trong trình duyệt web"
-#: builtin/help.c:46
+#: builtin/help.c:48
msgid "show info page"
msgstr "hiện trang info"
-#: builtin/help.c:52
-msgid "git help [--all] [--man|--web|--info] [command]"
-msgstr "git help [--all] [--man|--web|--info] [lệnh]"
+#: builtin/help.c:54
+msgid "git help [--all] [--guides] [--man|--web|--info] [command]"
+msgstr "git help [--all] [--guides] [--man|--web|--info] [lệnh]"
-#: builtin/help.c:64
+#: builtin/help.c:66
#, c-format
msgid "unrecognized help format '%s'"
msgstr "không nhận ra định dạng trợ giúp “%s”"
-#: builtin/help.c:92
+#: builtin/help.c:94
msgid "Failed to start emacsclient."
msgstr "Lỗi khởi chạy emacsclient."
-#: builtin/help.c:105
+#: builtin/help.c:107
msgid "Failed to parse emacsclient version."
msgstr "Gặp lỗi khi phân tích phiên bản emacsclient."
-#: builtin/help.c:113
+#: builtin/help.c:115
#, c-format
msgid "emacsclient version '%d' too old (< 22)."
msgstr "phiên bản của emacsclient “%d” quá cũ (< 22)."
-#: builtin/help.c:131 builtin/help.c:159 builtin/help.c:168 builtin/help.c:176
+#: builtin/help.c:133 builtin/help.c:161 builtin/help.c:170 builtin/help.c:178
#, c-format
msgid "failed to exec '%s': %s"
msgstr "gặp lỗi khi thực thi “%s”: %s"
-#: builtin/help.c:216
+#: builtin/help.c:218
#, c-format
msgid ""
"'%s': path for unsupported man viewer.\n"
"“%s”: đường dẫn không hỗ trợ bộ trình chiếu man.\n"
"Hãy cân nhắc đến việc sử dụng “man.<tool>.cmd” để thay thế."
-#: builtin/help.c:228
+#: builtin/help.c:230
#, c-format
msgid ""
"'%s': cmd for supported man viewer.\n"
"“%s”: cmd (lệnh) hỗ trợ bộ trình chiếu man.\n"
"Hãy cân nhắc đến việc sử dụng “man.<tool>.path” để thay thế."
-#: builtin/help.c:349
+#: builtin/help.c:351
#, c-format
msgid "'%s': unknown man viewer."
msgstr "“%s”: không rõ chương trình xem man."
-#: builtin/help.c:366
+#: builtin/help.c:368
msgid "no man viewer handled the request"
msgstr "không có trình xem trợ giúp dạng manpage tiếp hợp với yêu cầu"
-#: builtin/help.c:374
+#: builtin/help.c:376
msgid "no info viewer handled the request"
msgstr "không có trình xem trợ giúp dạng info tiếp hợp với yêu cầu"
-#: builtin/help.c:429 builtin/help.c:436
+#: builtin/help.c:422
+msgid "Defining attributes per path"
+msgstr "Định nghĩa các thuộc tính cho mỗi đường dẫn"
+
+#: builtin/help.c:423
+msgid "A Git glossary"
+msgstr "Thuật ngữ chuyên môn Git"
+
+#: builtin/help.c:424
+msgid "Specifies intentionally untracked files to ignore"
+msgstr "Chỉ định các tập tin chưa theo dõi dấu vết bị bỏ qua một cách cố ý"
+
+#: builtin/help.c:425
+msgid "Defining submodule properties"
+msgstr "Định nghĩa thuộc tính mô-đun-con"
+
+#: builtin/help.c:426
+msgid "Specifying revisions and ranges for Git"
+msgstr "Chỉ định điểm xét duyệt và vùng cho Git"
+
+#: builtin/help.c:427
+msgid "A tutorial introduction to Git (for version 1.5.1 or newer)"
+msgstr "Hướng dẫn cách dùng Git ở mức cơ bản (dành cho bản 1.5.1 hay mới hơn)"
+
+#: builtin/help.c:428
+msgid "An overview of recommended workflows with Git"
+msgstr "Tổng quan về luồng công việc khuyến nghị nên dùng với Git."
+
+#: builtin/help.c:440
+msgid "The common Git guides are:\n"
+msgstr "Các chỉ dẫn chung về cách dùng Git là:\n"
+
+#: builtin/help.c:462 builtin/help.c:478
#, c-format
msgid "usage: %s%s"
msgstr "cách sử dụng: %s%s"
-#: builtin/help.c:452
+#: builtin/help.c:494
#, c-format
msgid "`git %s' is aliased to `%s'"
msgstr "“git %s” được đặt bí danh thành “%s”"
msgstr "không thể truy cập cây (tree) làm việc “%s”"
#: builtin/log.c:40
-msgid "git log [<options>] [<since>..<until>] [[--] <path>...]\n"
-msgstr "git log [<các-tùy-chọn>] [<kể-từ>..<cho-đến>] [[--] <đường-dẫn>...]\n"
+msgid "git log [<options>] [<revision range>] [[--] <path>...]\n"
+msgstr "git log [<các-tùy-chọn>] [<vùng xem xét>] [[--] <đường-dẫn>...]\n"
#: builtin/log.c:41
msgid " or: git show [options] <object>..."
msgid "Unknown type: %d"
msgstr "Không nhận ra kiểu: %d"
-#: builtin/log.c:630
+#: builtin/log.c:638
msgid "format.headers without value"
msgstr "format.headers không có giá trị cụ thể"
-#: builtin/log.c:704
+#: builtin/log.c:720
msgid "name of output directory is too long"
msgstr "tên của thư mục kết xuất quá dài"
-#: builtin/log.c:720
+#: builtin/log.c:736
#, c-format
msgid "Cannot open patch file %s"
msgstr "Không thể mở tập tin miếng vá: %s"
-#: builtin/log.c:734
+#: builtin/log.c:750
msgid "Need exactly one range."
msgstr "Cần chính xác một vùng."
-#: builtin/log.c:742
+#: builtin/log.c:758
msgid "Not a range."
msgstr "Không phải là một vùng."
-#: builtin/log.c:815
+#: builtin/log.c:860
msgid "Cover letter needs email format"
msgstr "”Cover letter” cần cho định dạng thư"
-#: builtin/log.c:888
+#: builtin/log.c:936
#, c-format
msgid "insane in-reply-to: %s"
msgstr "in-reply-to điên rồ: %s"
-#: builtin/log.c:916
+#: builtin/log.c:964
msgid "git format-patch [options] [<since> | <revision range>]"
msgstr "git format-patch [các-tùy-chọn] [<kể-từ> | <vùng-xem-xét>]"
-#: builtin/log.c:961
+#: builtin/log.c:1009
msgid "Two output directories?"
msgstr "Hai thư mục kết xuất?"
-#: builtin/log.c:1099
+#: builtin/log.c:1108
msgid "use [PATCH n/m] even with a single patch"
msgstr "dùng [PATCH n/m] ngay cả với miếng vá đơn"
-#: builtin/log.c:1102
+#: builtin/log.c:1111
msgid "use [PATCH] even with multiple patches"
msgstr "dùng [VÁ] ngay cả với các miếng vá phức tạp"
-#: builtin/log.c:1106
+#: builtin/log.c:1115
msgid "print patches to standard out"
msgstr "hiển thị miếng vá ra đầu ra chuẩn"
-#: builtin/log.c:1108
+#: builtin/log.c:1117
msgid "generate a cover letter"
msgstr "tạo bì thư"
-#: builtin/log.c:1110
+#: builtin/log.c:1119
msgid "use simple number sequence for output file names"
msgstr "sử dụng chỗi dãy số dạng đơn giản cho tên tập-tin xuất ra"
-#: builtin/log.c:1111
+#: builtin/log.c:1120
msgid "sfx"
msgstr "sfx"
-#: builtin/log.c:1112
+#: builtin/log.c:1121
msgid "use <sfx> instead of '.patch'"
msgstr "sử dụng <sfx> thay cho “.patch”"
-#: builtin/log.c:1114
+#: builtin/log.c:1123
msgid "start numbering patches at <n> instead of 1"
msgstr "bắt đầu đánh số miếng vá từ <n> thay vì 1"
-#: builtin/log.c:1116
+#: builtin/log.c:1125
msgid "mark the series as Nth re-roll"
msgstr "đánh dấu chuỗi nối tiếp dạng thứ-N re-roll"
-#: builtin/log.c:1118
+#: builtin/log.c:1127
msgid "Use [<prefix>] instead of [PATCH]"
msgstr "Dùng [<tiền-tố>] thay cho [VÁ]"
-#: builtin/log.c:1121
+#: builtin/log.c:1130
msgid "store resulting files in <dir>"
msgstr "lưu các tập tin kết quả trong <t.mục>"
-#: builtin/log.c:1124
+#: builtin/log.c:1133
msgid "don't strip/add [PATCH]"
msgstr "không strip/add [VÁ]"
-#: builtin/log.c:1127
+#: builtin/log.c:1136
msgid "don't output binary diffs"
msgstr "không kết xuất diff (những khác biệt) nhị phân"
-#: builtin/log.c:1129
+#: builtin/log.c:1138
msgid "don't include a patch matching a commit upstream"
msgstr "không bao gồm miếng vá khớp với một lần chuyển giao thượng nguồn"
-#: builtin/log.c:1131
+#: builtin/log.c:1140
msgid "show patch format instead of default (patch + stat)"
msgstr "hiển thị định dạng miếng vá thay vì mặc định (miếng vá + thống kê)"
-#: builtin/log.c:1133
+#: builtin/log.c:1142
msgid "Messaging"
msgstr "Lời nhắn"
-#: builtin/log.c:1134
+#: builtin/log.c:1143
msgid "header"
msgstr "đầu đề thư"
-#: builtin/log.c:1135
+#: builtin/log.c:1144
msgid "add email header"
msgstr "thêm đầu đề thư"
-#: builtin/log.c:1136 builtin/log.c:1138
+#: builtin/log.c:1145 builtin/log.c:1147
msgid "email"
msgstr "thư điện tử"
-#: builtin/log.c:1136
+#: builtin/log.c:1145
msgid "add To: header"
msgstr "thêm To: đầu đề thư"
-#: builtin/log.c:1138
+#: builtin/log.c:1147
msgid "add Cc: header"
msgstr "thêm Cc: đầu đề thư"
-#: builtin/log.c:1140
+#: builtin/log.c:1149
msgid "message-id"
msgstr "message-id"
-#: builtin/log.c:1141
+#: builtin/log.c:1150
msgid "make first mail a reply to <message-id>"
msgstr "dùng thư đầu tiên để trả lời <message-id>"
-#: builtin/log.c:1142 builtin/log.c:1145
+#: builtin/log.c:1151 builtin/log.c:1154
msgid "boundary"
msgstr "ranh giới"
-#: builtin/log.c:1143
+#: builtin/log.c:1152
msgid "attach the patch"
msgstr "đính kèm miếng vá"
-#: builtin/log.c:1146
+#: builtin/log.c:1155
msgid "inline the patch"
msgstr "dùng miếng vá làm nội dung"
-#: builtin/log.c:1150
+#: builtin/log.c:1159
msgid "enable message threading, styles: shallow, deep"
msgstr "cho phép luồng lời nhắn, kiểu: “shallow”, “deep”"
-#: builtin/log.c:1152
+#: builtin/log.c:1161
msgid "signature"
msgstr "chữ ký"
-#: builtin/log.c:1153
+#: builtin/log.c:1162
msgid "add a signature"
msgstr "thêm chữ ký"
-#: builtin/log.c:1155
+#: builtin/log.c:1164
msgid "don't print the patch filenames"
msgstr "không hiển thị các tên tập tin của miếng vá"
-#: builtin/log.c:1239
+#: builtin/log.c:1248
msgid "-n and -k are mutually exclusive."
msgstr "-n và -k loại từ lẫn nhau."
-#: builtin/log.c:1241
+#: builtin/log.c:1250
msgid "--subject-prefix and -k are mutually exclusive."
msgstr "--subject-prefix và -k xung khắc nhau."
-#: builtin/log.c:1249
+#: builtin/log.c:1258
msgid "--name-only does not make sense"
msgstr "--name-only không hợp lý"
-#: builtin/log.c:1251
+#: builtin/log.c:1260
msgid "--name-status does not make sense"
msgstr "--name-status không hợp lý"
-#: builtin/log.c:1253
+#: builtin/log.c:1262
msgid "--check does not make sense"
msgstr "--check không hợp lý"
-#: builtin/log.c:1276
+#: builtin/log.c:1285
msgid "standard output, or directory, which one?"
msgstr "đầu ra chuẩn, hay thư mục, chọn cái nào?"
-#: builtin/log.c:1278
+#: builtin/log.c:1287
#, c-format
msgid "Could not create directory '%s'"
msgstr "Không thể tạo thư mục “%s”"
-#: builtin/log.c:1431
+#: builtin/log.c:1435
msgid "Failed to create output files"
msgstr "Gặp lỗi khi tạo các tập tin kết xuất"
-#: builtin/log.c:1480
+#: builtin/log.c:1484
msgid "git cherry [-v] [<upstream> [<head> [<limit>]]]"
msgstr "git cherry [-v] [<thượng-nguồn> [<head> [<giới-hạn>]]]"
-#: builtin/log.c:1535
+#: builtin/log.c:1539
#, c-format
msgid ""
"Could not find a tracked remote branch, please specify <upstream> manually.\n"
"Không tìm thấy nhánh mạng bị theo vết, hãy chỉ định <thượng-nguồn> một cách "
"thủ công.\n"
-#: builtin/log.c:1548 builtin/log.c:1550 builtin/log.c:1562
+#: builtin/log.c:1552 builtin/log.c:1554 builtin/log.c:1566
#, c-format
msgid "Unknown commit %s"
msgstr "Không hiểu lần chuyển giao (commit) %s"
-#: builtin/ls-files.c:409
+#: builtin/ls-files.c:402
msgid "git ls-files [options] [<file>...]"
msgstr "git ls-files [các-tùy-chọn] [<tập-tin>...]"
-#: builtin/ls-files.c:466
+#: builtin/ls-files.c:459
msgid "identify the file status with tags"
msgstr "nhận dạng các trạng thái tập tin với thẻ"
-#: builtin/ls-files.c:468
+#: builtin/ls-files.c:461
msgid "use lowercase letters for 'assume unchanged' files"
msgstr ""
"dùng chữ cái viết thường cho các tập tin “assume unchanged” (giả định không "
"thay đổi)"
-#: builtin/ls-files.c:470
+#: builtin/ls-files.c:463
msgid "show cached files in the output (default)"
msgstr "hiển thị các tập tin được nhớ tạm vào đầu ra (mặc định)"
-#: builtin/ls-files.c:472
+#: builtin/ls-files.c:465
msgid "show deleted files in the output"
msgstr "hiển thị các tập tin đã xóa trong kết xuất"
-#: builtin/ls-files.c:474
+#: builtin/ls-files.c:467
msgid "show modified files in the output"
msgstr "hiển thị các tập tin đã bị sửa đổi ra kết xuất"
-#: builtin/ls-files.c:476
+#: builtin/ls-files.c:469
msgid "show other files in the output"
msgstr "hiển thị các tập tin khác trong kết xuất"
-#: builtin/ls-files.c:478
+#: builtin/ls-files.c:471
msgid "show ignored files in the output"
msgstr "hiển thị các tập tin bị bỏ qua trong kết xuất"
-#: builtin/ls-files.c:481
+#: builtin/ls-files.c:474
msgid "show staged contents' object name in the output"
msgstr "hiển thị tên đối tượng của nội dung được lưu trạng thái ở kết xuất"
-#: builtin/ls-files.c:483
+#: builtin/ls-files.c:476
msgid "show files on the filesystem that need to be removed"
msgstr "hiển thị các tập tin trên hệ thống tập tin mà nó cần được gỡ bỏ"
-#: builtin/ls-files.c:485
+#: builtin/ls-files.c:478
msgid "show 'other' directories' name only"
msgstr "chỉ hiển thị tên của các thư mục “khác”"
-#: builtin/ls-files.c:488
+#: builtin/ls-files.c:481
msgid "don't show empty directories"
msgstr "không hiển thị thư mục rỗng"
-#: builtin/ls-files.c:491
+#: builtin/ls-files.c:484
msgid "show unmerged files in the output"
msgstr "hiển thị các tập tin chưa hòa trộn trong kết xuất"
-#: builtin/ls-files.c:493
+#: builtin/ls-files.c:486
msgid "show resolve-undo information"
msgstr "hiển thị thông tin resolve-undo"
-#: builtin/ls-files.c:495
+#: builtin/ls-files.c:488
msgid "skip files matching pattern"
msgstr "bỏ qua những tập tin khớp với một mẫu"
-#: builtin/ls-files.c:498
+#: builtin/ls-files.c:491
msgid "exclude patterns are read from <file>"
msgstr "mẫu loại trừ được đọc từ <tập tin>"
-#: builtin/ls-files.c:501
+#: builtin/ls-files.c:494
msgid "read additional per-directory exclude patterns in <file>"
msgstr "đọc thêm các mẫu ngoại trừ mỗi thư mục trong <tập tin>"
-#: builtin/ls-files.c:503
+#: builtin/ls-files.c:496
msgid "add the standard git exclusions"
msgstr "thêm loại trừ tiêu chuẩn kiểu git"
-#: builtin/ls-files.c:506
+#: builtin/ls-files.c:499
msgid "make the output relative to the project top directory"
msgstr "làm cho kết xuất liên quan đến thư mục ở mức cao nhất (gốc) của dự án"
-#: builtin/ls-files.c:509
+#: builtin/ls-files.c:502
msgid "if any <file> is not in the index, treat this as an error"
msgstr "nếu <tập tin> bất kỳ không ở trong bảng mục lục, xử lý nó như một lỗi"
-#: builtin/ls-files.c:510
+#: builtin/ls-files.c:503
msgid "tree-ish"
msgstr "tree-ish"
-#: builtin/ls-files.c:511
+#: builtin/ls-files.c:504
msgid "pretend that paths removed since <tree-ish> are still present"
msgstr ""
"giả định rằng các đường dẫn đã bị gỡ bỏ kể từ <tree-ish> nay vẫn hiện diện"
-#: builtin/ls-files.c:513
+#: builtin/ls-files.c:506
msgid "show debugging data"
msgstr "hiển thị dữ liệu gỡ lỗi"
"Chạy lệnh \"git rev-parse --parseopt -h\" để có thêm thông tin về cách dùng."
#: builtin/revert.c:22
-msgid "git revert [options] <commit-ish>"
-msgstr "git revert [các-tùy-chọn] <commit-ish>"
+msgid "git revert [options] <commit-ish>..."
+msgstr "git revert [các-tùy-chọn] <commit-ish>..."
#: builtin/revert.c:23
msgid "git revert <subcommand>"
msgstr "git revert <lệnh-con>"
#: builtin/revert.c:28
-msgid "git cherry-pick [options] <commit-ish>"
-msgstr "git cherry-pick [các-tùy-chọn] <commit-ish>"
+msgid "git cherry-pick [options] <commit-ish>..."
+msgstr "git cherry-pick [các-tùy-chọn] <commit-ish>..."
#: builtin/revert.c:29
msgid "git cherry-pick <subcommand>"
msgstr "git rm: không thể gỡ bỏ %s"
#: builtin/shortlog.c:13
-msgid "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
-msgstr "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
+msgid "git shortlog [<options>] [<revision range>] [[--] [<path>...]]"
+msgstr "git shortlog [các-tùy-chọn] [<vùng-xem-xét>] [[--] [<đường-dẫn>...]]"
-#: builtin/shortlog.c:133
+#: builtin/shortlog.c:131
#, c-format
msgid "Missing author: %s"
msgstr "Thiếu tên tác giả: %s"
-#: builtin/shortlog.c:229
+#: builtin/shortlog.c:227
msgid "sort output according to the number of commits per author"
msgstr "sắp xếp kết xuất tuân theo số lượng chuyển giao trên mỗi tác giả"
-#: builtin/shortlog.c:231
+#: builtin/shortlog.c:229
msgid "Suppress commit descriptions, only provides commit count"
msgstr "Chặn mọi mô tả lần chuyển giao, chỉ đưa ra số lượng lần chuyển giao"
-#: builtin/shortlog.c:233
+#: builtin/shortlog.c:231
msgid "Show the email address of each author"
msgstr "Hiển thị thư điện tử cho từng tác giả"
-#: builtin/shortlog.c:234
+#: builtin/shortlog.c:232
msgid "w[,i1[,i2]]"
msgstr "w[,i1[,i2]]"
-#: builtin/shortlog.c:235
+#: builtin/shortlog.c:233
msgid "Linewrap output"
msgstr "Ngắt dòng khi quá dài"
msgid "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]"
msgstr "git show-branch (-g|--reflog)[=<n>[,<nền>]] [--list] [<ref>]"
-#: builtin/show-branch.c:651
+#: builtin/show-branch.c:650
msgid "show remote-tracking and local branches"
msgstr "hiển thị các nhánh remote-tracking và nội bộ"
-#: builtin/show-branch.c:653
+#: builtin/show-branch.c:652
msgid "show remote-tracking branches"
msgstr "hiển thị các nhánh remote-tracking"
-#: builtin/show-branch.c:655
+#: builtin/show-branch.c:654
msgid "color '*!+-' corresponding to the branch"
msgstr "màu “*!+-” tương ứng với nhánh"
-#: builtin/show-branch.c:657
+#: builtin/show-branch.c:656
msgid "show <n> more commits after the common ancestor"
msgstr "hiển thị thêm <n> lần chuyển giao sau cha mẹ chung"
-#: builtin/show-branch.c:659
+#: builtin/show-branch.c:658
msgid "synonym to more=-1"
msgstr "đồng nghĩa với more=-1"
-#: builtin/show-branch.c:660
+#: builtin/show-branch.c:659
msgid "suppress naming strings"
msgstr "chặn các chuỗi đặt tên"
-#: builtin/show-branch.c:662
+#: builtin/show-branch.c:661
msgid "include the current branch"
msgstr "bao gồm nhánh hiện hành"
-#: builtin/show-branch.c:664
+#: builtin/show-branch.c:663
msgid "name commits with their object names"
msgstr "đặt tên các lần chuyển giao bằng các tên của đối tượng của chúng"
-#: builtin/show-branch.c:666
+#: builtin/show-branch.c:665
msgid "show possible merge bases"
msgstr "hiển thị mọi cơ sở có thể dùng để hòa trộn"
-#: builtin/show-branch.c:668
+#: builtin/show-branch.c:667
msgid "show refs unreachable from any other ref"
msgstr "hiển thị các tham chiếu không thể được đọc bởi bất kỳ tham chiếu khác"
-#: builtin/show-branch.c:670
+#: builtin/show-branch.c:669
msgid "show commits in topological order"
msgstr "hiển thị các lần chuyển giao theo thứ tự tôpô"
-#: builtin/show-branch.c:672
+#: builtin/show-branch.c:671
msgid "show only commits not on the first branch"
msgstr "chỉ hiển thị các lần chuyển giao không nằm trên nhánh đầu tiên"
-#: builtin/show-branch.c:674
+#: builtin/show-branch.c:673
msgid "show merges reachable from only one tip"
msgstr "hiển thị các lần hòa trộn có thể đọc được chỉ từ một đầu mút"
-#: builtin/show-branch.c:676
+#: builtin/show-branch.c:675
msgid "show commits where no parent comes before its children"
msgstr "hiển thị các lần chuyển giao nơi mà cha mẹ đến trước con của nó"
-#: builtin/show-branch.c:678
+#: builtin/show-branch.c:677
msgid "<n>[,<base>]"
msgstr "<n>[,<cơ sở>]"
-#: builtin/show-branch.c:679
+#: builtin/show-branch.c:678
msgid "show <n> most recent ref-log entries starting at base"
msgstr "hiển thị <n> các mục “ref-log” gần nhất kể từ nền (base)"
msgstr "chỉ hữu ích khi cần gỡ lỗi"
#: git.c:16
-msgid "See 'git help <command>' for more information on a specific command."
+msgid ""
+"'git help -a' and 'git help -g' lists available subcommands and some\n"
+"concept guides. See 'git help <command>' or 'git help <concept>'\n"
+"to read about a specific subcommand or concept."
msgstr ""
-"Chạy lệnh “git help <tên-lệnh>” để có thêm thông tin về lệnh được chỉ ra."
+"“git help -a” và “git help -g” liệt kê các câu lệnh con sẵn có và một số\n"
+"hướng dẫn khái niệm cơ bản. Xem “git help <lệnh>” hay “git help <khái "
+"niệm>”\n"
+"để đọc các đặc tả cho lệnh hay khái niệm đó."
#: parse-options.h:156
msgid "no-op (backward compatibility)"
"sai.\n"
"(Bạn có thể sử dụng \"git bisect bad\" và \"git bisect good\" cho chúng.)"
-#: git-bisect.sh:347 git-bisect.sh:474
+#: git-bisect.sh:363 git-bisect.sh:490
msgid "We are not bisecting."
msgstr "Chúng tôi không bisect."
-#: git-bisect.sh:354
+#: git-bisect.sh:370
#, sh-format
msgid "'$invalid' is not a valid commit"
msgstr "”$invalid” không phải là lần chuyển giao (commit) hợp lệ"
-#: git-bisect.sh:363
+#: git-bisect.sh:379
#, sh-format
msgid ""
"Could not check out original HEAD '$branch'.\n"
"Không thể check-out HEAD nguyên thủy của “$branch”.\n"
"Hãy thử “git bisect reset <lần-chuyển-giao>”."
-#: git-bisect.sh:390
+#: git-bisect.sh:406
msgid "No logfile given"
msgstr "Chưa chỉ ra tập tin ghi nhật ký"
-#: git-bisect.sh:391
+#: git-bisect.sh:407
#, sh-format
msgid "cannot read $file for replaying"
msgstr "không thể đọc $file để thao diễn lại"
-#: git-bisect.sh:408
+#: git-bisect.sh:424
msgid "?? what are you talking about?"
msgstr "?? bạn đang nói gì thế?"
-#: git-bisect.sh:420
+#: git-bisect.sh:436
#, sh-format
msgid "running $command"
msgstr "đang chạy lệnh $command"
-#: git-bisect.sh:427
+#: git-bisect.sh:443
#, sh-format
msgid ""
"bisect run failed:\n"
"chạy bisect gặp lỗi:\n"
"mã trả về $res từ lệnh “$command” là < 0 hoặc >= 128"
-#: git-bisect.sh:453
+#: git-bisect.sh:469
msgid "bisect run cannot continue any more"
msgstr "bisect không thể tiếp tục thêm được nữa"
-#: git-bisect.sh:459
+#: git-bisect.sh:475
#, sh-format
msgid ""
"bisect run failed:\n"
"chạy bisect gặp lỗi:\n"
"”bisect_state $state” đã thoát ra với mã lỗi $res"
-#: git-bisect.sh:466
+#: git-bisect.sh:482
msgid "bisect run success"
msgstr "bisect chạy thành công"
#: git-submodule.sh:588
#, sh-format
msgid "Use '.' if you really want to deinitialize all submodules"
-msgstr "Dùng '.' nếu bạn thực sự muốn gỡ bỏ mọi mô-đun-con"
+msgstr "Dùng “.” nếu bạn thực sự muốn gỡ bỏ mọi mô-đun-con"
#: git-submodule.sh:603
#, sh-format
msgid "Submodule work tree '$sm_path' contains a .git directory"
-msgstr "Cây làm việc mô-đun-con '$sm_path' có chứa thư mục .git"
+msgstr "Cây làm việc mô-đun-con “$sm_path” có chứa thư mục .git"
#: git-submodule.sh:604
#, sh-format
"Submodule work tree '$sm_path' contains local modifications; use '-f' to "
"discard them"
msgstr ""
-"Cây làm việc mô-đun-con '$sm_path' chứa các thay đổi nội bộ; hãy dùng '-f' "
+"Cây làm việc mô-đun-con “$sm_path” chứa các thay đổi nội bộ; hãy dùng “-f” "
"để loại bỏ chúng đi"
#: git-submodule.sh:613
msgid "Synchronizing submodule url for '$prefix$sm_path'"
msgstr "Url Mô-đun-con đồng bộ hóa cho “$prefix$sm_path”"
+#~ msgid "It took %.2f seconds to enumerate untracked files. 'status -uno'"
+#~ msgstr "Cần %.2f giây để đếm các tập tin chưa được theo vết. “status -uno”"
+
+#~ msgid "may speed it up, but you have to be careful not to forget to add"
+#~ msgstr ""
+#~ "có thể làm nó nhanh lên, nhưng bạn phải cẩn trọng đừng quên thêm nó vào"
+
+#~ msgid "new files yourself (see 'git help status')."
+#~ msgstr "tập tin mới của chính bạn (xem “git help status”.."
+
+#~ msgid "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
+#~ msgstr "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
+
+#~ msgid "See 'git help <command>' for more information on a specific command."
+#~ msgstr ""
+#~ "Chạy lệnh “git help <tên-lệnh>” để có thêm thông tin về lệnh được chỉ ra."
+
#~ msgid "use any ref in .git/refs"
#~ msgstr "sử dụng bất kỳ ref nào trong .git/refs"
msgstr ""
"Project-Id-Version: Git\n"
"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2013-04-10 15:16+0800\n"
+"POT-Creation-Date: 2013-04-30 08:25+0800\n"
"PO-Revision-Date: 2013-04-12 09:23+0800\n"
"Last-Translator: Jiang Xin <worldhello.net@gmail.com>\n"
"Language-Team: GitHub <https://github.com/gotgit/git/>\n"
msgid "archive format"
msgstr "归档格式"
-#: archive.c:324 builtin/log.c:1117
+#: archive.c:324 builtin/log.c:1126
msgid "prefix"
msgstr "前缀"
msgid "prepend prefix to each pathname in the archive"
msgstr "为归档中每个路径名加上前缀"
-#: archive.c:326 builtin/archive.c:88 builtin/blame.c:2366
-#: builtin/blame.c:2367 builtin/config.c:55 builtin/fast-export.c:659
-#: builtin/fast-export.c:661 builtin/grep.c:715 builtin/hash-object.c:77
-#: builtin/ls-files.c:497 builtin/ls-files.c:500 builtin/notes.c:536
+#: archive.c:326 builtin/archive.c:88 builtin/blame.c:2371
+#: builtin/blame.c:2372 builtin/config.c:55 builtin/fast-export.c:665
+#: builtin/fast-export.c:667 builtin/grep.c:715 builtin/hash-object.c:77
+#: builtin/ls-files.c:490 builtin/ls-files.c:493 builtin/notes.c:536
#: builtin/notes.c:693 builtin/read-tree.c:107 parse-options.h:149
msgid "file"
msgstr "文件"
"负值模版在 git attributes 中被忽略\n"
"当字符串确实要以感叹号开始时,使用 '\\!'。"
+#: branch.c:60
+#, c-format
+msgid "Not setting branch %s as its own upstream."
+msgstr "未设置分支 %s 作为它自己的上游。"
+
+#: branch.c:82
+#, c-format
+msgid "Branch %s set up to track remote branch %s from %s by rebasing."
+msgstr "分支 %1$s 设置为使用变基来跟踪来自 %3$s 的远程分支 %2$s。"
+
+#: branch.c:83
+#, c-format
+msgid "Branch %s set up to track remote branch %s from %s."
+msgstr "分支 %1$s 设置为跟踪来自 %3$s 的远程分支 %2$s。"
+
+#: branch.c:87
+#, c-format
+msgid "Branch %s set up to track local branch %s by rebasing."
+msgstr "分支 %s 设置为使用变基来跟踪本地分支 %s。"
+
+#: branch.c:88
+#, c-format
+msgid "Branch %s set up to track local branch %s."
+msgstr "分支 %s 设置为跟踪本地分支 %s。"
+
+#: branch.c:92
+#, c-format
+msgid "Branch %s set up to track remote ref %s by rebasing."
+msgstr "分支 %s 设置为使用变基来跟踪远程引用 %s。"
+
+#: branch.c:93
+#, c-format
+msgid "Branch %s set up to track remote ref %s."
+msgstr "分支 %s 设置为跟踪远程引用 %s。"
+
+#: branch.c:97
+#, c-format
+msgid "Branch %s set up to track local ref %s by rebasing."
+msgstr "分支 %s 设置为使用变基来跟踪本地引用 %s。"
+
+#: branch.c:98
+#, c-format
+msgid "Branch %s set up to track local ref %s."
+msgstr "分支 %s 设置为跟踪本地引用 %s。"
+
+#: branch.c:118
+#, c-format
+msgid "Tracking not set up: name too long: %s"
+msgstr "跟踪未设置:名字太长:%s"
+
+#: branch.c:137
+#, c-format
+msgid "Not tracking: ambiguous information for ref %s"
+msgstr "未跟踪:引用 %s 有歧义"
+
+#: branch.c:182
+#, c-format
+msgid "'%s' is not a valid branch name."
+msgstr "'%s' 不是一个有效的分支名称。"
+
+#: branch.c:187
+#, c-format
+msgid "A branch named '%s' already exists."
+msgstr "一个分支名 '%s' 已经存在。"
+
+#: branch.c:195
+msgid "Cannot force update the current branch."
+msgstr "无法强制更新当前分支。"
+
#: branch.c:201
#, c-format
msgid "Cannot setup tracking information; starting point '%s' is not a branch."
"如果您正计划推送一个能与对应远程分支建立跟踪的新的本地分支,\n"
"您可能需要使用 \"git push -u\" 推送分支并配置和上游的关联。"
+#: branch.c:250
+#, c-format
+msgid "Not a valid object name: '%s'."
+msgstr "不是一个有效的对象名:'%s'。"
+
+#: branch.c:270
+#, c-format
+msgid "Ambiguous object name: '%s'."
+msgstr "歧义的对象名:'%s'。"
+
+#: branch.c:275
+#, c-format
+msgid "Not a valid branch point: '%s'."
+msgstr "无效的分支点:'%s'。"
+
+#: branch.c:281
+msgid "Failed to lock ref for update"
+msgstr "无法为更新锁定引用"
+
+#: branch.c:299
+msgid "Failed to write ref"
+msgstr "不能写引用"
+
#: bundle.c:36
#, c-format
msgid "'%s' does not look like a v2 bundle file"
msgid "Repository lacks these prerequisite commits:"
msgstr "版本库缺少这些必备的提交:"
-#: bundle.c:164 sequencer.c:651 sequencer.c:1083 builtin/log.c:300
-#: builtin/log.c:754 builtin/log.c:1350 builtin/log.c:1566 builtin/merge.c:349
-#: builtin/shortlog.c:157
+#: bundle.c:164 sequencer.c:651 sequencer.c:1101 builtin/log.c:300
+#: builtin/log.c:770 builtin/log.c:1344 builtin/log.c:1570 builtin/merge.c:349
+#: builtin/shortlog.c:155
msgid "revision walk setup failed"
msgstr "版本遍历设置失败"
msgid "rev-list died"
msgstr "rev-list 终止"
-#: bundle.c:300 builtin/log.c:1246 builtin/shortlog.c:260
+#: bundle.c:300 builtin/log.c:1255 builtin/shortlog.c:258
#, c-format
msgid "unrecognized argument: %s"
msgstr "未能识别的参数:%s"
"发现配置变量 'diff.dirstat' 中的错误:\n"
"%s"
-#: diff.c:3480
+#: diff.c:3481
#, c-format
msgid ""
"Failed to parse --dirstat/-X option parameter:\n"
"无法解析 --dirstat/-X 选项的参数:\n"
"%s"
-#: diff.c:3494
+#: diff.c:3495
#, c-format
msgid "Failed to parse --submodule option parameter: '%s'"
msgstr "无法解析 --submodule 选项的参数:'%s'"
msgid "failed to read the cache"
msgstr "无法读取缓存"
-#: merge.c:110 builtin/checkout.c:362 builtin/checkout.c:563
-#: builtin/clone.c:635
+#: merge.c:110 builtin/checkout.c:365 builtin/checkout.c:566
+#: builtin/clone.c:645
msgid "unable to write new index file"
msgstr "无法写新的索引文件"
msgid "blob expected for %s '%s'"
msgstr "%s '%s' 应为数据(blob)对象"
-#: merge-recursive.c:773 builtin/clone.c:303
+#: merge-recursive.c:773 builtin/clone.c:313
#, c-format
msgid "failed to open '%s'"
msgstr "无法打开 '%s'"
msgid "Could not format %s."
msgstr "不能格式化 %s。"
-#: sequencer.c:1101
+#: sequencer.c:1083
+#, c-format
+msgid "%s: can't cherry-pick a %s"
+msgstr "%s:不能拣选一个%s"
+
+#: sequencer.c:1085
+#, c-format
+msgid "%s: bad revision"
+msgstr "%s:错误的版本"
+
+#: sequencer.c:1119
msgid "Can't revert as initial commit"
msgstr "不能作为初始提交还原"
-#: sequencer.c:1102
+#: sequencer.c:1120
msgid "Can't cherry-pick into empty head"
msgstr "不能拣选到空分支"
msgid "bug: unhandled diff status %c"
msgstr "bug:未处理的差异状态 %c"
-#: wt-status.c:805
+#: wt-status.c:803
msgid "You have unmerged paths."
msgstr "您有尚未合并的路径。"
# 译者:注意保持前导空格
-#: wt-status.c:808 wt-status.c:960
+#: wt-status.c:806 wt-status.c:958
msgid " (fix conflicts and run \"git commit\")"
msgstr " (解决冲突并运行 \"git commit\")"
-#: wt-status.c:811
+#: wt-status.c:809
msgid "All conflicts fixed but you are still merging."
msgstr "所有冲突已解决但您仍处于合并中。"
# 译者:注意保持前导空格
-#: wt-status.c:814
+#: wt-status.c:812
msgid " (use \"git commit\" to conclude merge)"
msgstr " (使用 \"git commit\" 结束合并)"
-#: wt-status.c:824
+#: wt-status.c:822
msgid "You are in the middle of an am session."
msgstr "您正处于一个 am 过程中。"
-#: wt-status.c:827
+#: wt-status.c:825
msgid "The current patch is empty."
msgstr "当前的补丁为空。"
# 译者:注意保持前导空格
-#: wt-status.c:831
+#: wt-status.c:829
msgid " (fix conflicts and then run \"git am --resolved\")"
msgstr " (解决冲突,然后运行 \"git am --resolved\")"
# 译者:注意保持前导空格
-#: wt-status.c:833
+#: wt-status.c:831
msgid " (use \"git am --skip\" to skip this patch)"
msgstr " (使用 \"git am --skip\" 跳过此补丁)"
# 译者:注意保持前导空格
-#: wt-status.c:835
+#: wt-status.c:833
msgid " (use \"git am --abort\" to restore the original branch)"
msgstr " (使用 \"git am --abort\" 恢复原有分支)"
-#: wt-status.c:895 wt-status.c:912
+#: wt-status.c:893 wt-status.c:910
#, c-format
msgid "You are currently rebasing branch '%s' on '%s'."
msgstr "您正在将分支 '%s' 变基到 '%s'。"
-#: wt-status.c:900 wt-status.c:917
+#: wt-status.c:898 wt-status.c:915
msgid "You are currently rebasing."
msgstr "您正在变基。"
# 译者:注意保持前导空格
-#: wt-status.c:903
+#: wt-status.c:901
msgid " (fix conflicts and then run \"git rebase --continue\")"
msgstr " (解决冲突,然后运行 \"git rebase --continue\")"
# 译者:注意保持前导空格
-#: wt-status.c:905
+#: wt-status.c:903
msgid " (use \"git rebase --skip\" to skip this patch)"
msgstr " (使用 \"git rebase --skip\" 跳过此补丁)"
# 译者:注意保持前导空格
-#: wt-status.c:907
+#: wt-status.c:905
msgid " (use \"git rebase --abort\" to check out the original branch)"
msgstr " (使用 \"git rebase --abort\" 以检出原有分支)"
# 译者:注意保持前导空格
-#: wt-status.c:920
+#: wt-status.c:918
msgid " (all conflicts fixed: run \"git rebase --continue\")"
msgstr " (所有冲突已解决:运行 \"git rebase --continue\")"
-#: wt-status.c:924
+#: wt-status.c:922
#, c-format
msgid ""
"You are currently splitting a commit while rebasing branch '%s' on '%s'."
msgstr "您正在将分支 '%s' 变基到 '%s' 过程中拆分一个提交。"
-#: wt-status.c:929
+#: wt-status.c:927
msgid "You are currently splitting a commit during a rebase."
msgstr "您正在变基过程中拆分一个提交。"
# 译者:注意保持前导空格
-#: wt-status.c:932
+#: wt-status.c:930
msgid " (Once your working directory is clean, run \"git rebase --continue\")"
msgstr " (一旦您工作目录提交干净后,运行 \"git rebase --continue\")"
-#: wt-status.c:936
+#: wt-status.c:934
#, c-format
msgid "You are currently editing a commit while rebasing branch '%s' on '%s'."
msgstr "您正在将分支 '%s' 变基到 '%s' 过程中编辑一个提交。"
-#: wt-status.c:941
+#: wt-status.c:939
msgid "You are currently editing a commit during a rebase."
msgstr "您正在变基过程中编辑一个提交。"
# 译者:注意保持前导空格
-#: wt-status.c:944
+#: wt-status.c:942
msgid " (use \"git commit --amend\" to amend the current commit)"
msgstr " (使用 \"git commit --amend\" 修补当前提交)"
# 译者:注意保持前导空格
-#: wt-status.c:946
+#: wt-status.c:944
msgid ""
" (use \"git rebase --continue\" once you are satisfied with your changes)"
msgstr " (当您对您的修改满意后执行 \"git rebase --continue\")"
-#: wt-status.c:956
+#: wt-status.c:954
msgid "You are currently cherry-picking."
msgstr "您正在做拣选操作。"
# 译者:注意保持前导空格
-#: wt-status.c:963
+#: wt-status.c:961
msgid " (all conflicts fixed: run \"git commit\")"
msgstr " (解决所有冲突后,执行 \"git commit\")"
-#: wt-status.c:972
+#: wt-status.c:970
#, c-format
msgid "You are currently reverting commit %s."
msgstr "您正在反转提交 %s 。"
# 译者:注意保持前导空格
-#: wt-status.c:977
+#: wt-status.c:975
msgid " (fix conflicts and run \"git revert --continue\")"
msgstr " (解决冲突并运行 \"git revert --continue\")"
# 译者:注意保持前导空格
-#: wt-status.c:980
+#: wt-status.c:978
msgid " (all conflicts fixed: run \"git revert --continue\")"
msgstr " (所有冲突已解决:运行 \"git revert --continue\")"
# 译者:注意保持前导空格
-#: wt-status.c:982
+#: wt-status.c:980
msgid " (use \"git revert --abort\" to cancel the revert operation)"
msgstr " (使用 \"git revert --abort\" 以取消反转提交操作)"
-#: wt-status.c:993
+#: wt-status.c:991
#, c-format
msgid "You are currently bisecting, started from branch '%s'."
msgstr "您正在从分支 '%s' 开始做二分查找。"
-#: wt-status.c:997
+#: wt-status.c:995
msgid "You are currently bisecting."
msgstr "您正在做二分查找。"
# 译者:注意保持前导空格
-#: wt-status.c:1000
+#: wt-status.c:998
msgid " (use \"git bisect reset\" to get back to the original branch)"
msgstr " (使用 \"git bisect reset\" 以回到原有分支)"
-#: wt-status.c:1175
+#: wt-status.c:1173
msgid "On branch "
msgstr "位于分支 "
-#: wt-status.c:1186
+#: wt-status.c:1184
msgid "HEAD detached at "
msgstr "头指针分离于 "
-#: wt-status.c:1188
+#: wt-status.c:1186
msgid "HEAD detached from "
msgstr "头指针分离自 "
-#: wt-status.c:1191
+#: wt-status.c:1189
msgid "Not currently on any branch."
msgstr "当前不在任何分支上。"
-#: wt-status.c:1208
+#: wt-status.c:1206
msgid "Initial commit"
msgstr "初始提交"
-#: wt-status.c:1222
+#: wt-status.c:1220
msgid "Untracked files"
msgstr "未跟踪的文件"
-#: wt-status.c:1224
+#: wt-status.c:1222
msgid "Ignored files"
msgstr "忽略的文件"
-# 译者:以下三条实际为一个段落
-#: wt-status.c:1228
+#: wt-status.c:1226
#, c-format
-msgid "It took %.2f seconds to enumerate untracked files. 'status -uno'"
-msgstr "耗费了 %.2f 秒以枚举未跟踪的文件。'status -uno'"
+msgid ""
+"It took %.2f seconds to enumerate untracked files. 'status -uno'\n"
+"may speed it up, but you have to be careful not to forget to add\n"
+"new files yourself (see 'git help status')."
+msgstr ""
+"耗费了 %.2f 秒以枚举未跟踪的文件。'status -uno' 也许能提高速度,\n"
+"但您需要小心不要忘了添加新文件(参见 'git help status')。"
-# 译者:为对齐,下面两句内容有调整
#: wt-status.c:1232
-msgid "may speed it up, but you have to be careful not to forget to add"
-msgstr "也许能提高速度,但您需要小心不要忘了添加新文件"
-
-#: wt-status.c:1235
-msgid "new files yourself (see 'git help status')."
-msgstr "(参见 'git help status')。"
-
-#: wt-status.c:1238
#, c-format
msgid "Untracked files not listed%s"
msgstr "未跟踪的文件没有列出%s"
# 译者:中文字符串拼接,可删除前导空格
-#: wt-status.c:1240
+#: wt-status.c:1234
msgid " (use -u option to show untracked files)"
msgstr "(使用 -u 参数显示未跟踪的文件)"
-#: wt-status.c:1246
+#: wt-status.c:1240
msgid "No changes"
msgstr "没有修改"
-#: wt-status.c:1251
+#: wt-status.c:1245
#, c-format
msgid "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"
msgstr "修改尚未加入提交(使用 \"git add\" 和/或 \"git commit -a\")\n"
-#: wt-status.c:1254
+#: wt-status.c:1248
#, c-format
msgid "no changes added to commit\n"
msgstr "修改尚未加入提交\n"
-#: wt-status.c:1257
+#: wt-status.c:1251
#, c-format
msgid ""
"nothing added to commit but untracked files present (use \"git add\" to "
"track)\n"
msgstr "提交为空,但是存在尚未跟踪的文件(使用 \"git add\" 建立跟踪)\n"
-#: wt-status.c:1260
+#: wt-status.c:1254
#, c-format
msgid "nothing added to commit but untracked files present\n"
msgstr "提交为空,但是存在尚未跟踪的文件\n"
# 译者:中文字符串拼接,可删除前导空格
-#: wt-status.c:1263
+#: wt-status.c:1257
#, c-format
msgid "nothing to commit (create/copy files and use \"git add\" to track)\n"
msgstr "无文件要提交(创建/拷贝文件并使用 \"git add\" 建立跟踪)\n"
-#: wt-status.c:1266 wt-status.c:1271
+#: wt-status.c:1260 wt-status.c:1265
#, c-format
msgid "nothing to commit\n"
msgstr "无文件要提交\n"
# 译者:中文字符串拼接,可删除前导空格
-#: wt-status.c:1269
+#: wt-status.c:1263
#, c-format
msgid "nothing to commit (use -u to show untracked files)\n"
msgstr "无文件要提交(使用 -u 显示未跟踪的文件)\n"
# 译者:中文字符串拼接,可删除前导空格
-#: wt-status.c:1273
+#: wt-status.c:1267
#, c-format
msgid "nothing to commit, working directory clean\n"
msgstr "无文件要提交,干净的工作区\n"
-#: wt-status.c:1381
+#: wt-status.c:1375
msgid "HEAD (no branch)"
msgstr "HEAD(非分支)"
# 译者:注意保持句尾空格
-#: wt-status.c:1387
+#: wt-status.c:1381
msgid "Initial commit on "
msgstr "初始提交于 "
# 译者:注意保持句尾空格
-#: wt-status.c:1402
+#: wt-status.c:1396
msgid "behind "
msgstr "落后 "
# 译者:注意保持句尾空格
-#: wt-status.c:1405 wt-status.c:1408
+#: wt-status.c:1399 wt-status.c:1402
msgid "ahead "
msgstr "领先 "
# 译者:注意保持句尾空格
-#: wt-status.c:1410
+#: wt-status.c:1404
msgid ", behind "
msgstr ",落后 "
-#: compat/precompose_utf8.c:58 builtin/clone.c:342
+#: compat/precompose_utf8.c:58 builtin/clone.c:352
#, c-format
msgid "failed to unlink '%s'"
msgstr "无法删除 '%s'"
msgid "git add [options] [--] <pathspec>..."
msgstr "git add [选项] [--] <路径匹配>..."
-#: builtin/add.c:63
+# 译者:字符串首行行首要添加“warning: ”字串,故此首行要较其余行短
+#.
+#. * To be consistent with "git add -p" and most Git
+#. * commands, we should default to being tree-wide, but
+#. * this is not the original behavior and can't be
+#. * changed until users trained themselves not to type
+#. * "git add -u" or "git add -A". For now, we warn and
+#. * keep the old behavior. Later, the behavior can be changed
+#. * to tree-wide, keeping the warning for a while, and
+#. * eventually we can drop the warning.
+#.
+#: builtin/add.c:58
+#, c-format
+msgid ""
+"The behavior of 'git add %s (or %s)' with no path argument from a\n"
+"subdirectory of the tree will change in Git 2.0 and should not be used "
+"anymore.\n"
+"To add content for the whole tree, run:\n"
+"\n"
+" git add %s :/\n"
+" (or git add %s :/)\n"
+"\n"
+"To restrict the command to the current directory, run:\n"
+"\n"
+" git add %s .\n"
+" (or git add %s .)\n"
+"\n"
+"With the current Git version, the command is restricted to the current "
+"directory.\n"
+msgstr ""
+"在 Git 2.0 版本,位于一个子目录下不带任何路径参数地执行命令\n"
+"'git add %s (或 %s)' 的行为将被改变,不要再继续使用了。\n"
+"如果要添加整个目录树的内容,执行:\n"
+"\n"
+" git add %s :/\n"
+" (或 git add %s :/)\n"
+"\n"
+"如果要限制该命令只作用于当前目录,执行:\n"
+"\n"
+" git add %s .\n"
+" (或 git add %s .)\n"
+"\n"
+"对于当前版本的 Git,这条命令只作用于当前目录。\n"
+
+#: builtin/add.c:100
+#, c-format
+msgid ""
+"You ran 'git add' with neither '-A (--all)' or '--ignore-removal',\n"
+"whose behaviour will change in Git 2.0 with respect to paths you removed.\n"
+"Paths like '%s' that are\n"
+"removed from your working tree are ignored with this version of Git.\n"
+"\n"
+"* 'git add --ignore-removal <pathspec>', which is the current default,\n"
+" ignores paths you removed from your working tree.\n"
+"\n"
+"* 'git add --all <pathspec>' will let you also record the removals.\n"
+"\n"
+"Run 'git status' to check the paths you removed from your working tree.\n"
+msgstr ""
+"您在运行 'git add' 时没有指定 '-A (--all)' 或 '--ignore-removal',\n"
+"针对其中本地移除路径的行为将在 Git 2.0 版本库发生变化。\n"
+"像本地工作区移除的路径 '%s'\n"
+"在此版本的 Git 中被忽略。\n"
+"\n"
+"* 'git add --ignore-removal <pathspec>',是当前版本的默认操作,\n"
+" 忽略您本地工作区中移除的文件。\n"
+"\n"
+"* 'git add --all <pathspec>' 将让您同时对删除操作进行记录。\n"
+"\n"
+"运行 'git status' 来检查您本地工作区中移除的路径。\n"
+
+#: builtin/add.c:144
#, c-format
msgid "unexpected diff status %c"
msgstr "意外的差异状态 %c"
-#: builtin/add.c:68 builtin/commit.c:233
+#: builtin/add.c:149 builtin/commit.c:233
msgid "updating files failed"
msgstr "更新文件失败"
-#: builtin/add.c:78
+#: builtin/add.c:163
#, c-format
msgid "remove '%s'\n"
msgstr "删除 '%s'\n"
-#: builtin/add.c:148
+#: builtin/add.c:253
msgid "Unstaged changes after refreshing the index:"
msgstr "刷新索引之后尚未被暂存的变更:"
-#: builtin/add.c:151 builtin/add.c:460 builtin/rm.c:275
+#: builtin/add.c:256 builtin/add.c:572 builtin/rm.c:275
#, c-format
msgid "pathspec '%s' did not match any files"
msgstr "路径 '%s' 未匹配任何文件"
-#: builtin/add.c:234
+#: builtin/add.c:339
msgid "Could not read the index"
msgstr "不能读取索引"
-#: builtin/add.c:244
+#: builtin/add.c:349
#, c-format
msgid "Could not open '%s' for writing."
msgstr "不能打开 '%s' 以写入。"
-#: builtin/add.c:248
+#: builtin/add.c:353
msgid "Could not write patch"
msgstr "不能生成补丁"
-#: builtin/add.c:253
+#: builtin/add.c:358
#, c-format
msgid "Could not stat '%s'"
msgstr "不能查看文件状态 '%s'"
-#: builtin/add.c:255
+#: builtin/add.c:360
msgid "Empty patch. Aborted."
msgstr "空补丁。异常终止。"
-#: builtin/add.c:261
+#: builtin/add.c:366
#, c-format
msgid "Could not apply '%s'"
msgstr "不能应用 '%s'"
-#: builtin/add.c:271
+#: builtin/add.c:376
msgid "The following paths are ignored by one of your .gitignore files:\n"
msgstr "下列路径根据您的一个 .gitignore 文件而被忽略:\n"
-#: builtin/add.c:277 builtin/clean.c:161 builtin/fetch.c:78 builtin/mv.c:63
+#: builtin/add.c:393 builtin/clean.c:161 builtin/fetch.c:78 builtin/mv.c:63
#: builtin/prune-packed.c:76 builtin/push.c:425 builtin/remote.c:1253
#: builtin/rm.c:206
msgid "dry run"
msgstr "演习"
-#: builtin/add.c:278 builtin/apply.c:4409 builtin/check-ignore.c:19
+#: builtin/add.c:394 builtin/apply.c:4409 builtin/check-ignore.c:19
#: builtin/commit.c:1152 builtin/count-objects.c:95 builtin/fsck.c:613
-#: builtin/log.c:1514 builtin/mv.c:62 builtin/read-tree.c:112
+#: builtin/log.c:1518 builtin/mv.c:62 builtin/read-tree.c:112
msgid "be verbose"
msgstr "冗长输出"
-#: builtin/add.c:280
+#: builtin/add.c:396
msgid "interactive picking"
msgstr "交互式拣选"
-#: builtin/add.c:281 builtin/checkout.c:1060 builtin/reset.c:258
+#: builtin/add.c:397 builtin/checkout.c:1063 builtin/reset.c:258
msgid "select hunks interactively"
msgstr "交互式挑选数据块"
-#: builtin/add.c:282
+#: builtin/add.c:398
msgid "edit current diff and apply"
msgstr "编辑当前差异并应用"
-#: builtin/add.c:283
+#: builtin/add.c:399
msgid "allow adding otherwise ignored files"
msgstr "允许添加忽略的文件"
-#: builtin/add.c:284
+#: builtin/add.c:400
msgid "update tracked files"
msgstr "更新已跟踪的文件"
-#: builtin/add.c:285
+#: builtin/add.c:401
msgid "record only the fact that the path will be added later"
msgstr "只记录,该路径稍后再添加"
-#: builtin/add.c:286
+#: builtin/add.c:402
msgid "add changes from all tracked and untracked files"
msgstr "添加所有改变的已跟踪文件和未跟踪文件"
-#: builtin/add.c:287
+#. takes no arguments
+#: builtin/add.c:405
+msgid "ignore paths removed in the working tree (same as --no-all)"
+msgstr "忽略工作区中移除的路径(和 --no-all 相同)"
+
+#: builtin/add.c:407
msgid "don't add, only refresh the index"
msgstr "不添加,只刷新索引"
-#: builtin/add.c:288
+#: builtin/add.c:408
msgid "just skip files which cannot be added because of errors"
msgstr "跳过因出错不能添加的文件"
-#: builtin/add.c:289
+#: builtin/add.c:409
msgid "check if - even missing - files are ignored in dry run"
msgstr "检查在演习模式下文件(即使不存在)是否被忽略"
-#: builtin/add.c:311
+#: builtin/add.c:431
#, c-format
msgid "Use -f if you really want to add them.\n"
msgstr "使用 -f 参数如果您确实要添加它们。\n"
-#: builtin/add.c:312
+#: builtin/add.c:432
msgid "no files added"
msgstr "没有文件被添加"
-#: builtin/add.c:318
+#: builtin/add.c:438
msgid "adding files failed"
msgstr "添加文件失败"
-# 译者:字符串首行行首要添加“warning: ”字串,故此首行要较其余行短
-#.
-#. * To be consistent with "git add -p" and most Git
-#. * commands, we should default to being tree-wide, but
-#. * this is not the original behavior and can't be
-#. * changed until users trained themselves not to type
-#. * "git add -u" or "git add -A". For now, we warn and
-#. * keep the old behavior. Later, the behavior can be changed
-#. * to tree-wide, keeping the warning for a while, and
-#. * eventually we can drop the warning.
-#.
-#: builtin/add.c:335
-#, c-format
-msgid ""
-"The behavior of 'git add %s (or %s)' with no path argument from a\n"
-"subdirectory of the tree will change in Git 2.0 and should not be used "
-"anymore.\n"
-"To add content for the whole tree, run:\n"
-"\n"
-" git add %s :/\n"
-" (or git add %s :/)\n"
-"\n"
-"To restrict the command to the current directory, run:\n"
-"\n"
-" git add %s .\n"
-" (or git add %s .)\n"
-"\n"
-"With the current Git version, the command is restricted to the current "
-"directory."
-msgstr ""
-"在 Git 2.0 版本,将不再允许在一个子目录下不带任何路径参数地执行\n"
-"命令 'git add %s (或 %s)'。所以不要再继续使用了。\n"
-"如果要添加整个目录树的内容,执行:\n"
-"\n"
-" git add %s :/\n"
-" (或 git add %s :/)\n"
-"\n"
-"如果要限制该命令只作用于当前目录,执行:\n"
-"\n"
-" git add %s .\n"
-" (或 git add %s .)\n"
-"\n"
-"对于当前版本的 Git,这条命令只作用于当前目录。"
-
-#: builtin/add.c:381
+#: builtin/add.c:477
msgid "-A and -u are mutually incompatible"
msgstr "-A 和 -u 选项互斥"
-#: builtin/add.c:383
+#: builtin/add.c:495
msgid "Option --ignore-missing can only be used together with --dry-run"
msgstr "选项 --ignore-missing 只能和 --dry-run 同时使用"
-#: builtin/add.c:414
+#: builtin/add.c:525
#, c-format
msgid "Nothing specified, nothing added.\n"
msgstr "没有指定文件,也没有文件被添加。\n"
-#: builtin/add.c:415
+#: builtin/add.c:526
#, c-format
msgid "Maybe you wanted to say 'git add .'?\n"
msgstr "也许您想要执行 'git add .'?\n"
-#: builtin/add.c:421 builtin/check-ignore.c:67 builtin/clean.c:204
+#: builtin/add.c:532 builtin/check-ignore.c:66 builtin/clean.c:204
#: builtin/commit.c:293 builtin/mv.c:82 builtin/rm.c:235
msgid "index file corrupt"
msgstr "索引文件损坏"
-#: builtin/add.c:481 builtin/apply.c:4505 builtin/mv.c:229 builtin/rm.c:370
+#: builtin/add.c:604 builtin/apply.c:4505 builtin/mv.c:229 builtin/rm.c:370
msgid "Unable to write new index file"
msgstr "无法写入新索引文件"
msgid "Checking patch %s..."
msgstr "检查补丁 %s..."
-#: builtin/apply.c:3679 builtin/checkout.c:215 builtin/reset.c:124
+#: builtin/apply.c:3679 builtin/checkout.c:216 builtin/reset.c:124
#, c-format
msgid "make_cache_entry failed for path '%s'"
msgstr "对路径 '%s' 的 make_cache_entry 操作失败"
msgid "build a temporary index based on embedded index information"
msgstr "创建一个临时索引基于嵌入的索引信息"
-#: builtin/apply.c:4388 builtin/checkout-index.c:197 builtin/ls-files.c:463
+#: builtin/apply.c:4388 builtin/checkout-index.c:197 builtin/ls-files.c:456
msgid "paths are separated with NUL character"
msgstr "路径以 NUL 字符分隔"
msgid "git blame [options] [rev-opts] [rev] [--] file"
msgstr "git blame [选项] [版本选项] [版本] [--] 文件"
-#: builtin/blame.c:30 builtin/shortlog.c:15
+#: builtin/blame.c:30
msgid "[rev-opts] are documented in git-rev-list(1)"
msgstr "[版本选项] 的文档记录在 git-rev-list(1) 中"
-#: builtin/blame.c:2350
+#: builtin/blame.c:2355
msgid "Show blame entries as we find them, incrementally"
msgstr "增量式地显示发现的 blame 条目"
-#: builtin/blame.c:2351
+#: builtin/blame.c:2356
msgid "Show blank SHA-1 for boundary commits (Default: off)"
msgstr "边界提交显示空的 SHA-1(默认:关闭)"
-#: builtin/blame.c:2352
+#: builtin/blame.c:2357
msgid "Do not treat root commits as boundaries (Default: off)"
msgstr "不把根提交作为边界(默认:关闭)"
-#: builtin/blame.c:2353
+#: builtin/blame.c:2358
msgid "Show work cost statistics"
msgstr "显示命令消耗统计"
-#: builtin/blame.c:2354
+#: builtin/blame.c:2359
msgid "Show output score for blame entries"
msgstr "显示判断 blame 条目位移的得分诊断信息"
-#: builtin/blame.c:2355
+#: builtin/blame.c:2360
msgid "Show original filename (Default: auto)"
msgstr "显示原始文件名(默认:自动)"
-#: builtin/blame.c:2356
+#: builtin/blame.c:2361
msgid "Show original linenumber (Default: off)"
msgstr "显示原始的行号(默认:关闭)"
-#: builtin/blame.c:2357
+#: builtin/blame.c:2362
msgid "Show in a format designed for machine consumption"
msgstr "显示为一个适合机器读取的格式"
-#: builtin/blame.c:2358
+#: builtin/blame.c:2363
msgid "Show porcelain format with per-line commit information"
msgstr "为每一行显示机器适用的提交信息"
-#: builtin/blame.c:2359
+#: builtin/blame.c:2364
msgid "Use the same output mode as git-annotate (Default: off)"
msgstr "使用和 git-annotate 相同的输出模式(默认:关闭)"
-#: builtin/blame.c:2360
+#: builtin/blame.c:2365
msgid "Show raw timestamp (Default: off)"
msgstr "显示原始时间戳(默认:关闭)"
-#: builtin/blame.c:2361
+#: builtin/blame.c:2366
msgid "Show long commit SHA1 (Default: off)"
msgstr "显示长的SHA1提交号(默认:关闭)"
-#: builtin/blame.c:2362
+#: builtin/blame.c:2367
msgid "Suppress author name and timestamp (Default: off)"
msgstr "隐藏作者名字和时间戳(默认:关闭)"
-#: builtin/blame.c:2363
+#: builtin/blame.c:2368
msgid "Show author email instead of name (Default: off)"
msgstr "显示作者的邮箱而不是名字(默认:关闭)"
-#: builtin/blame.c:2364
+#: builtin/blame.c:2369
msgid "Ignore whitespace differences"
msgstr "忽略空白差异"
-#: builtin/blame.c:2365
+#: builtin/blame.c:2370
msgid "Spend extra cycles to find better match"
msgstr "花费额外的循环来找到更好的匹配"
-#: builtin/blame.c:2366
+#: builtin/blame.c:2371
msgid "Use revisions from <file> instead of calling git-rev-list"
msgstr "使用来自 <file> 的修订集而不是调用 git-rev-list"
-#: builtin/blame.c:2367
+#: builtin/blame.c:2372
msgid "Use <file>'s contents as the final image"
msgstr "使用 <file> 的内容作为最终的图片"
-#: builtin/blame.c:2368 builtin/blame.c:2369
+#: builtin/blame.c:2373 builtin/blame.c:2374
msgid "score"
msgstr "得分"
-#: builtin/blame.c:2368
+#: builtin/blame.c:2373
msgid "Find line copies within and across files"
msgstr "找到文件内及跨文件的行拷贝"
-#: builtin/blame.c:2369
+#: builtin/blame.c:2374
msgid "Find line movements within and across files"
msgstr "找到文件内及跨文件的行移动"
-#: builtin/blame.c:2370
+#: builtin/blame.c:2375
msgid "n,m"
msgstr "n,m"
-#: builtin/blame.c:2370
+#: builtin/blame.c:2375
msgid "Process only line range n,m, counting from 1"
msgstr "只处理行范围在 n 和 m 之间的,从 1 开始"
msgstr "git branch [选项] (-m | -M) [<旧分支>] <新分支>"
# 译者:保持原换行格式,在输出时 %s 的替代内容会让字符串变长
-#: builtin/branch.c:146
+#: builtin/branch.c:150
#, c-format
msgid ""
"deleting branch '%s' that has been merged to\n"
" '%s',但未合并到 HEAD。"
# 译者:保持原换行格式,在输出时 %s 的替代内容会让字符串变长
-#: builtin/branch.c:150
+#: builtin/branch.c:154
#, c-format
msgid ""
"not deleting branch '%s' that is not yet merged to\n"
"并未删除分支 '%s', 虽然它已经合并到 HEAD,\n"
" 然而却尚未被合并到分支 '%s' 。"
-#: builtin/branch.c:164
+#: builtin/branch.c:168
#, c-format
msgid "Couldn't look up commit object for '%s'"
msgstr "无法查询 '%s' 指向的提交对象"
-#: builtin/branch.c:168
+#: builtin/branch.c:172
#, c-format
msgid ""
"The branch '%s' is not fully merged.\n"
"分支 '%s' 没有完全合并。\n"
"如果您确认要删除它,执行 'git branch -D %s'。"
-#: builtin/branch.c:181
+#: builtin/branch.c:185
msgid "Update of config-file failed"
msgstr "无法更新 config 文件"
-#: builtin/branch.c:209
+#: builtin/branch.c:213
msgid "cannot use -a with -d"
msgstr "不能将 -a 和 -d 同时使用"
-#: builtin/branch.c:215
+#: builtin/branch.c:219
msgid "Couldn't look up commit object for HEAD"
msgstr "无法查询 HEAD 指向的提交对象"
-#: builtin/branch.c:223
+#: builtin/branch.c:227
#, c-format
msgid "Cannot delete the branch '%s' which you are currently on."
msgstr "无法删除您当前所在的分支 '%s'。"
-#: builtin/branch.c:236
+#: builtin/branch.c:240
#, c-format
msgid "remote branch '%s' not found."
msgstr "远程分支 '%s' 未发现。"
-#: builtin/branch.c:237
+#: builtin/branch.c:241
#, c-format
msgid "branch '%s' not found."
msgstr "分支 '%s' 未发现。"
-#: builtin/branch.c:251
+#: builtin/branch.c:255
#, c-format
msgid "Error deleting remote branch '%s'"
msgstr "删除远程分支 '%s' 时出错"
-#: builtin/branch.c:252
+#: builtin/branch.c:256
#, c-format
msgid "Error deleting branch '%s'"
msgstr "删除分支 '%s' 时出错"
-#: builtin/branch.c:259
+#: builtin/branch.c:263
#, c-format
msgid "Deleted remote branch %s (was %s).\n"
msgstr "已删除远程分支 %s(曾为 %s)。\n"
-#: builtin/branch.c:260
+#: builtin/branch.c:264
#, c-format
msgid "Deleted branch %s (was %s).\n"
msgstr "已删除分支 %s(曾为 %s)。\n"
-#: builtin/branch.c:362
+#: builtin/branch.c:366
#, c-format
msgid "branch '%s' does not point at a commit"
msgstr "分支 '%s' 未指向一个提交"
-#: builtin/branch.c:434
+#: builtin/branch.c:453
#, c-format
msgid "[%s: behind %d]"
msgstr "[%s:落后 %d]"
-#: builtin/branch.c:436
+#: builtin/branch.c:455
#, c-format
msgid "[behind %d]"
msgstr "[落后 %d]"
-#: builtin/branch.c:440
+#: builtin/branch.c:459
#, c-format
msgid "[%s: ahead %d]"
msgstr "[%s:领先 %d]"
-#: builtin/branch.c:442
+#: builtin/branch.c:461
#, c-format
msgid "[ahead %d]"
msgstr "[领先 %d]"
-#: builtin/branch.c:445
+#: builtin/branch.c:464
#, c-format
msgid "[%s: ahead %d, behind %d]"
msgstr "[%s:领先 %d,落后 %d]"
-#: builtin/branch.c:448
+#: builtin/branch.c:467
#, c-format
msgid "[ahead %d, behind %d]"
msgstr "[领先 %d,落后 %d]"
-#: builtin/branch.c:470
+#: builtin/branch.c:490
msgid " **** invalid ref ****"
msgstr " **** 无效引用 ****"
-#: builtin/branch.c:562
+#: builtin/branch.c:582
#, c-format
msgid "(no branch, rebasing %s)"
msgstr "(非分支,正变基 %s)"
-#: builtin/branch.c:565
+#: builtin/branch.c:585
#, c-format
msgid "(no branch, bisect started on %s)"
msgstr "(非分支,二分查找开始于 %s)"
-#: builtin/branch.c:568
+#: builtin/branch.c:588
#, c-format
msgid "(detached from %s)"
msgstr "(分离自 %s)"
-#: builtin/branch.c:571
+#: builtin/branch.c:591
msgid "(no branch)"
msgstr "(非分支)"
-#: builtin/branch.c:617
+#: builtin/branch.c:637
#, c-format
msgid "object '%s' does not point to a commit"
msgstr "对象 '%s' 没有指向一个提交"
-#: builtin/branch.c:649
+#: builtin/branch.c:669
msgid "some refs could not be read"
msgstr "一些引用不能读取"
-#: builtin/branch.c:662
+#: builtin/branch.c:682
msgid "cannot rename the current branch while not on any."
msgstr "无法重命名当前分支因为不处于任何分支上。"
-#: builtin/branch.c:672
+#: builtin/branch.c:692
#, c-format
msgid "Invalid branch name: '%s'"
msgstr "无效的分支名:'%s'"
-#: builtin/branch.c:687
+#: builtin/branch.c:707
msgid "Branch rename failed"
msgstr "分支重命名失败"
-#: builtin/branch.c:691
+#: builtin/branch.c:711
#, c-format
msgid "Renamed a misnamed branch '%s' away"
msgstr "重命名掉一个错误命名的旧分支 '%s'"
-#: builtin/branch.c:695
+#: builtin/branch.c:715
#, c-format
msgid "Branch renamed to %s, but HEAD is not updated!"
msgstr "分支重命名为 %s,但 HEAD 没有更新!"
-#: builtin/branch.c:702
+#: builtin/branch.c:722
msgid "Branch is renamed, but update of config-file failed"
msgstr "分支被重命名,但更新 config 文件失败"
-#: builtin/branch.c:717
+#: builtin/branch.c:737
#, c-format
msgid "malformed object name %s"
msgstr "非法的对象名 %s"
-#: builtin/branch.c:741
+#: builtin/branch.c:761
#, c-format
msgid "could not write branch description template: %s"
msgstr "不能写分支描述模版:%s"
-#: builtin/branch.c:771
+#: builtin/branch.c:791
msgid "Generic options"
msgstr "通用选项"
-#: builtin/branch.c:773
+#: builtin/branch.c:793
msgid "show hash and subject, give twice for upstream branch"
msgstr "显示哈希值和主题,若参数出现两次则显示上游分支"
-#: builtin/branch.c:774
+#: builtin/branch.c:794
msgid "suppress informational messages"
msgstr "不显示信息"
-#: builtin/branch.c:775
+#: builtin/branch.c:795
msgid "set up tracking mode (see git-pull(1))"
msgstr "设置跟踪模式(参见 git-pull(1))"
-#: builtin/branch.c:777
+#: builtin/branch.c:797
msgid "change upstream info"
msgstr "改变上游信息"
-#: builtin/branch.c:781
+#: builtin/branch.c:801
msgid "use colored output"
msgstr "使用彩色输出"
-#: builtin/branch.c:782
+#: builtin/branch.c:802
msgid "act on remote-tracking branches"
msgstr "作用于远程跟踪分支"
-#: builtin/branch.c:785 builtin/branch.c:791 builtin/branch.c:812
-#: builtin/branch.c:818 builtin/commit.c:1368 builtin/commit.c:1369
+#: builtin/branch.c:805 builtin/branch.c:811 builtin/branch.c:832
+#: builtin/branch.c:838 builtin/commit.c:1368 builtin/commit.c:1369
#: builtin/commit.c:1370 builtin/commit.c:1371 builtin/tag.c:468
msgid "commit"
msgstr "提交"
-#: builtin/branch.c:786 builtin/branch.c:792
+#: builtin/branch.c:806 builtin/branch.c:812
msgid "print only branches that contain the commit"
msgstr "只打印包含该提交的分支"
-#: builtin/branch.c:798
+#: builtin/branch.c:818
msgid "Specific git-branch actions:"
msgstr "具体的 git-branch 动作:"
-#: builtin/branch.c:799
+#: builtin/branch.c:819
msgid "list both remote-tracking and local branches"
msgstr "列出远程跟踪及本地分支"
-#: builtin/branch.c:801
+#: builtin/branch.c:821
msgid "delete fully merged branch"
msgstr "删除完全合并的分支"
-#: builtin/branch.c:802
+#: builtin/branch.c:822
msgid "delete branch (even if not merged)"
msgstr "删除分支(即使没有合并)"
-#: builtin/branch.c:803
+#: builtin/branch.c:823
msgid "move/rename a branch and its reflog"
msgstr "移动/重命名一个分支,以及它的引用日志"
-#: builtin/branch.c:804
+#: builtin/branch.c:824
msgid "move/rename a branch, even if target exists"
msgstr "移动/重命名一个分支,即使目标已存在"
-#: builtin/branch.c:805
+#: builtin/branch.c:825
msgid "list branch names"
msgstr "列出分支名"
-#: builtin/branch.c:806
+#: builtin/branch.c:826
msgid "create the branch's reflog"
msgstr "创建分支的引用日志"
-#: builtin/branch.c:808
+#: builtin/branch.c:828
msgid "edit the description for the branch"
msgstr "标记分支的描述"
-#: builtin/branch.c:809
+#: builtin/branch.c:829
msgid "force creation (when already exists)"
msgstr "强制创建(当已经存在)"
-#: builtin/branch.c:812
+#: builtin/branch.c:832
msgid "print only not merged branches"
msgstr "只打印没有合并的分支"
-#: builtin/branch.c:818
+#: builtin/branch.c:838
msgid "print only merged branches"
msgstr "只打印合并的分支"
-#: builtin/branch.c:822
+#: builtin/branch.c:842
msgid "list branches in columns"
msgstr "以列的方式显示分支"
-#: builtin/branch.c:835
+#: builtin/branch.c:855
msgid "Failed to resolve HEAD as a valid ref."
msgstr "无法将 HEAD 解析为有效引用。"
-#: builtin/branch.c:840 builtin/clone.c:609
+#: builtin/branch.c:860 builtin/clone.c:619
msgid "HEAD not found below refs/heads!"
msgstr "HEAD 没有位于 /refs/heads 之下!"
-#: builtin/branch.c:863
+#: builtin/branch.c:883
msgid "--column and --verbose are incompatible"
msgstr "--column 和 --verbose 不兼容"
-#: builtin/branch.c:869 builtin/branch.c:908
+#: builtin/branch.c:889 builtin/branch.c:928
msgid "branch name required"
msgstr "必须提供分支名"
-#: builtin/branch.c:884
+#: builtin/branch.c:904
msgid "Cannot give description to detached HEAD"
msgstr "不能向分离头指针提供描述"
-#: builtin/branch.c:889
+#: builtin/branch.c:909
msgid "cannot edit description of more than one branch"
msgstr "不能为一个以上的分支编辑描述"
-#: builtin/branch.c:896
+#: builtin/branch.c:916
#, c-format
msgid "No commit on branch '%s' yet."
msgstr "分支 '%s' 尚无提交。"
-#: builtin/branch.c:899
+#: builtin/branch.c:919
#, c-format
msgid "No branch named '%s'."
msgstr "没有分支 '%s'。"
-#: builtin/branch.c:914
+#: builtin/branch.c:934
msgid "too many branches for a rename operation"
msgstr "为重命名操作提供了太多的分支名"
-#: builtin/branch.c:919
+#: builtin/branch.c:939
msgid "too many branches to set new upstream"
msgstr "为设置新上游提供了太多的分支名"
-#: builtin/branch.c:923
+#: builtin/branch.c:943
#, c-format
msgid ""
"could not set upstream of HEAD to %s when it does not point to any branch."
msgstr "无法设置 HEAD 的上游为 %s,因为 HEAD 没有指向任何分支。"
-#: builtin/branch.c:926 builtin/branch.c:948 builtin/branch.c:970
+#: builtin/branch.c:946 builtin/branch.c:968 builtin/branch.c:990
#, c-format
msgid "no such branch '%s'"
msgstr "没有此分支 '%s'"
-#: builtin/branch.c:930
+#: builtin/branch.c:950
#, c-format
msgid "branch '%s' does not exist"
msgstr "分支 '%s' 不存在"
-#: builtin/branch.c:942
+#: builtin/branch.c:962
msgid "too many branches to unset upstream"
msgstr "为取消上游设置操作提供了太多的分支名"
-#: builtin/branch.c:946
+#: builtin/branch.c:966
msgid "could not unset upstream of HEAD when it does not point to any branch."
msgstr "无法取消 HEAD 的上游设置因为它没有指向一个分支"
-#: builtin/branch.c:952
+#: builtin/branch.c:972
#, c-format
msgid "Branch '%s' has no upstream information"
msgstr "分支 '%s' 没有上游信息"
-#: builtin/branch.c:967
+#: builtin/branch.c:987
msgid "it does not make sense to create 'HEAD' manually"
msgstr "手工创建 'HEAD' 没有意义"
-#: builtin/branch.c:973
+#: builtin/branch.c:993
msgid "-a and -r options to 'git branch' do not make sense with a branch name"
msgstr "'git branch' 的 -a 和 -r 选项带一个分支名参数没有意义"
-#: builtin/branch.c:976
+#: builtin/branch.c:996
#, c-format
msgid ""
"The --set-upstream flag is deprecated and will be removed. Consider using --"
msgstr ""
"选项 --set-upstream 已弃用并将被移除。考虑使用 --track 或 --set-upstream-to\n"
-#: builtin/branch.c:993
+#: builtin/branch.c:1013
#, c-format
msgid ""
"\n"
"如果你想用 '%s' 跟踪 '%s', 这么做:\n"
"\n"
-#: builtin/branch.c:994
+#: builtin/branch.c:1014
#, c-format
msgid " git branch -d %s\n"
msgstr " git branch -d %s\n"
-#: builtin/branch.c:995
+#: builtin/branch.c:1015
#, c-format
msgid " git branch --set-upstream-to %s\n"
msgstr " git branch --set-upstream-to %s\n"
msgid "Need a repository to unbundle."
msgstr "需要一个版本库来解包。"
-#: builtin/cat-file.c:247
+#: builtin/cat-file.c:176
msgid "git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>"
msgstr "git cat-file (-t|-s|-e|-p|<类型>|--textconv) <对象>"
-#: builtin/cat-file.c:248
+#: builtin/cat-file.c:177
msgid "git cat-file (--batch|--batch-check) < <list_of_objects>"
msgstr "git cat-file (--batch|--batch-check) < <对象列表>"
-#: builtin/cat-file.c:266
+#: builtin/cat-file.c:195
msgid "<type> can be one of: blob, tree, commit, tag"
msgstr "<类型> 可以是其中之一:blob、tree、commit、tag"
-#: builtin/cat-file.c:267
+#: builtin/cat-file.c:196
msgid "show object type"
msgstr "显示对象类型"
-#: builtin/cat-file.c:268
+#: builtin/cat-file.c:197
msgid "show object size"
msgstr "显示对象大小"
-#: builtin/cat-file.c:270
+#: builtin/cat-file.c:199
msgid "exit with zero when there's no error"
msgstr "当没有错误时退出并返回零"
-#: builtin/cat-file.c:271
+#: builtin/cat-file.c:200
msgid "pretty-print object's content"
msgstr "美观地打印对象的内容"
-#: builtin/cat-file.c:273
+#: builtin/cat-file.c:202
msgid "for blob objects, run textconv on object's content"
msgstr "对于数据(blob)对象,对其内容执行 textconv"
-#: builtin/cat-file.c:275
+#: builtin/cat-file.c:204
msgid "show info and content of objects fed from the standard input"
msgstr "显示从标准输入提供的对象的信息和内容"
-#: builtin/cat-file.c:278
+#: builtin/cat-file.c:207
msgid "show info about objects fed from the standard input"
msgstr "显示从标准输入提供的对象的信息"
msgid "input paths are terminated by a null character"
msgstr "输入路径以null字符终止"
-#: builtin/check-ignore.c:18 builtin/checkout.c:1041 builtin/gc.c:177
+#: builtin/check-ignore.c:18 builtin/checkout.c:1044 builtin/gc.c:177
msgid "suppress progress reporting"
msgstr "不显示进度报告"
-#: builtin/check-ignore.c:151
+#: builtin/check-ignore.c:146
msgid "cannot specify pathnames with --stdin"
msgstr "不能同时提供路径及 --stdin 参数"
-#: builtin/check-ignore.c:154
+#: builtin/check-ignore.c:149
msgid "-z only makes sense with --stdin"
msgstr "-z 需要和 --stdin 参数共用才有意义"
-#: builtin/check-ignore.c:156
+#: builtin/check-ignore.c:151
msgid "no path specified"
msgstr "未指定路径"
-#: builtin/check-ignore.c:160
+#: builtin/check-ignore.c:155
msgid "--quiet is only valid with a single pathname"
msgstr "参数 --quiet 只在提供一个路径名时有效"
-#: builtin/check-ignore.c:162
+#: builtin/check-ignore.c:157
msgid "cannot have both --quiet and --verbose"
msgstr "不能同时提供 --quiet 和 --verbose 参数"
msgid "git checkout [options] [<branch>] -- <file>..."
msgstr "git checkout [选项] [<分支>] -- <文件>..."
-#: builtin/checkout.c:116 builtin/checkout.c:149
+#: builtin/checkout.c:117 builtin/checkout.c:150
#, c-format
msgid "path '%s' does not have our version"
msgstr "路径 '%s' 没有我们的版本"
-#: builtin/checkout.c:118 builtin/checkout.c:151
+#: builtin/checkout.c:119 builtin/checkout.c:152
#, c-format
msgid "path '%s' does not have their version"
msgstr "路径 '%s' 没有他们的版本"
-#: builtin/checkout.c:134
+#: builtin/checkout.c:135
#, c-format
msgid "path '%s' does not have all necessary versions"
msgstr "路径 '%s' 没有全部必须的版本"
-#: builtin/checkout.c:178
+#: builtin/checkout.c:179
#, c-format
msgid "path '%s' does not have necessary versions"
msgstr "路径 '%s' 没有必须的版本"
-#: builtin/checkout.c:195
+#: builtin/checkout.c:196
#, c-format
msgid "path '%s': cannot merge"
msgstr "path '%s':无法合并"
-#: builtin/checkout.c:212
+#: builtin/checkout.c:213
#, c-format
msgid "Unable to add merge result for '%s'"
msgstr "无法为 '%s' 添加合并结果"
-#: builtin/checkout.c:236 builtin/checkout.c:239 builtin/checkout.c:242
-#: builtin/checkout.c:245
+#: builtin/checkout.c:237 builtin/checkout.c:240 builtin/checkout.c:243
+#: builtin/checkout.c:246
#, c-format
msgid "'%s' cannot be used with updating paths"
msgstr "'%s' 不能在更新路径时使用"
-#: builtin/checkout.c:248 builtin/checkout.c:251
+#: builtin/checkout.c:249 builtin/checkout.c:252
#, c-format
msgid "'%s' cannot be used with %s"
msgstr "'%s' 不能和 %s 同时使用"
-#: builtin/checkout.c:254
+#: builtin/checkout.c:255
#, c-format
msgid "Cannot update paths and switch to branch '%s' at the same time."
msgstr "不能同时更新路径并切换到分支'%s'。"
-#: builtin/checkout.c:265 builtin/checkout.c:455
+#: builtin/checkout.c:266 builtin/checkout.c:458
msgid "corrupt index file"
msgstr "损坏的索引文件"
-#: builtin/checkout.c:326 builtin/checkout.c:333
+#: builtin/checkout.c:329 builtin/checkout.c:336
#, c-format
msgid "path '%s' is unmerged"
msgstr "路径 '%s' 未合并"
-#: builtin/checkout.c:477
+#: builtin/checkout.c:480
msgid "you need to resolve your current index first"
msgstr "您需要先解决当前索引的冲突"
-#: builtin/checkout.c:598
+#: builtin/checkout.c:601
#, c-format
msgid "Can not do reflog for '%s'\n"
msgstr "不能对 '%s' 执行 reflog 操作\n"
-#: builtin/checkout.c:631
+#: builtin/checkout.c:634
msgid "HEAD is now at"
msgstr "HEAD 目前位于"
-#: builtin/checkout.c:638
+#: builtin/checkout.c:641
#, c-format
msgid "Reset branch '%s'\n"
msgstr "重置分支 '%s'\n"
-#: builtin/checkout.c:641
+#: builtin/checkout.c:644
#, c-format
msgid "Already on '%s'\n"
msgstr "已经位于 '%s'\n"
-#: builtin/checkout.c:645
+#: builtin/checkout.c:648
#, c-format
msgid "Switched to and reset branch '%s'\n"
msgstr "切换并重置分支 '%s'\n"
-#: builtin/checkout.c:647 builtin/checkout.c:984
+#: builtin/checkout.c:650 builtin/checkout.c:987
#, c-format
msgid "Switched to a new branch '%s'\n"
msgstr "切换到一个新分支 '%s'\n"
-#: builtin/checkout.c:649
+#: builtin/checkout.c:652
#, c-format
msgid "Switched to branch '%s'\n"
msgstr "切换到分支 '%s'\n"
# 译者:注意保持前导空格
-#: builtin/checkout.c:705
+#: builtin/checkout.c:708
#, c-format
msgid " ... and %d more.\n"
msgstr " ... 及其它 %d 个。\n"
#. The singular version
-#: builtin/checkout.c:711
+#: builtin/checkout.c:714
#, c-format
msgid ""
"Warning: you are leaving %d commit behind, not connected to\n"
"\n"
"%s\n"
-#: builtin/checkout.c:729
+#: builtin/checkout.c:732
#, c-format
msgid ""
"If you want to keep them by creating a new branch, this may be a good time\n"
" git branch new_branch_name %s\n"
"\n"
-#: builtin/checkout.c:759
+#: builtin/checkout.c:762
msgid "internal error in revision walk"
msgstr "在版本遍历时遇到内部错误"
-#: builtin/checkout.c:763
+#: builtin/checkout.c:766
msgid "Previous HEAD position was"
msgstr "之前的 HEAD 位置是"
-#: builtin/checkout.c:790 builtin/checkout.c:979
+#: builtin/checkout.c:793 builtin/checkout.c:982
msgid "You are on a branch yet to be born"
msgstr "您位于一个尚未初始化的分支"
#. case (1)
-#: builtin/checkout.c:915
+#: builtin/checkout.c:918
#, c-format
msgid "invalid reference: %s"
msgstr "无效引用:%s"
#. case (1): want a tree
-#: builtin/checkout.c:954
+#: builtin/checkout.c:957
#, c-format
msgid "reference is not a tree: %s"
msgstr "引用不是一个树:%s"
-#: builtin/checkout.c:993
+#: builtin/checkout.c:996
msgid "paths cannot be used with switching branches"
msgstr "路径不能和切换分支同时使用"
-#: builtin/checkout.c:996 builtin/checkout.c:1000
+#: builtin/checkout.c:999 builtin/checkout.c:1003
#, c-format
msgid "'%s' cannot be used with switching branches"
msgstr "'%s' 不能和切换分支同时使用"
-#: builtin/checkout.c:1004 builtin/checkout.c:1007 builtin/checkout.c:1012
-#: builtin/checkout.c:1015
+#: builtin/checkout.c:1007 builtin/checkout.c:1010 builtin/checkout.c:1015
+#: builtin/checkout.c:1018
#, c-format
msgid "'%s' cannot be used with '%s'"
msgstr "'%s' 不能和 '%s' 同时使用"
-#: builtin/checkout.c:1020
+#: builtin/checkout.c:1023
#, c-format
msgid "Cannot switch branch to a non-commit '%s'"
msgstr "不能切换分支到一个非提交 '%s'"
-#: builtin/checkout.c:1042 builtin/checkout.c:1044 builtin/clone.c:90
+#: builtin/checkout.c:1045 builtin/checkout.c:1047 builtin/clone.c:90
#: builtin/remote.c:169 builtin/remote.c:171
msgid "branch"
msgstr "分支"
-#: builtin/checkout.c:1043
+#: builtin/checkout.c:1046
msgid "create and checkout a new branch"
msgstr "创建并检出一个新的分支"
-#: builtin/checkout.c:1045
+#: builtin/checkout.c:1048
msgid "create/reset and checkout a branch"
msgstr "创建/重置并检出一个分支"
-#: builtin/checkout.c:1046
+#: builtin/checkout.c:1049
msgid "create reflog for new branch"
msgstr "为新的分支创建引用日志"
-#: builtin/checkout.c:1047
+#: builtin/checkout.c:1050
msgid "detach the HEAD at named commit"
msgstr "成为指向该提交的分离头指针"
-#: builtin/checkout.c:1048
+#: builtin/checkout.c:1051
msgid "set upstream info for new branch"
msgstr "为新的分支设置上游信息"
-#: builtin/checkout.c:1050
+#: builtin/checkout.c:1053
msgid "new branch"
msgstr "新分支"
-#: builtin/checkout.c:1050
+#: builtin/checkout.c:1053
msgid "new unparented branch"
msgstr "新的没有父提交的分支"
-#: builtin/checkout.c:1051
+#: builtin/checkout.c:1054
msgid "checkout our version for unmerged files"
msgstr "对尚未合并的文件检出我们的版本"
-#: builtin/checkout.c:1053
+#: builtin/checkout.c:1056
msgid "checkout their version for unmerged files"
msgstr "对尚未合并的文件检出他们的版本"
-#: builtin/checkout.c:1055
+#: builtin/checkout.c:1058
msgid "force checkout (throw away local modifications)"
msgstr "强制检出(丢弃本地修改)"
-#: builtin/checkout.c:1056
+#: builtin/checkout.c:1059
msgid "perform a 3-way merge with the new branch"
msgstr "和新的分支执行三路合并"
-#: builtin/checkout.c:1057 builtin/merge.c:217
+#: builtin/checkout.c:1060 builtin/merge.c:217
msgid "update ignored files (default)"
msgstr "更新忽略的文件(默认)"
-#: builtin/checkout.c:1058 builtin/log.c:1149 parse-options.h:245
+#: builtin/checkout.c:1061 builtin/log.c:1158 parse-options.h:245
msgid "style"
msgstr "风格"
-#: builtin/checkout.c:1059
+#: builtin/checkout.c:1062
msgid "conflict style (merge or diff3)"
msgstr "冲突输出风格(merge 或 diff3)"
-#: builtin/checkout.c:1062
+#: builtin/checkout.c:1065
+msgid "do not limit pathspecs to sparse entries only"
+msgstr "对路径不做稀疏检出的限制"
+
+#: builtin/checkout.c:1067
msgid "second guess 'git checkout no-such-branch'"
msgstr "再者猜测'git checkout no-such-branch'"
-#: builtin/checkout.c:1086
+#: builtin/checkout.c:1091
msgid "-b, -B and --orphan are mutually exclusive"
msgstr "-b、-B 和 --orphan 是互斥的"
-#: builtin/checkout.c:1103
+#: builtin/checkout.c:1108
msgid "--track needs a branch name"
msgstr "--track 需要一个分支名"
-#: builtin/checkout.c:1110
+#: builtin/checkout.c:1115
msgid "Missing branch name; try -b"
msgstr "缺少分支名;尝试 -b"
-#: builtin/checkout.c:1145
+#: builtin/checkout.c:1150
msgid "invalid path specification"
msgstr "无效的路径规格"
-#: builtin/checkout.c:1152
+#: builtin/checkout.c:1157
#, c-format
msgid ""
"Cannot update paths and switch to branch '%s' at the same time.\n"
"不能同时更新路径并切换到分支'%s'。\n"
"您是想要检出 '%s' 但其未能解析为提交么?"
-#: builtin/checkout.c:1157
+#: builtin/checkout.c:1162
#, c-format
msgid "git checkout: --detach does not take a path argument '%s'"
msgstr "git checkout:--detach 不能接收路径参数 '%s'"
-#: builtin/checkout.c:1161
+#: builtin/checkout.c:1166
msgid ""
"git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
"checking out of the index."
msgstr "删除整个目录"
#: builtin/clean.c:165 builtin/describe.c:412 builtin/grep.c:717
-#: builtin/ls-files.c:494 builtin/name-rev.c:231 builtin/show-ref.c:182
+#: builtin/ls-files.c:487 builtin/name-rev.c:231 builtin/show-ref.c:182
msgid "pattern"
msgstr "模式"
msgid "set config inside the new repository"
msgstr "在新版本库中设置配置信息"
-#: builtin/clone.c:244
+#: builtin/clone.c:254
#, c-format
-msgid "reference repository '%s' is not a local directory."
-msgstr "å¼\95ç\94¨ç\89\88æ\9c¬åº\93 '%s' ä¸\8dæ\98¯ä¸\80个æ\9c¬å\9c°ç\9b®å½\95。"
+msgid "reference repository '%s' is not a local repository."
+msgstr "å¼\95ç\94¨ç\89\88æ\9c¬åº\93 '%s' ä¸\8dæ\98¯ä¸\80个æ\9c¬å\9c°ç\89\88æ\9c¬åº\93。"
-#: builtin/clone.c:307
+#: builtin/clone.c:317
#, c-format
msgid "failed to create directory '%s'"
msgstr "无法创建目录 '%s'"
-#: builtin/clone.c:309 builtin/diff.c:77
+#: builtin/clone.c:319 builtin/diff.c:77
#, c-format
msgid "failed to stat '%s'"
msgstr "无法枚举 '%s' 状态"
-#: builtin/clone.c:311
+#: builtin/clone.c:321
#, c-format
msgid "%s exists and is not a directory"
msgstr "%s 存在且不是一个目录"
-#: builtin/clone.c:325
+#: builtin/clone.c:335
#, c-format
msgid "failed to stat %s\n"
msgstr "无法枚举 %s 状态\n"
-#: builtin/clone.c:347
+#: builtin/clone.c:357
#, c-format
msgid "failed to create link '%s'"
msgstr "无法创建链接 '%s'"
-#: builtin/clone.c:351
+#: builtin/clone.c:361
#, c-format
msgid "failed to copy file to '%s'"
msgstr "无法拷贝文件至 '%s'"
-#: builtin/clone.c:374
+#: builtin/clone.c:384
#, c-format
msgid "done.\n"
msgstr "完成。\n"
-#: builtin/clone.c:387
+#: builtin/clone.c:397
msgid ""
"Clone succeeded, but checkout failed.\n"
"You can inspect what was checked out with 'git status'\n"
"您可以通过 'git status' 检查哪些已被检出,然后使用命令\n"
"'git checkout -f HEAD' 重试\n"
-#: builtin/clone.c:466
+#: builtin/clone.c:476
#, c-format
msgid "Could not find remote branch %s to clone."
msgstr "不能发现要克隆的远程分支 %s。"
-#: builtin/clone.c:540
+#: builtin/clone.c:550
msgid "remote did not send all necessary objects"
msgstr "远程没有发送所有必须的对象"
-#: builtin/clone.c:600
+#: builtin/clone.c:610
msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
msgstr "远程 HEAD 指向一个不存在的引用,无法检出。\n"
-#: builtin/clone.c:631
+#: builtin/clone.c:641
msgid "unable to checkout working tree"
msgstr "不能检出工作区"
-#: builtin/clone.c:739
+#: builtin/clone.c:749
msgid "Too many arguments."
msgstr "太多参数。"
-#: builtin/clone.c:743
+#: builtin/clone.c:753
msgid "You must specify a repository to clone."
msgstr "您必须指定一个版本库来克隆。"
-#: builtin/clone.c:754
+#: builtin/clone.c:764
#, c-format
msgid "--bare and --origin %s options are incompatible."
msgstr "--bare 和 --origin %s 选项不兼容。"
-#: builtin/clone.c:757
+#: builtin/clone.c:767
msgid "--bare and --separate-git-dir are incompatible."
msgstr "--bare 和 --separate-git-dir 选项不兼容。"
-#: builtin/clone.c:770
+#: builtin/clone.c:780
#, c-format
msgid "repository '%s' does not exist"
msgstr "版本库 '%s' 不存在"
-#: builtin/clone.c:775
+#: builtin/clone.c:785
msgid "--depth is ignored in local clones; use file:// instead."
msgstr "--depth 在本地克隆被忽略,改为 file:// 协议试试。"
-#: builtin/clone.c:785
+#: builtin/clone.c:795
#, c-format
msgid "destination path '%s' already exists and is not an empty directory."
msgstr "目标路径 '%s' 已经存在,并且不是一个空目录。"
-#: builtin/clone.c:795
+#: builtin/clone.c:805
#, c-format
msgid "working tree '%s' already exists."
msgstr "工作区 '%s' 已经存在。"
-#: builtin/clone.c:808 builtin/clone.c:820
+#: builtin/clone.c:818 builtin/clone.c:830
#, c-format
msgid "could not create leading directories of '%s'"
msgstr "不能为 '%s' 创建先导目录"
-#: builtin/clone.c:811
+#: builtin/clone.c:821
#, c-format
msgid "could not create work tree dir '%s'."
msgstr "不能为 '%s' 创建工作区目录。"
-#: builtin/clone.c:830
+#: builtin/clone.c:840
#, c-format
msgid "Cloning into bare repository '%s'...\n"
msgstr "克隆到裸版本库 '%s'...\n"
-#: builtin/clone.c:832
+#: builtin/clone.c:842
#, c-format
msgid "Cloning into '%s'...\n"
msgstr "正克隆到 '%s'...\n"
-#: builtin/clone.c:867
+#: builtin/clone.c:877
#, c-format
msgid "Don't know how to clone %s"
msgstr "不知道如何克隆 %s"
-#: builtin/clone.c:916
+#: builtin/clone.c:926
#, c-format
msgid "Remote branch %s not found in upstream %s"
msgstr "远程分支 %s 在上游 %s 未发现"
-#: builtin/clone.c:923
+#: builtin/clone.c:933
msgid "You appear to have cloned an empty repository."
msgstr "您似乎克隆了一个空版本库。"
msgid "could not lookup commit %s"
msgstr "不能查询提交 %s"
-#: builtin/commit.c:612 builtin/shortlog.c:272
+#: builtin/commit.c:612 builtin/shortlog.c:270
#, c-format
msgid "(reading log message from standard input)\n"
msgstr "(正从标准输入中读取日志信息)\n"
msgid "terminate entries with NUL"
msgstr "条目以NUL字符结尾"
-#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/fast-export.c:653
-#: builtin/fast-export.c:656 builtin/tag.c:459
+#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/fast-export.c:659
+#: builtin/fast-export.c:662 builtin/tag.c:459
msgid "mode"
msgstr "模式"
msgid "the commit is authored by me now (used with -C/-c/--amend)"
msgstr "现在将该提交的作者改为我(和 -C/-c/--amend 参数共用)"
-#: builtin/commit.c:1373 builtin/log.c:1104 builtin/revert.c:109
+#: builtin/commit.c:1373 builtin/log.c:1113 builtin/revert.c:109
msgid "add Signed-off-by:"
msgstr "添加 Signed-off-by: 签名"
msgstr "查询时参照 include 指令递归查找"
#: builtin/count-objects.c:82
-msgid "git count-objects [-v]"
-msgstr "git count-objects [-v]"
+msgid "git count-objects [-v] [-H | --human-readable]"
+msgstr "git count-objects [-v] [-H | --human-readable]"
+
+#: builtin/count-objects.c:97
+msgid "print sizes in human readable format"
+msgstr "以用户可读的格式显示大小"
#: builtin/describe.c:15
msgid "git describe [options] <committish>*"
msgid "git fast-export [rev-list-opts]"
msgstr "git fast-export [rev-list-opts]"
-#: builtin/fast-export.c:652
+#: builtin/fast-export.c:658
msgid "show progress after <n> objects"
msgstr "在 <n> 个对象之后显示进度"
-#: builtin/fast-export.c:654
+#: builtin/fast-export.c:660
msgid "select handling of signed tags"
msgstr "选择如何处理签名 tags"
-#: builtin/fast-export.c:657
+#: builtin/fast-export.c:663
msgid "select handling of tags that tag filtered objects"
msgstr "选择当 tag 指向被过滤时 tags 的处理方式"
-#: builtin/fast-export.c:660
+#: builtin/fast-export.c:666
msgid "Dump marks to this file"
msgstr "把标记存储到这个文件"
-#: builtin/fast-export.c:662
+#: builtin/fast-export.c:668
msgid "Import marks from this file"
msgstr "从这个文件导入标记"
-#: builtin/fast-export.c:664
+#: builtin/fast-export.c:670
msgid "Fake a tagger when tags lack one"
msgstr "当 tags 缺少标记者字段时,假装提供一个"
-#: builtin/fast-export.c:666
+#: builtin/fast-export.c:672
msgid "Output full tree for each commit"
msgstr "每次提交都输出整个树"
-#: builtin/fast-export.c:668
+#: builtin/fast-export.c:674
msgid "Use the done feature to terminate the stream"
msgstr "使用 done 功能来终止流"
-#: builtin/fast-export.c:669
+#: builtin/fast-export.c:675
msgid "Skip output of blob data"
msgstr "跳过数据对象的输出"
msgid "convert to a complete repository"
msgstr "转换为一个完整的版本库"
-#: builtin/fetch.c:88 builtin/log.c:1121
+#: builtin/fetch.c:88 builtin/log.c:1130
msgid "dir"
msgstr "目录"
msgstr ""
"git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]"
-#: builtin/fmt-merge-msg.c:659 builtin/fmt-merge-msg.c:662 builtin/grep.c:701
-#: builtin/merge.c:188 builtin/show-branch.c:656 builtin/show-ref.c:175
+#: builtin/fmt-merge-msg.c:663 builtin/fmt-merge-msg.c:666 builtin/grep.c:701
+#: builtin/merge.c:188 builtin/show-branch.c:655 builtin/show-ref.c:175
#: builtin/tag.c:446 parse-options.h:133 parse-options.h:239
msgid "n"
msgstr "n"
-#: builtin/fmt-merge-msg.c:660
+#: builtin/fmt-merge-msg.c:664
msgid "populate log with at most <n> entries from shortlog"
msgstr "向提交说明中最多复制指定条目(合并而来的提交)的简短说明"
-#: builtin/fmt-merge-msg.c:663
+#: builtin/fmt-merge-msg.c:667
msgid "alias for --log (deprecated)"
msgstr "参数 --log 的别名(已弃用)"
-#: builtin/fmt-merge-msg.c:666
+#: builtin/fmt-merge-msg.c:670
msgid "text"
msgstr "文本"
-#: builtin/fmt-merge-msg.c:667
+#: builtin/fmt-merge-msg.c:671
msgid "use <text> as start of message"
msgstr "使用 <文本> 作为提交说明的开始"
-#: builtin/fmt-merge-msg.c:668
+#: builtin/fmt-merge-msg.c:672
msgid "file to read from"
msgstr "从文件中读取"
msgid "process file as it were from this path"
msgstr "处理文件并假设其来自于此路径"
-#: builtin/help.c:42
+#: builtin/help.c:43
msgid "print all available commands"
msgstr "打印所有可用的命令"
-#: builtin/help.c:43
+#: builtin/help.c:44
+msgid "print list of useful guides"
+msgstr "显示有用的指南列表"
+
+#: builtin/help.c:45
msgid "show man page"
msgstr "显示 man 手册"
-#: builtin/help.c:44
+#: builtin/help.c:46
msgid "show manual in web browser"
msgstr "在 web 浏览器中显示手册"
-#: builtin/help.c:46
+#: builtin/help.c:48
msgid "show info page"
msgstr "显示 info 手册"
-#: builtin/help.c:52
-msgid "git help [--all] [--man|--web|--info] [command]"
-msgstr "git help [--all] [--man|--web|--info] [命令]"
+#: builtin/help.c:54
+msgid "git help [--all] [--guides] [--man|--web|--info] [command]"
+msgstr "git help [--all] [--guides] [--man|--web|--info] [命令]"
-#: builtin/help.c:64
+#: builtin/help.c:66
#, c-format
msgid "unrecognized help format '%s'"
msgstr "未能识别的帮助格式 '%s'"
-#: builtin/help.c:92
+#: builtin/help.c:94
msgid "Failed to start emacsclient."
msgstr "无法启动 emacsclient。"
-#: builtin/help.c:105
+#: builtin/help.c:107
msgid "Failed to parse emacsclient version."
msgstr "无法解析 emacsclient 版本。"
-#: builtin/help.c:113
+#: builtin/help.c:115
#, c-format
msgid "emacsclient version '%d' too old (< 22)."
msgstr "emacsclient 版本 '%d' 太老(< 22)。"
-#: builtin/help.c:131 builtin/help.c:159 builtin/help.c:168 builtin/help.c:176
+#: builtin/help.c:133 builtin/help.c:161 builtin/help.c:170 builtin/help.c:178
#, c-format
msgid "failed to exec '%s': %s"
msgstr "无法执行 '%s':%s"
-#: builtin/help.c:216
+#: builtin/help.c:218
#, c-format
msgid ""
"'%s': path for unsupported man viewer.\n"
"'%s':不支持的 man 手册查看器的路径。\n"
"请使用 'man.<tool>.cmd'。"
-#: builtin/help.c:228
+#: builtin/help.c:230
#, c-format
msgid ""
"'%s': cmd for supported man viewer.\n"
"'%s': 支持的 man 手册查看器命令。\n"
"请使用 'man.<tool>.path'。"
-#: builtin/help.c:349
+#: builtin/help.c:351
#, c-format
msgid "'%s': unknown man viewer."
msgstr "'%s':未知的 man 查看器。"
-#: builtin/help.c:366
+#: builtin/help.c:368
msgid "no man viewer handled the request"
msgstr "没有 man 查看器处理此请求"
-#: builtin/help.c:374
+#: builtin/help.c:376
msgid "no info viewer handled the request"
msgstr "没有 info 查看器处理此请求"
-#: builtin/help.c:429 builtin/help.c:436
+#: builtin/help.c:422
+msgid "Defining attributes per path"
+msgstr "定义路径的属性"
+
+#: builtin/help.c:423
+msgid "A Git glossary"
+msgstr "Git 词汇表"
+
+#: builtin/help.c:424
+msgid "Specifies intentionally untracked files to ignore"
+msgstr "忽略指定的未跟踪文件"
+
+#: builtin/help.c:425
+msgid "Defining submodule properties"
+msgstr "定义子模组属性"
+
+#: builtin/help.c:426
+msgid "Specifying revisions and ranges for Git"
+msgstr "指定 Git 的版本和版本范围"
+
+#: builtin/help.c:427
+msgid "A tutorial introduction to Git (for version 1.5.1 or newer)"
+msgstr "一个 Git 教程(针对 1.5.1 或更新版本)"
+
+#: builtin/help.c:428
+msgid "An overview of recommended workflows with Git"
+msgstr "Git 推荐的工作流概览"
+
+#: builtin/help.c:440
+msgid "The common Git guides are:\n"
+msgstr "最常用的 Git 向导有:\n"
+
+#: builtin/help.c:462 builtin/help.c:478
#, c-format
msgid "usage: %s%s"
msgstr "用法:%s%s"
-#: builtin/help.c:452
+#: builtin/help.c:494
#, c-format
msgid "`git %s' is aliased to `%s'"
msgstr "`git %s' 是 `%s' 的别名"
msgstr "不能访问工作区 '%s'"
#: builtin/log.c:40
-msgid "git log [<options>] [<since>..<until>] [[--] <path>...]\n"
-msgstr "git log [<选项>] [<从>..<到>] [[--] <路径>...]\n"
+msgid "git log [<options>] [<revision range>] [[--] <path>...]\n"
+msgstr "git log [<选项>] [<版本范围>] [[--] <路径>...]\n"
#: builtin/log.c:41
msgid " or: git show [options] <object>..."
msgid "Unknown type: %d"
msgstr "未知类型:%d"
-#: builtin/log.c:630
+#: builtin/log.c:638
msgid "format.headers without value"
msgstr "format.headers 没有值"
-#: builtin/log.c:704
+#: builtin/log.c:720
msgid "name of output directory is too long"
msgstr "输出目录名太长"
-#: builtin/log.c:720
+#: builtin/log.c:736
#, c-format
msgid "Cannot open patch file %s"
msgstr "无法打开补丁文件 %s"
-#: builtin/log.c:734
+#: builtin/log.c:750
msgid "Need exactly one range."
msgstr "只需要一个范围。"
-#: builtin/log.c:742
+#: builtin/log.c:758
msgid "Not a range."
msgstr "不是一个范围。"
-#: builtin/log.c:815
+#: builtin/log.c:860
msgid "Cover letter needs email format"
msgstr "信封需要邮件地址格式"
-#: builtin/log.c:888
+#: builtin/log.c:936
#, c-format
msgid "insane in-reply-to: %s"
msgstr "不正常的 in-reply-to:%s"
-#: builtin/log.c:916
+#: builtin/log.c:964
msgid "git format-patch [options] [<since> | <revision range>]"
msgstr "git format-patch [选项] [<从> | <修订集范围>]"
-#: builtin/log.c:961
+#: builtin/log.c:1009
msgid "Two output directories?"
msgstr "两个输出目录?"
-#: builtin/log.c:1099
+#: builtin/log.c:1108
msgid "use [PATCH n/m] even with a single patch"
msgstr "使用 [PATCH n/m],即使只有一个补丁"
-#: builtin/log.c:1102
+#: builtin/log.c:1111
msgid "use [PATCH] even with multiple patches"
msgstr "使用 [PATCH],即使有多个补丁"
-#: builtin/log.c:1106
+#: builtin/log.c:1115
msgid "print patches to standard out"
msgstr "打印补丁到标准输出"
-#: builtin/log.c:1108
+#: builtin/log.c:1117
msgid "generate a cover letter"
msgstr "生成一封附信"
-#: builtin/log.c:1110
+#: builtin/log.c:1119
msgid "use simple number sequence for output file names"
msgstr "使用简单的数字序列作为输出文件名"
-#: builtin/log.c:1111
+#: builtin/log.c:1120
msgid "sfx"
msgstr "后缀"
-#: builtin/log.c:1112
+#: builtin/log.c:1121
msgid "use <sfx> instead of '.patch'"
msgstr "使用 <后缀> 代替 '.patch'"
-#: builtin/log.c:1114
+#: builtin/log.c:1123
msgid "start numbering patches at <n> instead of 1"
msgstr "补丁以 <n> 开始编号,而不是1"
-#: builtin/log.c:1116
+#: builtin/log.c:1125
msgid "mark the series as Nth re-roll"
msgstr "标记补丁系列是第几次重制"
-#: builtin/log.c:1118
+#: builtin/log.c:1127
msgid "Use [<prefix>] instead of [PATCH]"
msgstr "使用 [<前缀>] 代替 [PATCH]"
-#: builtin/log.c:1121
+#: builtin/log.c:1130
msgid "store resulting files in <dir>"
msgstr "把结果文件存储在 <dir>"
-#: builtin/log.c:1124
+#: builtin/log.c:1133
msgid "don't strip/add [PATCH]"
msgstr "不删除/添加 [PATCH]"
-#: builtin/log.c:1127
+#: builtin/log.c:1136
msgid "don't output binary diffs"
msgstr "不输出二进制差异"
-#: builtin/log.c:1129
+#: builtin/log.c:1138
msgid "don't include a patch matching a commit upstream"
msgstr "不包含已在上游提交中的补丁"
-#: builtin/log.c:1131
+#: builtin/log.c:1140
msgid "show patch format instead of default (patch + stat)"
msgstr "显示纯补丁格式而非默认的(补丁+状态)"
-#: builtin/log.c:1133
+#: builtin/log.c:1142
msgid "Messaging"
msgstr "邮件发送"
-#: builtin/log.c:1134
+#: builtin/log.c:1143
msgid "header"
msgstr "header"
-#: builtin/log.c:1135
+#: builtin/log.c:1144
msgid "add email header"
msgstr "添加邮件头"
-#: builtin/log.c:1136 builtin/log.c:1138
+#: builtin/log.c:1145 builtin/log.c:1147
msgid "email"
msgstr "邮件地址"
-#: builtin/log.c:1136
+#: builtin/log.c:1145
msgid "add To: header"
msgstr "添加收件人"
-#: builtin/log.c:1138
+#: builtin/log.c:1147
msgid "add Cc: header"
msgstr "添加抄送"
-#: builtin/log.c:1140
+#: builtin/log.c:1149
msgid "message-id"
msgstr "message-id"
-#: builtin/log.c:1141
+#: builtin/log.c:1150
msgid "make first mail a reply to <message-id>"
msgstr "使第一封邮件作为对 <message-id> 的回复"
-#: builtin/log.c:1142 builtin/log.c:1145
+#: builtin/log.c:1151 builtin/log.c:1154
msgid "boundary"
msgstr "边界"
-#: builtin/log.c:1143
+#: builtin/log.c:1152
msgid "attach the patch"
msgstr "附件方式添加补丁"
-#: builtin/log.c:1146
+#: builtin/log.c:1155
msgid "inline the patch"
msgstr "内联显示补丁"
-#: builtin/log.c:1150
+#: builtin/log.c:1159
msgid "enable message threading, styles: shallow, deep"
msgstr "启用邮件线索,风格:浅,深"
-#: builtin/log.c:1152
+#: builtin/log.c:1161
msgid "signature"
msgstr "签名"
-#: builtin/log.c:1153
+#: builtin/log.c:1162
msgid "add a signature"
msgstr "添加一个签名"
-#: builtin/log.c:1155
+#: builtin/log.c:1164
msgid "don't print the patch filenames"
msgstr "不要打印补丁文件名"
-#: builtin/log.c:1239
+#: builtin/log.c:1248
msgid "-n and -k are mutually exclusive."
msgstr "-n 和 -k 互斥。"
-#: builtin/log.c:1241
+#: builtin/log.c:1250
msgid "--subject-prefix and -k are mutually exclusive."
msgstr "--subject-prefix 和 -k 互斥。"
-#: builtin/log.c:1249
+#: builtin/log.c:1258
msgid "--name-only does not make sense"
msgstr "--name-only 无意义"
-#: builtin/log.c:1251
+#: builtin/log.c:1260
msgid "--name-status does not make sense"
msgstr "--name-status 无意义"
-#: builtin/log.c:1253
+#: builtin/log.c:1262
msgid "--check does not make sense"
msgstr "--check 无意义"
-#: builtin/log.c:1276
+#: builtin/log.c:1285
msgid "standard output, or directory, which one?"
msgstr "标准输出或目录,哪一个?"
-#: builtin/log.c:1278
+#: builtin/log.c:1287
#, c-format
msgid "Could not create directory '%s'"
msgstr "不能创建目录 '%s'"
-#: builtin/log.c:1431
+#: builtin/log.c:1435
msgid "Failed to create output files"
msgstr "无法创建输出文件"
-#: builtin/log.c:1480
+#: builtin/log.c:1484
msgid "git cherry [-v] [<upstream> [<head> [<limit>]]]"
msgstr "git cherry [-v] [<上游> [<头> [<限制>]]]"
-#: builtin/log.c:1535
+#: builtin/log.c:1539
#, c-format
msgid ""
"Could not find a tracked remote branch, please specify <upstream> manually.\n"
msgstr "不能找到跟踪的远程分支,请手工指定 <upstream>。\n"
-#: builtin/log.c:1548 builtin/log.c:1550 builtin/log.c:1562
+#: builtin/log.c:1552 builtin/log.c:1554 builtin/log.c:1566
#, c-format
msgid "Unknown commit %s"
msgstr "未知提交 %s"
-#: builtin/ls-files.c:409
+#: builtin/ls-files.c:402
msgid "git ls-files [options] [<file>...]"
msgstr "git ls-files [选项] [<文件>...]"
-#: builtin/ls-files.c:466
+#: builtin/ls-files.c:459
msgid "identify the file status with tags"
msgstr "用标签标识文件的状态"
-#: builtin/ls-files.c:468
+#: builtin/ls-files.c:461
msgid "use lowercase letters for 'assume unchanged' files"
msgstr "使用小写字母表示 '假设未改变的' 文件"
-#: builtin/ls-files.c:470
+#: builtin/ls-files.c:463
msgid "show cached files in the output (default)"
msgstr "显示缓存的文件(默认)"
-#: builtin/ls-files.c:472
+#: builtin/ls-files.c:465
msgid "show deleted files in the output"
msgstr "显示已删除的文件"
-#: builtin/ls-files.c:474
+#: builtin/ls-files.c:467
msgid "show modified files in the output"
msgstr "显示已修改的文件"
-#: builtin/ls-files.c:476
+#: builtin/ls-files.c:469
msgid "show other files in the output"
msgstr "显示其它文件"
-#: builtin/ls-files.c:478
+#: builtin/ls-files.c:471
msgid "show ignored files in the output"
msgstr "显示忽略的文件"
-#: builtin/ls-files.c:481
+#: builtin/ls-files.c:474
msgid "show staged contents' object name in the output"
msgstr "显示暂存区内容的对象名称"
-#: builtin/ls-files.c:483
+#: builtin/ls-files.c:476
msgid "show files on the filesystem that need to be removed"
msgstr "显示文件系统需要删除的文件"
-#: builtin/ls-files.c:485
+#: builtin/ls-files.c:478
msgid "show 'other' directories' name only"
msgstr "只显示“其他”目录的名称"
-#: builtin/ls-files.c:488
+#: builtin/ls-files.c:481
msgid "don't show empty directories"
msgstr "不显示空目录"
-#: builtin/ls-files.c:491
+#: builtin/ls-files.c:484
msgid "show unmerged files in the output"
msgstr "显示未合并的文件"
-#: builtin/ls-files.c:493
+#: builtin/ls-files.c:486
msgid "show resolve-undo information"
msgstr "显示 resolve-undo 信息"
-#: builtin/ls-files.c:495
+#: builtin/ls-files.c:488
msgid "skip files matching pattern"
msgstr "匹配排除文件的模式"
-#: builtin/ls-files.c:498
+#: builtin/ls-files.c:491
msgid "exclude patterns are read from <file>"
msgstr "从 <文件> 中读取排除模式"
-#: builtin/ls-files.c:501
+#: builtin/ls-files.c:494
msgid "read additional per-directory exclude patterns in <file>"
msgstr "从 <文件> 读取额外的每个目录的排除模式"
-#: builtin/ls-files.c:503
+#: builtin/ls-files.c:496
msgid "add the standard git exclusions"
msgstr "添加标准的 git 排除"
-#: builtin/ls-files.c:506
+#: builtin/ls-files.c:499
msgid "make the output relative to the project top directory"
msgstr "显示相对于顶级目录的文件名"
-#: builtin/ls-files.c:509
+#: builtin/ls-files.c:502
msgid "if any <file> is not in the index, treat this as an error"
msgstr "如果任何 <文件> 都不在索引区,视为错误"
-#: builtin/ls-files.c:510
+#: builtin/ls-files.c:503
msgid "tree-ish"
msgstr "树或提交"
-#: builtin/ls-files.c:511
+#: builtin/ls-files.c:504
msgid "pretend that paths removed since <tree-ish> are still present"
msgstr "假装自从 <树或提交> 之后删除的路径仍然存在"
-#: builtin/ls-files.c:513
+#: builtin/ls-files.c:506
msgid "show debugging data"
msgstr "显示调试数据"
"初次使用时执行 \"git rev-parse --parseopt -h\" 来获得更多信息。"
#: builtin/revert.c:22
-msgid "git revert [options] <commit-ish>"
-msgstr "git revert [选项] <提交号>"
+msgid "git revert [options] <commit-ish>..."
+msgstr "git revert [选项] <提交号>..."
#: builtin/revert.c:23
msgid "git revert <subcommand>"
msgstr "git revert <子命令>"
#: builtin/revert.c:28
-msgid "git cherry-pick [options] <commit-ish>"
-msgstr "git cherry-pick [选项] <提交号>"
+msgid "git cherry-pick [options] <commit-ish>..."
+msgstr "git cherry-pick [选项] <提交号>..."
#: builtin/revert.c:29
msgid "git cherry-pick <subcommand>"
msgstr "git rm:不能删除 %s"
#: builtin/shortlog.c:13
-msgid "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
-msgstr "git shortlog [-n] [-s] [-e] [-w] [版本选项] [--] [<提交号>... ]"
+msgid "git shortlog [<options>] [<revision range>] [[--] [<path>...]]"
+msgstr "git shortlog [<选项>] [<版本范围>] [[--] [<路径>...]]"
-#: builtin/shortlog.c:133
+#: builtin/shortlog.c:131
#, c-format
msgid "Missing author: %s"
msgstr "缺少作者:%s"
-#: builtin/shortlog.c:229
+#: builtin/shortlog.c:227
msgid "sort output according to the number of commits per author"
msgstr "根据每个作者的提交数量排序"
-#: builtin/shortlog.c:231
+#: builtin/shortlog.c:229
msgid "Suppress commit descriptions, only provides commit count"
msgstr "隐藏提交说明,只提供提交数量"
-#: builtin/shortlog.c:233
+#: builtin/shortlog.c:231
msgid "Show the email address of each author"
msgstr "显示每个作者的电子邮件地址"
-#: builtin/shortlog.c:234
+#: builtin/shortlog.c:232
msgid "w[,i1[,i2]]"
msgstr "w[,i1[,i2]]"
-#: builtin/shortlog.c:235
+#: builtin/shortlog.c:233
msgid "Linewrap output"
msgstr "折行输出"
msgid "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]"
msgstr "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]"
-#: builtin/show-branch.c:651
+#: builtin/show-branch.c:650
msgid "show remote-tracking and local branches"
msgstr "显示远程跟踪的和本地的分支"
-#: builtin/show-branch.c:653
+#: builtin/show-branch.c:652
msgid "show remote-tracking branches"
msgstr "显示远程跟踪的分支"
-#: builtin/show-branch.c:655
+#: builtin/show-branch.c:654
msgid "color '*!+-' corresponding to the branch"
msgstr "着色 '*!+-' 到相应的分支"
-#: builtin/show-branch.c:657
+#: builtin/show-branch.c:656
msgid "show <n> more commits after the common ancestor"
msgstr "显示共同祖先后的 <n> 个提交"
-#: builtin/show-branch.c:659
+#: builtin/show-branch.c:658
msgid "synonym to more=-1"
msgstr "和 more=-1 同义"
-#: builtin/show-branch.c:660
+#: builtin/show-branch.c:659
msgid "suppress naming strings"
msgstr "不显示字符串命名"
-#: builtin/show-branch.c:662
+#: builtin/show-branch.c:661
msgid "include the current branch"
msgstr "包括当前分支"
-#: builtin/show-branch.c:664
+#: builtin/show-branch.c:663
msgid "name commits with their object names"
msgstr "以对象名字命名提交"
-#: builtin/show-branch.c:666
+#: builtin/show-branch.c:665
msgid "show possible merge bases"
msgstr "显示可能合并的基线"
-#: builtin/show-branch.c:668
+#: builtin/show-branch.c:667
msgid "show refs unreachable from any other ref"
msgstr "显示没有任何引用的的引用"
-#: builtin/show-branch.c:670
+#: builtin/show-branch.c:669
msgid "show commits in topological order"
msgstr "以拓扑顺序显示提交"
-#: builtin/show-branch.c:672
+#: builtin/show-branch.c:671
msgid "show only commits not on the first branch"
msgstr "只显示不在第一个分支上的提交"
-#: builtin/show-branch.c:674
+#: builtin/show-branch.c:673
msgid "show merges reachable from only one tip"
msgstr "显示仅一个分支可访问的合并提交"
-#: builtin/show-branch.c:676
+#: builtin/show-branch.c:675
msgid "show commits where no parent comes before its children"
msgstr "显示提交时以时间为序且父提交不能领先子提交"
-#: builtin/show-branch.c:678
+#: builtin/show-branch.c:677
msgid "<n>[,<base>]"
msgstr "<n>[,<base>]"
-#: builtin/show-branch.c:679
+#: builtin/show-branch.c:678
msgid "show <n> most recent ref-log entries starting at base"
msgstr "显示从 base 开始的 <n> 条最近的引用日志记录"
msgstr "只对调试有用"
#: git.c:16
-msgid "See 'git help <command>' for more information on a specific command."
-msgstr "参见 'git help <command>' 以获得该特定命令的详细信息。"
+msgid ""
+"'git help -a' and 'git help -g' lists available subcommands and some\n"
+"concept guides. See 'git help <command>' or 'git help <concept>'\n"
+"to read about a specific subcommand or concept."
+msgstr ""
+"命令 'git help -a' 和 'git help -g' 显示可用的子命令和一些指南。参见\n"
+"'git help <命令>' 或 'git help <指南>' 来查看给定的子命令帮助或指南。"
#: parse-options.h:156
msgid "no-op (backward compatibility)"
"然后需要提供我至少一个好版本和一个坏版本。\n"
"(您可以用 \"git bisect bad\" 和 \"git bisect good\" 命令来标识。)"
-#: git-bisect.sh:347 git-bisect.sh:474
+#: git-bisect.sh:363 git-bisect.sh:490
msgid "We are not bisecting."
msgstr "我们没有在二分查找。"
-#: git-bisect.sh:354
+#: git-bisect.sh:370
#, sh-format
msgid "'$invalid' is not a valid commit"
msgstr "'$invalid' 不是一个有效的提交"
-#: git-bisect.sh:363
+#: git-bisect.sh:379
#, sh-format
msgid ""
"Could not check out original HEAD '$branch'.\n"
"不能检出原始 HEAD '$branch'。\n"
"尝试 'git bisect reset <提交>'。"
-#: git-bisect.sh:390
+#: git-bisect.sh:406
msgid "No logfile given"
msgstr "未提供日志文件"
-#: git-bisect.sh:391
+#: git-bisect.sh:407
#, sh-format
msgid "cannot read $file for replaying"
msgstr "不能读取 $file 来重放"
-#: git-bisect.sh:408
+#: git-bisect.sh:424
msgid "?? what are you talking about?"
msgstr "?? 您在说什么?"
-#: git-bisect.sh:420
+#: git-bisect.sh:436
#, sh-format
msgid "running $command"
msgstr "运行 $command"
-#: git-bisect.sh:427
+#: git-bisect.sh:443
#, sh-format
msgid ""
"bisect run failed:\n"
"二分查找运行失败:\n"
"命令 '$command' 的退出码 $res 小于 0 或大于等于 128"
-#: git-bisect.sh:453
+#: git-bisect.sh:469
msgid "bisect run cannot continue any more"
msgstr "二分查找不能继续运行"
-#: git-bisect.sh:459
+#: git-bisect.sh:475
#, sh-format
msgid ""
"bisect run failed:\n"
"二分查找运行失败:\n"
"'bisect_state $state' 退出码为 $res"
-#: git-bisect.sh:466
+#: git-bisect.sh:482
msgid "bisect run success"
msgstr "二分查找运行成功"
msgid "Synchronizing submodule url for '$prefix$sm_path'"
msgstr "为 '$prefix$sm_path' 同步子模组 url"
+#~ msgid "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"
+#~ msgstr "git shortlog [-n] [-s] [-e] [-w] [版本选项] [--] [<提交号>... ]"
+
+#~ msgid "See 'git help <command>' for more information on a specific command."
+#~ msgstr "参见 'git help <command>' 以获得该特定命令的详细信息。"
+
+#~ msgid "BUG: impossible combination of %d and %p"
+#~ msgstr "BUG: 无法组合 %d 和 %p"
+
+# 译者:以下三条实际为一个段落
+#~ msgid "It took %.2f seconds to enumerate untracked files. 'status -uno'"
+#~ msgstr "耗费了 %.2f 秒以枚举未跟踪的文件。'status -uno'"
+
+# 译者:为对齐,下面两句内容有调整
+#~ msgid "may speed it up, but you have to be careful not to forget to add"
+#~ msgstr "也许能提高速度,但您需要小心不要忘了添加新文件"
+
+#~ msgid "new files yourself (see 'git help status')."
+#~ msgstr "(参见 'git help status')。"
+
#, fuzzy
#~ msgid "aaaSynchronizing submodule url for '$prefix$sm_path'"
#~ msgstr "为 '$prefix$sm_path' 同步子模组 url"
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)
{
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,
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,
}
}
-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,
}
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"
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)
}
}
-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)
{
--- /dev/null
+#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;
+}
--- /dev/null
+#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 */
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;
}
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);
add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
}
+void fill_stat_data(struct stat_data *sd, struct stat *st)
+{
+ sd->sd_ctime.sec = (unsigned int)st->st_ctime;
+ sd->sd_mtime.sec = (unsigned int)st->st_mtime;
+ sd->sd_ctime.nsec = ST_CTIME_NSEC(*st);
+ sd->sd_mtime.nsec = ST_MTIME_NSEC(*st);
+ sd->sd_dev = st->st_dev;
+ sd->sd_ino = st->st_ino;
+ sd->sd_uid = st->st_uid;
+ sd->sd_gid = st->st_gid;
+ sd->sd_size = st->st_size;
+}
+
+int match_stat_data(const struct stat_data *sd, struct stat *st)
+{
+ int changed = 0;
+
+ if (sd->sd_mtime.sec != (unsigned int)st->st_mtime)
+ changed |= MTIME_CHANGED;
+ if (trust_ctime && check_stat &&
+ sd->sd_ctime.sec != (unsigned int)st->st_ctime)
+ changed |= CTIME_CHANGED;
+
+#ifdef USE_NSEC
+ if (check_stat && sd->sd_mtime.nsec != ST_MTIME_NSEC(*st))
+ changed |= MTIME_CHANGED;
+ if (trust_ctime && check_stat &&
+ sd->sd_ctime.nsec != ST_CTIME_NSEC(*st))
+ changed |= CTIME_CHANGED;
+#endif
+
+ if (check_stat) {
+ if (sd->sd_uid != (unsigned int) st->st_uid ||
+ sd->sd_gid != (unsigned int) st->st_gid)
+ changed |= OWNER_CHANGED;
+ if (sd->sd_ino != (unsigned int) st->st_ino)
+ changed |= INODE_CHANGED;
+ }
+
+#ifdef USE_STDEV
+ /*
+ * st_dev breaks on network filesystems where different
+ * clients will have different views of what "device"
+ * the filesystem is on
+ */
+ if (check_stat && sd->sd_dev != (unsigned int) st->st_dev)
+ changed |= INODE_CHANGED;
+#endif
+
+ if (sd->sd_size != (unsigned int) st->st_size)
+ changed |= DATA_CHANGED;
+
+ return changed;
+}
+
/*
* This only updates the "non-critical" parts of the directory
* cache, ie the parts that aren't tracked by GIT, and only used
*/
void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
{
- ce->ce_ctime.sec = (unsigned int)st->st_ctime;
- ce->ce_mtime.sec = (unsigned int)st->st_mtime;
- ce->ce_ctime.nsec = ST_CTIME_NSEC(*st);
- ce->ce_mtime.nsec = ST_MTIME_NSEC(*st);
- ce->ce_dev = st->st_dev;
- ce->ce_ino = st->st_ino;
- ce->ce_uid = st->st_uid;
- ce->ce_gid = st->st_gid;
- ce->ce_size = st->st_size;
+ fill_stat_data(&ce->ce_stat_data, st);
if (assume_unchanged)
ce->ce_flags |= CE_VALID;
ce_mark_uptodate(ce);
}
-static int ce_compare_data(struct cache_entry *ce, struct stat *st)
+static int ce_compare_data(const struct cache_entry *ce, struct stat *st)
{
int match = -1;
int fd = open(ce->name, O_RDONLY);
return match;
}
-static int ce_compare_link(struct cache_entry *ce, size_t expected_size)
+static int ce_compare_link(const struct cache_entry *ce, size_t expected_size)
{
int match = -1;
void *buffer;
return match;
}
-static int ce_compare_gitlink(struct cache_entry *ce)
+static int ce_compare_gitlink(const struct cache_entry *ce)
{
unsigned char sha1[20];
return hashcmp(sha1, ce->sha1);
}
-static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
+static int ce_modified_check_fs(const struct cache_entry *ce, struct stat *st)
{
switch (st->st_mode & S_IFMT) {
case S_IFREG:
return 0;
}
-static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
+static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
{
unsigned int changed = 0;
default:
die("internal error: ce_mode is %o", ce->ce_mode);
}
- if (ce->ce_mtime.sec != (unsigned int)st->st_mtime)
- changed |= MTIME_CHANGED;
- if (trust_ctime && check_stat &&
- ce->ce_ctime.sec != (unsigned int)st->st_ctime)
- changed |= CTIME_CHANGED;
-#ifdef USE_NSEC
- if (check_stat && ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))
- changed |= MTIME_CHANGED;
- if (trust_ctime && check_stat &&
- ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))
- changed |= CTIME_CHANGED;
-#endif
-
- if (check_stat) {
- if (ce->ce_uid != (unsigned int) st->st_uid ||
- ce->ce_gid != (unsigned int) st->st_gid)
- changed |= OWNER_CHANGED;
- if (ce->ce_ino != (unsigned int) st->st_ino)
- changed |= INODE_CHANGED;
- }
-
-#ifdef USE_STDEV
- /*
- * st_dev breaks on network filesystems where different
- * clients will have different views of what "device"
- * the filesystem is on
- */
- if (check_stat && ce->ce_dev != (unsigned int) st->st_dev)
- changed |= INODE_CHANGED;
-#endif
-
- if (ce->ce_size != (unsigned int) st->st_size)
- changed |= DATA_CHANGED;
+ changed |= match_stat_data(&ce->ce_stat_data, st);
/* Racily smudged entry? */
- if (!ce->ce_size) {
+ if (!ce->ce_stat_data.sd_size) {
if (!is_empty_blob_sha1(ce->sha1))
changed |= DATA_CHANGED;
}
return changed;
}
-static int is_racy_timestamp(const struct index_state *istate, struct cache_entry *ce)
+static int is_racy_timestamp(const struct index_state *istate,
+ const struct cache_entry *ce)
{
return (!S_ISGITLINK(ce->ce_mode) &&
istate->timestamp.sec &&
#ifdef USE_NSEC
/* nanosecond timestamped files can also be racy! */
- (istate->timestamp.sec < ce->ce_mtime.sec ||
- (istate->timestamp.sec == ce->ce_mtime.sec &&
- istate->timestamp.nsec <= ce->ce_mtime.nsec))
+ (istate->timestamp.sec < ce->ce_stat_data.sd_mtime.sec ||
+ (istate->timestamp.sec == ce->ce_stat_data.sd_mtime.sec &&
+ istate->timestamp.nsec <= ce->ce_stat_data.sd_mtime.nsec))
#else
- istate->timestamp.sec <= ce->ce_mtime.sec
+ istate->timestamp.sec <= ce->ce_stat_data.sd_mtime.sec
#endif
);
}
int ie_match_stat(const struct index_state *istate,
- struct cache_entry *ce, struct stat *st,
+ const struct cache_entry *ce, struct stat *st,
unsigned int options)
{
unsigned int changed;
}
int ie_modified(const struct index_state *istate,
- struct cache_entry *ce, struct stat *st, unsigned int options)
+ const struct cache_entry *ce,
+ struct stat *st, unsigned int options)
{
int changed, changed_fs;
* then we know it is.
*/
if ((changed & DATA_CHANGED) &&
- (S_ISGITLINK(ce->ce_mode) || ce->ce_size != 0))
+ (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
return changed;
changed_fs = ce_modified_check_fs(ce, st);
if (*ptr == '/') {
struct cache_entry *foundce;
++ptr;
- foundce = index_name_exists(&the_index, ce->name, ptr - ce->name, ignore_case);
+ foundce = index_name_exists(istate, ce->name, ptr - ce->name, ignore_case);
if (foundce) {
memcpy((void *)startPtr, foundce->name + (startPtr - ce->name), ptr - startPtr);
startPtr = ptr;
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);
if (istate->cache_nr == istate->cache_alloc) {
istate->cache_alloc = alloc_nr(istate->cache_alloc);
istate->cache = xrealloc(istate->cache,
- istate->cache_alloc * sizeof(struct cache_entry *));
+ istate->cache_alloc * sizeof(*istate->cache));
}
/* Add it in.. */
{
struct cache_entry *ce = xmalloc(cache_entry_size(len));
- ce->ce_ctime.sec = ntoh_l(ondisk->ctime.sec);
- ce->ce_mtime.sec = ntoh_l(ondisk->mtime.sec);
- ce->ce_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
- ce->ce_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
- ce->ce_dev = ntoh_l(ondisk->dev);
- ce->ce_ino = ntoh_l(ondisk->ino);
+ ce->ce_stat_data.sd_ctime.sec = ntoh_l(ondisk->ctime.sec);
+ ce->ce_stat_data.sd_mtime.sec = ntoh_l(ondisk->mtime.sec);
+ ce->ce_stat_data.sd_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
+ ce->ce_stat_data.sd_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
+ ce->ce_stat_data.sd_dev = ntoh_l(ondisk->dev);
+ ce->ce_stat_data.sd_ino = ntoh_l(ondisk->ino);
ce->ce_mode = ntoh_l(ondisk->mode);
- ce->ce_uid = ntoh_l(ondisk->uid);
- ce->ce_gid = ntoh_l(ondisk->gid);
- ce->ce_size = ntoh_l(ondisk->size);
+ ce->ce_stat_data.sd_uid = ntoh_l(ondisk->uid);
+ ce->ce_stat_data.sd_gid = ntoh_l(ondisk->gid);
+ ce->ce_stat_data.sd_size = ntoh_l(ondisk->size);
ce->ce_flags = flags & ~CE_NAMEMASK;
ce->ce_namelen = len;
hashcpy(ce->sha1, ondisk->sha1);
istate->version = ntohl(hdr->hdr_version);
istate->cache_nr = ntohl(hdr->hdr_entries);
istate->cache_alloc = alloc_nr(istate->cache_nr);
- istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *));
+ istate->cache = xcalloc(istate->cache_alloc, sizeof(*istate->cache));
istate->initialized = 1;
if (istate->version == 4)
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;
}
* The only thing we care about in this function is to smudge the
* falsely clean entry due to touch-update-touch race, so we leave
* everything else as they are. We are called for entries whose
- * ce_mtime match the index file mtime.
+ * ce_stat_data.sd_mtime match the index file mtime.
*
* Note that this actually does not do much for gitlinks, for
* which ce_match_stat_basic() always goes to the actual
* file, and never calls us, so the cached size information
* for "frotz" stays 6 which does not match the filesystem.
*/
- ce->ce_size = 0;
+ ce->ce_stat_data.sd_size = 0;
}
}
{
short flags;
- ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
- ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
- ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
- ondisk->mtime.nsec = htonl(ce->ce_mtime.nsec);
- ondisk->dev = htonl(ce->ce_dev);
- ondisk->ino = htonl(ce->ce_ino);
+ ondisk->ctime.sec = htonl(ce->ce_stat_data.sd_ctime.sec);
+ ondisk->mtime.sec = htonl(ce->ce_stat_data.sd_mtime.sec);
+ ondisk->ctime.nsec = htonl(ce->ce_stat_data.sd_ctime.nsec);
+ ondisk->mtime.nsec = htonl(ce->ce_stat_data.sd_mtime.nsec);
+ ondisk->dev = htonl(ce->ce_stat_data.sd_dev);
+ ondisk->ino = htonl(ce->ce_stat_data.sd_ino);
ondisk->mode = htonl(ce->ce_mode);
- ondisk->uid = htonl(ce->ce_uid);
- ondisk->gid = htonl(ce->ce_gid);
- ondisk->size = htonl(ce->ce_size);
+ ondisk->uid = htonl(ce->ce_stat_data.sd_uid);
+ ondisk->gid = htonl(ce->ce_stat_data.sd_gid);
+ ondisk->size = htonl(ce->ce_stat_data.sd_size);
hashcpy(ondisk->sha1, ce->sha1);
flags = ce->ce_flags;
*size = sz;
return data;
}
+
+void stat_validity_clear(struct stat_validity *sv)
+{
+ free(sv->sd);
+ sv->sd = NULL;
+}
+
+int stat_validity_check(struct stat_validity *sv, const char *path)
+{
+ struct stat st;
+
+ if (stat(path, &st) < 0)
+ return sv->sd == NULL;
+ if (!sv->sd)
+ return 0;
+ return S_ISREG(st.st_mode) && !match_stat_data(sv->sd, &st);
+}
+
+void stat_validity_update(struct stat_validity *sv, int fd)
+{
+ struct stat st;
+
+ if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode))
+ stat_validity_clear(sv);
+ else {
+ if (!sv->sd)
+ sv->sd = xcalloc(1, sizeof(struct stat_data));
+ fill_stat_data(sv->sd, &st);
+ }
+}
{
int component_len, component_count = 0;
+ if (!strcmp(refname, "@"))
+ /* Refname is a single character '@'. */
+ return -1;
+
while (1) {
/* We are at the start of a path component. */
component_len = check_refname_component(refname, flags);
* (ref_entry->flag & REF_DIR) is zero.
*/
struct ref_value {
+ /*
+ * The name of the object to which this reference resolves
+ * (which may be a tag object). If REF_ISBROKEN, this is
+ * null. If REF_ISSYMREF, then this is the name of the object
+ * referred to by the last reference in the symlink chain.
+ */
unsigned char sha1[20];
+
+ /*
+ * If REF_KNOWS_PEELED, then this field holds the peeled value
+ * of this reference, or null if the reference is known not to
+ * be peelable. See the documentation for peel_ref() for an
+ * exact definition of "peelable".
+ */
unsigned char peeled[20];
};
struct ref_entry **entries;
};
-/* ISSYMREF=0x01, ISPACKED=0x02, and ISBROKEN=0x04 are public interfaces */
+/*
+ * Bit values for ref_entry::flag. REF_ISSYMREF=0x01,
+ * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see
+ * refs.h.
+ */
+
+/*
+ * The field ref_entry->u.value.peeled of this value entry contains
+ * the correct peeled value for the reference, which might be
+ * null_sha1 if the reference is not a tag or if it is broken.
+ */
#define REF_KNOWS_PEELED 0x08
/* ref_entry represents a directory of references */
}
/*
- * Return the entry with the given refname from the ref_dir
- * (non-recursively), sorting dir if necessary. Return NULL if no
- * such entry is found. dir must already be complete.
+ * Return the index of the entry with the given refname from the
+ * ref_dir (non-recursively), sorting dir if necessary. Return -1 if
+ * no such entry is found. dir must already be complete.
*/
-static struct ref_entry *search_ref_dir(struct ref_dir *dir,
- const char *refname, size_t len)
+static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len)
{
struct ref_entry **r;
struct string_slice key;
if (refname == NULL || !dir->nr)
- return NULL;
+ return -1;
sort_ref_dir(dir);
key.len = len;
ref_entry_cmp_sslice);
if (r == NULL)
- return NULL;
+ return -1;
- return *r;
+ return r - dir->entries;
}
/*
const char *subdirname, size_t len,
int mkdir)
{
- struct ref_entry *entry = search_ref_dir(dir, subdirname, len);
- if (!entry) {
+ int entry_index = search_ref_dir(dir, subdirname, len);
+ struct ref_entry *entry;
+ if (entry_index == -1) {
if (!mkdir)
return NULL;
/*
*/
entry = create_dir_entry(dir->ref_cache, subdirname, len, 0);
add_entry_to_dir(dir, entry);
+ } else {
+ entry = dir->entries[entry_index];
}
return get_ref_dir(entry);
}
*/
static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname)
{
+ int entry_index;
struct ref_entry *entry;
dir = find_containing_dir(dir, refname, 0);
if (!dir)
return NULL;
- entry = search_ref_dir(dir, refname, strlen(refname));
- return (entry && !(entry->flag & REF_DIR)) ? entry : NULL;
+ entry_index = search_ref_dir(dir, refname, strlen(refname));
+ if (entry_index == -1)
+ return NULL;
+ entry = dir->entries[entry_index];
+ return (entry->flag & REF_DIR) ? NULL : entry;
+}
+
+/*
+ * Remove the entry with the given name from dir, recursing into
+ * subdirectories as necessary. If refname is the name of a directory
+ * (i.e., ends with '/'), then remove the directory and its contents.
+ * If the removal was successful, return the number of entries
+ * remaining in the directory entry that contained the deleted entry.
+ * If the name was not found, return -1. Please note that this
+ * function only deletes the entry from the cache; it does not delete
+ * it from the filesystem or ensure that other cache entries (which
+ * might be symbolic references to the removed entry) are updated.
+ * Nor does it remove any containing dir entries that might be made
+ * empty by the removal. dir must represent the top-level directory
+ * and must already be complete.
+ */
+static int remove_entry(struct ref_dir *dir, const char *refname)
+{
+ int refname_len = strlen(refname);
+ int entry_index;
+ struct ref_entry *entry;
+ int is_dir = refname[refname_len - 1] == '/';
+ if (is_dir) {
+ /*
+ * refname represents a reference directory. Remove
+ * the trailing slash; otherwise we will get the
+ * directory *representing* refname rather than the
+ * one *containing* it.
+ */
+ char *dirname = xmemdupz(refname, refname_len - 1);
+ dir = find_containing_dir(dir, dirname, 0);
+ free(dirname);
+ } else {
+ dir = find_containing_dir(dir, refname, 0);
+ }
+ if (!dir)
+ return -1;
+ entry_index = search_ref_dir(dir, refname, refname_len);
+ if (entry_index == -1)
+ return -1;
+ entry = dir->entries[entry_index];
+
+ memmove(&dir->entries[entry_index],
+ &dir->entries[entry_index + 1],
+ (dir->nr - entry_index - 1) * sizeof(*dir->entries)
+ );
+ dir->nr--;
+ if (dir->sorted > entry_index)
+ dir->sorted--;
+ free_ref_entry(entry);
+ return dir->nr;
}
/*
dir->sorted = dir->nr = i;
}
-#define DO_FOR_EACH_INCLUDE_BROKEN 01
+/* Include broken references in a do_for_each_ref*() iteration: */
+#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
+/*
+ * Return true iff the reference described by entry can be resolved to
+ * an object in the database. Emit a warning if the referred-to
+ * object does not exist.
+ */
+static int ref_resolves_to_object(struct ref_entry *entry)
+{
+ if (entry->flag & REF_ISBROKEN)
+ return 0;
+ if (!has_sha1_file(entry->u.value.sha1)) {
+ error("%s does not point to a valid object!", entry->name);
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * current_ref is a performance hack: when iterating over references
+ * using the for_each_ref*() functions, current_ref is set to the
+ * current reference's entry before calling the callback function. If
+ * the callback function calls peel_ref(), then peel_ref() first
+ * checks whether the reference to be peeled is the current reference
+ * (it usually is) and if so, returns that reference's peeled version
+ * if it is available. This avoids a refname lookup in a common case.
+ */
static struct ref_entry *current_ref;
-static int do_one_ref(const char *base, each_ref_fn fn, int trim,
- int flags, void *cb_data, struct ref_entry *entry)
+typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data);
+
+struct ref_entry_cb {
+ const char *base;
+ int trim;
+ int flags;
+ each_ref_fn *fn;
+ void *cb_data;
+};
+
+/*
+ * Handle one reference in a do_for_each_ref*()-style iteration,
+ * calling an each_ref_fn for each entry.
+ */
+static int do_one_ref(struct ref_entry *entry, void *cb_data)
{
+ struct ref_entry_cb *data = cb_data;
+ struct ref_entry *old_current_ref;
int retval;
- if (prefixcmp(entry->name, base))
+
+ if (prefixcmp(entry->name, data->base))
return 0;
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
- if (entry->flag & REF_ISBROKEN)
- return 0; /* ignore broken refs e.g. dangling symref */
- if (!has_sha1_file(entry->u.value.sha1)) {
- error("%s does not point to a valid object!", entry->name);
- return 0;
- }
- }
+ if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+ !ref_resolves_to_object(entry))
+ return 0;
+
+ /* Store the old value, in case this is a recursive call: */
+ old_current_ref = current_ref;
current_ref = entry;
- retval = fn(entry->name + trim, entry->u.value.sha1, entry->flag, cb_data);
- current_ref = NULL;
+ retval = data->fn(entry->name + data->trim, entry->u.value.sha1,
+ entry->flag, data->cb_data);
+ current_ref = old_current_ref;
return retval;
}
* Call fn for each reference in dir that has index in the range
* offset <= index < dir->nr. Recurse into subdirectories that are in
* that index range, sorting them before iterating. This function
- * does not sort dir itself; it should be sorted beforehand.
+ * does not sort dir itself; it should be sorted beforehand. fn is
+ * called for all references, including broken ones.
*/
-static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset,
- const char *base,
- each_ref_fn fn, int trim, int flags, void *cb_data)
+static int do_for_each_entry_in_dir(struct ref_dir *dir, int offset,
+ each_ref_entry_fn fn, void *cb_data)
{
int i;
assert(dir->sorted == dir->nr);
if (entry->flag & REF_DIR) {
struct ref_dir *subdir = get_ref_dir(entry);
sort_ref_dir(subdir);
- retval = do_for_each_ref_in_dir(subdir, 0,
- base, fn, trim, flags, cb_data);
+ retval = do_for_each_entry_in_dir(subdir, 0, fn, cb_data);
} else {
- retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
+ retval = fn(entry, cb_data);
}
if (retval)
return retval;
* by refname. Recurse into subdirectories. If a value entry appears
* in both dir1 and dir2, then only process the version that is in
* dir2. The input dirs must already be sorted, but subdirs will be
- * sorted as needed.
+ * sorted as needed. fn is called for all references, including
+ * broken ones.
*/
-static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
- struct ref_dir *dir2,
- const char *base, each_ref_fn fn, int trim,
- int flags, void *cb_data)
+static int do_for_each_entry_in_dirs(struct ref_dir *dir1,
+ struct ref_dir *dir2,
+ each_ref_entry_fn fn, void *cb_data)
{
int retval;
int i1 = 0, i2 = 0;
struct ref_entry *e1, *e2;
int cmp;
if (i1 == dir1->nr) {
- return do_for_each_ref_in_dir(dir2, i2,
- base, fn, trim, flags, cb_data);
+ return do_for_each_entry_in_dir(dir2, i2, fn, cb_data);
}
if (i2 == dir2->nr) {
- return do_for_each_ref_in_dir(dir1, i1,
- base, fn, trim, flags, cb_data);
+ return do_for_each_entry_in_dir(dir1, i1, fn, cb_data);
}
e1 = dir1->entries[i1];
e2 = dir2->entries[i2];
struct ref_dir *subdir2 = get_ref_dir(e2);
sort_ref_dir(subdir1);
sort_ref_dir(subdir2);
- retval = do_for_each_ref_in_dirs(
- subdir1, subdir2,
- base, fn, trim, flags, cb_data);
+ retval = do_for_each_entry_in_dirs(
+ subdir1, subdir2, fn, cb_data);
i1++;
i2++;
} else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) {
/* Both are references; ignore the one from dir1. */
- retval = do_one_ref(base, fn, trim, flags, cb_data, e2);
+ retval = fn(e2, cb_data);
i1++;
i2++;
} else {
if (e->flag & REF_DIR) {
struct ref_dir *subdir = get_ref_dir(e);
sort_ref_dir(subdir);
- retval = do_for_each_ref_in_dir(
- subdir, 0,
- base, fn, trim, flags, cb_data);
+ retval = do_for_each_entry_in_dir(
+ subdir, 0, fn, cb_data);
} else {
- retval = do_one_ref(base, fn, trim, flags, cb_data, e);
+ retval = fn(e, cb_data);
}
}
if (retval)
return retval;
}
- if (i1 < dir1->nr)
- return do_for_each_ref_in_dir(dir1, i1,
- base, fn, trim, flags, cb_data);
- if (i2 < dir2->nr)
- return do_for_each_ref_in_dir(dir2, i2,
- base, fn, trim, flags, cb_data);
- return 0;
}
+/*
+ * Load all of the refs from the dir into our in-memory cache. The hard work
+ * of loading loose refs is done by get_ref_dir(), so we just need to recurse
+ * through all of the sub-directories. We do not even need to care about
+ * sorting, as traversal order does not matter to us.
+ */
+static void prime_ref_dir(struct ref_dir *dir)
+{
+ int i;
+ for (i = 0; i < dir->nr; i++) {
+ struct ref_entry *entry = dir->entries[i];
+ if (entry->flag & REF_DIR)
+ prime_ref_dir(get_ref_dir(entry));
+ }
+}
/*
* Return true iff refname1 and refname2 conflict with each other.
* Two reference names conflict if one of them exactly matches the
const char *conflicting_refname;
};
-static int name_conflict_fn(const char *existingrefname, const unsigned char *sha1,
- int flags, void *cb_data)
+static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
{
struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
- if (data->oldrefname && !strcmp(data->oldrefname, existingrefname))
+ if (data->oldrefname && !strcmp(data->oldrefname, entry->name))
return 0;
- if (names_conflict(data->refname, existingrefname)) {
- data->conflicting_refname = existingrefname;
+ if (names_conflict(data->refname, entry->name)) {
+ data->conflicting_refname = entry->name;
return 1;
}
return 0;
/*
* Return true iff a reference named refname could be created without
- * conflicting with the name of an existing reference in array. If
+ * conflicting with the name of an existing reference in dir. If
* oldrefname is non-NULL, ignore potential conflicts with oldrefname
* (e.g., because oldrefname is scheduled for deletion in the same
* operation).
data.conflicting_refname = NULL;
sort_ref_dir(dir);
- if (do_for_each_ref_in_dir(dir, 0, "", name_conflict_fn,
- 0, DO_FOR_EACH_INCLUDE_BROKEN,
- &data)) {
+ if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {
error("'%s' exists; cannot create '%s'",
data.conflicting_refname, refname);
return 0;
return 1;
}
+struct packed_ref_cache {
+ struct ref_entry *root;
+
+ /*
+ * Count of references to the data structure in this instance,
+ * including the pointer from ref_cache::packed if any. The
+ * data will not be freed as long as the reference count is
+ * nonzero.
+ */
+ unsigned int referrers;
+
+ /*
+ * Iff the packed-refs file associated with this instance is
+ * currently locked for writing, this points at the associated
+ * lock (which is owned by somebody else). The referrer count
+ * is also incremented when the file is locked and decremented
+ * when it is unlocked.
+ */
+ struct lock_file *lock;
+
+ /* The metadata from when this packed-refs cache was read */
+ struct stat_validity validity;
+};
+
/*
* Future: need to be in "struct repository"
* when doing a full libification.
static struct ref_cache {
struct ref_cache *next;
struct ref_entry *loose;
- struct ref_entry *packed;
- /* The submodule name, or "" for the main repo. */
- char name[FLEX_ARRAY];
-} *ref_cache;
+ struct packed_ref_cache *packed;
+ /*
+ * The submodule name, or "" for the main repo. We allocate
+ * length 1 rather than FLEX_ARRAY so that the main ref_cache
+ * is initialized correctly.
+ */
+ char name[1];
+} ref_cache, *submodule_ref_caches;
+
+/* Lock used for the main packed-refs file: */
+static struct lock_file packlock;
+
+/*
+ * Increment the reference count of *packed_refs.
+ */
+static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs)
+{
+ packed_refs->referrers++;
+}
+
+/*
+ * Decrease the reference count of *packed_refs. If it goes to zero,
+ * free *packed_refs and return true; otherwise return false.
+ */
+static int release_packed_ref_cache(struct packed_ref_cache *packed_refs)
+{
+ if (!--packed_refs->referrers) {
+ free_ref_entry(packed_refs->root);
+ stat_validity_clear(&packed_refs->validity);
+ free(packed_refs);
+ return 1;
+ } else {
+ return 0;
+ }
+}
static void clear_packed_ref_cache(struct ref_cache *refs)
{
if (refs->packed) {
- free_ref_entry(refs->packed);
+ struct packed_ref_cache *packed_refs = refs->packed;
+
+ if (packed_refs->lock)
+ die("internal error: packed-ref cache cleared while locked");
refs->packed = NULL;
+ release_packed_ref_cache(packed_refs);
}
}
*/
static struct ref_cache *get_ref_cache(const char *submodule)
{
- struct ref_cache *refs = ref_cache;
- if (!submodule)
- submodule = "";
- while (refs) {
+ struct ref_cache *refs;
+
+ if (!submodule || !*submodule)
+ return &ref_cache;
+
+ for (refs = submodule_ref_caches; refs; refs = refs->next)
if (!strcmp(submodule, refs->name))
return refs;
- refs = refs->next;
- }
refs = create_ref_cache(submodule);
- refs->next = ref_cache;
- ref_cache = refs;
+ refs->next = submodule_ref_caches;
+ submodule_ref_caches = refs;
return refs;
}
clear_loose_ref_cache(refs);
}
+/* The length of a peeled reference line in packed-refs, including EOL: */
+#define PEELED_LINE_LENGTH 42
+
+/*
+ * The packed-refs header line that we write out. Perhaps other
+ * traits will be added later. The trailing space is required.
+ */
+static const char PACKED_REFS_HEADER[] =
+ "# pack-refs with: peeled fully-peeled \n";
+
/*
* Parse one line from a packed-refs file. Write the SHA1 to sha1.
* Return a pointer to the refname within the line (null-terminated),
}
if (last &&
refline[0] == '^' &&
- strlen(refline) == 42 &&
- refline[41] == '\n' &&
+ strlen(refline) == PEELED_LINE_LENGTH &&
+ refline[PEELED_LINE_LENGTH - 1] == '\n' &&
!get_sha1_hex(refline + 1, sha1)) {
hashcpy(last->u.value.peeled, sha1);
/*
}
}
-static struct ref_dir *get_packed_refs(struct ref_cache *refs)
+/*
+ * Get the packed_ref_cache for the specified ref_cache, creating it
+ * if necessary.
+ */
+static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
{
+ const char *packed_refs_file;
+
+ if (*refs->name)
+ packed_refs_file = git_path_submodule(refs->name, "packed-refs");
+ else
+ packed_refs_file = git_path("packed-refs");
+
+ if (refs->packed &&
+ !stat_validity_check(&refs->packed->validity, packed_refs_file))
+ clear_packed_ref_cache(refs);
+
if (!refs->packed) {
- const char *packed_refs_file;
FILE *f;
- refs->packed = create_dir_entry(refs, "", 0, 0);
- if (*refs->name)
- packed_refs_file = git_path_submodule(refs->name, "packed-refs");
- else
- packed_refs_file = git_path("packed-refs");
+ refs->packed = xcalloc(1, sizeof(*refs->packed));
+ acquire_packed_ref_cache(refs->packed);
+ refs->packed->root = create_dir_entry(refs, "", 0, 0);
f = fopen(packed_refs_file, "r");
if (f) {
- read_packed_refs(f, get_ref_dir(refs->packed));
+ stat_validity_update(&refs->packed->validity, fileno(f));
+ read_packed_refs(f, get_ref_dir(refs->packed->root));
fclose(f);
}
}
- return get_ref_dir(refs->packed);
+ return refs->packed;
+}
+
+static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache)
+{
+ return get_ref_dir(packed_ref_cache->root);
+}
+
+static struct ref_dir *get_packed_refs(struct ref_cache *refs)
+{
+ return get_packed_ref_dir(get_packed_ref_cache(refs));
}
void add_packed_ref(const char *refname, const unsigned char *sha1)
{
- add_ref(get_packed_refs(get_ref_cache(NULL)),
- create_ref_entry(refname, sha1, REF_ISPACKED, 1));
+ struct packed_ref_cache *packed_ref_cache =
+ get_packed_ref_cache(&ref_cache);
+
+ if (!packed_ref_cache->lock)
+ die("internal error: packed refs not locked");
+ add_ref(get_packed_ref_dir(packed_ref_cache),
+ create_ref_entry(refname, sha1, REF_ISPACKED, 1));
}
/*
}
/*
- * Try to read ref from the packed references. On success, set sha1
- * and return 0; otherwise, return -1.
+ * Return the ref_entry for the given refname from the packed
+ * references. If it does not exist, return NULL.
*/
-static int get_packed_ref(const char *refname, unsigned char *sha1)
+static struct ref_entry *get_packed_ref(const char *refname)
{
- struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL));
- struct ref_entry *entry = find_ref(packed, 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);
- return 0;
+ if (flag)
+ *flag |= REF_ISPACKED;
+ return refname;
+ }
+ /* The reference is not a packed reference, either. */
+ if (reading) {
+ return NULL;
+ } else {
+ hashclr(sha1);
+ return refname;
}
- return -1;
}
const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
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) {
- if (errno != ENOENT)
- return NULL;
- /*
- * The loose reference file does not exist;
- * check for a packed reference.
- */
- if (!get_packed_ref(refname, sha1)) {
- if (flag)
- *flag |= REF_ISPACKED;
- return refname;
- }
- /* The reference is not a packed reference, either. */
- if (reading) {
+ if (errno == ENOENT)
+ return handle_missing_loose_ref(refname, sha1,
+ reading, flag);
+ else
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)) {
* 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)
/*
* 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;
}
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)
return filter->fn(refname, sha1, flags, filter->cb_data);
}
+enum peel_status {
+ /* object was peeled successfully: */
+ PEEL_PEELED = 0,
+
+ /*
+ * object cannot be peeled because the named object (or an
+ * object referred to by a tag in the peel chain), does not
+ * exist.
+ */
+ PEEL_INVALID = -1,
+
+ /* object cannot be peeled because it is not a tag: */
+ PEEL_NON_TAG = -2,
+
+ /* ref_entry contains no peeled value because it is a symref: */
+ PEEL_IS_SYMREF = -3,
+
+ /*
+ * ref_entry cannot be peeled because it is broken (i.e., the
+ * symbolic reference cannot even be resolved to an object
+ * name):
+ */
+ PEEL_BROKEN = -4
+};
+
+/*
+ * Peel the named object; i.e., if the object is a tag, resolve the
+ * tag recursively until a non-tag is found. If successful, store the
+ * result to sha1 and return PEEL_PEELED. If the object is not a tag
+ * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively,
+ * and leave sha1 unchanged.
+ */
+static enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
+{
+ struct object *o = lookup_unknown_object(name);
+
+ if (o->type == OBJ_NONE) {
+ int type = sha1_object_info(name, NULL);
+ if (type < 0)
+ return PEEL_INVALID;
+ o->type = type;
+ }
+
+ if (o->type != OBJ_TAG)
+ return PEEL_NON_TAG;
+
+ o = deref_tag_noverify(o);
+ if (!o)
+ return PEEL_INVALID;
+
+ hashcpy(sha1, o->sha1);
+ return PEEL_PEELED;
+}
+
+/*
+ * Peel the entry (if possible) and return its new peel_status. If
+ * repeel is true, re-peel the entry even if there is an old peeled
+ * value that is already stored in it.
+ *
+ * It is OK to call this function with a packed reference entry that
+ * might be stale and might even refer to an object that has since
+ * been garbage-collected. In such a case, if the entry has
+ * REF_KNOWS_PEELED then leave the status unchanged and return
+ * PEEL_PEELED or PEEL_NON_TAG; otherwise, return PEEL_INVALID.
+ */
+static enum peel_status peel_entry(struct ref_entry *entry, int repeel)
+{
+ enum peel_status status;
+
+ if (entry->flag & REF_KNOWS_PEELED) {
+ if (repeel) {
+ entry->flag &= ~REF_KNOWS_PEELED;
+ hashclr(entry->u.value.peeled);
+ } else {
+ return is_null_sha1(entry->u.value.peeled) ?
+ PEEL_NON_TAG : PEEL_PEELED;
+ }
+ }
+ if (entry->flag & REF_ISBROKEN)
+ return PEEL_BROKEN;
+ if (entry->flag & REF_ISSYMREF)
+ return PEEL_IS_SYMREF;
+
+ status = peel_object(entry->u.value.sha1, entry->u.value.peeled);
+ if (status == PEEL_PEELED || status == PEEL_NON_TAG)
+ entry->flag |= REF_KNOWS_PEELED;
+ return status;
+}
+
int peel_ref(const char *refname, unsigned char *sha1)
{
int flag;
unsigned char base[20];
- struct object *o;
if (current_ref && (current_ref->name == refname
- || !strcmp(current_ref->name, refname))) {
- if (current_ref->flag & REF_KNOWS_PEELED) {
- if (is_null_sha1(current_ref->u.value.peeled))
- return -1;
- hashcpy(sha1, current_ref->u.value.peeled);
- return 0;
- }
- hashcpy(base, current_ref->u.value.sha1);
- goto fallback;
+ || !strcmp(current_ref->name, refname))) {
+ if (peel_entry(current_ref, 0))
+ return -1;
+ hashcpy(sha1, current_ref->u.value.peeled);
+ return 0;
}
if (read_ref_full(refname, base, 1, &flag))
return -1;
- if ((flag & REF_ISPACKED)) {
- struct ref_dir *dir = get_packed_refs(get_ref_cache(NULL));
- struct ref_entry *r = find_ref(dir, refname);
-
- if (r != NULL && r->flag & REF_KNOWS_PEELED) {
+ /*
+ * If the reference is packed, read its ref_entry from the
+ * cache in the hope that we already know its peeled value.
+ * We only try this optimization on packed references because
+ * (a) forcing the filling of the loose reference cache could
+ * be expensive and (b) loose references anyway usually do not
+ * have REF_KNOWS_PEELED.
+ */
+ if (flag & REF_ISPACKED) {
+ struct ref_entry *r = get_packed_ref(refname);
+ if (r) {
+ if (peel_entry(r, 0))
+ return -1;
hashcpy(sha1, r->u.value.peeled);
return 0;
}
}
-fallback:
- o = lookup_unknown_object(base);
- if (o->type == OBJ_NONE) {
- int type = sha1_object_info(base, NULL);
- if (type < 0)
- return -1;
- o->type = type;
- }
-
- if (o->type == OBJ_TAG) {
- o = deref_tag_noverify(o);
- if (o) {
- hashcpy(sha1, o->sha1);
- return 0;
- }
- }
- return -1;
+ return peel_object(base, sha1);
}
struct warn_if_dangling_data {
for_each_rawref(warn_if_dangling_symref, &data);
}
-static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
- int trim, int flags, void *cb_data)
+/*
+ * Call fn for each reference in the specified ref_cache, omitting
+ * references not in the containing_dir of base. fn is called for all
+ * references, including broken ones. If fn ever returns a non-zero
+ * value, stop the iteration and return that value; otherwise, return
+ * 0.
+ */
+static int do_for_each_entry(struct ref_cache *refs, const char *base,
+ each_ref_entry_fn fn, void *cb_data)
{
- struct ref_cache *refs = get_ref_cache(submodule);
- struct ref_dir *packed_dir = get_packed_refs(refs);
- struct ref_dir *loose_dir = get_loose_refs(refs);
+ struct packed_ref_cache *packed_ref_cache;
+ struct ref_dir *loose_dir;
+ struct ref_dir *packed_dir;
int retval = 0;
+ /*
+ * We must make sure that all loose refs are read before accessing the
+ * packed-refs file; this avoids a race condition in which loose refs
+ * are migrated to the packed-refs file by a simultaneous process, but
+ * our in-memory view is from before the migration. get_packed_ref_cache()
+ * takes care of making sure our view is up to date with what is on
+ * disk.
+ */
+ loose_dir = get_loose_refs(refs);
if (base && *base) {
- packed_dir = find_containing_dir(packed_dir, base, 0);
loose_dir = find_containing_dir(loose_dir, base, 0);
}
+ if (loose_dir)
+ prime_ref_dir(loose_dir);
+
+ packed_ref_cache = get_packed_ref_cache(refs);
+ acquire_packed_ref_cache(packed_ref_cache);
+ packed_dir = get_packed_ref_dir(packed_ref_cache);
+ if (base && *base) {
+ packed_dir = find_containing_dir(packed_dir, base, 0);
+ }
if (packed_dir && loose_dir) {
sort_ref_dir(packed_dir);
sort_ref_dir(loose_dir);
- retval = do_for_each_ref_in_dirs(
- packed_dir, loose_dir,
- base, fn, trim, flags, cb_data);
+ retval = do_for_each_entry_in_dirs(
+ packed_dir, loose_dir, fn, cb_data);
} else if (packed_dir) {
sort_ref_dir(packed_dir);
- retval = do_for_each_ref_in_dir(
- packed_dir, 0,
- base, fn, trim, flags, cb_data);
+ retval = do_for_each_entry_in_dir(
+ packed_dir, 0, fn, cb_data);
} else if (loose_dir) {
sort_ref_dir(loose_dir);
- retval = do_for_each_ref_in_dir(
- loose_dir, 0,
- base, fn, trim, flags, cb_data);
+ retval = do_for_each_entry_in_dir(
+ loose_dir, 0, fn, cb_data);
}
+ release_packed_ref_cache(packed_ref_cache);
return retval;
}
+/*
+ * Call fn for each reference in the specified ref_cache for which the
+ * refname begins with base. If trim is non-zero, then trim that many
+ * characters off the beginning of each refname before passing the
+ * refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to include
+ * broken references in the iteration. If fn ever returns a non-zero
+ * value, stop the iteration and return that value; otherwise, return
+ * 0.
+ */
+static int do_for_each_ref(struct ref_cache *refs, const char *base,
+ each_ref_fn fn, int trim, int flags, void *cb_data)
+{
+ struct ref_entry_cb data;
+ data.base = base;
+ data.trim = trim;
+ data.flags = flags;
+ data.fn = fn;
+ data.cb_data = cb_data;
+
+ return do_for_each_entry(refs, base, do_one_ref, &data);
+}
+
static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
{
unsigned char sha1[20];
int for_each_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
+ return do_for_each_ref(&ref_cache, "", fn, 0, 0, cb_data);
}
int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(submodule, "", fn, 0, 0, cb_data);
+ return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data);
}
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
+ return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
}
int for_each_ref_in_submodule(const char *submodule, const char *prefix,
each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
+ return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data);
}
int for_each_tag_ref(each_ref_fn fn, void *cb_data)
int for_each_replace_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data);
+ return do_for_each_ref(&ref_cache, "refs/replace/", fn, 13, 0, cb_data);
}
int head_ref_namespaced(each_ref_fn fn, void *cb_data)
struct strbuf buf = STRBUF_INIT;
int ret;
strbuf_addf(&buf, "%srefs/", get_git_namespace());
- ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data);
+ ret = do_for_each_ref(&ref_cache, buf.buf, fn, 0, 0, cb_data);
strbuf_release(&buf);
return ret;
}
int for_each_rawref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(NULL, "", fn, 0,
+ return do_for_each_ref(&ref_cache, "", fn, 0,
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
* name is a proper prefix of our refname.
*/
if (missing &&
- !is_refname_available(refname, NULL, get_packed_refs(get_ref_cache(NULL)))) {
+ !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) {
last_errno = ENOTDIR;
goto error_return;
}
return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
}
-struct repack_without_ref_sb {
- const char *refname;
- int fd;
-};
-
-static int repack_without_ref_fn(const char *refname, const unsigned char *sha1,
- int flags, void *cb_data)
+/*
+ * Write an entry to the packed-refs file for the specified refname.
+ * If peeled is non-NULL, write it as the entry's peeled value.
+ */
+static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
+ unsigned char *peeled)
{
- struct repack_without_ref_sb *data = cb_data;
char line[PATH_MAX + 100];
int len;
- if (!strcmp(data->refname, refname))
- return 0;
len = snprintf(line, sizeof(line), "%s %s\n",
sha1_to_hex(sha1), refname);
/* this should not happen but just being defensive */
if (len > sizeof(line))
die("too long a refname '%s'", refname);
- write_or_die(data->fd, line, len);
+ write_or_die(fd, line, len);
+
+ if (peeled) {
+ if (snprintf(line, sizeof(line), "^%s\n",
+ sha1_to_hex(peeled)) != PEELED_LINE_LENGTH)
+ die("internal error");
+ write_or_die(fd, line, PEELED_LINE_LENGTH);
+ }
+}
+
+/*
+ * An each_ref_entry_fn that writes the entry to a packed-refs file.
+ */
+static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
+{
+ int *fd = cb_data;
+ enum peel_status peel_status = peel_entry(entry, 0);
+
+ if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
+ error("internal error: %s is not a valid packed reference!",
+ entry->name);
+ write_packed_entry(*fd, entry->name, entry->u.value.sha1,
+ peel_status == PEEL_PEELED ?
+ entry->u.value.peeled : NULL);
return 0;
}
-static struct lock_file packlock;
+int lock_packed_refs(int flags)
+{
+ struct packed_ref_cache *packed_ref_cache;
-static int repack_without_ref(const char *refname)
+ if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0)
+ return -1;
+ /*
+ * Get the current packed-refs while holding the lock. If the
+ * packed-refs file has been modified since we last read it,
+ * this will automatically invalidate the cache and re-read
+ * the packed-refs file.
+ */
+ packed_ref_cache = get_packed_ref_cache(&ref_cache);
+ packed_ref_cache->lock = &packlock;
+ /* Increment the reference count to prevent it from being freed: */
+ acquire_packed_ref_cache(packed_ref_cache);
+ return 0;
+}
+
+int commit_packed_refs(void)
+{
+ struct packed_ref_cache *packed_ref_cache =
+ get_packed_ref_cache(&ref_cache);
+ int error = 0;
+
+ if (!packed_ref_cache->lock)
+ die("internal error: packed-refs not locked");
+ write_or_die(packed_ref_cache->lock->fd,
+ PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
+
+ do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
+ 0, write_packed_entry_fn,
+ &packed_ref_cache->lock->fd);
+ if (commit_lock_file(packed_ref_cache->lock))
+ error = -1;
+ packed_ref_cache->lock = NULL;
+ release_packed_ref_cache(packed_ref_cache);
+ return error;
+}
+
+void rollback_packed_refs(void)
{
- struct repack_without_ref_sb data;
- struct ref_cache *refs = get_ref_cache(NULL);
- struct ref_dir *packed = get_packed_refs(refs);
- if (find_ref(packed, refname) == NULL)
+ struct packed_ref_cache *packed_ref_cache =
+ get_packed_ref_cache(&ref_cache);
+
+ if (!packed_ref_cache->lock)
+ die("internal error: packed-refs not locked");
+ rollback_lock_file(packed_ref_cache->lock);
+ packed_ref_cache->lock = NULL;
+ release_packed_ref_cache(packed_ref_cache);
+ clear_packed_ref_cache(&ref_cache);
+}
+
+struct ref_to_prune {
+ struct ref_to_prune *next;
+ unsigned char sha1[20];
+ char name[FLEX_ARRAY];
+};
+
+struct pack_refs_cb_data {
+ unsigned int flags;
+ struct ref_dir *packed_refs;
+ struct ref_to_prune *ref_to_prune;
+};
+
+/*
+ * An each_ref_entry_fn that is run over loose references only. If
+ * the loose reference can be packed, add an entry in the packed ref
+ * cache. If the reference should be pruned, also add it to
+ * ref_to_prune in the pack_refs_cb_data.
+ */
+static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
+{
+ struct pack_refs_cb_data *cb = cb_data;
+ enum peel_status peel_status;
+ struct ref_entry *packed_entry;
+ int is_tag_ref = !prefixcmp(entry->name, "refs/tags/");
+
+ /* ALWAYS pack tags */
+ if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
return 0;
- data.refname = refname;
- data.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
- if (data.fd < 0) {
+
+ /* Do not pack symbolic or broken refs: */
+ if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry))
+ return 0;
+
+ /* Add a packed ref cache entry equivalent to the loose entry. */
+ peel_status = peel_entry(entry, 1);
+ if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
+ die("internal error peeling reference %s (%s)",
+ entry->name, sha1_to_hex(entry->u.value.sha1));
+ packed_entry = find_ref(cb->packed_refs, entry->name);
+ if (packed_entry) {
+ /* Overwrite existing packed entry with info from loose entry */
+ packed_entry->flag = REF_ISPACKED | REF_KNOWS_PEELED;
+ hashcpy(packed_entry->u.value.sha1, entry->u.value.sha1);
+ } else {
+ packed_entry = create_ref_entry(entry->name, entry->u.value.sha1,
+ REF_ISPACKED | REF_KNOWS_PEELED, 0);
+ add_ref(cb->packed_refs, packed_entry);
+ }
+ hashcpy(packed_entry->u.value.peeled, entry->u.value.peeled);
+
+ /* Schedule the loose reference for pruning if requested. */
+ if ((cb->flags & PACK_REFS_PRUNE)) {
+ int namelen = strlen(entry->name) + 1;
+ struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
+ hashcpy(n->sha1, entry->u.value.sha1);
+ strcpy(n->name, entry->name);
+ n->next = cb->ref_to_prune;
+ cb->ref_to_prune = n;
+ }
+ return 0;
+}
+
+/*
+ * Remove empty parents, but spare refs/ and immediate subdirs.
+ * Note: munges *name.
+ */
+static void try_remove_empty_parents(char *name)
+{
+ char *p, *q;
+ int i;
+ p = name;
+ for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
+ while (*p && *p != '/')
+ p++;
+ /* tolerate duplicate slashes; see check_refname_format() */
+ while (*p == '/')
+ p++;
+ }
+ for (q = p; *q; q++)
+ ;
+ while (1) {
+ while (q > p && *q != '/')
+ q--;
+ while (q > p && *(q-1) == '/')
+ q--;
+ if (q == p)
+ break;
+ *q = '\0';
+ if (rmdir(git_path("%s", name)))
+ break;
+ }
+}
+
+/* make sure nobody touched the ref, and unlink */
+static void prune_ref(struct ref_to_prune *r)
+{
+ struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
+
+ if (lock) {
+ unlink_or_warn(git_path("%s", r->name));
+ unlock_ref(lock);
+ try_remove_empty_parents(r->name);
+ }
+}
+
+static void prune_refs(struct ref_to_prune *r)
+{
+ while (r) {
+ prune_ref(r);
+ r = r->next;
+ }
+}
+
+int pack_refs(unsigned int flags)
+{
+ struct pack_refs_cb_data cbdata;
+
+ memset(&cbdata, 0, sizeof(cbdata));
+ cbdata.flags = flags;
+
+ lock_packed_refs(LOCK_DIE_ON_ERROR);
+ cbdata.packed_refs = get_packed_refs(&ref_cache);
+
+ do_for_each_entry_in_dir(get_loose_refs(&ref_cache), 0,
+ pack_if_possible_fn, &cbdata);
+
+ if (commit_packed_refs())
+ die_errno("unable to overwrite old ref-pack file");
+
+ prune_refs(cbdata.ref_to_prune);
+ return 0;
+}
+
+/*
+ * If entry is no longer needed in packed-refs, add it to the string
+ * list pointed to by cb_data. Reasons for deleting entries:
+ *
+ * - Entry is broken.
+ * - Entry is overridden by a loose ref.
+ * - Entry does not point at a valid object.
+ *
+ * In the first and third cases, also emit an error message because these
+ * are indications of repository corruption.
+ */
+static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)
+{
+ struct string_list *refs_to_delete = cb_data;
+
+ if (entry->flag & REF_ISBROKEN) {
+ /* This shouldn't happen to packed refs. */
+ error("%s is broken!", entry->name);
+ string_list_append(refs_to_delete, entry->name);
+ return 0;
+ }
+ if (!has_sha1_file(entry->u.value.sha1)) {
+ unsigned char sha1[20];
+ int flags;
+
+ if (read_ref_full(entry->name, sha1, 0, &flags))
+ /* We should at least have found the packed ref. */
+ die("Internal error");
+ if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) {
+ /*
+ * This packed reference is overridden by a
+ * loose reference, so it is OK that its value
+ * is no longer valid; for example, it might
+ * refer to an object that has been garbage
+ * collected. For this purpose we don't even
+ * care whether the loose reference itself is
+ * invalid, broken, symbolic, etc. Silently
+ * remove the packed reference.
+ */
+ string_list_append(refs_to_delete, entry->name);
+ return 0;
+ }
+ /*
+ * There is no overriding loose reference, so the fact
+ * that this reference doesn't refer to a valid object
+ * indicates some kind of repository corruption.
+ * Report the problem, then omit the reference from
+ * the output.
+ */
+ error("%s does not point to a valid object!", entry->name);
+ string_list_append(refs_to_delete, entry->name);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int repack_without_ref(const char *refname)
+{
+ struct ref_dir *packed;
+ struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
+ struct string_list_item *ref_to_delete;
+
+ if (!get_packed_ref(refname))
+ return 0; /* refname does not exist in packed refs */
+
+ if (lock_packed_refs(0)) {
unable_to_lock_error(git_path("packed-refs"), errno);
return error("cannot delete '%s' from packed refs", refname);
}
- clear_packed_ref_cache(refs);
- packed = get_packed_refs(refs);
- do_for_each_ref_in_dir(packed, 0, "", repack_without_ref_fn, 0, 0, &data);
- return commit_lock_file(&packlock);
+ packed = get_packed_refs(&ref_cache);
+
+ /* Remove refname from the cache: */
+ if (remove_entry(packed, refname) == -1) {
+ /*
+ * The packed entry disappeared while we were
+ * acquiring the lock.
+ */
+ rollback_packed_refs();
+ return 0;
+ }
+
+ /* Remove any other accumulated cruft: */
+ do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);
+ for_each_string_list_item(ref_to_delete, &refs_to_delete) {
+ if (remove_entry(packed, ref_to_delete->string) == -1)
+ die("internal error");
+ }
+
+ /* Write what remains: */
+ return commit_packed_refs();
}
int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
ret |= repack_without_ref(lock->ref_name);
unlink_or_warn(git_path("logs/%s", lock->ref_name));
- invalidate_ref_cache(NULL);
+ clear_loose_ref_cache(&ref_cache);
unlock_ref(lock);
return ret;
}
struct stat loginfo;
int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
const char *symref = NULL;
- struct ref_cache *refs = get_ref_cache(NULL);
if (log && S_ISLNK(loginfo.st_mode))
return error("reflog for %s is a symlink", oldrefname);
if (!symref)
return error("refname %s not found", oldrefname);
- if (!is_refname_available(newrefname, oldrefname, get_packed_refs(refs)))
+ if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache)))
return 1;
- if (!is_refname_available(newrefname, oldrefname, get_loose_refs(refs)))
+ if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))
return 1;
if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
unlock_ref(lock);
return -1;
}
- clear_loose_ref_cache(get_ref_cache(NULL));
+ clear_loose_ref_cache(&ref_cache);
if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
(strcmp(lock->ref_name, lock->orig_ref_name) &&
log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) {
int force_write;
};
+/*
+ * Bit values set in the flags argument passed to each_ref_fn():
+ */
+
+/* Reference is a symbolic reference. */
#define REF_ISSYMREF 0x01
+
+/* Reference is a packed reference. */
#define REF_ISPACKED 0x02
+
+/*
+ * Reference cannot be resolved to an object name: dangling symbolic
+ * reference (directly or indirectly), corrupt reference file, or
+ * symbolic reference refers to ill-formatted reference name.
+ */
#define REF_ISBROKEN 0x04
/*
- * Calls the specified function for each ref file until it returns
- * nonzero, and returns the value. Please note that it is not safe to
- * modify references while an iteration is in progress, unless the
- * same callback function invocation that modifies the reference also
- * returns a nonzero value to immediately stop the iteration.
+ * The signature for the callback function for the for_each_*()
+ * functions below. The memory pointed to by the refname and sha1
+ * arguments is only guaranteed to be valid for the duration of a
+ * single callback invocation.
+ */
+typedef int each_ref_fn(const char *refname,
+ const unsigned char *sha1, int flags, void *cb_data);
+
+/*
+ * The following functions invoke the specified callback function for
+ * each reference indicated. If the function ever returns a nonzero
+ * value, stop the iteration and return that value. Please note that
+ * it is not safe to modify references while an iteration is in
+ * progress, unless the same callback function invocation that
+ * modifies the reference also returns a nonzero value to immediately
+ * stop the iteration.
*/
-typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
extern int head_ref(each_ref_fn, void *);
extern int for_each_ref(each_ref_fn, void *);
extern int for_each_ref_in(const char *, each_ref_fn, void *);
extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
/*
- * Add a reference to the in-memory packed reference cache. To actually
- * write the reference to the packed-refs file, call pack_refs().
+ * Lock the packed-refs file for writing. Flags is passed to
+ * hold_lock_file_for_update(). Return 0 on success.
+ */
+extern int lock_packed_refs(int flags);
+
+/*
+ * Add a reference to the in-memory packed reference cache. This may
+ * only be called while the packed-refs file is locked (see
+ * lock_packed_refs()). To actually write the packed-refs file, call
+ * commit_packed_refs().
*/
extern void add_packed_ref(const char *refname, const unsigned char *sha1);
+/*
+ * Write the current version of the packed refs cache from memory to
+ * disk. The packed-refs file must already be locked for writing (see
+ * lock_packed_refs()). Return zero on success.
+ */
+extern int commit_packed_refs(void);
+
+/*
+ * Rollback the lockfile for the packed-refs file, and discard the
+ * in-memory packed reference cache. (The packed-refs file will be
+ * read anew if it is needed again after this function is called.)
+ */
+extern void rollback_packed_refs(void);
+
+/*
+ * Flags for controlling behaviour of pack_refs()
+ * PACK_REFS_PRUNE: Prune loose refs after packing
+ * PACK_REFS_ALL: Pack _all_ refs, not just tags and already packed refs
+ */
+#define PACK_REFS_PRUNE 0x0001
+#define PACK_REFS_ALL 0x0002
+
+/*
+ * Write a packed-refs file for the current repository.
+ * flags: Combination of the above PACK_REFS_* flags.
+ */
+int pack_refs(unsigned int flags);
+
extern int ref_exists(const char *);
+/*
+ * If refname is a non-symbolic reference that refers to a tag object,
+ * and the tag can be (recursively) dereferenced to a non-tag object,
+ * store the SHA1 of the referred-to object to sha1 and return 0. If
+ * any of these conditions are not met, return a non-zero value.
+ * Symbolic references are considered unpeelable, even if they
+ * ultimately resolve to a peelable tag.
+ */
extern int peel_ref(const char *refname, unsigned char *sha1);
/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
#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 */
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;
}
return 0;
}
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
struct strbuf buf = STRBUF_INIT, url_sb = STRBUF_INIT,
private_ref_sb = STRBUF_INIT, marksfilename_sb = STRBUF_INIT,
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;
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.
*/
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));
/*
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
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;
/* 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;
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. */
/* 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;
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;
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;
ret->remote = remote_get(ret->remote_name);
if (ret->merge_nr) {
int i;
- ret->merge = xcalloc(sizeof(*ret->merge),
- ret->merge_nr);
+ ret->merge = xcalloc(ret->merge_nr, sizeof(*ret->merge));
for (i = 0; i < ret->merge_nr; i++) {
ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
ret->merge[i]->src = xstrdup(ret->merge_name[i]);
{
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);
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;
/* 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) &&
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);
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);
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;
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);
}
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);
#include "decorate.h"
#include "log-tree.h"
#include "string-list.h"
+#include "line-log.h"
#include "mailmap.h"
volatile show_early_output_fn_t show_early_output;
return ours || emitted;
}
-void show_object_with_name(FILE *out, struct object *obj, const struct name_path *path, const char *component)
+void show_object_with_name(FILE *out, struct object *obj,
+ const struct name_path *path, const char *component)
{
struct name_path leaf;
leaf.up = (struct name_path *)path;
struct name_path *path,
const char *name)
{
- add_object_array(obj, path_name(path, name), p);
+ char *pn = path_name(path, name);
+ add_object_array(obj, pn, p);
+ free(pn);
}
static void mark_blob_uninteresting(struct blob *blob)
}
}
-static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
+static void add_pending_object_with_mode(struct rev_info *revs,
+ struct object *obj,
+ const char *name, unsigned mode)
{
if (!obj)
return;
add_object_array_with_mode(obj, name, &revs->pending, mode);
}
-void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+void add_pending_object(struct rev_info *revs,
+ struct object *obj, const char *name)
{
add_pending_object_with_mode(revs, obj, name, S_IFINVALID);
}
add_pending_object(revs, obj, "HEAD");
}
-static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+static struct object *get_reference(struct rev_info *revs, const char *name,
+ const unsigned char *sha1,
+ unsigned int flags)
{
struct object *object;
add_pending_object(revs, object, name);
}
-static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
+static struct commit *handle_commit(struct rev_info *revs,
+ struct object *object, const char *name)
{
unsigned long flags = object->flags;
return 1;
}
+/*
+ * A definition of "relevant" commit that we can use to simplify limited graphs
+ * by eliminating side branches.
+ *
+ * A "relevant" commit is one that is !UNINTERESTING (ie we are including it
+ * in our list), or that is a specified BOTTOM commit. Then after computing
+ * a limited list, during processing we can generally ignore boundary merges
+ * coming from outside the graph, (ie from irrelevant parents), and treat
+ * those merges as if they were single-parent. TREESAME is defined to consider
+ * only relevant parents, if any. If we are TREESAME to our on-graph parents,
+ * we don't care if we were !TREESAME to non-graph parents.
+ *
+ * Treating bottom commits as relevant ensures that a limited graph's
+ * connection to the actual bottom commit is not viewed as a side branch, but
+ * treated as part of the graph. For example:
+ *
+ * ....Z...A---X---o---o---B
+ * . /
+ * W---Y
+ *
+ * When computing "A..B", the A-X connection is at least as important as
+ * Y-X, despite A being flagged UNINTERESTING.
+ *
+ * And when computing --ancestry-path "A..B", the A-X connection is more
+ * important than Y-X, despite both A and Y being flagged UNINTERESTING.
+ */
+static inline int relevant_commit(struct commit *commit)
+{
+ return (commit->object.flags & (UNINTERESTING | BOTTOM)) != UNINTERESTING;
+}
+
+/*
+ * Return a single relevant commit from a parent list. If we are a TREESAME
+ * commit, and this selects one of our parents, then we can safely simplify to
+ * that parent.
+ */
+static struct commit *one_relevant_parent(const struct rev_info *revs,
+ struct commit_list *orig)
+{
+ struct commit_list *list = orig;
+ struct commit *relevant = NULL;
+
+ if (!orig)
+ return NULL;
+
+ /*
+ * For 1-parent commits, or if first-parent-only, then return that
+ * first parent (even if not "relevant" by the above definition).
+ * TREESAME will have been set purely on that parent.
+ */
+ if (revs->first_parent_only || !orig->next)
+ return orig->item;
+
+ /*
+ * For multi-parent commits, identify a sole relevant parent, if any.
+ * If we have only one relevant parent, then TREESAME will be set purely
+ * with regard to that parent, and we can simplify accordingly.
+ *
+ * If we have more than one relevant parent, or no relevant parents
+ * (and multiple irrelevant ones), then we can't select a parent here
+ * and return NULL.
+ */
+ while (list) {
+ struct commit *commit = list->item;
+ list = list->next;
+ if (relevant_commit(commit)) {
+ if (relevant)
+ return NULL;
+ relevant = commit;
+ }
+ }
+ return relevant;
+}
+
/*
* The goal is to get REV_TREE_NEW as the result only if the
* diff consists of all '+' (and no other changes), REV_TREE_OLD
DIFF_OPT_SET(options, HAS_CHANGES);
}
-static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct commit *commit)
+static int rev_compare_tree(struct rev_info *revs,
+ struct commit *parent, struct commit *commit)
{
struct tree *t1 = parent->tree;
struct tree *t2 = commit->tree;
return retval >= 0 && (tree_difference == REV_TREE_SAME);
}
+struct treesame_state {
+ unsigned int nparents;
+ unsigned char treesame[FLEX_ARRAY];
+};
+
+static struct treesame_state *initialise_treesame(struct rev_info *revs, struct commit *commit)
+{
+ unsigned n = commit_list_count(commit->parents);
+ struct treesame_state *st = xcalloc(1, sizeof(*st) + n);
+ st->nparents = n;
+ add_decoration(&revs->treesame, &commit->object, st);
+ return st;
+}
+
+/*
+ * Must be called immediately after removing the nth_parent from a commit's
+ * parent list, if we are maintaining the per-parent treesame[] decoration.
+ * This does not recalculate the master TREESAME flag - update_treesame()
+ * should be called to update it after a sequence of treesame[] modifications
+ * that may have affected it.
+ */
+static int compact_treesame(struct rev_info *revs, struct commit *commit, unsigned nth_parent)
+{
+ struct treesame_state *st;
+ int old_same;
+
+ if (!commit->parents) {
+ /*
+ * Have just removed the only parent from a non-merge.
+ * Different handling, as we lack decoration.
+ */
+ if (nth_parent != 0)
+ die("compact_treesame %u", nth_parent);
+ old_same = !!(commit->object.flags & TREESAME);
+ if (rev_same_tree_as_empty(revs, commit))
+ commit->object.flags |= TREESAME;
+ else
+ commit->object.flags &= ~TREESAME;
+ return old_same;
+ }
+
+ st = lookup_decoration(&revs->treesame, &commit->object);
+ if (!st || nth_parent >= st->nparents)
+ die("compact_treesame %u", nth_parent);
+
+ old_same = st->treesame[nth_parent];
+ memmove(st->treesame + nth_parent,
+ st->treesame + nth_parent + 1,
+ st->nparents - nth_parent - 1);
+
+ /*
+ * If we've just become a non-merge commit, update TREESAME
+ * immediately, and remove the no-longer-needed decoration.
+ * If still a merge, defer update until update_treesame().
+ */
+ if (--st->nparents == 1) {
+ if (commit->parents->next)
+ die("compact_treesame parents mismatch");
+ if (st->treesame[0] && revs->dense)
+ commit->object.flags |= TREESAME;
+ else
+ commit->object.flags &= ~TREESAME;
+ free(add_decoration(&revs->treesame, &commit->object, NULL));
+ }
+
+ return old_same;
+}
+
+static unsigned update_treesame(struct rev_info *revs, struct commit *commit)
+{
+ if (commit->parents && commit->parents->next) {
+ unsigned n;
+ struct treesame_state *st;
+ struct commit_list *p;
+ unsigned relevant_parents;
+ unsigned relevant_change, irrelevant_change;
+
+ st = lookup_decoration(&revs->treesame, &commit->object);
+ if (!st)
+ die("update_treesame %s", sha1_to_hex(commit->object.sha1));
+ relevant_parents = 0;
+ relevant_change = irrelevant_change = 0;
+ for (p = commit->parents, n = 0; p; n++, p = p->next) {
+ if (relevant_commit(p->item)) {
+ relevant_change |= !st->treesame[n];
+ relevant_parents++;
+ } else
+ irrelevant_change |= !st->treesame[n];
+ }
+ if (relevant_parents ? relevant_change : irrelevant_change)
+ commit->object.flags &= ~TREESAME;
+ else
+ commit->object.flags |= TREESAME;
+ }
+
+ return commit->object.flags & TREESAME;
+}
+
+static inline int limiting_can_increase_treesame(const struct rev_info *revs)
+{
+ /*
+ * TREESAME is irrelevant unless prune && dense;
+ * if simplify_history is set, we can't have a mixture of TREESAME and
+ * !TREESAME INTERESTING parents (and we don't have treesame[]
+ * decoration anyway);
+ * if first_parent_only is set, then the TREESAME flag is locked
+ * against the first parent (and again we lack treesame[] decoration).
+ */
+ return revs->prune && revs->dense &&
+ !revs->simplify_history &&
+ !revs->first_parent_only;
+}
+
static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
{
struct commit_list **pp, *parent;
- int tree_changed = 0, tree_same = 0, nth_parent = 0;
+ struct treesame_state *ts = NULL;
+ int relevant_change = 0, irrelevant_change = 0;
+ int relevant_parents, nth_parent;
/*
* If we don't do pruning, everything is interesting
if (!revs->dense && !commit->parents->next)
return;
- pp = &commit->parents;
- while ((parent = *pp) != NULL) {
+ for (pp = &commit->parents, nth_parent = 0, relevant_parents = 0;
+ (parent = *pp) != NULL;
+ pp = &parent->next, nth_parent++) {
struct commit *p = parent->item;
+ if (relevant_commit(p))
+ relevant_parents++;
- /*
- * Do not compare with later parents when we care only about
- * the first parent chain, in order to avoid derailing the
- * traversal to follow a side branch that brought everything
- * in the path we are limited to by the pathspec.
- */
- if (revs->first_parent_only && nth_parent++)
- break;
+ if (nth_parent == 1) {
+ /*
+ * This our second loop iteration - so we now know
+ * we're dealing with a merge.
+ *
+ * Do not compare with later parents when we care only about
+ * the first parent chain, in order to avoid derailing the
+ * traversal to follow a side branch that brought everything
+ * in the path we are limited to by the pathspec.
+ */
+ if (revs->first_parent_only)
+ break;
+ /*
+ * If this will remain a potentially-simplifiable
+ * merge, remember per-parent treesame if needed.
+ * Initialise the array with the comparison from our
+ * first iteration.
+ */
+ if (revs->treesame.name &&
+ !revs->simplify_history &&
+ !(commit->object.flags & UNINTERESTING)) {
+ ts = initialise_treesame(revs, commit);
+ if (!(irrelevant_change || relevant_change))
+ ts->treesame[0] = 1;
+ }
+ }
if (parse_commit(p) < 0)
die("cannot simplify commit %s (because of %s)",
sha1_to_hex(commit->object.sha1),
sha1_to_hex(p->object.sha1));
switch (rev_compare_tree(revs, p, commit)) {
case REV_TREE_SAME:
- tree_same = 1;
- if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
+ if (!revs->simplify_history || !relevant_commit(p)) {
/* Even if a merge with an uninteresting
* side branch brought the entire change
* we are interested in, we do not want
* to lose the other branches of this
* merge, so we just keep going.
*/
- pp = &parent->next;
+ if (ts)
+ ts->treesame[nth_parent] = 1;
continue;
}
parent->next = NULL;
/* fallthrough */
case REV_TREE_OLD:
case REV_TREE_DIFFERENT:
- tree_changed = 1;
- pp = &parent->next;
+ if (relevant_commit(p))
+ relevant_change = 1;
+ else
+ irrelevant_change = 1;
continue;
}
die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
}
- if (tree_changed && !tree_same)
- return;
- commit->object.flags |= TREESAME;
+
+ /*
+ * TREESAME is straightforward for single-parent commits. For merge
+ * commits, it is most useful to define it so that "irrelevant"
+ * parents cannot make us !TREESAME - if we have any relevant
+ * parents, then we only consider TREESAMEness with respect to them,
+ * allowing irrelevant merges from uninteresting branches to be
+ * simplified away. Only if we have only irrelevant parents do we
+ * base TREESAME on them. Note that this logic is replicated in
+ * update_treesame, which should be kept in sync.
+ */
+ if (relevant_parents ? !relevant_change : !irrelevant_change)
+ commit->object.flags |= TREESAME;
}
static void commit_list_insert_by_date_cached(struct commit *p, struct commit_list **head,
* to filter the result of "A..B" further to the ones that can actually
* reach A.
*/
-static struct commit_list *collect_bottom_commits(struct rev_info *revs)
+static struct commit_list *collect_bottom_commits(struct commit_list *list)
{
- struct commit_list *bottom = NULL;
- int i;
- for (i = 0; i < revs->cmdline.nr; i++) {
- struct rev_cmdline_entry *elem = &revs->cmdline.rev[i];
- if ((elem->flags & UNINTERESTING) &&
- elem->item->type == OBJ_COMMIT)
- commit_list_insert((struct commit *)elem->item, &bottom);
- }
+ struct commit_list *elem, *bottom = NULL;
+ for (elem = list; elem; elem = elem->next)
+ if (elem->item->object.flags & BOTTOM)
+ commit_list_insert(elem->item, &bottom);
return bottom;
}
struct commit_list *bottom = NULL;
if (revs->ancestry_path) {
- bottom = collect_bottom_commits(revs);
+ bottom = collect_bottom_commits(list);
if (!bottom)
die("--ancestry-path given but there are no bottom commits");
}
free_commit_list(bottom);
}
+ /*
+ * Check if any commits have become TREESAME by some of their parents
+ * becoming UNINTERESTING.
+ */
+ if (limiting_can_increase_treesame(revs))
+ for (list = newlist; list; list = list->next) {
+ struct commit *c = list->item;
+ if (c->object.flags & (UNINTERESTING | TREESAME))
+ continue;
+ update_treesame(revs, c);
+ }
+
revs->commits = newlist;
return 0;
}
+/*
+ * Add an entry to refs->cmdline with the specified information.
+ * *name is copied.
+ */
static void add_rev_cmdline(struct rev_info *revs,
struct object *item,
const char *name,
ALLOC_GROW(info->rev, nr + 1, info->alloc);
info->rev[nr].item = item;
- info->rev[nr].name = name;
+ info->rev[nr].name = xstrdup(name);
info->rev[nr].whence = whence;
info->rev[nr].flags = flags;
info->nr++;
}
+static void add_rev_cmdline_list(struct rev_info *revs,
+ struct commit_list *commit_list,
+ int whence,
+ unsigned flags)
+{
+ while (commit_list) {
+ struct object *object = &commit_list->item->object;
+ add_rev_cmdline(revs, object, sha1_to_hex(object->sha1),
+ whence, flags);
+ commit_list = commit_list->next;
+ }
+}
+
struct all_refs_cb {
int all_flags;
int warned_bad_reflog;
const char *arg = arg_;
if (*arg == '^') {
- flags ^= UNINTERESTING;
+ flags ^= UNINTERESTING | BOTTOM;
arg++;
}
if (get_sha1_committish(arg, sha1))
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;
add_pending_object(revs, &head->object, "HEAD");
add_pending_object(revs, &other->object, "MERGE_HEAD");
bases = get_merge_bases(head, other, 1);
- add_pending_commit_list(revs, bases, UNINTERESTING);
+ add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM);
+ add_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM);
free_commit_list(bases);
head->object.flags |= SYMMETRIC_LEFT;
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)) {
int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
unsigned get_sha1_flags = 0;
+ flags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;
+
dotdot = strstr(arg, "..");
if (dotdot) {
unsigned char from_sha1[20];
const char *next = dotdot + 2;
const char *this = arg;
int symmetric = *next == '.';
- unsigned int flags_exclude = flags ^ UNINTERESTING;
+ unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM);
static const char head_by_default[] = "HEAD";
unsigned int a_flags;
if (symmetric) {
exclude = get_merge_bases(a, b, 1);
+ add_rev_cmdline_list(revs, exclude,
+ REV_CMD_MERGE_BASE,
+ flags_exclude);
add_pending_commit_list(revs, exclude,
flags_exclude);
free_commit_list(exclude);
dotdot = strstr(arg, "^!");
if (dotdot && !dotdot[2]) {
*dotdot = 0;
- if (!add_parents_only(revs, arg, flags ^ UNINTERESTING))
+ if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM)))
*dotdot = '^';
}
local_flags = 0;
if (*arg == '^') {
- local_flags = UNINTERESTING;
+ local_flags = UNINTERESTING | BOTTOM;
arg++;
}
}
die("options not supported in --stdin mode");
}
- if (handle_revision_arg(xstrdup(sb.buf), revs, 0,
+ if (handle_revision_arg(sb.buf, revs, 0,
REVARG_CANNOT_BE_FILENAME))
die("bad revision '%s'", sb.buf);
}
} 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;
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;
handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
} else if (!strcmp(arg, "--bisect")) {
handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);
- handle_refs(submodule, revs, *flags ^ UNINTERESTING, for_each_good_bisect_ref);
+ handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref);
revs->bisect = 1;
} else if (!strcmp(arg, "--tags")) {
handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule);
} else if (!strcmp(arg, "--reflog")) {
handle_reflog(revs, *flags);
} else if (!strcmp(arg, "--not")) {
- *flags ^= UNINTERESTING;
+ *flags ^= UNINTERESTING | BOTTOM;
} else if (!strcmp(arg, "--no-walk")) {
revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
} else if (!prefixcmp(arg, "--no-walk=")) {
if (revs->combine_merges)
revs->ignore_merges = 0;
revs->diffopt.abbrev = revs->abbrev;
+
+ if (revs->line_level_traverse) {
+ revs->limited = 1;
+ revs->topo_order = 1;
+ }
+
diff_setup_done(&revs->diffopt);
grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED,
l->next = add_decoration(&revs->children, &parent->object, l);
}
-static int remove_duplicate_parents(struct commit *commit)
+static int remove_duplicate_parents(struct rev_info *revs, struct commit *commit)
{
+ struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
struct commit_list **pp, *p;
int surviving_parents;
/* Examine existing parents while marking ones we have seen... */
pp = &commit->parents;
+ surviving_parents = 0;
while ((p = *pp) != NULL) {
struct commit *parent = p->item;
if (parent->object.flags & TMP_MARK) {
*pp = p->next;
+ if (ts)
+ compact_treesame(revs, commit, surviving_parents);
continue;
}
parent->object.flags |= TMP_MARK;
+ surviving_parents++;
pp = &p->next;
}
- /* count them while clearing the temporary mark */
- surviving_parents = 0;
+ /* clear the temporary mark */
for (p = commit->parents; p; p = p->next) {
p->item->object.flags &= ~TMP_MARK;
- surviving_parents++;
}
+ /* no update_treesame() - removing duplicates can't affect TREESAME */
return surviving_parents;
}
return st;
}
+static int mark_redundant_parents(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list *h = reduce_heads(commit->parents);
+ int i = 0, marked = 0;
+ struct commit_list *po, *pn;
+
+ /* Want these for sanity-checking only */
+ int orig_cnt = commit_list_count(commit->parents);
+ int cnt = commit_list_count(h);
+
+ /*
+ * Not ready to remove items yet, just mark them for now, based
+ * on the output of reduce_heads(). reduce_heads outputs the reduced
+ * set in its original order, so this isn't too hard.
+ */
+ po = commit->parents;
+ pn = h;
+ while (po) {
+ if (pn && po->item == pn->item) {
+ pn = pn->next;
+ i++;
+ } else {
+ po->item->object.flags |= TMP_MARK;
+ marked++;
+ }
+ po=po->next;
+ }
+
+ if (i != cnt || cnt+marked != orig_cnt)
+ die("mark_redundant_parents %d %d %d %d", orig_cnt, cnt, i, marked);
+
+ free_commit_list(h);
+
+ return marked;
+}
+
+static int mark_treesame_root_parents(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list *p;
+ int marked = 0;
+
+ for (p = commit->parents; p; p = p->next) {
+ struct commit *parent = p->item;
+ if (!parent->parents && (parent->object.flags & TREESAME)) {
+ parent->object.flags |= TMP_MARK;
+ marked++;
+ }
+ }
+
+ return marked;
+}
+
+/*
+ * Awkward naming - this means one parent we are TREESAME to.
+ * cf mark_treesame_root_parents: root parents that are TREESAME (to an
+ * empty tree). Better name suggestions?
+ */
+static int leave_one_treesame_to_parent(struct rev_info *revs, struct commit *commit)
+{
+ struct treesame_state *ts = lookup_decoration(&revs->treesame, &commit->object);
+ struct commit *unmarked = NULL, *marked = NULL;
+ struct commit_list *p;
+ unsigned n;
+
+ for (p = commit->parents, n = 0; p; p = p->next, n++) {
+ if (ts->treesame[n]) {
+ if (p->item->object.flags & TMP_MARK) {
+ if (!marked)
+ marked = p->item;
+ } else {
+ if (!unmarked) {
+ unmarked = p->item;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * If we are TREESAME to a marked-for-deletion parent, but not to any
+ * unmarked parents, unmark the first TREESAME parent. This is the
+ * parent that the default simplify_history==1 scan would have followed,
+ * and it doesn't make sense to omit that path when asking for a
+ * simplified full history. Retaining it improves the chances of
+ * understanding odd missed merges that took an old version of a file.
+ *
+ * Example:
+ *
+ * I--------*X A modified the file, but mainline merge X used
+ * \ / "-s ours", so took the version from I. X is
+ * `-*A--' TREESAME to I and !TREESAME to A.
+ *
+ * Default log from X would produce "I". Without this check,
+ * --full-history --simplify-merges would produce "I-A-X", showing
+ * the merge commit X and that it changed A, but not making clear that
+ * it had just taken the I version. With this check, the topology above
+ * is retained.
+ *
+ * Note that it is possible that the simplification chooses a different
+ * TREESAME parent from the default, in which case this test doesn't
+ * activate, and we _do_ drop the default parent. Example:
+ *
+ * I------X A modified the file, but it was reverted in B,
+ * \ / meaning mainline merge X is TREESAME to both
+ * *A-*B parents.
+ *
+ * Default log would produce "I" by following the first parent;
+ * --full-history --simplify-merges will produce "I-A-B". But this is a
+ * reasonable result - it presents a logical full history leading from
+ * I to X, and X is not an important merge.
+ */
+ if (!unmarked && marked) {
+ marked->object.flags &= ~TMP_MARK;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int remove_marked_parents(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list **pp, *p;
+ int nth_parent, removed = 0;
+
+ pp = &commit->parents;
+ nth_parent = 0;
+ while ((p = *pp) != NULL) {
+ struct commit *parent = p->item;
+ if (parent->object.flags & TMP_MARK) {
+ parent->object.flags &= ~TMP_MARK;
+ *pp = p->next;
+ free(p);
+ removed++;
+ compact_treesame(revs, commit, nth_parent);
+ continue;
+ }
+ pp = &p->next;
+ nth_parent++;
+ }
+
+ /* Removing parents can only increase TREESAMEness */
+ if (removed && !(commit->object.flags & TREESAME))
+ update_treesame(revs, commit);
+
+ return nth_parent;
+}
+
static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)
{
struct commit_list *p;
+ struct commit *parent;
struct merge_simplify_state *st, *pst;
int cnt;
}
/*
- * Rewrite our list of parents.
+ * Rewrite our list of parents. Note that this cannot
+ * affect our TREESAME flags in any way - a commit is
+ * always TREESAME to its simplification.
*/
for (p = commit->parents; p; p = p->next) {
pst = locate_simplify_state(revs, p->item);
if (revs->first_parent_only)
cnt = 1;
else
- cnt = remove_duplicate_parents(commit);
+ cnt = remove_duplicate_parents(revs, commit);
/*
* It is possible that we are a merge and one side branch
* does not have any commit that touches the given paths;
- * in such a case, the immediate parents will be rewritten
- * to different commits.
+ * in such a case, the immediate parent from that branch
+ * will be rewritten to be the merge base.
*
* o----X X: the commit we are looking at;
* / / o: a commit that touches the paths;
* ---o----'
*
- * Further reduce the parents by removing redundant parents.
+ * Further, a merge of an independent branch that doesn't
+ * touch the path will reduce to a treesame root parent:
+ *
+ * ----o----X X: the commit we are looking at;
+ * / o: a commit that touches the paths;
+ * r r: a root commit not touching the paths
+ *
+ * Detect and simplify both cases.
*/
if (1 < cnt) {
- struct commit_list *h = reduce_heads(commit->parents);
- cnt = commit_list_count(h);
- free_commit_list(commit->parents);
- commit->parents = h;
+ int marked = mark_redundant_parents(revs, commit);
+ marked += mark_treesame_root_parents(revs, commit);
+ if (marked)
+ marked -= leave_one_treesame_to_parent(revs, commit);
+ if (marked)
+ cnt = remove_marked_parents(revs, commit);
}
/*
* A commit simplifies to itself if it is a root, if it is
* UNINTERESTING, if it touches the given paths, or if it is a
- * merge and its parents simplifies to more than one commits
+ * merge and its parents don't simplify to one relevant commit
* (the first two cases are already handled at the beginning of
* this function).
*
- * Otherwise, it simplifies to what its sole parent simplifies to.
+ * Otherwise, it simplifies to what its sole relevant parent
+ * simplifies to.
*/
if (!cnt ||
(commit->object.flags & UNINTERESTING) ||
!(commit->object.flags & TREESAME) ||
- (1 < cnt))
+ (parent = one_relevant_parent(revs, commit->parents)) == NULL)
st->simplified = commit;
else {
- pst = locate_simplify_state(revs, commit->parents->item);
+ pst = locate_simplify_state(revs, parent);
st->simplified = pst->simplified;
}
return tail;
if (!revs->leak_pending)
free(list);
+ /* Signal whether we need per-parent treesame decoration */
+ if (revs->simplify_merges ||
+ (revs->limited && limiting_can_increase_treesame(revs)))
+ revs->treesame.name = "treesame";
+
if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
commit_list_sort_by_date(&revs->commits);
if (revs->no_walk)
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)
simplify_merges(revs);
if (revs->children.name)
return 0;
}
-enum rewrite_result {
- rewrite_one_ok,
- rewrite_one_noparents,
- rewrite_one_error
-};
-
static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
{
struct commit_list *cache = NULL;
if (!revs->limited)
if (add_parents_to_list(revs, p, &revs->commits, &cache) < 0)
return rewrite_one_error;
- if (p->parents && p->parents->next)
- return rewrite_one_ok;
if (p->object.flags & UNINTERESTING)
return rewrite_one_ok;
if (!(p->object.flags & TREESAME))
return rewrite_one_ok;
if (!p->parents)
return rewrite_one_noparents;
- *pp = p->parents->item;
+ if ((p = one_relevant_parent(revs, p->parents)) == NULL)
+ return rewrite_one_ok;
+ *pp = p;
}
}
-static int rewrite_parents(struct rev_info *revs, struct commit *commit)
+int rewrite_parents(struct rev_info *revs, struct commit *commit,
+ rewrite_parent_fn_t rewrite_parent)
{
struct commit_list **pp = &commit->parents;
while (*pp) {
struct commit_list *parent = *pp;
- switch (rewrite_one(revs, &parent->item)) {
+ switch (rewrite_parent(revs, &parent->item)) {
case rewrite_one_ok:
break;
case rewrite_one_noparents:
}
pp = &parent->next;
}
- remove_duplicate_parents(commit);
+ remove_duplicate_parents(revs, commit);
return 0;
}
if (revs->min_age != -1 && (commit->date > revs->min_age))
return commit_ignore;
if (revs->min_parents || (revs->max_parents >= 0)) {
- int n = 0;
- struct commit_list *p;
- for (p = commit->parents; p; p = p->next)
- n++;
+ int n = commit_list_count(commit->parents);
if ((n < revs->min_parents) ||
((revs->max_parents >= 0) && (n > revs->max_parents)))
return commit_ignore;
if (revs->prune && revs->dense) {
/* Commit without changes? */
if (commit->object.flags & TREESAME) {
+ int n;
+ struct commit_list *p;
/* drop merges unless we want parenthood */
if (!want_ancestry(revs))
return commit_ignore;
- /* non-merge - always ignore it */
- if (!commit->parents || !commit->parents->next)
- return commit_ignore;
+ /*
+ * If we want ancestry, then need to keep any merges
+ * between relevant commits to tie together topology.
+ * For consistency with TREESAME and simplification
+ * use "relevant" here rather than just INTERESTING,
+ * to treat bottom commit(s) as part of the topology.
+ */
+ for (n = 0, p = commit->parents; p; p = p->next)
+ if (relevant_commit(p->item))
+ if (++n >= 2)
+ return commit_show;
+ return commit_ignore;
}
}
return commit_show;
if (action == commit_show &&
!revs->show_all &&
revs->prune && revs->dense && want_ancestry(revs)) {
- if (rewrite_parents(revs, commit) < 0)
+ if (rewrite_parents(revs, commit, rewrite_one) < 0)
return commit_error;
}
return action;
return NULL;
}
-static void gc_boundary(struct object_array *array)
+/*
+ * Return true for entries that have not yet been shown. (This is an
+ * object_array_each_func_t.)
+ */
+static int entry_unshown(struct object_array_entry *entry, void *cb_data_unused)
{
- unsigned nr = array->nr;
- unsigned alloc = array->alloc;
- struct object_array_entry *objects = array->objects;
+ return !(entry->item->flags & SHOWN);
+}
- if (alloc <= nr) {
- unsigned i, j;
- for (i = j = 0; i < nr; i++) {
- if (objects[i].item->flags & SHOWN)
- continue;
- if (i != j)
- objects[j] = objects[i];
- j++;
- }
- for (i = j; i < nr; i++)
- objects[i].item = NULL;
- array->nr = j;
- }
+/*
+ * If array is on the verge of a realloc, garbage-collect any entries
+ * that have already been shown to try to free up some space.
+ */
+static void gc_boundary(struct object_array *array)
+{
+ if (array->nr == array->alloc)
+ object_array_filter(array, entry_unshown, NULL);
}
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)
#include "parse-options.h"
#include "grep.h"
#include "notes.h"
+#include "commit.h"
#define SEEN (1u<<0)
#define UNINTERESTING (1u<<1)
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
#define PATCHSAME (1u<<9)
-#define ALL_REV_FLAGS ((1u<<10)-1)
+#define BOTTOM (1u<<10)
+#define ALL_REV_FLAGS ((1u<<11)-1)
#define DECORATE_SHORT_REFS 1
#define DECORATE_FULL_REFS 2
REV_CMD_PARENTS_ONLY,
REV_CMD_LEFT,
REV_CMD_RIGHT,
+ REV_CMD_MERGE_BASE,
REV_CMD_REV
} whence;
unsigned flags;
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;
show_all:1,
remove_empty_trees:1,
simplify_history:1,
- lifo:1,
topo_order:1,
simplify_merges:1,
simplify_by_decoration:1,
cherry_mark:1,
bisect:1,
ancestry_path:1,
- first_parent_only:1;
+ first_parent_only:1,
+ line_level_traverse:1;
/* Diff flags */
unsigned int diff:1,
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;
struct reflog_walk_info *reflog_info;
struct decoration children;
struct decoration merge_simplification;
+ struct decoration treesame;
/* notes-specific options: which refs to show */
struct display_notes_opt notes_opt;
int count_left;
int count_right;
int count_same;
+
+ /* line level range that we are chasing */
+ struct decoration line_log_data;
};
#define REV_TREE_SAME 0
};
extern void init_revisions(struct rev_info *revs, const char *prefix);
-extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *);
+extern int setup_revisions(int argc, const char **argv, struct rev_info *revs,
+ struct setup_revision_opt *);
extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
- const struct option *options,
- const char * const usagestr[]);
+ const struct option *options,
+ const char * const usagestr[]);
#define REVARG_CANNOT_BE_FILENAME 01
#define REVARG_COMMITTISH 02
-extern int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt);
+extern int handle_revision_arg(const char *arg, struct rev_info *revs,
+ int flags, unsigned revarg_opt);
extern void reset_revision_walk(void);
extern int prepare_revision_walk(struct rev_info *revs);
extern struct commit *get_revision(struct rev_info *revs);
-extern char *get_revision_mark(const struct rev_info *revs, const struct commit *commit);
-extern void put_revision_mark(const struct rev_info *revs, const struct commit *commit);
+extern char *get_revision_mark(const struct rev_info *revs,
+ const struct commit *commit);
+extern void put_revision_mark(const struct rev_info *revs,
+ const struct commit *commit);
extern void mark_parents_uninteresting(struct commit *commit);
extern void mark_tree_uninteresting(struct tree *tree);
char *path_name(const struct name_path *path, const char *name);
-extern void show_object_with_name(FILE *, struct object *, const struct name_path *, const char *);
+extern void show_object_with_name(FILE *, struct object *,
+ const struct name_path *, const char *);
extern void add_object(struct object *obj,
struct object_array *p,
struct name_path *path,
const char *name);
-extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
-extern void add_pending_sha1(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags);
+extern void add_pending_object(struct rev_info *revs,
+ struct object *obj, const char *name);
+extern void add_pending_sha1(struct rev_info *revs,
+ const char *name, const unsigned char *sha1,
+ unsigned int flags);
extern void add_head_to_pending(struct rev_info *);
commit_error
};
-extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);
-extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);
+extern enum commit_action get_commit_action(struct rev_info *revs,
+ struct commit *commit);
+extern enum commit_action simplify_commit(struct rev_info *revs,
+ struct commit *commit);
+
+enum rewrite_result {
+ rewrite_one_ok,
+ rewrite_one_noparents,
+ rewrite_one_error
+};
+
+typedef enum rewrite_result (*rewrite_parent_fn_t)(struct rev_info *revs, struct commit **pp);
+extern int rewrite_parents(struct rev_info *revs, struct commit *commit,
+ rewrite_parent_fn_t rewrite_parent);
#endif
close(fd[1]);
}
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
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
die("BUG: shell command is empty");
if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) {
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
nargv[nargc++] = SHELL_PATH;
#else
nargv[nargc++] = "sh";
return nargv;
}
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
static int execv_shell_cmd(const char **argv)
{
const char **nargv = prepare_shell_cmd(argv);
}
#endif
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
static int child_err = 2;
static int child_notifier = -1;
trace_argv_printf(cmd->argv, "trace: run_command:");
fflush(NULL);
-#ifndef WIN32
+#ifndef GIT_WINDOWS_NATIVE
{
int notify_pipe[2];
if (pipe(notify_pipe))
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
}
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,
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);
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);
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) {
/*
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
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);
continue;
if (!get_sha1(name, sha1)) {
- enum object_type type = sha1_object_info(sha1, NULL);
-
- if (type > 0 && type != OBJ_COMMIT)
+ if (!lookup_commit_reference_gently(sha1, 1)) {
+ enum object_type type = sha1_object_info(sha1, NULL);
die(_("%s: can't cherry-pick a %s"), name, typename(type));
+ }
} else
die(_("%s: bad revision"), name);
}
void setup_work_tree(void)
{
+ struct strbuf sb = STRBUF_INIT;
const char *work_tree, *git_dir;
static int initialized = 0;
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)
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);
+}
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
}
}
+static int stat_sha1_file(const unsigned char *sha1, struct stat *st)
+{
+ char *name = sha1_file_name(sha1);
+ struct alternate_object_database *alt;
+
+ if (!lstat(name, st))
+ return 0;
+
+ prepare_alt_odb();
+ errno = ENOENT;
+ for (alt = alt_odb_list; alt; alt = alt->next) {
+ name = alt->name;
+ fill_sha1_path(name, sha1);
+ if (!lstat(alt->base, st))
+ return 0;
+ }
+
+ return -1;
+}
+
static int open_sha1_file(const unsigned char *sha1)
{
int fd;
return type;
}
-
#define POI_STACK_PREALLOC 64
-static int packed_object_info(struct packed_git *p, off_t obj_offset,
- unsigned long *sizep, int *rtype)
+static enum object_type packed_to_object_type(struct packed_git *p,
+ off_t obj_offset,
+ enum object_type type,
+ struct pack_window **w_curs,
+ off_t curpos)
{
- struct pack_window *w_curs = NULL;
- unsigned long size;
- off_t curpos = obj_offset;
- enum object_type type;
off_t small_poi_stack[POI_STACK_PREALLOC];
off_t *poi_stack = small_poi_stack;
int poi_stack_nr = 0, poi_stack_alloc = POI_STACK_PREALLOC;
- type = unpack_object_header(p, &w_curs, &curpos, &size);
-
- if (rtype)
- *rtype = type; /* representation type */
-
- if (sizep) {
- if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
- off_t tmp_pos = curpos;
- off_t base_offset = get_delta_base(p, &w_curs, &tmp_pos,
- type, obj_offset);
- if (!base_offset) {
- type = OBJ_BAD;
- goto out;
- }
- *sizep = get_size_from_delta(p, &w_curs, tmp_pos);
- if (*sizep == 0) {
- type = OBJ_BAD;
- goto out;
- }
- } else {
- *sizep = size;
- }
- }
-
while (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
off_t base_offset;
+ unsigned long size;
/* Push the object we're going to leave behind */
if (poi_stack_nr >= poi_stack_alloc && poi_stack == small_poi_stack) {
poi_stack_alloc = alloc_nr(poi_stack_nr);
}
poi_stack[poi_stack_nr++] = obj_offset;
/* If parsing the base offset fails, just unwind */
- base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
+ base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset);
if (!base_offset)
goto unwind;
curpos = obj_offset = base_offset;
- type = unpack_object_header(p, &w_curs, &curpos, &size);
+ type = unpack_object_header(p, w_curs, &curpos, &size);
if (type <= OBJ_NONE) {
/* If getting the base itself fails, we first
* retry the base, otherwise unwind */
out:
if (poi_stack != small_poi_stack)
free(poi_stack);
- unuse_pack(&w_curs);
return type;
unwind:
goto out;
}
+static int packed_object_info(struct packed_git *p, off_t obj_offset,
+ struct object_info *oi)
+{
+ struct pack_window *w_curs = NULL;
+ unsigned long size;
+ off_t curpos = obj_offset;
+ enum object_type type;
+
+ /*
+ * We always get the representation type, but only convert it to
+ * a "real" type later if the caller is interested.
+ */
+ type = unpack_object_header(p, &w_curs, &curpos, &size);
+
+ if (oi->sizep) {
+ if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
+ off_t tmp_pos = curpos;
+ off_t base_offset = get_delta_base(p, &w_curs, &tmp_pos,
+ type, obj_offset);
+ if (!base_offset) {
+ type = OBJ_BAD;
+ goto out;
+ }
+ *oi->sizep = get_size_from_delta(p, &w_curs, tmp_pos);
+ if (*oi->sizep == 0) {
+ type = OBJ_BAD;
+ goto out;
+ }
+ } else {
+ *oi->sizep = size;
+ }
+ }
+
+ if (oi->disk_sizep) {
+ struct revindex_entry *revidx = find_pack_revindex(p, obj_offset);
+ *oi->disk_sizep = revidx[1].offset - obj_offset;
+ }
+
+ if (oi->typep) {
+ *oi->typep = packed_to_object_type(p, obj_offset, type, &w_curs, curpos);
+ if (*oi->typep < 0) {
+ type = OBJ_BAD;
+ goto out;
+ }
+ }
+
+out:
+ unuse_pack(&w_curs);
+ return type;
+}
+
static void *unpack_compressed_entry(struct packed_git *p,
struct pack_window **w_curs,
off_t curpos,
{
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;
}
}
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 */
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);
+ free(delta_data);
}
*final_type = type;
}
-static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *sizep)
+static int sha1_loose_object_info(const unsigned char *sha1,
+ struct object_info *oi)
{
int status;
unsigned long mapsize, size;
git_zstream stream;
char hdr[32];
+ /*
+ * If we don't care about type or size, then we don't
+ * need to look inside the object at all.
+ */
+ if (!oi->typep && !oi->sizep) {
+ if (oi->disk_sizep) {
+ struct stat st;
+ if (stat_sha1_file(sha1, &st) < 0)
+ return -1;
+ *oi->disk_sizep = st.st_size;
+ }
+ return 0;
+ }
+
map = map_sha1_file(sha1, &mapsize);
if (!map)
- return error("unable to find %s", sha1_to_hex(sha1));
+ return -1;
+ if (oi->disk_sizep)
+ *oi->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));
else if ((status = parse_sha1_header(hdr, &size)) < 0)
status = error("unable to parse %s header", sha1_to_hex(sha1));
- else if (sizep)
- *sizep = size;
+ else if (oi->sizep)
+ *oi->sizep = size;
git_inflate_end(&stream);
munmap(map, mapsize);
- return status;
+ if (oi->typep)
+ *oi->typep = status;
+ return 0;
}
/* returns enum object_type or negative */
{
struct cached_object *co;
struct pack_entry e;
- int status, rtype;
+ int rtype;
co = find_cached_object(sha1);
if (co) {
+ if (oi->typep)
+ *(oi->typep) = co->type;
if (oi->sizep)
*(oi->sizep) = co->size;
+ if (oi->disk_sizep)
+ *(oi->disk_sizep) = 0;
oi->whence = OI_CACHED;
- return co->type;
+ return 0;
}
if (!find_pack_entry(sha1, &e)) {
/* Most likely it's a loose object. */
- status = sha1_loose_object_info(sha1, oi->sizep);
- if (status >= 0) {
+ if (!sha1_loose_object_info(sha1, oi)) {
oi->whence = OI_LOOSE;
- return status;
+ return 0;
}
/* Not a loose object; someone else may have just packed it. */
reprepare_packed_git();
if (!find_pack_entry(sha1, &e))
- return status;
+ return -1;
}
- status = packed_object_info(e.p, e.offset, oi->sizep, &rtype);
- if (status < 0) {
+ rtype = packed_object_info(e.p, e.offset, oi);
+ if (rtype < 0) {
mark_bad_packed_object(e.p, sha1);
- status = sha1_object_info_extended(sha1, oi);
+ return sha1_object_info_extended(sha1, oi);
} else if (in_delta_base_cache(e.p, e.offset)) {
oi->whence = OI_DBCACHED;
} else {
rtype == OBJ_OFS_DELTA);
}
- return status;
+ return 0;
}
int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
{
- struct object_info oi;
+ enum object_type type;
+ struct object_info oi = {NULL};
+ oi.typep = &type;
oi.sizep = sizep;
- return sha1_object_info_extended(sha1, &oi);
+ if (sha1_object_info_extended(sha1, &oi) < 0)
+ return -1;
+ return type;
}
static void *read_packed_sha1(const unsigned char *sha1,
}
if (!ds->candidate_ok) {
- /* discard the candidate; we know it does not satisify fn */
+ /* discard the candidate; we know it does not satisfy fn */
hashcpy(ds->candidate, current);
ds->candidate_checked = 0;
return;
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;
}
static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
+static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf);
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
{
static const char *warn_msg = "refname '%.*s' is ambiguous.";
+ static const char *object_name_msg = N_(
+ "Git normally never creates a ref that ends with 40 hex characters\n"
+ "because it will be ignored when you just specify 40-hex. These refs\n"
+ "may be created by mistake. For example,\n"
+ "\n"
+ " git checkout -b $br $(git rev-parse ...)\n"
+ "\n"
+ "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n"
+ "examine these refs and maybe delete them. Turn this message off by\n"
+ "running \"git config advice.object_name_warning false\"");
+ unsigned char tmp_sha1[20];
char *real_ref = NULL;
int refs_found = 0;
- int at, reflog_len;
-
- if (len == 40 && !get_sha1_hex(str, sha1))
+ int at, reflog_len, nth_prior = 0;
+
+ if (len == 40 && !get_sha1_hex(str, sha1)) {
+ if (warn_on_object_refname_ambiguity) {
+ refs_found = dwim_ref(str, len, tmp_sha1, &real_ref);
+ if (refs_found > 0 && warn_ambiguous_refs) {
+ warning(warn_msg, len, str);
+ if (advice_object_name_warning)
+ fprintf(stderr, "%s\n", _(object_name_msg));
+ }
+ free(real_ref);
+ }
return 0;
+ }
/* basic@{time or number or -number} format to query ref-log */
reflog_len = at = 0;
if (len && str[len-1] == '}') {
- for (at = len-2; at >= 0; at--) {
+ for (at = len-4; at >= 0; at--) {
if (str[at] == '@' && str[at+1] == '{') {
+ if (str[at+2] == '-') {
+ if (at != 0)
+ /* @{-N} not at start */
+ return -1;
+ nth_prior = 1;
+ continue;
+ }
if (!upstream_mark(str + at, len - at)) {
reflog_len = (len-1) - (at+2);
len = at;
if (len && ambiguous_path(str, len))
return -1;
- if (!len && reflog_len) {
+ if (nth_prior) {
struct strbuf buf = STRBUF_INIT;
- int ret;
- /* try the @{-N} syntax for n-th checkout */
- ret = interpret_branch_name(str+at, &buf);
- if (ret > 0) {
- /* substitute this branch name and restart */
- return get_sha1_1(buf.buf, buf.len, sha1, 0);
- } else if (ret == 0) {
- return -1;
+ int detached;
+
+ if (interpret_nth_prior_checkout(str, &buf) > 0) {
+ detached = (buf.len == 40 && !get_sha1_hex(buf.buf, sha1));
+ strbuf_release(&buf);
+ if (detached)
+ return 0;
}
+ }
+
+ if (!len && reflog_len)
/* allow "@{...}" to mean the current branch reflog */
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
- } else if (reflog_len)
+ else if (reflog_len)
refs_found = dwim_log(str, len, sha1, &real_ref);
else
refs_found = dwim_ref(str, len, sha1, &real_ref);
if (!refs_found)
return -1;
- if (warn_ambiguous_refs && refs_found > 1)
+ if (warn_ambiguous_refs &&
+ (refs_found > 1 ||
+ !get_short_sha1(str, len, tmp_sha1, GET_SHA1_QUIETLY)))
warning(warn_msg, len, str);
if (reflog_len) {
unsigned long co_time;
int co_tz, co_cnt;
- /* a @{-N} placed anywhere except the start is an error */
- if (str[at+2] == '-')
- return -1;
-
/* Is it asking for N-th entry, or approxidate? */
for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
char ch = str[at+2+i];
}
if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
&co_time, &co_tz, &co_cnt)) {
+ if (!len) {
+ if (!prefixcmp(real_ref, "refs/heads/")) {
+ str = real_ref + 11;
+ len = strlen(real_ref + 11);
+ } else {
+ /* detached HEAD */
+ str = "HEAD";
+ len = 4;
+ }
+ }
if (at_time)
warning("Log for '%.*s' only goes "
"back to %s.", len, str,
show_date(co_time, co_tz, DATE_RFC2822));
else {
- free(real_ref);
die("Log for '%.*s' only has %d entries.",
len, str, co_cnt);
}
return st;
}
+/* parse @something syntax, when 'something' is not {.*} */
+static int interpret_empty_at(const char *name, int namelen, int len, struct strbuf *buf)
+{
+ if (len || name[1] == '{')
+ return -1;
+
+ strbuf_reset(buf);
+ strbuf_add(buf, "HEAD", 4);
+ return 1;
+}
+
+static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf)
+{
+ /* we have extra data, which might need further processing */
+ struct strbuf tmp = STRBUF_INIT;
+ int used = buf->len;
+ int ret;
+
+ strbuf_add(buf, name + len, namelen - len);
+ ret = interpret_branch_name(buf->buf, &tmp);
+ /* that data was not interpreted, remove our cruft */
+ if (ret < 0) {
+ strbuf_setlen(buf, used);
+ return len;
+ }
+ strbuf_reset(buf);
+ strbuf_addbuf(buf, &tmp);
+ strbuf_release(&tmp);
+ /* tweak for size of {-N} versus expanded ref name */
+ return ret - used + len;
+}
+
/*
* This reads short-hand syntax that not only evaluates to a commit
* object name, but also can act as if the end user spelled the name
int len = interpret_nth_prior_checkout(name, buf);
int tmp_len;
- if (!len)
+ if (!len) {
return len; /* syntax Ok, not enough switches */
- if (0 < len && len == namelen)
- return len; /* consumed all */
- else if (0 < len) {
- /* we have extra data, which might need further processing */
- struct strbuf tmp = STRBUF_INIT;
- int used = buf->len;
- int ret;
-
- strbuf_add(buf, name + len, namelen - len);
- ret = interpret_branch_name(buf->buf, &tmp);
- /* that data was not interpreted, remove our cruft */
- if (ret < 0) {
- strbuf_setlen(buf, used);
- return len;
- }
- strbuf_reset(buf);
- strbuf_addbuf(buf, &tmp);
- strbuf_release(&tmp);
- /* tweak for size of {-N} versus expanded ref name */
- return ret - used + len;
+ } else if (len > 0) {
+ if (len == namelen)
+ return len; /* consumed all */
+ else
+ return reinterpret(name, namelen, len, buf);
}
cp = strchr(name, '@');
if (!cp)
return -1;
+
+ len = interpret_empty_at(name, namelen, cp - name, buf);
+ if (len > 0)
+ return reinterpret(name, namelen, len, buf);
+
tmp_len = upstream_mark(cp, namelen - (cp - name));
if (!tmp_len)
return -1;
+
len = cp + tmp_len - name;
cp = xstrndup(name, cp - name);
upstream = branch_get(*cp ? cp : NULL);
* points to something different than a branch.
*/
if (!upstream)
- return error(_("HEAD does not point to a branch"));
+ die(_("HEAD does not point to a branch"));
if (!upstream->merge || !upstream->merge[0]->dst) {
if (!ref_exists(upstream->refname))
- return error(_("No such branch: '%s'"), cp);
- if (!upstream->merge)
- return error(_("No upstream configured for branch '%s'"),
- upstream->name);
- return error(
+ die(_("No such branch: '%s'"), cp);
+ if (!upstream->merge) {
+ die(_("No upstream configured for branch '%s'"),
+ upstream->name);
+ }
+ die(
_("Upstream branch '%s' not stored as a remote-tracking branch"),
upstream->merge[0]->src);
}
int strbuf_branchname(struct strbuf *sb, const char *name)
{
int len = strlen(name);
- if (interpret_branch_name(name, sb) == len)
+ int used = interpret_branch_name(name, sb);
+
+ if (used == len)
return 0;
- strbuf_add(sb, name, len);
+ if (used < 0)
+ used = 0;
+ strbuf_add(sb, name + used, len - used);
return len;
}
const char *filename)
{
struct stat st;
- struct cache_entry *ce;
+ const struct cache_entry *ce;
int pos;
unsigned namelen = strlen(filename);
unsigned fullnamelen;
*/
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] == '/') {
#include "tag.h"
static int is_shallow = -1;
+static struct stat shallow_stat;
+static char *alternate_shallow_file;
+
+void set_alternate_shallow_file(const char *path)
+{
+ if (is_shallow != -1)
+ die("BUG: is_repository_shallow must not be called before set_alternate_shallow_file");
+ free(alternate_shallow_file);
+ alternate_shallow_file = path ? xstrdup(path) : NULL;
+}
int register_shallow(const unsigned char *sha1)
{
{
FILE *fp;
char buf[1024];
+ const char *path = alternate_shallow_file;
if (is_shallow >= 0)
return is_shallow;
- fp = fopen(git_path("shallow"), "r");
- if (!fp) {
+ if (!path)
+ path = git_path("shallow");
+ /*
+ * fetch-pack sets '--shallow-file ""' as an indicator that no
+ * shallow file should be used. We could just open it and it
+ * will likely fail. But let's do an explicit check instead.
+ */
+ if (!*path ||
+ stat(path, &shallow_stat) ||
+ (fp = fopen(path, "r")) == NULL) {
is_shallow = 0;
return is_shallow;
}
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;
}
}
}
return result;
}
+
+void check_shallow_file_for_update(void)
+{
+ struct stat st;
+
+ if (!is_shallow)
+ return;
+ else if (is_shallow == -1)
+ die("BUG: shallow must be initialized by now");
+
+ if (stat(git_path("shallow"), &st))
+ die("shallow file was removed during fetch");
+ else if (st.st_mtime != shallow_stat.st_mtime
+#ifdef USE_NSEC
+ || ST_MTIME_NSEC(st) != ST_MTIME_NSEC(shallow_stat)
+#endif
+ )
+ die("shallow file was changed during fetch");
+}
char *prog;
const char **user_argv;
struct commands *cmd;
- int devnull_fd;
int count;
git_setup_gettext();
/*
* 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
unsigned long size;
int status;
+ oi->typep = type;
oi->sizep = &size;
status = sha1_object_info_extended(sha1, oi);
if (status < 0)
return stream_error;
- *type = status;
switch (oi->whence) {
case OI_LOOSE:
struct stream_filter *filter)
{
struct git_istream *st;
- struct object_info oi;
+ struct object_info oi = {NULL};
const unsigned char *real = lookup_replace_object(sha1);
enum input_source src = istream_source(real, type, &oi);
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)
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) {
if (!work_tree)
goto out;
- if (!the_index.initialized)
- if (read_cache() < 0)
- die("index file corrupt");
+ if (read_cache() < 0)
+ die("index file corrupt");
argv_array_push(&argv, "fetch");
for (i = 0; i < options->argc; i++)
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))
struct commit *a, struct commit *b)
{
int i, j;
- struct object_array merges;
+ struct object_array merges = OBJECT_ARRAY_INIT;
struct commit *commit;
int contains_another;
struct rev_info revs;
struct setup_revision_opt rev_opts;
- memset(&merges, 0, sizeof(merges));
memset(result, 0, sizeof(struct object_array));
memset(&rev_opts, 0, sizeof(rev_opts));
}
if (!contains_another)
- add_object_array(merges.objects[i].item,
- merges.objects[i].name, result);
+ add_object_array(merges.objects[i].item, NULL, result);
}
free(merges.objects);
DEFAULT_TEST_TARGET ?= test
TEST_LINT ?= test-lint-duplicates test-lint-executable
+ifdef TEST_OUTPUT_DIRECTORY
+TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results
+else
+TEST_RESULTS_DIRECTORY = test-results
+endif
+
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY))
T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh))
TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh))
@echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
pre-clean:
- $(RM) -r test-results
+ $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'
clean-except-prove-cache:
- $(RM) -r 'trash directory'.* test-results
+ $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)'
$(RM) -r valgrind/bin
clean: clean-except-prove-cache
$(MAKE) clean
aggregate-results:
- for f in test-results/t*-*.counts; do \
+ for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \
echo "$$f"; \
done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh
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.
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'.
use 'test_must_fail git cmd'. This will signal a failure if git
dies in an unexpected way (e.g. segfault).
+ On the other hand, don't use test_must_fail for running regular
+ platform commands; just use '! cmd'.
+
- use perl without spelling it as "$PERL_PATH". This is to help our
friends on Windows where the platform Perl often adds CR before
the end of line, and they bundle Git with a version of Perl that
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
-------------
# 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>) {
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
+'
/^\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;
}
ServerName dummy
-LockFile accept.lock
PidFile httpd.pid
DocumentRoot www
LogFormat "%h %l %u %t \"%r\" %>s %b" common
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
</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
# ("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"
}
: 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
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;
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\""
}
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
--- /dev/null
+#!/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
--- /dev/null
+#!/bin/sh
+
+test_description='Tests log -L performance'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+# Pick a file to log pseudo-randomly. The sort key is the blob hash,
+# so it is stable.
+test_expect_success 'select a file' '
+ git ls-tree HEAD | grep ^100644 |
+ sort -k 3 | head -1 | cut -f 2 >filelist
+'
+
+file=$(cat filelist)
+export file
+
+test_perf 'git rev-list --topo-order (baseline)' '
+ git rev-list --topo-order HEAD >/dev/null
+'
+
+test_perf 'git log --follow (baseline for -M)' '
+ git log --oneline --follow -- "$file" >/dev/null
+'
+
+test_perf 'git log -L' '
+ git log -L 1:"$file" >/dev/null
+'
+
+test_perf 'git log -M -L' '
+ git log -M -L 1:"$file" >/dev/null
+'
+
+test_done
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"
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
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
cat >>"$name.sh" &&
chmod +x "$name.sh" &&
export TEST_DIRECTORY &&
- ./"$name.sh" >out 2>err
+ ./"$name.sh" "$@" >out 2>err
)
}
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' '
# 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" &&
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
'
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
'
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
'
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
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
'
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' '
'
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' '
'
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
'
################################################################
-P=$expectedtree
+P=087704a96baf1c2d1c869a8b084481e121c88b5b
test_expect_success 'git commit-tree records the correct tree in a commit' '
commit0=$(echo NO | git commit-tree $P) &&
test_cmp expect actual
'
+test_expect_success !MINGW 'signals are propagated using shell convention' '
+ # we use exec here to avoid any sub-shell interpretation
+ # of the exit code
+ git config alias.sigterm "!exec test-sigchain" &&
+ test_expect_code 143 git sigterm
+'
+
test_done
init_vars &&
rm -f "$HOME/stdout" "$HOME/stderr" "$HOME/cmd" &&
- echo git $global_args check-ignore $quiet_opt $verbose_opt $args \
+ echo git $global_args check-ignore $quiet_opt $verbose_opt $non_matching_opt $args \
>"$HOME/cmd" &&
+ echo "$expect_code" >"$HOME/expected-exit-code" &&
test_expect_code "$expect_code" \
- git $global_args check-ignore $quiet_opt $verbose_opt $args \
+ git $global_args check-ignore $quiet_opt $verbose_opt $non_matching_opt $args \
>"$HOME/stdout" 2>"$HOME/stderr" &&
test_cmp "$HOME/expected-stdout" "$HOME/stdout" &&
stderr_empty_on_success "$expect_code"
}
-# Runs the same code with 3 different levels of output verbosity,
+# Runs the same code with 4 different levels of output verbosity:
+#
+# 1. with -q / --quiet
+# 2. with default verbosity
+# 3. with -v / --verbose
+# 4. with -v / --verbose, *and* -n / --non-matching
+#
# expecting success each time. Takes advantage of the fact that
# check-ignore --verbose output is the same as normal output except
# for the extra first column.
# Arguments:
# - (optional) prereqs for this test, e.g. 'SYMLINKS'
# - test name
-# - output to expect from -v / --verbose mode
+# - output to expect from the fourth verbosity mode (the output
+# from the other verbosity modes is automatically inferred
+# from this value)
# - code to run (should invoke test_check_ignore)
test_expect_success_multi () {
prereq=
prereq=$1
shift
fi
- testname="$1" expect_verbose="$2" code="$3"
+ testname="$1" expect_all="$2" code="$3"
+ expect_verbose=$( echo "$expect_all" | grep -v '^:: ' )
expect=$( echo "$expect_verbose" | sed -e 's/.* //' )
test_expect_success $prereq "$testname" '
eval "$code"
'
- for quiet_opt in '-q' '--quiet'
- do
- test_expect_success $prereq "$testname${quiet_opt:+ with $quiet_opt}" "
+ # --quiet is only valid when a single pattern is passed
+ if test $( echo "$expect_all" | wc -l ) = 1
+ then
+ for quiet_opt in '-q' '--quiet'
+ do
+ test_expect_success $prereq "$testname${quiet_opt:+ with $quiet_opt}" "
expect '' &&
$code
"
- done
- quiet_opt=
+ done
+ quiet_opt=
+ fi
for verbose_opt in '-v' '--verbose'
do
- test_expect_success $prereq "$testname${verbose_opt:+ with $verbose_opt}" "
- expect '$expect_verbose' &&
- $code
- "
+ for non_matching_opt in '' ' -n' ' --non-matching'
+ do
+ if test -n "$non_matching_opt"
+ then
+ my_expect="$expect_all"
+ else
+ my_expect="$expect_verbose"
+ fi
+
+ test_code="
+ expect '$my_expect' &&
+ $code
+ "
+ opts="$verbose_opt$non_matching_opt"
+ test_expect_success $prereq "$testname${opts:+ with $opts}" "$test_code"
+ done
done
verbose_opt=
+ non_matching_opt=
}
test_expect_success 'setup' '
#
# test invalid inputs
-test_expect_success_multi '. corner-case' '' '
+test_expect_success_multi '. corner-case' ':: .' '
test_check_ignore . 1
'
test_expect_success_multi '--stdin with empty STDIN' '' '
test_check_ignore "--stdin" 1 </dev/null &&
- if test -n "$quiet_opt"; then
- test_stderr ""
- else
- test_stderr "no pathspec given."
- fi
+ test_stderr ""
'
test_expect_success '-q with multiple args' '
where="in subdir $subdir"
fi
- test_expect_success_multi "non-existent file $where not ignored" '' "
- test_check_ignore '${subdir}non-existent' 1
- "
+ test_expect_success_multi "non-existent file $where not ignored" \
+ ":: ${subdir}non-existent" \
+ "test_check_ignore '${subdir}non-existent' 1"
test_expect_success_multi "non-existent file $where ignored" \
- ".gitignore:1:one ${subdir}one" "
- test_check_ignore '${subdir}one'
- "
+ ".gitignore:1:one ${subdir}one" \
+ "test_check_ignore '${subdir}one'"
- test_expect_success_multi "existing untracked file $where not ignored" '' "
- test_check_ignore '${subdir}not-ignored' 1
- "
+ test_expect_success_multi "existing untracked file $where not ignored" \
+ ":: ${subdir}not-ignored" \
+ "test_check_ignore '${subdir}not-ignored' 1"
- test_expect_success_multi "existing tracked file $where not ignored" '' "
- test_check_ignore '${subdir}ignored-but-in-index' 1
- "
+ test_expect_success_multi "existing tracked file $where not ignored" \
+ ":: ${subdir}ignored-but-in-index" \
+ "test_check_ignore '${subdir}ignored-but-in-index' 1"
test_expect_success_multi "existing untracked file $where ignored" \
- ".gitignore:2:ignored-* ${subdir}ignored-and-untracked" "
- test_check_ignore '${subdir}ignored-and-untracked'
- "
+ ".gitignore:2:ignored-* ${subdir}ignored-and-untracked" \
+ "test_check_ignore '${subdir}ignored-and-untracked'"
+
+ test_expect_success_multi "mix of file types $where" \
+":: ${subdir}non-existent
+.gitignore:1:one ${subdir}one
+:: ${subdir}not-ignored
+:: ${subdir}ignored-but-in-index
+.gitignore:2:ignored-* ${subdir}ignored-and-untracked" \
+ "test_check_ignore '
+ ${subdir}non-existent
+ ${subdir}one
+ ${subdir}not-ignored
+ ${subdir}ignored-but-in-index
+ ${subdir}ignored-and-untracked'
+ "
done
# Having established the above, from now on we mostly test against
#
# test handling of symlinks
-test_expect_success_multi SYMLINKS 'symlink' '' '
+test_expect_success_multi SYMLINKS 'symlink' ':: a/symlink' '
test_check_ignore "a/symlink" 1
'
globaltwo
b/globaltwo
../b/globaltwo
+ c/not-ignored
EOF
-cat <<-\EOF >expected-default
- ../one
- one
- b/on
- b/one
- b/one one
- b/one two
- "b/one\"three"
- b/two
- b/twooo
- ../globaltwo
- globaltwo
- b/globaltwo
- ../b/globaltwo
-EOF
-cat <<-EOF >expected-verbose
+# N.B. we deliberately end STDIN with a non-matching pattern in order
+# to test that the exit code indicates that one or more of the
+# provided paths is ignored - in other words, that it represents an
+# aggregation of all the results, not just the final result.
+
+cat <<-EOF >expected-all
.gitignore:1:one ../one
+ :: ../not-ignored
.gitignore:1:one one
+ :: not-ignored
a/b/.gitignore:8:!on* b/on
a/b/.gitignore:8:!on* b/one
a/b/.gitignore:8:!on* b/one one
a/b/.gitignore:8:!on* b/one two
a/b/.gitignore:8:!on* "b/one\"three"
a/b/.gitignore:9:!two b/two
+ :: b/not-ignored
a/.gitignore:1:two* b/twooo
$global_excludes:2:!globaltwo ../globaltwo
$global_excludes:2:!globaltwo globaltwo
$global_excludes:2:!globaltwo b/globaltwo
$global_excludes:2:!globaltwo ../b/globaltwo
+ :: c/not-ignored
EOF
+grep -v '^:: ' expected-all >expected-verbose
+sed -e 's/.* //' expected-verbose >expected-default
sed -e 's/^"//' -e 's/\\//' -e 's/"$//' stdin | \
tr "\n" "\0" >stdin0
)
'
+test_expect_success '--stdin from subdirectory with -v -n' '
+ expect_from_stdin <expected-all &&
+ (
+ cd a &&
+ test_check_ignore "--stdin -v -n" <../stdin
+ )
+'
+
for opts in '--stdin -z' '-z --stdin'
do
test_expect_success "$opts from subdirectory" '
'
done
+test_expect_success PIPE 'streaming support for --stdin' '
+ mkfifo in out &&
+ (git check-ignore -n -v --stdin <in >out &) &&
+
+ # We cannot just "echo >in" because check-ignore would get EOF
+ # after echo exited; instead we open the descriptor in our
+ # 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 <&8 &&
+ echo "$response" | grep "^\.gitignore:1:one one" &&
+ echo >&9 two &&
+ read response <&8 &&
+ echo "$response" | grep "^:: two"
+'
test_done
--- /dev/null
+#!/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
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 &&
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
'
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
}
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
}
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
}
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
}
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
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
. ./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.
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*)
;;
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
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
grep "doesnotexist/test" err
'
-test_expect_success POSIXPERM 'mktemp to unwritable directory prints filename' '
+test_expect_success POSIXPERM,SANITY 'mktemp to unwritable directory prints filename' '
mkdir cannotwrite &&
chmod -w cannotwrite &&
test_when_finished "chmod +w cannotwrite" &&
test_commit B &&
git checkout A &&
test_commit C &&
+ test_commit D &&
git branch -f master B &&
git branch -f other &&
git checkout other &&
git cat-file commit HEAD | grep "Merge branch '\''other'\''"
'
-test_expect_success 'merge @{-1} when there is not enough switches yet' '
+test_expect_success 'merge @{-1}~1' '
+ git checkout master &&
+ git reset --hard B &&
+ git checkout other &&
+ git checkout master &&
+ git merge @{-1}~1 &&
+ git cat-file commit HEAD >actual &&
+ grep "Merge branch '\''other'\''" actual
+'
+
+test_expect_success 'merge @{-100} before checking out that many branches yet' '
git reflog expire --expire=now &&
git checkout -f master &&
git reset --hard B &&
git branch -f other C &&
git checkout other &&
git checkout master &&
- test_must_fail git merge @{-12}
+ test_must_fail git merge @{-100}
'
test_done
'
-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 &&
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 &&
'
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
'
}
--- /dev/null
+#!/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
--- /dev/null
+#!/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
test_expect_success 'branch@{u} error message when no upstream' '
cat >expect <<-EOF &&
- error: No upstream configured for branch ${sq}non-tracking${sq}
- fatal: Needed a single revision
+ fatal: No upstream configured for branch ${sq}non-tracking${sq}
EOF
error_message non-tracking@{u} 2>actual &&
test_i18ncmp expect actual
test_expect_success '@{u} error message when no upstream' '
cat >expect <<-EOF &&
- error: No upstream configured for branch ${sq}master${sq}
- fatal: Needed a single revision
+ fatal: No upstream configured for branch ${sq}master${sq}
EOF
test_must_fail git rev-parse --verify @{u} 2>actual &&
test_i18ncmp expect actual
test_expect_success 'branch@{u} error message with misspelt branch' '
cat >expect <<-EOF &&
- error: No such branch: ${sq}no-such-branch${sq}
- fatal: Needed a single revision
+ fatal: No such branch: ${sq}no-such-branch${sq}
EOF
error_message no-such-branch@{u} 2>actual &&
test_i18ncmp expect actual
test_expect_success '@{u} error message when not on a branch' '
cat >expect <<-EOF &&
- error: HEAD does not point to a branch
- fatal: Needed a single revision
+ fatal: HEAD does not point to a branch
EOF
git checkout HEAD^0 &&
test_must_fail git rev-parse --verify @{u} 2>actual &&
test_expect_success 'branch@{u} error message if upstream branch not fetched' '
cat >expect <<-EOF &&
- error: Upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
- fatal: Needed a single revision
+ fatal: Upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
EOF
error_message bad-upstream@{u} 2>actual &&
test_i18ncmp expect actual
. ./test-lib.sh
check() {
-test_expect_${3:-success} "$1 = $2" "
- echo '$2' >expect &&
- git log -1 --format=%s '$1' >actual &&
- test_cmp expect actual
-"
+ test_expect_${4:-success} "$1 = $3" "
+ echo '$3' >expect &&
+ if test '$2' = 'commit'
+ then
+ git log -1 --format=%s '$1' >actual
+ else
+ git rev-parse --symbolic-full-name '$1' >actual
+ fi &&
+ test_cmp expect actual
+ "
}
+
nonsense() {
-test_expect_${2:-success} "$1 is nonsensical" "
- test_must_fail git log -1 '$1'
-"
+ test_expect_${2:-success} "$1 is nonsensical" "
+ test_must_fail git rev-parse --verify '$1'
+ "
}
+
fail() {
"$@" failure
}
git checkout -b new-branch &&
test_commit new-one &&
test_commit new-two &&
- git config branch.old-branch.remote . &&
- git config branch.old-branch.merge refs/heads/master &&
- git config branch.new-branch.remote . &&
- git config branch.new-branch.merge refs/heads/upstream-branch
+ git branch -u master old-branch &&
+ git branch -u upstream-branch new-branch
'
-check HEAD new-two
-check "@{1}" new-one
-check "@{-1}" old-two
-check "@{-1}@{1}" old-one
-check "@{u}" upstream-two
-check "@{u}@{1}" upstream-one
-check "@{-1}@{u}" master-two
-check "@{-1}@{u}@{1}" master-one
+check HEAD ref refs/heads/new-branch
+check "@{1}" commit new-one
+check "HEAD@{1}" commit new-one
+check "@{now}" commit new-two
+check "HEAD@{now}" commit new-two
+check "@{-1}" ref refs/heads/old-branch
+check "@{-1}@{0}" commit old-two
+check "@{-1}@{1}" commit old-one
+check "@{u}" ref refs/heads/upstream-branch
+check "HEAD@{u}" ref refs/heads/upstream-branch
+check "@{u}@{1}" commit upstream-one
+check "@{-1}@{u}" ref refs/heads/master
+check "@{-1}@{u}@{1}" commit master-one
+check "@" commit new-two
+check "@@{u}" ref refs/heads/upstream-branch
nonsense "@{u}@{-1}"
+nonsense "@{0}@{0}"
nonsense "@{1}@{u}"
+nonsense "HEAD@{-1}"
+nonsense "@{-1}@{-1}"
+
+# @{N} versus HEAD@{N}
+
+check "HEAD@{3}" commit old-two
+nonsense "@{3}"
+
+test_expect_success 'switch to old-branch' '
+ git checkout old-branch
+'
+
+check HEAD ref refs/heads/old-branch
+check "HEAD@{1}" commit new-two
+check "@{1}" commit old-one
test_done
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)
'
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.. &&
'
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
'
'
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' '
# 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 &&
test "$(sed -e "s/^\(.........\).*/\1/" actual | sort -u)" = 000000000
'
+test_expect_success 'ambiguous 40-hex ref' '
+ TREE=$(git mktree </dev/null) &&
+ REF=`git rev-parse HEAD` &&
+ VAL=$(git commit-tree $TREE </dev/null) &&
+ git update-ref refs/heads/$REF $VAL &&
+ test `git rev-parse $REF 2>err` = $REF &&
+ grep "refname.*${REF}.*ambiguous" err
+'
+
+test_expect_success 'ambiguous short sha1 ref' '
+ TREE=$(git mktree </dev/null) &&
+ REF=`git rev-parse --short HEAD` &&
+ VAL=$(git commit-tree $TREE </dev/null) &&
+ git update-ref refs/heads/$REF $VAL &&
+ test `git rev-parse $REF 2>err` = $VAL &&
+ grep "refname.*${REF}.*ambiguous" err
+'
+
test_done
--- /dev/null
+#!/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
'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)'
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 &&
. ./test-lib.sh
-test_expect_success SYMLINKS setup '
+test_expect_success setup '
mkdir frotz &&
echo hello >frotz/filfre &&
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
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
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
'
--- /dev/null
+#!/bin/sh
+
+test_description='checkout <branch>
+
+Ensures that checkout on an unborn branch does what the user expects'
+
+. ./test-lib.sh
+
+# Is the current branch "refs/heads/$1"?
+test_branch () {
+ printf "%s\n" "refs/heads/$1" >expect.HEAD &&
+ git symbolic-ref HEAD >actual.HEAD &&
+ test_cmp expect.HEAD actual.HEAD
+}
+
+# Is branch "refs/heads/$1" set to pull from "$2/$3"?
+test_branch_upstream () {
+ printf "%s\n" "$2" "refs/heads/$3" >expect.upstream &&
+ {
+ git config "branch.$1.remote" &&
+ git config "branch.$1.merge"
+ } >actual.upstream &&
+ test_cmp expect.upstream actual.upstream
+}
+
+test_expect_success 'setup' '
+ test_commit my_master &&
+ git init repo_a &&
+ (
+ cd repo_a &&
+ test_commit a_master &&
+ git checkout -b foo &&
+ test_commit a_foo &&
+ git checkout -b bar &&
+ test_commit a_bar
+ ) &&
+ git init repo_b &&
+ (
+ cd repo_b &&
+ test_commit b_master &&
+ git checkout -b foo &&
+ test_commit b_foo &&
+ git checkout -b baz &&
+ test_commit b_baz
+ ) &&
+ git remote add repo_a repo_a &&
+ git remote add repo_b repo_b &&
+ git config remote.repo_b.fetch \
+ "+refs/heads/*:refs/remotes/other_b/*" &&
+ git fetch --all
+'
+
+test_expect_success 'checkout of non-existing branch fails' '
+ git checkout -B master &&
+ test_might_fail git branch -D xyzzy &&
+
+ test_must_fail git checkout xyzzy &&
+ test_must_fail git rev-parse --verify refs/heads/xyzzy &&
+ test_branch master
+'
+
+test_expect_success 'checkout of branch from multiple remotes fails #1' '
+ git checkout -B master &&
+ test_might_fail git branch -D foo &&
+
+ test_must_fail git checkout foo &&
+ test_must_fail git rev-parse --verify refs/heads/foo &&
+ test_branch master
+'
+
+test_expect_success 'checkout of branch from a single remote succeeds #1' '
+ git checkout -B master &&
+ test_might_fail git branch -D bar &&
+
+ git checkout bar &&
+ test_branch bar &&
+ test_cmp_rev remotes/repo_a/bar HEAD &&
+ test_branch_upstream bar repo_a bar
+'
+
+test_expect_success 'checkout of branch from a single remote succeeds #2' '
+ git checkout -B master &&
+ test_might_fail git branch -D baz &&
+
+ git checkout baz &&
+ test_branch baz &&
+ test_cmp_rev remotes/other_b/baz HEAD &&
+ test_branch_upstream baz repo_b baz
+'
+
+test_expect_success '--no-guess suppresses branch auto-vivification' '
+ git checkout -B master &&
+ test_might_fail git branch -D bar &&
+
+ test_must_fail git checkout --no-guess bar &&
+ test_must_fail git rev-parse --verify refs/heads/bar &&
+ test_branch master
+'
+
+test_expect_success 'setup more remotes with unconventional refspecs' '
+ git checkout -B master &&
+ git init repo_c &&
+ (
+ cd repo_c &&
+ test_commit c_master &&
+ git checkout -b bar &&
+ test_commit c_bar
+ git checkout -b spam &&
+ test_commit c_spam
+ ) &&
+ git init repo_d &&
+ (
+ cd repo_d &&
+ test_commit d_master &&
+ git checkout -b baz &&
+ test_commit f_baz
+ git checkout -b eggs &&
+ test_commit c_eggs
+ ) &&
+ git remote add repo_c repo_c &&
+ git config remote.repo_c.fetch \
+ "+refs/heads/*:refs/remotes/extra_dir/repo_c/extra_dir/*" &&
+ git remote add repo_d repo_d &&
+ git config remote.repo_d.fetch \
+ "+refs/heads/*:refs/repo_d/*" &&
+ git fetch --all
+'
+
+test_expect_success 'checkout of branch from multiple remotes fails #2' '
+ git checkout -B master &&
+ test_might_fail git branch -D bar &&
+
+ test_must_fail git checkout bar &&
+ test_must_fail git rev-parse --verify refs/heads/bar &&
+ test_branch master
+'
+
+test_expect_success 'checkout of branch from multiple remotes fails #3' '
+ git checkout -B master &&
+ test_might_fail git branch -D baz &&
+
+ test_must_fail git checkout baz &&
+ test_must_fail git rev-parse --verify refs/heads/baz &&
+ test_branch master
+'
+
+test_expect_success 'checkout of branch from a single remote succeeds #3' '
+ git checkout -B master &&
+ test_might_fail git branch -D spam &&
+
+ git checkout spam &&
+ test_branch spam &&
+ test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
+ test_branch_upstream spam repo_c spam
+'
+
+test_expect_success 'checkout of branch from a single remote succeeds #4' '
+ git checkout -B master &&
+ test_might_fail git branch -D eggs &&
+
+ git checkout eggs &&
+ test_branch eggs &&
+ test_cmp_rev refs/repo_d/eggs HEAD &&
+ test_branch_upstream eggs repo_d eggs
+'
+
+test_done
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
'
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
grep "^a.1" output
'
+test_expect_success 'excluded directory overrides content patterns' '
+
+ git ls-files --others --exclude="one" --exclude="!one/a.1" >output &&
+ if grep "^one/a.1" output
+ then
+ false
+ fi
+'
+
+test_expect_success 'negated directory doesn'\''t affect content patterns' '
+
+ git ls-files --others --exclude="!one" --exclude="one/a.1" >output &&
+ if grep "^one/a.1" output
+ then
+ false
+ fi
+'
+
test_expect_success 'subdirectory ignore (setup)' '
mkdir -p top/l1/l2 &&
(
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:
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:
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
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 &&
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' '
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
. ./test-lib.sh
test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b
-test_have_prereq MINGW && export GREP_OPTIONS=-U
+test_have_prereq GREP_STRIPS_CR && export GREP_OPTIONS=-U
test_expect_success 'setup' '
conflict_hunks () {
match() {
if [ $1 = 1 ]; then
- test_expect_success "wildmatch: match '$3' '$4'" "
+ test_expect_success "wildmatch: match '$3' '$4'" "
test-wildmatch wildmatch '$3' '$4'
"
else
- test_expect_success "wildmatch: no match '$3' '$4'" "
+ test_expect_success "wildmatch: no match '$3' '$4'" "
! test-wildmatch wildmatch '$3' '$4'
"
fi
if [ $2 = 1 ]; then
- test_expect_success "fnmatch: match '$3' '$4'" "
+ test_expect_success "fnmatch: match '$3' '$4'" "
test-wildmatch fnmatch '$3' '$4'
"
elif [ $2 = 0 ]; then
- test_expect_success "fnmatch: no match '$3' '$4'" "
+ test_expect_success "fnmatch: no match '$3' '$4'" "
! test-wildmatch fnmatch '$3' '$4'
"
# else
fi
}
+imatch() {
+ if [ $1 = 1 ]; then
+ test_expect_success "iwildmatch: match '$2' '$3'" "
+ test-wildmatch iwildmatch '$2' '$3'
+ "
+ else
+ test_expect_success "iwildmatch: no match '$2' '$3'" "
+ ! test-wildmatch iwildmatch '$2' '$3'
+ "
+ fi
+}
+
pathmatch() {
if [ $1 = 1 ]; then
- test_expect_success "pathmatch: match '$2' '$3'" "
+ test_expect_success "pathmatch: match '$2' '$3'" "
test-wildmatch pathmatch '$2' '$3'
"
else
- test_expect_success "pathmatch: no match '$2' '$3'" "
+ test_expect_success "pathmatch: no match '$2' '$3'" "
! test-wildmatch pathmatch '$2' '$3'
"
fi
pathmatch 1 ab/cXd/efXg/hi '*/*X*/*/*i'
pathmatch 1 ab/cXd/efXg/hi '*Xg*i'
+# Case-sensitivy features
+match 0 x 'a' '[A-Z]'
+match 1 x 'A' '[A-Z]'
+match 0 x 'A' '[a-z]'
+match 1 x 'a' '[a-z]'
+match 0 x 'a' '[[:upper:]]'
+match 1 x 'A' '[[:upper:]]'
+match 0 x 'A' '[[:lower:]]'
+match 1 x 'a' '[[:lower:]]'
+match 0 x 'A' '[B-Za]'
+match 1 x 'a' '[B-Za]'
+match 0 x 'A' '[B-a]'
+match 1 x 'a' '[B-a]'
+match 0 x 'z' '[Z-y]'
+match 1 x 'Z' '[Z-y]'
+
+imatch 1 'a' '[A-Z]'
+imatch 1 'A' '[A-Z]'
+imatch 1 'A' '[a-z]'
+imatch 1 'a' '[a-z]'
+imatch 1 'a' '[[:upper:]]'
+imatch 1 'A' '[[:upper:]]'
+imatch 1 'A' '[[:lower:]]'
+imatch 1 'a' '[[:lower:]]'
+imatch 1 'A' '[B-Za]'
+imatch 1 'a' '[B-Za]'
+imatch 1 'A' '[B-a]'
+imatch 1 'a' '[B-a]'
+imatch 1 'z' '[Z-y]'
+imatch 1 'Z' '[Z-y]'
+
test_done
'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 |
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
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
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
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
test_expect_success \
'ls-tree filtered with path' \
'git ls-tree $tree path >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
EOF
test_output'
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
test_expect_success \
'ls-tree filtered with path0/' \
'git ls-tree $tree path0/ >current &&
- make_expected <<\EOF &&
+ cat >expected <<\EOF &&
EOF
test_output'
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'
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
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'
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'
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
test $(git config branch.my4.merge) = refs/heads/master
'
-test_expect_success 'test tracking setup (non-wildcard, not matching)' '
+test_expect_success 'tracking setup fails on non-matching refspec' '
git config remote.local.url . &&
git config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
(git show-ref -q refs/remotes/local/master || git fetch local) &&
- git branch --track my5 local/master &&
- ! test "$(git config branch.my5.remote)" = local &&
- ! test "$(git config branch.my5.merge)" = refs/heads/master
+ test_must_fail git branch --track my5 local/master &&
+ test_must_fail git config branch.my5.remote &&
+ test_must_fail git config branch.my5.merge
'
test_expect_success 'test tracking setup via config' '
test_cmp all-of-them again
'
+test_expect_success 'explicit pack-refs with dangling packed reference' '
+ git commit --allow-empty -m "soon to be garbage-collected" &&
+ git pack-refs --all &&
+ git reset --hard HEAD^ &&
+ git reflog expire --expire=all --all &&
+ git prune --expire=all &&
+ git pack-refs --all 2>result &&
+ test_cmp /dev/null result
+'
+
+test_expect_success 'delete ref with dangling packed version' '
+ git checkout -b lamb &&
+ git commit --allow-empty -m "future garbage" &&
+ git pack-refs --all &&
+ git reset --hard HEAD^ &&
+ git checkout master &&
+ git reflog expire --expire=all --all &&
+ git prune --expire=all &&
+ git branch -d lamb 2>result &&
+ test_cmp /dev/null result
+'
+
+test_expect_success 'delete ref while another dangling packed ref' '
+ git branch lamb &&
+ git commit --allow-empty -m "future garbage" &&
+ git pack-refs --all &&
+ git reset --hard HEAD^ &&
+ git reflog expire --expire=all --all &&
+ git prune --expire=all &&
+ git branch -d lamb 2>result &&
+ test_cmp /dev/null result
+'
+
test_done
test_cmp expect actual
'
+test_expect_success 'peeled refs survive deletion of packed ref' '
+ git pack-refs --all &&
+ cp .git/packed-refs fully-peeled &&
+ git branch yadda &&
+ git pack-refs --all &&
+ git branch -d yadda &&
+ test_cmp fully-peeled .git/packed-refs
+'
+
test_done
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
'
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' '
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 &&
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' '
+++ /dev/null
-#!/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
test_expect_success 'rebase --skip with --merge' '
git rebase --skip
- '
+'
-test_expect_success 'merge and reference trees equal' \
- 'test -z "`git diff-tree skip-merge skip-reference`"'
+test_expect_success 'merge and reference trees equal' '
+ test -z "`git diff-tree skip-merge skip-reference`"
+'
test_expect_success 'moved back to branch correctly' '
test refs/heads/skip-merge = $(git symbolic-ref HEAD)
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^ &&
test_commit n2 &&
test_commit n3 &&
git notes add -m"a note" n3 &&
- git rebase --onto n1 n2 &&
+ git rebase -i --onto n1 n2 &&
test "a note" = "$(git notes show HEAD)"
'
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 &&
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
. ./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
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' '
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
# \--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 &&
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" &&
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 &&
)
'
-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
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
test_expect_success setup '
echo 0 >file0 &&
git add . &&
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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
'
test_expect_success 'cherry-pick an empty commit' '
- git checkout master && {
- git cherry-pick empty-branch^
- test "$?" = 1
- }
+ git checkout master &&
+ test_expect_code 1 git cherry-pick empty-branch^
'
test_expect_success 'index lockfile was removed' '
-
test ! -f .git/index.lock
-
'
test_expect_success 'cherry-pick a commit with an empty message' '
- git checkout master && {
- git cherry-pick empty-branch
- test "$?" = 1
- }
+ git checkout master &&
+ test_expect_code 1 git cherry-pick empty-branch
'
test_expect_success 'index lockfile was removed' '
-
test ! -f .git/index.lock
-
'
test_expect_success 'cherry-pick a commit with an empty message with --allow-empty-message' '
git reset --hard &&
git checkout fork^0 &&
git cherry-pick --keep-redundant-commits master &&
- git show -s --format='%s' >actual &&
+ git show -s --format=%s >actual &&
echo "add file2 on master" >expect &&
test_cmp expect actual
'
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 &&
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
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
*) 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);;
*) 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);;
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
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' '
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' '
# 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)"
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
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 ]
-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
'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' \
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1
EOF
-test_expect_success SYMLINKS \
+test_expect_success \
'validate result of -B (#5)' \
'compare_diff_raw expected current'
: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'
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M file1
EOF
-test_expect_success SYMLINKS \
+test_expect_success \
'validate result of -M (#7)' \
'compare_diff_raw expected current'
. ./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
@@ -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
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 &&
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
+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
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) &&
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.
. ./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 &&
'
-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 &&
{
'
-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 &&
{
'
-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 &&
{
+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 &&
compare_diff_patch expected actual
'
+# Test for a bug reported at
+# http://thread.gmane.org/gmane.comp.version-control.git/224410
+# where a delete lines were missing from combined diff output when they
+# occurred exactly before the context lines of a later change.
+test_expect_success 'combine diff missing delete bug' '
+ git commit -m initial --allow-empty &&
+ cat <<-\EOF >test &&
+ 1
+ 2
+ 3
+ 4
+ EOF
+ git add test &&
+ git commit -a -m side1 &&
+ git checkout -B side1 &&
+ git checkout HEAD^ &&
+ cat <<-\EOF >test &&
+ 0
+ 1
+ 2
+ 3
+ 4modified
+ EOF
+ git add test &&
+ git commit -m side2 &&
+ git branch -f side2 &&
+ test_must_fail git merge --no-commit side1 &&
+ cat <<-\EOF >test &&
+ 1
+ 2
+ 3
+ 4modified
+ EOF
+ git add test &&
+ git commit -a -m merge &&
+ git diff-tree -c -p HEAD >actual.tmp &&
+ sed -e "1,/^@@@/d" < actual.tmp >actual &&
+ tr -d Q <<-\EOF >expected &&
+ - 0
+ 1
+ 2
+ 3
+ -4
+ +4modified
+ EOF
+ compare_diff_patch expected actual
+'
+
test_done
#!/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
. ./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" &&
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
)
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
'
git diff --submodule=log >actual &&
cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
- > Add foo3
+ > Add foo3 ($added foo3)
EOF
test_cmp expected actual
'
git diff --submodule >actual &&
cat >expected <<-EOF &&
Submodule sm1 $head1..$head2:
- > Add foo3
+ > Add foo3 ($added foo3)
EOF
test_cmp expected actual
'
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
'
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
'
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 &&
. ./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 &&
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
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
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
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
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
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
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
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
. ./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 &&
'
-test_expect_success SYMLINKS 'apply --index symlink patch' '
+test_expect_success 'apply --index symlink patch' '
git checkout -f side &&
git apply --index patch &&
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 &&
'
-test_expect_success SYMLINKS apply '
+test_expect_success apply '
git checkout test &&
git diff --exit-code test &&
'
-test_expect_success SYMLINKS 'check result' '
+test_expect_success 'check result' '
git diff --exit-code master &&
git diff --exit-code --cached master &&
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^)"
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^)"
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^)"
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^)"
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^ &&
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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 &&
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
'
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
'
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' '
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' '
)
'
+test_expect_success 'git log -c --follow' '
+ test_create_repo follow-c &&
+ (
+ cd follow-c &&
+ test_commit initial file original &&
+ git rm file &&
+ test_commit rename file2 original &&
+ git reset --hard initial &&
+ test_commit modify file foo &&
+ git merge -m merge rename &&
+ git log -c --follow file2
+ )
+'
+
cat >expect <<\EOF
* commit COMMIT_OBJECT_NAME
|\ Merge: MERGE_PARENTS
}
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 &&
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
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):
#!/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' '
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" &&
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
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
'
--- /dev/null
+#!/bin/sh
+
+test_description='test log -L'
+. ./test-lib.sh
+
+test_expect_success 'setup (import history)' '
+ git fast-import < "$TEST_DIRECTORY"/t4211/history.export &&
+ git reset --hard
+'
+
+canned_test_1 () {
+ test_expect_$1 "$2" "
+ git log $2 >actual &&
+ test_cmp \"\$TEST_DIRECTORY\"/t4211/expect.$3 actual
+ "
+}
+
+canned_test () {
+ canned_test_1 success "$@"
+}
+canned_test_failure () {
+ canned_test_1 failure "$@"
+}
+
+test_bad_opts () {
+ test_expect_success "invalid args: $1" "
+ test_must_fail git log $1 2>errors &&
+ grep '$2' errors
+ "
+}
+
+canned_test "-L 4,12:a.c simple" simple-f
+canned_test "-L 4,+9:a.c simple" simple-f
+canned_test "-L '/long f/,/^}/:a.c' simple" simple-f
+canned_test "-L :f:a.c simple" simple-f-to-main
+
+canned_test "-L '/main/,/^}/:a.c' simple" simple-main
+canned_test "-L :main:a.c simple" simple-main-to-end
+
+canned_test "-L 1,+4:a.c simple" beginning-of-file
+
+canned_test "-L 20:a.c simple" end-of-file
+
+canned_test "-L '/long f/',/^}/:a.c -L /main/,/^}/:a.c simple" two-ranges
+canned_test "-L 24,+1:a.c simple" vanishes-early
+
+canned_test "-M -L '/long f/,/^}/:b.c' move-support" move-support-f
+canned_test "-M -L ':f:b.c' parallel-change" parallel-change-f-to-main
+
+canned_test "-L 4,12:a.c -L :main:a.c simple" multiple
+canned_test "-L 4,18:a.c -L :main:a.c simple" multiple-overlapping
+canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping
+canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset
+canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset
+
+test_bad_opts "-L" "switch.*requires a value"
+test_bad_opts "-L b.c" "argument.*not of the form"
+test_bad_opts "-L 1:" "argument.*not of the form"
+test_bad_opts "-L 1:nonexistent" "There is no path"
+test_bad_opts "-L 1:simple" "There is no path"
+test_bad_opts "-L '/foo:b.c'" "argument.*not of the form"
+test_bad_opts "-L 1000:b.c" "has only.*lines"
+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
--- /dev/null
+commit 4a23ae5c98d59a58c6da036156959f2dc9f472ad
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:47:40 2013 +0100
+
+ change at very beginning
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -1,3 +1,4 @@
++#include <unistd.h>
+ #include <stdio.h>
+
+ long f(long x)
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -1,3 +1,3 @@
+ #include <stdio.h>
+
+-int f(int x)
++long f(long x)
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +1,3 @@
++#include <stdio.h>
++
++int f(int x)
--- /dev/null
+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
+@@ -20,3 +20,5 @@
+ 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
+@@ -20,3 +20,3 @@
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -19,3 +19,3 @@
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +18,3 @@
++ printf("%d\n", f(15));
++ return 0;
++}
--- /dev/null
+commit 6ce3c4ff690136099bb17e1a8766b75764726ea7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:49:50 2013 +0100
+
+ another simple change
+
+diff --git a/b.c b/b.c
+--- a/b.c
++++ b/b.c
+@@ -4,9 +4,9 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+- x >>= 1;
++ x /= 2;
+ s++;
+ }
+ return s;
+ }
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
--- /dev/null
+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
+@@ -18,5 +18,7 @@
+ 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
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
--- /dev/null
+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
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,19 +3,19 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ 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>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,18 +3,19 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+ int main ()
+ {
+ printf("%d\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,18 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
++
++/*
++ * A comment.
++ */
++
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
--- /dev/null
+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
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,19 +3,19 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ 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>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,18 +3,19 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+ int main ()
+ {
+ printf("%d\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,18 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
++
++/*
++ * A comment.
++ */
++
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
--- /dev/null
+commit 0469c60bc4837d52d97b1f081dec5f98dea20fed
+Merge: ba227c6 6ce3c4f
+Author: Thomas Rast <trast@inf.ethz.ch>
+Date: Fri Apr 12 16:16:24 2013 +0200
+
+ Merge across the rename
+
+
+commit 6ce3c4ff690136099bb17e1a8766b75764726ea7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:49:50 2013 +0100
+
+ another simple change
+
+diff --git a/b.c b/b.c
+--- a/b.c
++++ b/b.c
+@@ -4,14 +4,14 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+- x >>= 1;
++ x /= 2;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * This is only an example!
+ */
+
+
+commit ba227c6632349700fbb957dec2b50f5e2358be3f
+Author: Thomas Rast <trast@inf.ethz.ch>
+Date: Fri Apr 12 16:15:57 2013 +0200
+
+ change on another line of history while rename happens
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -4,14 +4,14 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+- * This is only an example!
++ * This is only a short example!
+ */
+
+
+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,14 +3,14 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+- * A comment.
++ * This is only an example!
+ */
+
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,13 +3,14 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,13 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
++
++/*
++ * A comment.
++ */
++
--- /dev/null
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
--- /dev/null
+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,14 +3,14 @@
+ long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+- * A comment.
++ * This is only an example!
+ */
+
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,13 +3,14 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+ /*
+ * A comment.
+ */
+
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,13 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
++
++/*
++ * A comment.
++ */
++
--- /dev/null
+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
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
+
+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
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
--- /dev/null
+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
+@@ -18,5 +18,7 @@
+ 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
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
--- /dev/null
+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
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
+
+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
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,9 +3,9 @@
+-int f(int x)
++long f(long x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+ }
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:55 2013 +0100
+
+ change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,8 +3,9 @@
+ int f(int x)
+ {
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
++ return s;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,8 @@
++int f(int x)
++{
++ int s = 0;
++ while (x) {
++ x >>= 1;
++ s++;
++ }
++}
--- /dev/null
+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
+@@ -22,1 +24,1 @@
+-}
+\ 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
+@@ -22,1 +22,1 @@
+-}
++}
+\ No newline at end of file
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +20,1 @@
++}
--- /dev/null
+blob
+mark :1
+data 157
+#include <stdio.h>
+
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+}
+
+/*
+ * A comment.
+ */
+
+int main ()
+{
+ printf("%d\n", f(15));
+ return 0;
+}
+
+reset refs/tags/simple
+commit refs/tags/simple
+mark :2
+author Thomas Rast <trast@student.ethz.ch> 1362044688 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044688 +0100
+data 8
+initial
+M 100644 :1 a.c
+
+blob
+mark :3
+data 168
+#include <stdio.h>
+
+int f(int x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * A comment.
+ */
+
+int main ()
+{
+ printf("%d\n", f(15));
+ return 0;
+}
+
+commit refs/tags/simple
+mark :4
+author Thomas Rast <trast@student.ethz.ch> 1362044695 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044695 +0100
+data 11
+change f()
+from :2
+M 100644 :3 a.c
+
+blob
+mark :5
+data 171
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * A comment.
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+commit refs/tags/simple
+mark :6
+author Thomas Rast <trast@student.ethz.ch> 1362044716 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044716 +0100
+data 21
+touch both functions
+from :4
+M 100644 :5 a.c
+
+blob
+mark :7
+data 185
+#include <stdio.h>
+
+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;
+}
+
+commit refs/tags/simple
+mark :8
+author Thomas Rast <trast@student.ethz.ch> 1362044741 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044741 +0100
+data 14
+touch comment
+from :6
+M 100644 :7 a.c
+
+blob
+mark :9
+data 205
+#include <unistd.h>
+#include <stdio.h>
+
+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;
+}
+
+commit refs/tags/simple
+mark :10
+author Thomas Rast <trast@student.ethz.ch> 1362044860 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044860 +0100
+data 25
+change at very beginning
+from :8
+M 100644 :9 a.c
+
+blob
+mark :11
+data 204
+#include <unistd.h>
+#include <stdio.h>
+
+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;
+}
+commit refs/tags/simple
+mark :12
+author Thomas Rast <trast@student.ethz.ch> 1362044890 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044890 +0100
+data 36
+change to an incomplete line at end
+from :10
+M 100644 :11 a.c
+
+blob
+mark :13
+data 238
+#include <unistd.h>
+#include <stdio.h>
+
+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;
+}
+
+/* incomplete lines are bad! */
+
+commit refs/tags/simple
+mark :14
+author Thomas Rast <trast@student.ethz.ch> 1362044923 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044923 +0100
+data 29
+change back to complete line
+from :12
+M 100644 :13 a.c
+
+commit refs/tags/move-support
+mark :15
+author Thomas Rast <trast@student.ethz.ch> 1362044968 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044968 +0100
+data 10
+move file
+from :14
+D a.c
+M 100644 :13 b.c
+
+blob
+mark :16
+data 237
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x /= 2;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * This is only an example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+/* incomplete lines are bad! */
+
+commit refs/tags/move-support
+mark :17
+author Thomas Rast <trast@student.ethz.ch> 1362044990 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362044990 +0100
+data 22
+another simple change
+from :15
+M 100644 :16 b.c
+
+blob
+mark :18
+data 254
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x);
+
+/*
+ * This is only an example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+/* incomplete lines are bad! */
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x /= 2;
+ s++;
+ }
+ return s;
+}
+
+commit refs/heads/master
+mark :19
+author Thomas Rast <trast@student.ethz.ch> 1362045024 +0100
+committer Thomas Rast <trast@student.ethz.ch> 1362045024 +0100
+data 21
+move within the file
+from :17
+M 100644 :18 b.c
+
+blob
+mark :20
+data 243
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x >>= 1;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * This is only a short example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+/* incomplete lines are bad! */
+
+commit refs/heads/parallel-change
+mark :21
+author Thomas Rast <trast@inf.ethz.ch> 1365776157 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1365776157 +0200
+data 55
+change on another line of history while rename happens
+from :14
+M 100644 :20 a.c
+
+blob
+mark :22
+data 242
+#include <unistd.h>
+#include <stdio.h>
+
+long f(long x)
+{
+ int s = 0;
+ while (x) {
+ x /= 2;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * This is only a short example!
+ */
+
+int main ()
+{
+ printf("%ld\n", f(15));
+ return 0;
+}
+
+/* incomplete lines are bad! */
+
+commit refs/heads/parallel-change
+mark :23
+author Thomas Rast <trast@inf.ethz.ch> 1365776184 +0200
+committer Thomas Rast <trast@inf.ethz.ch> 1365776191 +0200
+data 24
+Merge across the rename
+from :21
+merge :17
+D a.c
+M 100644 :22 b.c
+
+reset refs/heads/parallel-change
+from :23
+
test_cmp expected actual
'
+test_expect_success 'tree add A, B (same)' '
+ cat >expect <<-\EOF &&
+ EOF
+ git reset --hard initial &&
+ mkdir sub &&
+ test_commit "add sub/file" "sub/file" "file" add-tree-A &&
+ git merge-tree initial add-tree-A add-tree-A >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'tree add A, B (different)' '
+ cat >expect <<-\EOF &&
+ added in both
+ our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d sub/file
+ their 100644 ba629238ca89489f2b350e196ca445e09d8bb834 sub/file
+ @@ -1 +1,5 @@
+ +<<<<<<< .our
+ AAA
+ +=======
+ +BBB
+ +>>>>>>> .their
+ EOF
+ git reset --hard initial &&
+ mkdir sub &&
+ test_commit "add sub/file" "sub/file" "AAA" add-tree-a-b-A &&
+ git reset --hard initial &&
+ mkdir sub &&
+ test_commit "add sub/file" "sub/file" "BBB" add-tree-a-b-B &&
+ git merge-tree initial add-tree-a-b-A add-tree-a-b-B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'tree unchanged A, removed B' '
+ cat >expect <<-\EOF &&
+ removed in remote
+ base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d sub/file
+ our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d sub/file
+ @@ -1 +0,0 @@
+ -AAA
+ EOF
+ git reset --hard initial &&
+ mkdir sub &&
+ test_commit "add sub/file" "sub/file" "AAA" tree-remove-b-initial &&
+ git rm sub/file &&
+ test_tick &&
+ git commit -m "remove sub/file" &&
+ git tag tree-remove-b-B &&
+ git merge-tree tree-remove-b-initial tree-remove-b-initial tree-remove-b-B >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'turn file to tree' '
git reset --hard initial &&
rm initial-file &&
SUBSTFORMAT=%H%n
+test_lazy_prereq TAR_NEEDS_PAX_FALLBACK '
+ (
+ mkdir pax &&
+ cd pax &&
+ "$TAR" xf "$TEST_DIRECTORY"/t5000/pax.tar &&
+ test -f PaxHeaders.1791/file
+ )
+'
+
+get_pax_header() {
+ file=$1
+ header=$2=
+
+ while read len rest
+ do
+ if test "$len" = $(echo "$len $rest" | wc -c)
+ then
+ case "$rest" in
+ $header*)
+ echo "${rest#$header}"
+ ;;
+ esac
+ fi
+ done <"$file"
+}
+
+check_tar() {
+ tarfile=$1.tar
+ listfile=$1.lst
+ dir=$1
+ dir_with_prefix=$dir/$2
+
+ test_expect_success ' extract tar archive' '
+ (mkdir $dir && cd $dir && "$TAR" xf -) <$tarfile
+ '
+
+ test_expect_success TAR_NEEDS_PAX_FALLBACK ' interpret pax headers' '
+ (
+ cd $dir &&
+ for header in *.paxheader
+ do
+ data=${header%.paxheader}.data &&
+ if test -h $data -o -e $data
+ then
+ path=$(get_pax_header $header path) &&
+ if test -n "$path"
+ then
+ mv "$data" "$path"
+ fi
+ fi
+ done
+ )
+ '
+
+ test_expect_success ' validate filenames' '
+ (cd ${dir_with_prefix}a && find .) | sort >$listfile &&
+ test_cmp a.lst $listfile
+ '
+
+ test_expect_success ' validate file contents' '
+ diff -r a ${dir_with_prefix}a
+ '
+}
+
test_expect_success \
'populate workdir' \
- 'mkdir a b c &&
+ 'mkdir a &&
echo simple textfile >a/a &&
+ ten=0123456789 && hundred=$ten$ten$ten$ten$ten$ten$ten$ten$ten$ten &&
+ echo long filename >a/four$hundred &&
mkdir a/bin &&
cp /bin/sh a/bin &&
printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 &&
git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
git commit-tree $treeid </dev/null)'
+test_expect_success 'setup export-subst' '
+ echo "substfile?" export-subst >>.git/info/attributes &&
+ git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
+ >a/substfile1
+'
+
test_expect_success \
'create bare clone' \
'git clone --bare . bare.git &&
'git archive' \
'git archive HEAD >b.tar'
-test_expect_success \
- 'git tar-tree' \
- 'git tar-tree HEAD >b2.tar'
+check_tar b
-test_expect_success \
- 'git archive vs. git tar-tree' \
- 'test_cmp b.tar b2.tar'
+test_expect_success 'git archive --prefix=prefix/' '
+ git archive --prefix=prefix/ HEAD >with_prefix.tar
+'
+
+check_tar with_prefix prefix/
+
+test_expect_success 'git-archive --prefix=olde-' '
+ git archive --prefix=olde- HEAD >with_olde-prefix.tar
+'
+
+check_tar with_olde-prefix olde-
test_expect_success 'git archive on large files' '
test_config core.bigfilethreshold 1 &&
'git get-tar-commit-id <b.tar >b.commitid &&
test_cmp .git/$(git symbolic-ref HEAD) b.commitid'
-test_expect_success \
- 'extract tar archive' \
- '(cd b && "$TAR" xf -) <b.tar'
-
-test_expect_success \
- 'validate filenames' \
- '(cd b/a && find .) | sort >b.lst &&
- test_cmp a.lst b.lst'
-
-test_expect_success \
- 'validate file contents' \
- 'diff -r a b/a'
-
-test_expect_success \
- 'git tar-tree with prefix' \
- 'git tar-tree HEAD prefix >c.tar'
-
-test_expect_success \
- 'extract tar archive with prefix' \
- '(cd c && "$TAR" xf -) <c.tar'
-
-test_expect_success \
- 'validate filenames with prefix' \
- '(cd c/prefix/a && find .) | sort >c.lst &&
- test_cmp a.lst c.lst'
-
-test_expect_success \
- 'validate file contents with prefix' \
- 'diff -r a c/prefix/a'
-
-test_expect_success \
- 'create archives with substfiles' \
- 'cp .git/info/attributes .git/info/attributes.before &&
- echo "substfile?" export-subst >>.git/info/attributes &&
- git archive HEAD >f.tar &&
- git archive --prefix=prefix/ HEAD >g.tar &&
- mv .git/info/attributes.before .git/info/attributes'
-
-test_expect_success \
- 'extract substfiles' \
- '(mkdir f && cd f && "$TAR" xf -) <f.tar'
-
-test_expect_success \
- 'validate substfile contents' \
- 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
- >f/a/substfile1.expected &&
- test_cmp f/a/substfile1.expected f/a/substfile1 &&
- test_cmp a/substfile2 f/a/substfile2
+test_expect_success 'git tar-tree' '
+ git tar-tree HEAD >tar-tree.tar &&
+ test_cmp b.tar tar-tree.tar
'
-test_expect_success \
- 'extract substfiles from archive with prefix' \
- '(mkdir g && cd g && "$TAR" xf -) <g.tar'
-
-test_expect_success \
- 'validate substfile contents from archive with prefix' \
- 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
- >g/prefix/a/substfile1.expected &&
- test_cmp g/prefix/a/substfile1.expected g/prefix/a/substfile1 &&
- test_cmp a/substfile2 g/prefix/a/substfile2
+test_expect_success 'git tar-tree with prefix' '
+ git tar-tree HEAD prefix >tar-tree_with_prefix.tar &&
+ test_cmp with_prefix.tar tar-tree_with_prefix.tar
'
test_expect_success 'git archive with --output, override inferred format' '
test_must_fail git archive --remote=. $sha1 >remote.tar
'
-test_expect_success 'git-archive --prefix=olde-' '
- git archive --prefix=olde- >h.tar HEAD &&
- (
- mkdir h &&
- cd h &&
- "$TAR" xf - <../h.tar
- ) &&
- test -d h/olde-a &&
- test -d h/olde-a/bin &&
- test -f h/olde-a/bin/sh
-'
-
test_expect_success 'setup tar filters' '
git config tar.tar.foo.command "tr ab ba" &&
git config tar.bar.command "tr ab ba" &&
test_expect_success \
'populate workdir' \
- 'mkdir a b c &&
+ 'mkdir a &&
echo simple textfile >a/a &&
mkdir a/bin &&
cp /bin/sh a/bin &&
echo "$dir/$i"
done
} | sort >expect &&
- find "$dir" -print | sort >actual &&
+ find "$dir" ! -name pax_global_header -print | sort >actual &&
test_cmp expect actual
}
-test_expect_success 'tar archive of empty tree is empty' '
- git archive --format=tar HEAD: >empty.tar &&
+
+# bsdtar/libarchive versions before 3.1.3 consider a tar file with a
+# global pax header that is not followed by a file record as corrupt.
+if "$TAR" tf "$TEST_DIRECTORY"/t5004/empty-with-pax-header.tar >/dev/null 2>&1
+then
+ test_set_prereq HEADER_ONLY_TAR_OK
+fi
+
+test_expect_success HEADER_ONLY_TAR_OK 'tar archive of commit with empty tree' '
+ git archive --format=tar HEAD >empty-with-pax-header.tar &&
make_dir extract &&
- "$TAR" xf empty.tar -C extract &&
+ "$TAR" xf empty-with-pax-header.tar -C extract &&
check_dir extract
'
+test_expect_success 'tar archive of empty tree is empty' '
+ git archive --format=tar HEAD: >empty.tar &&
+ perl -e "print \"\\0\" x 10240" >10knuls.tar &&
+ test_cmp 10knuls.tar empty.tar
+'
+
test_expect_success 'tar archive of empty tree with prefix' '
git archive --format=tar --prefix=foo/ HEAD >prefix.tar &&
make_dir extract &&
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/
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
}
git cat-file blob $blob_2 > /dev/null &&
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 &&
}
verify_hook_input () {
- test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
- test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+ test_cmp expected.args "$TRASH_DIRECTORY"/post-rewrite.args &&
+ test_cmp expected.data "$TRASH_DIRECTORY"/post-rewrite.data
}
test_expect_success 'git commit --amend' '
test "`git --git-dir=shallow0/.git rev-list --count HEAD`" = 1
'
+test_expect_success 'clone shallow depth 1 with fsck' '
+ git config --global fetch.fsckobjects true &&
+ git clone --no-single-branch --depth 1 "file://$(pwd)/." shallow0fsck &&
+ test "`git --git-dir=shallow0fsck/.git rev-list --count HEAD`" = 1 &&
+ git config --global --unset fetch.fsckobjects
+'
+
test_expect_success 'clone shallow' '
git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
'
test_cmp count8.expected count8.actual
'
+test_expect_success 'fetch in shallow repo unreachable shallow objects' '
+ (
+ git clone --bare --branch B --single-branch "file://$(pwd)/." no-reflog &&
+ git clone --depth 1 "file://$(pwd)/no-reflog" shallow9 &&
+ cd no-reflog &&
+ git tag -d TAGB1 TAGB2 &&
+ git update-ref refs/heads/B B~~ &&
+ git gc --prune=now &&
+ cd ../shallow9 &&
+ git fetch origin &&
+ git fsck --no-dangling
+ )
+'
+
test_expect_success 'setup tests for the --stdin parameter' '
for head in C D E F
do
}
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
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
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
)
'
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
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
'
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
'
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
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
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
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 &&
(
'
-test_expect_success 'explicit fetch should not update tracking' '
+test_expect_success 'mark initial state of origin/master' '
+ (
+ cd three &&
+ git tag base-origin-master refs/remotes/origin/master
+ )
+'
+
+test_expect_success 'explicit fetch should update tracking' '
cd "$D" &&
git branch -f side &&
(
cd three &&
+ git update-ref refs/remotes/origin/master base-origin-master &&
o=$(git rev-parse --verify refs/remotes/origin/master) &&
git fetch origin master &&
n=$(git rev-parse --verify refs/remotes/origin/master) &&
- test "$o" = "$n" &&
+ test "$o" != "$n" &&
test_must_fail git rev-parse --verify refs/remotes/origin/side
)
'
-test_expect_success 'explicit pull should not update tracking' '
+test_expect_success 'explicit pull should update tracking' '
cd "$D" &&
git branch -f side &&
(
cd three &&
+ git update-ref refs/remotes/origin/master base-origin-master &&
o=$(git rev-parse --verify refs/remotes/origin/master) &&
git pull origin master &&
n=$(git rev-parse --verify refs/remotes/origin/master) &&
- test "$o" = "$n" &&
+ test "$o" != "$n" &&
test_must_fail git rev-parse --verify refs/remotes/origin/side
)
'
git branch -f side &&
(
cd three &&
+ git update-ref refs/remotes/origin/master base-origin-master &&
o=$(git rev-parse --verify refs/remotes/origin/master) &&
git fetch origin &&
n=$(git rev-parse --verify refs/remotes/origin/master) &&
)
'
+test_expect_success 'non-matching refspecs do not confuse tracking update' '
+ cd "$D" &&
+ git update-ref refs/odd/location HEAD &&
+ (
+ cd three &&
+ git update-ref refs/remotes/origin/master base-origin-master &&
+ git config --add remote.origin.fetch \
+ refs/odd/location:refs/remotes/origin/odd &&
+ o=$(git rev-parse --verify refs/remotes/origin/master) &&
+ git fetch origin master &&
+ n=$(git rev-parse --verify refs/remotes/origin/master) &&
+ test "$o" != "$n" &&
+ test_must_fail git rev-parse --verify refs/remotes/origin/odd
+ )
+'
+
test_expect_success 'pushing nonexistent branch by mistake should not segv' '
cd "$D" &&
)
'
+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 &&
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' '
(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' '
(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' '
(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' '
(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)
'
# $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
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 &&
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
'
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
'
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
# now assign tags to all the dangling commits we created above
tag=$("$PERL_PATH" -e "print \"bla\" x 30") &&
- sed -e "s/^:\(.\+\) \(.\+\)$/\2 refs\/tags\/$tag-\1/" <marks >>packed-refs
+ sed -e "s|^:\([^ ]*\) \(.*\)$|\2 refs/tags/$tag-\1|" <marks >>packed-refs
)
'
test_expect_success EXPENSIVE 'clone the 50,000 tag repo to check OS command line overflow' '
git clone $HTTPD_URL/smart/repo.git too-many-refs 2>err &&
- test_line_count = 0 err
+ test_line_count = 0 err &&
+ (
+ cd too-many-refs &&
+ test $(git for-each-ref refs/tags | wc -l) = 50000
+ )
'
stop_httpd
HTTPD_DOCUMENT_ROOT_PATH="$TRASH_DIRECTORY"
-test_have_prereq MINGW && export GREP_OPTIONS=-U
+test_have_prereq GREP_STRIPS_CR && export GREP_OPTIONS=-U
run_backend() {
echo "$2" |
test_cmp fetch.expected fetch.actual
'
+test_expect_success NOT_MINGW,NOT_CYGWIN 'clone local path foo:bar' '
+ cp -R src "foo:bar" &&
+ git clone "./foo:bar" foobar
+'
+
test_done
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' '
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"
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-gpg.sh
-if ! type "${BASH-bash}" >/dev/null 2>&1; then
- skip_all='skipping remote-testgit tests, bash not available'
- test_done
-fi
-
compare_refs() {
git --git-dir="$1/.git" rev-parse --verify $2 >expect &&
git --git-dir="$3/.git" rev-parse --verify $4 >actual &&
test_expect_success 'cloning without refspec' '
GIT_REMOTE_TESTGIT_REFSPEC="" \
- git clone "testgit::${PWD}/server" local2 &&
+ git clone "testgit::${PWD}/server" local2 2>error &&
+ grep "This remote helper should implement refspec capability" error &&
compare_refs local2 HEAD server HEAD
'
test_expect_success 'pulling without refspecs' '
(cd local2 &&
git reset --hard &&
- GIT_REMOTE_TESTGIT_REFSPEC="" git pull) &&
+ GIT_REMOTE_TESTGIT_REFSPEC="" git pull 2>../error) &&
+ grep "This remote helper should implement refspec capability" error &&
compare_refs local2 HEAD server HEAD
'
-test_expect_failure 'pushing without refspecs' '
+test_expect_success 'pushing without refspecs' '
test_when_finished "(cd local2 && git reset --hard origin)" &&
(cd local2 &&
echo content >>file &&
git commit -a -m ten &&
- GIT_REMOTE_TESTGIT_REFSPEC="" git push) &&
- compare_refs local2 HEAD server HEAD
-'
-
-test_expect_success 'pulling with straight refspec' '
- (cd local2 &&
- GIT_REMOTE_TESTGIT_REFSPEC="*:*" git pull) &&
- compare_refs local2 HEAD server HEAD
-'
-
-test_expect_failure 'pushing with straight refspec' '
- test_when_finished "(cd local2 && git reset --hard origin)" &&
- (cd local2 &&
- echo content >>file &&
- git commit -a -m eleven &&
- GIT_REMOTE_TESTGIT_REFSPEC="*:*" git push) &&
- compare_refs local2 HEAD server HEAD
+ GIT_REMOTE_TESTGIT_REFSPEC="" &&
+ export GIT_REMOTE_TESTGIT_REFSPEC &&
+ test_must_fail git push 2>../error) &&
+ grep "remote-helper doesn.t support push; refspec needed" error
'
test_expect_success 'pulling without marks' '
compare_refs local signed-tag-2 server signed-tag-2
'
+test_expect_success 'push update refs' '
+ (cd local &&
+ git checkout -b update master &&
+ echo update >>file &&
+ git commit -a -m update &&
+ git push origin update &&
+ git rev-parse --verify remotes/origin/update >expect &&
+ git rev-parse --verify testgit/origin/heads/update >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'push update refs failure' '
+ (cd local &&
+ git checkout update &&
+ echo "update fail" >>file &&
+ git commit -a -m "update fail" &&
+ git rev-parse --verify testgit/origin/heads/update >expect &&
+ GIT_REMOTE_TESTGIT_PUSH_ERROR="non-fast forward" &&
+ export GIT_REMOTE_TESTGIT_PUSH_ERROR &&
+ test_expect_code 1 git push origin update &&
+ git rev-parse --verify testgit/origin/heads/update >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'proper failure checks for fetching' '
+ (GIT_REMOTE_TESTGIT_FAILURE=1 &&
+ export GIT_REMOTE_TESTGIT_FAILURE &&
+ cd local &&
+ test_must_fail git fetch 2> error &&
+ cat error &&
+ grep -q "Error while running fast-import" error
+ )
+'
+
+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
+ )
+'
+
+test_expect_success 'push messages' '
+ (cd local &&
+ git checkout -b new_branch master &&
+ echo new >>file &&
+ git commit -a -m new &&
+ git push origin new_branch &&
+ git fetch origin &&
+ echo new >>file &&
+ git commit -a -m new &&
+ git push origin new_branch 2> msg &&
+ ! grep "\[new branch\]" msg
+ )
+'
+
test_done
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)
# 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()
{
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
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
#!/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
"
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
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
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
\e[31mfoo\e[32mbar\e[34mbaz\e[mxyzzy
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+commit $head1
\e[31mfoo\e[32mbar\e[34mbaz\e[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
\e[1;31;43mfoo\e[m
-commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+commit $head1
\e[1;31;43mfoo\e[m
EOF
)
'
-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 &&
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' '
test_expect_success setup '
echo "Hi there" >file &&
- git add file &&
- test_tick && git commit -m "Initial file" &&
+ echo "initial" >lost &&
+ git add file lost &&
+ test_tick && git commit -m "Initial file and lost" &&
note A &&
git branch other-branch &&
echo "Hello" >file &&
- git add file &&
- test_tick && git commit -m "Modified file" &&
+ echo "second" >lost &&
+ git add file lost &&
+ test_tick && git commit -m "Modified file and lost" &&
note B &&
git checkout other-branch &&
echo "Hello" >file &&
- git add file &&
+ >lost &&
+ git add file lost &&
test_tick && git commit -m "Modified the file identically" &&
note C &&
test_tick && git commit -m "Add another file" &&
note D &&
- test_tick && git merge -m "merge" master &&
+ test_tick &&
+ test_must_fail git merge -m "merge" master &&
+ >lost && git commit -a -m "merge" &&
note E &&
echo "Yet another" >elif &&
check_result 'K I H E C B A' --full-history -- file
check_result 'K I H E C B A' --full-history --topo-order -- file
check_result 'K I H E C B A' --full-history --date-order -- file
-check_outcome failure 'I E C B A' --simplify-merges -- file
+check_result 'I E C B A' --simplify-merges -- file
check_result 'I B A' -- file
check_result 'I B A' --topo-order -- file
check_result 'H' --first-parent -- another-file
+check_result 'E C B A' --full-history E -- lost
+test_expect_success 'full history simplification without parent' '
+ printf "%s\n" E C B A >expect &&
+ git log --pretty="$FMT" --full-history E -- lost |
+ unnote >actual &&
+ sed -e "s/^.* \([^ ]*\) .*/\1/" >check <actual &&
+ test_cmp expect check || {
+ cat actual
+ false
+ }
+'
+
test_done
#
# D..M -- M.t == M
# --ancestry-path D..M -- M.t == M
+#
+# F...I == F G H I
+# --ancestry-path F...I == F H I
+#
+# G..M -- G.t == [nothing - was dropped in "-s ours" merge L]
+# --ancestry-path G..M -- G.t == L
+# --ancestry-path --simplify-merges G^..M -- G.t == G L
. ./test-lib.sh
test_cmp expect actual
'
-test_expect_success 'rev-list --ancestry-patch D..M -- M.t' '
+test_expect_success 'rev-list --ancestry-path D..M -- M.t' '
echo M >expect &&
git rev-list --ancestry-path --format=%s D..M -- M.t |
sed -e "/^commit /d" >actual &&
test_cmp expect actual
'
+test_expect_success 'rev-list F...I' '
+ for c in F G H I; do echo $c; done >expect &&
+ git rev-list --format=%s F...I |
+ sed -e "/^commit /d" |
+ sort >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path F...I' '
+ for c in F H I; do echo $c; done >expect &&
+ git rev-list --ancestry-path --format=%s F...I |
+ sed -e "/^commit /d" |
+ sort >actual &&
+ test_cmp expect actual
+'
+
+# G.t is dropped in an "-s ours" merge
+test_expect_success 'rev-list G..M -- G.t' '
+ >expect &&
+ git rev-list --format=%s G..M -- G.t |
+ sed -e "/^commit /d" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path G..M -- G.t' '
+ echo L >expect &&
+ git rev-list --ancestry-path --format=%s G..M -- G.t |
+ sed -e "/^commit /d" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rev-list --ancestry-path --simplify-merges G^..M -- G.t' '
+ for c in G L; do echo $c; done >expect &&
+ git rev-list --ancestry-path --simplify-merges --format=%s G^..M -- G.t |
+ sed -e "/^commit /d" |
+ sort >actual &&
+ test_cmp expect actual
+'
+
# b---bc
# / \ /
# a X
# 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.
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 &&
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 &&
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 &&
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 &&
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 &&
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 &&
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
--- /dev/null
+#!/bin/sh
+#
+# ,---E--. *H----------. * marks !TREESAME parent paths
+# / \ / \*
+# *A--*B---D--*F-*G---------K-*L-*M
+# \ /* \ /
+# `-C-' `-*I-*J
+#
+# A creates "file", B and F change it.
+# Odd merge G takes the old version from B.
+# I changes it, but J reverts it, so K is TREESAME to both parents.
+# H and L both change "file", and M merges those changes.
+
+test_description='TREESAME and limiting'
+
+. ./test-lib.sh
+
+note () {
+ git tag "$1"
+}
+
+unnote () {
+ git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\))\([ ]\)|\1\2|g"
+}
+
+test_expect_success setup '
+ test_commit "Initial file" file "Hi there" A &&
+ git branch other-branch &&
+
+ test_commit "file=Hello" file "Hello" B &&
+ git branch third-branch &&
+
+ git checkout other-branch &&
+ test_commit "Added other" other "Hello" C &&
+
+ git checkout master &&
+ test_merge D other-branch &&
+
+ git checkout third-branch &&
+ test_commit "Third file" third "Nothing" E &&
+
+ git checkout master &&
+ test_commit "file=Blah" file "Blah" F &&
+
+ test_tick && git merge --no-commit third-branch &&
+ git checkout third-branch file &&
+ git commit &&
+ note G &&
+ git branch fiddler-branch &&
+
+ git checkout -b part2-branch &&
+ test_commit "file=Part 2" file "Part 2" H &&
+
+ git checkout fiddler-branch &&
+ test_commit "Bad commit" file "Silly" I &&
+
+ test_tick && git revert I && note J &&
+
+ git checkout master &&
+ test_tick && git merge --no-ff fiddler-branch &&
+ note K
+
+ test_commit "file=Part 1" file "Part 1" L &&
+
+ test_tick && test_must_fail git merge part2-branch &&
+ test_commit M file "Parts 1+2"
+'
+
+check_outcome () {
+ outcome=$1
+ shift
+
+ case "$1" in
+ *"("*)
+ FMT="%P %H | %s"
+ munge_actual="
+ s/^\([^ ]*\) \([^ ]*\) .*/(\1)\2/
+ s/ //g
+ s/()//
+ "
+ ;;
+ *)
+ FMT="%H | %s"
+ munge_actual="s/^\([^ ]*\) .*/\1/"
+ ;;
+ esac &&
+ printf "%s\n" $1 >expect &&
+ shift
+
+ param="$*" &&
+ test_expect_$outcome "log $param" '
+ git log --format="$FMT" $param |
+ unnote >actual &&
+ sed -e "$munge_actual" <actual >check &&
+ test_cmp expect check || {
+ cat actual
+ false
+ }
+ '
+}
+
+check_result () {
+ check_outcome success "$@"
+}
+
+# Odd merge G drops a change in F. Important that G is listed in all
+# except the most basic list. Achieving this means normal merge D will also be
+# shown in normal full-history, as we can't distinguish unless we do a
+# simplification pass. After simplification, D is dropped but G remains.
+# Also, merge simplification of G should not drop the parent B that the default
+# simple history follows.
+check_result 'M L K J I H G F E D C B A'
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G (D)F (B)E (BC)D (A)C (A)B A'
+check_result 'M H L K J I G E F D C B A' --topo-order
+check_result 'M L H B A' -- file
+check_result '(LH)M (B)L (B)H (A)B A' --parents -- file
+check_result 'M L J I H G F D B A' --full-history -- file
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G (D)F (BA)D (A)B A' --full-history --parents -- file
+check_result '(LH)M (G)H (J)L (I)J (G)I (FB)G (B)F (A)B A' --simplify-merges -- file
+check_result 'M L K G F D B A' --first-parent
+check_result 'M L G F B A' --first-parent -- file
+
+# Check that odd merge G remains shown when F is the bottom.
+check_result 'M L K J I H G E' F..M
+check_result 'M H L K J I G E' F..M --topo-order
+check_result 'M L H' F..M -- file
+check_result '(LH)M (B)L (B)H' --parents F..M -- file
+check_result 'M L J I H G' F..M --full-history -- file
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FB)G' F..M --full-history --parents -- file
+check_result '(LH)M (G)H (J)L (I)J (G)I (FB)G' F..M --simplify-merges -- file
+check_result 'M L K J I H G' F..M --ancestry-path
+check_result 'M L J I H G' F..M --ancestry-path -- file
+check_result '(LH)M (K)L (GJ)K (I)J (G)I (G)H (FE)G' F..M --ancestry-path --parents -- file
+check_result '(LH)M (G)H (J)L (I)J (G)I (FE)G' F..M --ancestry-path --simplify-merges -- file
+check_result 'M L K G' F..M --first-parent
+check_result 'M L G' F..M --first-parent -- file
+
+# Note that G is pruned when E is the bottom, even if it's the same commit list
+# If we want history since E, then we're quite happy to ignore G that took E.
+check_result 'M L K J I H G' E..M --ancestry-path
+check_result 'M L J I H' E..M --ancestry-path -- file
+check_result '(LH)M (K)L (EJ)K (I)J (E)I (E)H' E..M --ancestry-path --parents -- file
+check_result '(LH)M (E)H (J)L (I)J (E)I' E..M --ancestry-path --simplify-merges -- file
+
+# Should still be able to ignore I-J branch in simple log, despite limiting
+# to G.
+check_result 'M L K J I H' G..M
+check_result 'M H L K J I' G..M --topo-order
+check_result 'M L H' G..M -- file
+check_result '(LH)M (G)L (G)H' G..M --parents -- file
+check_result 'M L J I H' G..M --full-history -- file
+check_result 'M L K J I H' G..M --full-history --parents -- file
+check_result 'M H L J I' G..M --simplify-merges -- file
+check_result 'M L K J I H' G..M --ancestry-path
+check_result 'M L J I H' G..M --ancestry-path -- file
+check_result 'M L K J I H' G..M --ancestry-path --parents -- file
+check_result 'M H L J I' G..M --ancestry-path --simplify-merges -- file
+
+# B..F should be able to simplify the merge D from irrelevant side branch C.
+# Default log should also be free to follow B-D, and ignore C.
+# But --full-history shouldn't drop D on its own - without simplification,
+# we can't decide if the merge from INTERESTING commit C was sensible.
+check_result 'F D C' B..F
+check_result 'F' B..F -- file
+check_result '(B)F' B..F --parents -- file
+check_result 'F D' B..F --full-history -- file
+check_result '(D)F (BA)D' B..F --full-history --parents -- file
+check_result '(B)F' B..F --simplify-merges -- file
+check_result 'F D' B..F --ancestry-path
+check_result 'F' B..F --ancestry-path -- file
+check_result 'F' B..F --ancestry-path --parents -- file
+check_result 'F' B..F --ancestry-path --simplify-merges -- file
+check_result 'F D' B..F --first-parent
+check_result 'F' B..F --first-parent -- file
+
+# E...F should be equivalent to E F ^B, and be able to drop D as above.
+check_result 'F' E F ^B -- file # includes D
+check_result 'F' E...F -- file # includes D
+
+# Any sort of full history of C..F should show D, as it's the connection to C,
+# and it differs from it.
+check_result 'F D B' C..F
+check_result 'F B' C..F -- file
+check_result '(B)F (A)B' C..F --parents -- file
+check_result 'F D B' C..F --full-history -- file
+check_result '(D)F (BC)D (A)B' C..F --full-history --parents -- file
+check_result '(D)F (BC)D (A)B' C..F --simplify-merges -- file
+check_result 'F D' C..F --ancestry-path
+check_result 'F D' C..F --ancestry-path -- file
+check_result 'F D' C..F --ancestry-path --parents -- file
+check_result 'F D' C..F --ancestry-path --simplify-merges -- file
+check_result 'F D B' C..F --first-parent
+check_result 'F B' C..F --first-parent -- file
+
+
+test_done
check_describe B-0-* --long HEAD^^2^
check_describe A-3-* --long HEAD^^2
+check_describe c-7-* --tags
+check_describe e-3-* --first-parent --tags
+
: >err.expect
check_describe A --all A^0
test_expect_success 'no warning was displayed for A' '
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
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 &&
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
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)"
'
. ./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 &&
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
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
'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
'
git reset --hard HEAD~2 &&
check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
test "$(git rev-parse ORIG_HEAD)" = \
- 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d
+ $head4
'
>.diff_expect
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
'
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
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
'
test_expect_success \
'checkout with --track fakes a sensible -b <name>' '
+ git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" &&
git update-ref refs/remotes/origin/koala/bear renamer &&
git checkout --track origin/koala/bear &&
--- /dev/null
+#!/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
(
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 &&
(
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
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
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 &&
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 &&
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 &&
(
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 &&
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
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)
)
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
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
"
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 &&
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
"
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 &&
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:
"
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
. ./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"
+ )
)
'
)
'
+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 &&
)
'
+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 &&
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
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 &&
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.
test_i18ngrep " changed, 5 insertions" output
'
+test_expect_success '--only works on to-be-born branch' '
+ # This test relies on having something in the index, as it
+ # would not otherwise actually prove much. So check this.
+ test -n "$(git ls-files)" &&
+ git checkout --orphan orphan &&
+ echo foo >newfile &&
+ git add newfile &&
+ git commit --only newfile -m"--only on unborn branch" &&
+ echo newfile >expected &&
+ git ls-tree -r --name-only HEAD >actual &&
+ test_cmp expected actual
+'
+
test_done
test_cmp expect .git/result
'
+test_expect_success 'do not fire editor if -m <msg> was given' '
+ echo tick >file &&
+ git add file &&
+ echo "editor not started" >.git/result &&
+ (GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" git commit -m tick) &&
+ test "$(cat .git/result)" = "editor not started"
+'
+
+test_expect_success 'do not fire editor if -m "" was given' '
+ echo tock >file &&
+ git add file &&
+ echo "editor not started" >.git/result &&
+ (GIT_EDITOR="\"$(pwd)/.git/FAKE_EDITOR\"" \
+ git commit -m "" --allow-empty-message) &&
+ test "$(cat .git/result)" = "editor not started"
+'
+
test_expect_success 'do not fire editor in the presence of conflicts' '
git clean -f &&
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
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)
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")
#
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)
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")
#
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)
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")
#
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)
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)
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")
#
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)
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)
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")
#
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)
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)
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")
#
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)
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)
#
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)
#
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:
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)
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:
#
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
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
'
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
'
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)' '
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
'
)
'
+run_dir_diff_test 'difftool --dir-diff when worktree file is missing' '
+ test_when_finished git reset --hard &&
+ rm file2 &&
+ git difftool --dir-diff $symlinks --extcmd ls branch master >output &&
+ grep file2 output
+'
+
write_script .git/CHECK_SYMLINKS <<\EOF
for f in file file2 sub/sub
do
test_cmp actual expect
'
+write_script modify-right-file <<\EOF
+echo "new content" >"$2/file"
+EOF
+
+run_dir_diff_test 'difftool --dir-diff syncs worktree with unstaged change' '
+ test_when_finished git reset --hard &&
+ echo "orig content" >file &&
+ git difftool -d $symlinks --extcmd "$(pwd)/modify-right-file" branch &&
+ echo "new content" >expect &&
+ test_cmp expect file
+'
+
+run_dir_diff_test 'difftool --dir-diff syncs worktree without unstaged change' '
+ test_when_finished git reset --hard &&
+ git difftool -d $symlinks --extcmd "$(pwd)/modify-right-file" branch &&
+ echo "new content" >expect &&
+ test_cmp expect file
+'
+
write_script modify-file <<\EOF
echo "new content" >file
EOF
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)
'
. "$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
grep "has only 2 lines" errors
'
+test_expect_success 'blame parses <end> part of -L' '
+ git blame -L1,1 tres >out &&
+ cat out &&
+ test $(wc -l < out) -eq 1
+'
+
test_expect_success 'indent of line numbers, nine lines' '
git blame nine_lines >actual &&
test $(grep -c " " actual) = 0
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"
'
# 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
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 &&
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 &&
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
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
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 \
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 &&
grep "In-Reply-To: " stdout
'
-test_expect_success $PREREQ 'warning with an implicit --chain-reply-to' '
- git send-email \
- --dry-run \
- --from="Example <nobody@example.com>" \
- --to=nobody@example.com \
- outdir/000?-*.patch 2>errors >out &&
- grep "no-chain-reply-to" errors
-'
-
-test_expect_success $PREREQ 'no warning with an explicit --chain-reply-to' '
- git send-email \
- --dry-run \
- --from="Example <nobody@example.com>" \
- --to=nobody@example.com \
- --chain-reply-to \
- outdir/000?-*.patch 2>errors >out &&
- ! grep "no-chain-reply-to" errors
-'
-
-test_expect_success $PREREQ 'no warning with an explicit --no-chain-reply-to' '
- git send-email \
- --dry-run \
- --from="Example <nobody@example.com>" \
- --to=nobody@example.com \
- --nochain-reply-to \
- outdir/000?-*.patch 2>errors >out &&
- ! grep "no-chain-reply-to" errors
-'
-
-test_expect_success $PREREQ 'no warning with sendemail.chainreplyto = false' '
- git config sendemail.chainreplyto false &&
- git send-email \
- --dry-run \
- --from="Example <nobody@example.com>" \
- --to=nobody@example.com \
- outdir/000?-*.patch 2>errors >out &&
- ! grep "no-chain-reply-to" errors
-'
-
-test_expect_success $PREREQ 'no warning with sendemail.chainreplyto = true' '
- git config sendemail.chainreplyto true &&
- git send-email \
- --dry-run \
- --from="Example <nobody@example.com>" \
- --to=nobody@example.com \
- outdir/000?-*.patch 2>errors >out &&
- ! grep "no-chain-reply-to" errors
-'
-
test_expect_success $PREREQ 'sendemail.to works' '
git config --replace-all sendemail.to "Somebody <somebody@ex.com>" &&
git send-email \
test_expect_success 'setup git mirror and merge' '
git svn init "$svnrepo" -t tags -T trunk -b branches &&
git svn fetch &&
- git checkout --track -b svn remotes/trunk &&
+ git checkout -b svn remotes/trunk &&
git checkout -b merge &&
echo new file > new_file &&
git add new_file &&
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2013 Paul Walmsley - based on t9134 by Vitaly Shukela
+#
+
+test_description='git svn property tests'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup test repository' '
+ svn_cmd co "$svnrepo" s &&
+ (
+ cd s &&
+ mkdir qqq www xxx &&
+ echo test_qqq > qqq/test_qqq.txt &&
+ echo test_www > www/test_www.txt &&
+ echo test_xxx > xxx/test_xxx.txt &&
+ svn_cmd add qqq &&
+ svn_cmd add www &&
+ svn_cmd add xxx &&
+ svn_cmd commit -m "create some files" &&
+ svn_cmd up &&
+ echo hi >> www/test_www.txt &&
+ svn_cmd commit -m "modify www/test_www.txt" &&
+ svn_cmd up
+ )
+'
+
+test_expect_success 'clone an SVN repository with filter to include qqq directory' '
+ git svn clone --include-paths="qqq" "$svnrepo" g &&
+ echo test_qqq > expect &&
+ for i in g/*/*.txt; do cat $i >> expect2; done &&
+ test_cmp expect expect2
+'
+
+
+test_expect_success 'init+fetch an SVN repository with included qqq directory' '
+ git svn init "$svnrepo" c &&
+ ( cd c && git svn fetch --include-paths="qqq" ) &&
+ rm expect2 &&
+ echo test_qqq > expect &&
+ for i in c/*/*.txt; do cat $i >> expect2; done &&
+ test_cmp expect expect2
+'
+
+test_expect_success 'verify include-paths config saved by clone' '
+ (
+ cd g &&
+ git config --get svn-remote.svn.include-paths | fgrep "qqq"
+ )
+'
+
+test_expect_success 'SVN-side change outside of www' '
+ (
+ cd s &&
+ echo b >> qqq/test_qqq.txt &&
+ svn_cmd commit -m "SVN-side change outside of www" &&
+ svn_cmd up &&
+ svn_cmd log -v | fgrep "SVN-side change outside of www"
+ )
+'
+
+test_expect_success 'update git svn-cloned repo (config include)' '
+ (
+ cd g &&
+ git svn rebase &&
+ printf "test_qqq\nb\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_expect_success 'update git svn-cloned repo (option include)' '
+ (
+ cd c &&
+ git svn rebase --include-paths="qqq" &&
+ printf "test_qqq\nb\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_expect_success 'SVN-side change inside of ignored www' '
+ (
+ cd s &&
+ echo zaq >> www/test_www.txt
+ svn_cmd commit -m "SVN-side change inside of www/test_www.txt" &&
+ svn_cmd up &&
+ svn_cmd log -v | fgrep "SVN-side change inside of www/test_www.txt"
+ )
+'
+
+test_expect_success 'update git svn-cloned repo (config include)' '
+ (
+ cd g &&
+ git svn rebase &&
+ printf "test_qqq\nb\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_expect_success 'update git svn-cloned repo (option include)' '
+ (
+ cd c &&
+ git svn rebase --include-paths="qqq" &&
+ printf "test_qqq\nb\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_expect_success 'SVN-side change in and out of included qqq' '
+ (
+ cd s &&
+ echo cvf >> www/test_www.txt
+ echo ygg >> qqq/test_qqq.txt
+ svn_cmd commit -m "SVN-side change in and out of ignored www" &&
+ svn_cmd up &&
+ svn_cmd log -v | fgrep "SVN-side change in and out of ignored www"
+ )
+'
+
+test_expect_success 'update git svn-cloned repo again (config include)' '
+ (
+ cd g &&
+ git svn rebase &&
+ printf "test_qqq\nb\nygg\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_expect_success 'update git svn-cloned repo again (option include)' '
+ (
+ cd c &&
+ git svn rebase --include-paths="qqq" &&
+ printf "test_qqq\nb\nygg\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_done
test "$mergeinfo" = "/branches/svnb1:2-4,7-9,13-18
/branches/svnb2:3,8,16-17
/branches/svnb3:4,9
-/branches/svnb4:5-6,10-12
/branches/svnb5:6,11"
'
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2013 Tobias Schulte
+#
+
+test_description='git svn branch for subproject clones'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize svnrepo' '
+ mkdir import &&
+ (
+ cd import &&
+ mkdir -p trunk/project branches tags &&
+ (
+ cd trunk/project &&
+ echo foo > foo
+ ) &&
+ svn_cmd import -m "import for git-svn" . "$svnrepo" >/dev/null
+ ) &&
+ rm -rf import &&
+ svn_cmd co "$svnrepo"/trunk/project trunk/project &&
+ (
+ cd trunk/project &&
+ echo bar >> foo &&
+ svn_cmd ci -m "updated trunk"
+ ) &&
+ rm -rf trunk
+'
+
+test_expect_success 'import into git' '
+ git svn init --trunk=trunk/project --branches=branches/*/project \
+ --tags=tags/*/project "$svnrepo" &&
+ git svn fetch &&
+ git checkout remotes/trunk
+'
+
+test_expect_success 'git svn branch tests' '
+ test_must_fail git svn branch a &&
+ git svn branch --parents a &&
+ test_must_fail git svn branch -t tag1 &&
+ git svn branch --parents -t tag1 &&
+ test_must_fail git svn branch --tag tag2 &&
+ git svn branch --parents --tag tag2 &&
+ test_must_fail git svn tag tag3 &&
+ git svn tag --parents tag3
+'
+
+test_done
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 &&
(
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
) &&
(
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 &&
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
'
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 &&
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"'
# ----------------------------------------------------------------------
# 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 &&
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" &&
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"
)
'
)
'
+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
'
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
)
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
)
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
}
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
--exec-path Z
--exec-path=
--html-path Z
+ --man-path Z
--info-path Z
--work-tree=
--namespace=
test_completion "git send-email ma" "master "
'
+test_expect_success 'complete files' '
+ git init tmp && cd tmp &&
+ test_when_finished "cd .. && rm -rf tmp" &&
+
+ echo "expected" > .gitignore &&
+ echo "out" >> .gitignore &&
+
+ git add .gitignore &&
+ test_completion "git commit " ".gitignore" &&
+
+ git commit -m ignore &&
+
+ touch new &&
+ test_completion "git add " "new" &&
+
+ git add new &&
+ git commit -a -m new &&
+ test_completion "git add " "" &&
+
+ git mv new modified &&
+ echo modify > modified &&
+ test_completion "git add " "modified" &&
+
+ touch untracked &&
+
+ : TODO .gitignore should not be here &&
+ test_completion "git rm " <<-\EOF &&
+ .gitignore
+ modified
+ EOF
+
+ test_completion "git clean " "untracked" &&
+
+ : TODO .gitignore should not be here &&
+ test_completion "git mv " <<-\EOF &&
+ .gitignore
+ modified
+ EOF
+
+ mkdir dir &&
+ touch dir/file-in-dir &&
+ git add dir/file-in-dir &&
+ git commit -m dir &&
+
+ mkdir untracked-dir &&
+
+ : TODO .gitignore should not be here &&
+ test_completion "git mv modified " <<-\EOF &&
+ .gitignore
+ dir
+ modified
+ untracked
+ untracked-dir
+ EOF
+
+ test_completion "git commit " "modified" &&
+
+ : TODO .gitignore should not be here &&
+ test_completion "git ls-files " <<-\EOF
+ .gitignore
+ dir
+ modified
+ EOF
+
+ touch momified &&
+ test_completion "git add mom" "momified"
+'
+
+test_expect_failure 'complete with tilde expansion' '
+ git init tmp && cd tmp &&
+ test_when_finished "cd .. && rm -rf tmp" &&
+
+ touch ~/tmp/file &&
+
+ test_completion "git add ~/tmp/" "~/tmp/file"
+'
+
test_done
. "$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 " ((t2)|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"
'
}
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"
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"
test_failure_ "$@"
fi
fi
- echo >&3 ""
+ test_finish_
}
# test_external runs external test scripts that provide continuous
$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 &&
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
+}
# do not redirect again
;;
*' --tee '*|*' --va'*)
- mkdir -p test-results
- BASE=test-results/$(basename "$0" .sh)
+ mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results"
+ BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)"
(GIT_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1;
echo $? > $BASE.exit) | tee $BASE.out
test "$(cat $BASE.exit)" = 0
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
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.
--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
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; }
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.
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
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
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
;;
*)
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"
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
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" &&
*)
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 ;;
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 () {
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) ||
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 () {
test_set_prereq MINGW
test_set_prereq NOT_CYGWIN
test_set_prereq SED_STRIPS_CR
+ test_set_prereq GREP_STRIPS_CR
;;
*CYGWIN*)
test_set_prereq POSIXPERM
test_set_prereq NOT_MINGW
test_set_prereq CYGWIN
test_set_prereq SED_STRIPS_CR
+ test_set_prereq GREP_STRIPS_CR
;;
*)
test_set_prereq POSIXPERM
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 '
#!/bin/sh
-out_prefix=$(dirname "$0")/../test-results/valgrind.out
+# Get TEST_OUTPUT_DIRECTORY from GIT-BUILD-OPTIONS if it's there...
+. "$(dirname "$0")/../../GIT-BUILD-OPTIONS"
+# ... otherwise set it to the default value.
+: ${TEST_OUTPUT_DIRECTORY=$(dirname "$0")/..}
+
output=
count=0
total_count=0
finish_output
}
-for test_script in "$(dirname "$0")"/../test-results/*.out
+for test_script in "$TEST_OUTPUT_DIRECTORY"/test-results/*.out
do
handle_one $test_script
done
TOOL_OPTIONS='--leak-check=no'
+test -z "$GIT_VALGRIND_ENABLED" &&
+exec "$GIT_VALGRIND"/../../"$base" "$@"
+
case "$GIT_VALGRIND_MODE" in
memcheck-fast)
;;
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
-# If you want to allow non-ascii filenames set this variable to true.
+# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
-# Cross platform projects tend to avoid non-ascii filenames; prevent
+# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
test $(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
- echo "Error: Attempt to add a non-ascii file name."
- echo
- echo "This can cause problems if you want to work"
- echo "with people on other platforms."
- echo
- echo "To be portable it is advisable to rename the file ..."
- echo
- echo "If you know what you are doing you can disable this"
- echo "check using:"
- echo
- echo " git config hooks.allownonascii true"
- echo
+ cat <<\EOF
+Error: Attempt to add a non-ASCII file name.
+
+This can cause problems if you want to work with people on other platforms.
+
+To be portable it is advisable to rename the file.
+
+If you know what you are doing you can disable this check using:
+
+ git config hooks.allownonascii true
+EOF
exit 1
fi
return 1;
}
-int main(int argc, const char *argv[])
+int main(int argc, char *argv[])
{
static int verbose;
if (stat(argv[i], &sb) < 0) {
fprintf(stderr, "Failed to stat %s: %s\n",
argv[i], strerror(errno));
- return -1;
+ return 1;
}
-#ifdef WIN32
+#ifdef GIT_WINDOWS_NATIVE
if (!(sb.st_mode & S_IWUSR) &&
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
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;
}
}
usage:
fprintf(stderr, "usage: %s %s\n", argv[0], usage_str);
- return -1;
+ return 1;
}
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, "");
}
#include "cache.h"
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
struct cache_header hdr;
int version;
return strcmp(x->text, y->text);
}
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
struct line *line, *p = NULL, *lines = NULL;
struct strbuf sb = STRBUF_INIT;
return 0;
}
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
const char *prefix = "prefix/";
const char *usage[] = {
};
int i;
- argc = parse_options(argc, argv, prefix, options, usage, 0);
+ argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);
printf("boolean: %d\n", boolean);
printf("integer: %u\n", integer);
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")) {
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;
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+}
#include "cache.h"
#include "run-command.h"
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
{
struct child_process cp;
int nogit = 0;
}
memset(&cp, 0, sizeof(cp));
cp.git_cmd = 1;
- cp.argv = argv + 1;
+ cp.argv = (const char **)argv + 1;
return run_command(&cp);
}
long ncpus;
#endif
-#ifdef _WIN32
+#ifdef GIT_WINDOWS_NATIVE
SYSTEM_INFO info;
GetSystemInfo(&info);
strbuf_release(&buf);
}
+__attribute__((format (printf, 2, 3)))
static void trace_printf_key(const char *key, const char *fmt, ...)
{
va_list ap;
#include "thread-utils.h"
#include "sigchain.h"
#include "argv-array.h"
+#include "refs.h"
static int debug;
die_errno("Full write to remote helper failed");
}
-static int recvline_fh(FILE *helper, struct strbuf *buffer)
+static int recvline_fh(FILE *helper, struct strbuf *buffer, const char *name)
{
strbuf_reset(buffer);
if (debug)
static int recvline(struct helper_data *helper, struct strbuf *buffer)
{
- return recvline_fh(helper->out, buffer);
+ return recvline_fh(helper->out, buffer, helper->name);
}
static void xchgline(struct helper_data *helper, struct strbuf *buffer)
int i;
data->refspec_nr = refspec_nr;
data->refspecs = parse_fetch_refspec(refspec_nr, refspecs);
- for (i = 0; i < refspec_nr; i++) {
+ for (i = 0; i < refspec_nr; i++)
free((char *)refspecs[i]);
- }
free(refspecs);
+ } else if (data->import || data->bidi_import || data->export) {
+ warning("This remote helper should implement refspec capability.");
}
strbuf_release(&buf);
if (debug)
* were fetching.
*
* (If no "refspec" capability was specified, for historical
- * reasons we default to *:*.)
+ * reasons we default to the equivalent of *:*.)
*
* Store the result in to_fetch[i].old_sha1. Callers such
* as "git fetch" can use the value to write feedback to the
goto exit;
sendline(data, &cmdbuf);
- recvline_fh(input, &cmdbuf);
+ recvline_fh(input, &cmdbuf, name);
if (!strcmp(cmdbuf.buf, "")) {
data->no_disconnect_req = 1;
if (debug)
return -1;
}
-static void push_update_ref_status(struct strbuf *buf,
+static int push_update_ref_status(struct strbuf *buf,
struct ref **ref,
struct ref *remote_refs)
{
*ref = find_ref_by_name(remote_refs, refname);
if (!*ref) {
warning("helper reported unexpected status of %s", refname);
- return;
+ return 1;
}
if ((*ref)->status != REF_STATUS_NONE) {
* status reported by the remote helper if the latter is 'no match'.
*/
if (status == REF_STATUS_NONE)
- return;
+ return 1;
}
(*ref)->status = status;
(*ref)->remote_status = msg;
+ return !(status == REF_STATUS_OK);
}
static void push_update_refs_status(struct helper_data *data,
struct strbuf buf = STRBUF_INIT;
struct ref *ref = remote_refs;
for (;;) {
+ char *private;
+
recvline(data, &buf);
if (!buf.len)
break;
- push_update_ref_status(&buf, &ref, remote_refs);
+ if (push_update_ref_status(&buf, &ref, remote_refs))
+ continue;
+
+ if (!data->refspecs)
+ continue;
+
+ /* propagate back the update to the remote namespace */
+ private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
+ if (!private)
+ continue;
+ update_ref("update by helper", private, ref->new_sha1, NULL, 0, 0);
+ free(private);
}
strbuf_release(&buf);
}
struct string_list revlist_args = STRING_LIST_INIT_NODUP;
struct strbuf buf = STRBUF_INIT;
+ if (!data->refspecs)
+ die("remote-helper doesn't support push; refspec needed");
+
+ if (flags & TRANSPORT_PUSH_DRY_RUN) {
+ if (set_helper_option(transport, "dry-run", "true") != 0)
+ die("helper %s does not support dry-run", data->name);
+ }
+
helper = get_helper(transport);
write_constant(helper->in, "export\n");
char *private;
unsigned char sha1[20];
- if (!data->refspecs)
- continue;
+ if (ref->deletion)
+ die("remote-helpers do not support ref deletion");
+
private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
if (private && !get_sha1(private, sha1)) {
strbuf_addf(&buf, "^%s", private);
string_list_append(&revlist_args, strbuf_detach(&buf, NULL));
+ hashcpy(ref->old_sha1, sha1);
}
free(private);
- if (ref->deletion) {
+ if (ref->deletion)
die("remote-helpers do not support ref deletion");
- }
- if (ref->peer_ref)
+ if (ref->peer_ref) {
+ if (strcmp(ref->peer_ref->name, ref->name))
+ die("remote-helpers do not support old:new syntax");
string_list_append(&revlist_args, ref->peer_ref->name);
-
+ }
}
if (get_exporter(transport, &exporter, &revlist_args))
#define PBUFFERSIZE 8192
/* Print bidirectional transfer loop debug message. */
+__attribute__((format (printf, 1, 2)))
static void transfer_debug(const char *fmt, ...)
{
va_list args;
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;
args.quiet = (transport->verbose < 0);
args.no_progress = !transport->progress;
args.depth = data->options.depth;
+ args.check_self_contained_and_connected =
+ data->options.check_self_contained_and_connected;
if (!data->got_remote_heads) {
connect_setup(transport, 0, 0);
refs = NULL;
data->conn = NULL;
data->got_remote_heads = 0;
+ data->options.self_contained_and_connected =
+ args.self_contained_and_connected;
free_refs(refs_tmp);
* 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)
unsigned thin : 1;
unsigned keep : 1;
unsigned followtags : 1;
+ unsigned check_self_contained_and_connected : 1;
+ unsigned self_contained_and_connected : 1;
int depth;
const char *uploadpack;
const char *receivepack;
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);
int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
{
- int ret = 0;
int error = 0;
struct name_entry *entry = xmalloc(n*sizeof(*entry));
int i;
strbuf_setlen(&base, info->pathlen);
}
for (;;) {
+ int trees_used;
unsigned long mask, dirmask;
const char *first = NULL;
int first_len = 0;
if (interesting < 0)
break;
if (interesting) {
- ret = info->fn(n, mask, dirmask, entry, info);
- if (ret < 0) {
- error = ret;
+ trees_used = info->fn(n, mask, dirmask, entry, info);
+ if (trees_used < 0) {
+ error = trees_used;
if (!info->show_all_errors)
break;
}
- mask &= ret;
+ mask &= trees_used;
}
- ret = 0;
for (i = 0; i < n; i++)
if (mask & (1ul << i))
update_extended_entry(tx + i, entry + i);
int pathlen;
struct pathspec *pathspec;
- unsigned long conflicts;
+ unsigned long df_conflicts;
traverse_callback_t fn;
void *data;
int show_all_errors;
* 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;
}
ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
}
-static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
- unsigned int set, unsigned int clear)
+static struct cache_entry *dup_entry(const struct cache_entry *ce)
{
unsigned int size = ce_size(ce);
struct cache_entry *new = xmalloc(size);
memcpy(new, ce, size);
- do_add_entry(o, new, set, clear);
+ return new;
+}
+
+static void add_entry(struct unpack_trees_options *o,
+ const struct cache_entry *ce,
+ unsigned int set, unsigned int clear)
+{
+ do_add_entry(o, dup_entry(ce), set, clear);
}
/*
* 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;
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++;
}
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);
return errs != 0;
}
-static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o);
-static int verify_absent_sparse(struct cache_entry *ce, enum unpack_trees_error_types, struct unpack_trees_options *o);
+static int verify_uptodate_sparse(const struct cache_entry *ce,
+ struct unpack_trees_options *o);
+static int verify_absent_sparse(const struct cache_entry *ce,
+ enum unpack_trees_error_types,
+ struct unpack_trees_options *o);
static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o)
{
return 0;
}
-static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o)
+static inline int call_unpack_fn(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
{
int ret = o->fn(src, o);
if (ret > 0)
index->cache[i]->ce_flags &= ~(CE_UNPACKED | CE_ADDED | CE_NEW_SKIP_WORKTREE);
}
-static int locate_in_src_index(struct cache_entry *ce,
+static int locate_in_src_index(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
struct index_state *index = o->src_index;
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;
static int unpack_index_entry(struct cache_entry *ce,
struct unpack_trees_options *o)
{
- struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
+ const struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
int ret;
src[0] = ce;
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;
{
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.
src[i + o->merge] = create_ce_entry(info, names + i, stage);
}
- if (o->merge)
- return call_unpack_fn(src, o);
+ if (o->merge) {
+ int rc = call_unpack_fn((const struct cache_entry * const *)src,
+ o);
+ for (i = 0; i < n; i++) {
+ struct cache_entry *ce = src[i + o->merge];
+ if (ce != o->df_conflict_entry)
+ free(ce);
+ }
+ return rc;
+ }
for (i = 0; i < n; i++)
if (src[i] && src[i] != o->df_conflict_entry)
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;
/* 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)) {
}
}
- if (traverse_trees_recursive(n, dirmask, conflicts,
+ if (traverse_trees_recursive(n, dirmask, mask & ~dirmask,
names, info) < 0)
return -1;
return mask;
select_flag, skip_wt_flag, el);
}
-static int verify_absent(struct cache_entry *, enum unpack_trees_error_types, struct unpack_trees_options *);
+static int verify_absent(const struct cache_entry *,
+ enum unpack_trees_error_types,
+ struct unpack_trees_options *);
/*
* N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the
* resulting index, -2 on failure to reflect the changes to the work tree.
/* Here come the merge functions */
-static int reject_merge(struct cache_entry *ce, struct unpack_trees_options *o)
+static int reject_merge(const struct cache_entry *ce,
+ struct unpack_trees_options *o)
{
return add_rejected_path(o, ERROR_WOULD_OVERWRITE, ce->name);
}
-static int same(struct cache_entry *a, struct cache_entry *b)
+static int same(const struct cache_entry *a, const struct cache_entry *b)
{
if (!!a != !!b)
return 0;
* When a CE gets turned into an unmerged entry, we
* want it to be up-to-date
*/
-static int verify_uptodate_1(struct cache_entry *ce,
- struct unpack_trees_options *o,
- enum unpack_trees_error_types error_type)
+static int verify_uptodate_1(const struct cache_entry *ce,
+ struct unpack_trees_options *o,
+ enum unpack_trees_error_types error_type)
{
struct stat st;
add_rejected_path(o, error_type, ce->name);
}
-static int verify_uptodate(struct cache_entry *ce,
+static int verify_uptodate(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE);
}
-static int verify_uptodate_sparse(struct cache_entry *ce,
+static int verify_uptodate_sparse(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE);
}
-static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o)
+static void invalidate_ce_path(const struct cache_entry *ce,
+ struct unpack_trees_options *o)
{
if (ce)
cache_tree_invalidate_path(o->src_index->cache_tree, ce->name);
* Currently, git does not checkout subprojects during a superproject
* checkout, so it is not going to overwrite anything.
*/
-static int verify_clean_submodule(struct cache_entry *ce,
- enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+static int verify_clean_submodule(const struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
{
return 0;
}
-static int verify_clean_subdirectory(struct cache_entry *ce,
- enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+static int verify_clean_subdirectory(const struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
{
/*
* we are about to extract "ce->name"; we would not want to lose
*/
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);
}
static int check_ok_to_remove(const char *name, int len, int dtype,
- struct cache_entry *ce, struct stat *st,
+ const struct cache_entry *ce, struct stat *st,
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
* We do not want to remove or overwrite a working tree file that
* is not tracked, unless it is ignored.
*/
-static int verify_absent_1(struct cache_entry *ce,
- enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+static int verify_absent_1(const struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
{
int len;
struct stat st;
}
}
-static int verify_absent(struct cache_entry *ce,
+static int verify_absent(const struct cache_entry *ce,
enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
return verify_absent_1(ce, error_type, o);
}
-static int verify_absent_sparse(struct cache_entry *ce,
- enum unpack_trees_error_types error_type,
- struct unpack_trees_options *o)
+static int verify_absent_sparse(const struct cache_entry *ce,
+ enum unpack_trees_error_types error_type,
+ struct unpack_trees_options *o)
{
enum unpack_trees_error_types orphaned_error = error_type;
if (orphaned_error == ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN)
return verify_absent_1(ce, orphaned_error, o);
}
-static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
- struct unpack_trees_options *o)
+static int merged_entry(const struct cache_entry *ce,
+ const struct cache_entry *old,
+ struct unpack_trees_options *o)
{
int update = CE_UPDATE;
+ struct cache_entry *merge = dup_entry(ce);
if (!old) {
/*
update |= CE_ADDED;
merge->ce_flags |= CE_NEW_SKIP_WORKTREE;
- if (verify_absent(merge, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
+ if (verify_absent(merge,
+ ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
+ free(merge);
return -1;
+ }
invalidate_ce_path(merge, o);
} else if (!(old->ce_flags & CE_CONFLICTED)) {
/*
copy_cache_entry(merge, old);
update = 0;
} else {
- if (verify_uptodate(old, o))
+ if (verify_uptodate(old, o)) {
+ free(merge);
return -1;
+ }
/* Migrate old flags over */
update |= old->ce_flags & (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
invalidate_ce_path(old, o);
invalidate_ce_path(old, o);
}
- add_entry(o, merge, update, CE_STAGEMASK);
+ do_add_entry(o, merge, update, CE_STAGEMASK);
return 1;
}
-static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
- struct unpack_trees_options *o)
+static int deleted_entry(const struct cache_entry *ce,
+ const struct cache_entry *old,
+ struct unpack_trees_options *o)
{
/* Did it exist in the index? */
if (!old) {
return 1;
}
-static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o)
+static int keep_entry(const struct cache_entry *ce,
+ struct unpack_trees_options *o)
{
add_entry(o, ce, 0, 0);
return 1;
}
#endif
-int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
+int threeway_merge(const struct cache_entry * const *stages,
+ struct unpack_trees_options *o)
{
- struct cache_entry *index;
- struct cache_entry *head;
- struct cache_entry *remote = stages[o->head_idx + 1];
+ const struct cache_entry *index;
+ const struct cache_entry *head;
+ const struct cache_entry *remote = stages[o->head_idx + 1];
int count;
int head_match = 0;
int remote_match = 0;
if (o->aggressive) {
int head_deleted = !head;
int remote_deleted = !remote;
- struct cache_entry *ce = NULL;
+ const struct cache_entry *ce = NULL;
if (index)
ce = index;
* "carry forward" rule, please see <Documentation/git-read-tree.txt>.
*
*/
-int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o)
+int twoway_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
{
- struct cache_entry *current = src[0];
- struct cache_entry *oldtree = src[1];
- struct cache_entry *newtree = src[2];
+ const struct cache_entry *current = src[0];
+ const struct cache_entry *oldtree = src[1];
+ const struct cache_entry *newtree = src[2];
if (o->merge_size != 2)
return error("Cannot do a twoway merge of %d trees",
* Keep the index entries at stage0, collapse stage1 but make sure
* stage0 does not have anything there.
*/
-int bind_merge(struct cache_entry **src,
- struct unpack_trees_options *o)
+int bind_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
{
- struct cache_entry *old = src[0];
- struct cache_entry *a = src[1];
+ const struct cache_entry *old = src[0];
+ const struct cache_entry *a = src[1];
if (o->merge_size != 1)
return error("Cannot do a bind merge of %d trees",
* The rule is:
* - take the stat information from stage0, take the data from stage1
*/
-int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o)
+int oneway_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o)
{
- struct cache_entry *old = src[0];
- struct cache_entry *a = src[1];
+ const struct cache_entry *old = src[0];
+ const struct cache_entry *a = src[1];
if (o->merge_size != 1)
return error("Cannot do a oneway merge of %d trees",
struct unpack_trees_options;
struct exclude_list;
-typedef int (*merge_fn_t)(struct cache_entry **src,
+typedef int (*merge_fn_t)(const struct cache_entry * const *src,
struct unpack_trees_options *options);
enum unpack_trees_error_types {
extern int unpack_trees(unsigned n, struct tree_desc *t,
struct unpack_trees_options *options);
-int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o);
-int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o);
-int bind_merge(struct cache_entry **src, struct unpack_trees_options *o);
-int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o);
+int threeway_merge(const struct cache_entry * const *stages,
+ struct unpack_trees_options *o);
+int twoway_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o);
+int bind_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o);
+int oneway_merge(const struct cache_entry * const *src,
+ struct unpack_trees_options *o);
#endif
die("invalid shallow line: %s", line);
object = parse_object(sha1);
if (!object)
- die("did not find object for %s", line);
+ continue;
if (object->type != OBJ_COMMIT)
die("invalid shallow object %s", sha1_to_hex(sha1));
if (!(object->flags & CLIENT_SHALLOW)) {
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,
}
if (t_ch <= p_ch && t_ch >= prev_ch)
matched = 1;
+ else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) {
+ uchar t_ch_upper = toupper(t_ch);
+ if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch)
+ matched = 1;
+ }
p_ch = 0; /* This makes "prev_ch" get set to 0. */
} else if (p_ch == '[' && p[1] == ':') {
const uchar *s;
} else if (CC_EQ(s,i, "upper")) {
if (ISUPPER(t_ch))
matched = 1;
+ else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch))
+ matched = 1;
} else if (CC_EQ(s,i, "xdigit")) {
if (ISXDIGIT(t_ch))
matched = 1;
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
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).
warning(_("unable to access '%s': %s"), path, strerror(errno));
}
-int access_or_warn(const char *path, int mode)
+static int access_error_is_ok(int err, unsigned flag)
+{
+ return err == ENOENT || err == ENOTDIR ||
+ ((flag & ACCESS_EACCES_OK) && err == EACCES);
+}
+
+int access_or_warn(const char *path, int mode, unsigned flag)
{
int ret = access(path, mode);
- if (ret && errno != ENOENT && errno != ENOTDIR)
+ if (ret && !access_error_is_ok(errno, flag))
warn_on_inaccessible(path);
return ret;
}
-int access_or_die(const char *path, int mode)
+int access_or_die(const char *path, int mode, unsigned flag)
{
int ret = access(path, mode);
- if (ret && errno != ENOENT && errno != ENOTDIR)
+ if (ret && !access_error_is_ok(errno, flag))
die_errno(_("unable to access '%s'"), path);
return ret;
}
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)
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;
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) {
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)
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;
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;
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,
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);
}
}
struct grab_1st_switch_cbdata {
- int found;
struct strbuf buf;
unsigned char nsha1[20];
};
for (end = target; *end && *end != '\n'; end++)
;
strbuf_add(&cb->buf, target, end - target);
- cb->found = 1;
return 1;
}
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) &&
} 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);
}
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, '"');
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, '"');
} 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);
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 */
#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)
xch->i2 = i2;
xch->chg1 = chg1;
xch->chg2 = chg2;
+ xch->ignore = 0;
return xch;
}
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)
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;
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);
struct s_xdchange *next;
long i1, i2;
long chg1, chg2;
+ int ignore;
} xdchange_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;
}
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);
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);
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;
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);