git-am
git-annotate
git-apply
-git-applymbox
-git-applypatch
git-archimport
git-archive
git-bisect
git-fetch
git-fetch--tool
git-fetch-pack
+git-filter-branch
git-findtags
git-fmt-merge-msg
git-for-each-ref
git-quiltimport
git-read-tree
git-rebase
+git-rebase--interactive
git-receive-pack
git-reflog
git-relink
git-ssh-pull
git-ssh-push
git-ssh-upload
+git-stash
git-status
git-stripspace
+git-submodule
git-svn
git-svnimport
git-symbolic-ref
git-core-*/?*
gitk-wish
gitweb/gitweb.cgi
+test-absolute-path
test-chmtime
test-date
test-delta
test-dump-cache-tree
test-genrandom
test-match-trees
+test-sha1
common-cmds.h
*.tar.gz
*.dsc
*.deb
git.spec
*.exe
-*.[ao]
+*.[aos]
*.py[co]
config.mak
autom4te.cache
Aneesh Kumar K.V <aneesh.kumar@gmail.com>
Chris Shoemaker <c.shoemaker@cox.net>
+Dana L. How <danahow@gmail.com>
+Dana L. How <how@deathvalley.cswitch.com>
Daniel Barkalow <barkalow@iabervon.org>
David Kågedal <davidk@lysator.liu.se>
Fredrik Kuivinen <freku045@student.liu.se>
Joachim Berdal Haga <cjhaga@fys.uio.no>
Jon Loeliger <jdl@freescale.com>
Jon Seymour <jon@blackcubes.dyndns.org>
+Junio C Hamano <junio@twinsun.com>
Karl Hasselström <kha@treskal.com>
Kent Engstrom <kent@lysator.liu.se>
-Lars Doelle <lars.doelle@on-line.de>
Lars Doelle <lars.doelle@on-line ! de>
+Lars Doelle <lars.doelle@on-line.de>
Lukas Sandström <lukass@etek.chalmers.se>
Martin Langhoff <martin@catalyst.net.nz>
+Michael Coleman <tutufan@gmail.com>
Michele Ballabio <barra_cuda@katamail.com>
+Nanako Shiraishi <nanako3@bluebottle.com>
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk>
René Scharfe <rene.scharfe@lsrfire.ath.cx>
Shawn O. Pearce <spearce@spearce.org>
Theodore Ts'o <tytso@mit.edu>
Tony Luck <tony.luck@intel.com>
-Uwe Kleine-König <zeisberg@informatik.uni-freiburg.de>
Uwe Kleine-König <Uwe_Zeisberger@digi.com>
-Uwe Kleine-König <uzeisberger@io.fsforth.de>
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>
+William Pursell <bill.pursell@gmail.com>
YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
anonymous <linux@horizon.com>
anonymous <linux@horizon.net>
-Dana L. How <how@deathvalley.cswitch.com>
$(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
$(wildcard git-*.txt)) \
gitk.txt
-MAN5_TXT=gitattributes.txt gitignore.txt
+MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt
MAN7_TXT=git.txt
DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT))
prefix?=$(HOME)
bindir?=$(prefix)/bin
-mandir?=$(prefix)/man
+mandir?=$(prefix)/share/man
man1dir=$(mandir)/man1
man5dir=$(mandir)/man5
man7dir=$(mandir)/man7
ASCIIDOC_EXTRA += -a asciidoc7compatible
endif
INSTALL?=install
+RM ?= rm -f
DOC_REF = origin/man
-include ../config.mak.autogen
# Determine "include::" file references in asciidoc files.
#
doc.dep : $(wildcard *.txt) build-docdep.perl
- rm -f $@+ $@
+ $(RM) $@+ $@
perl ./build-docdep.perl >$@+
mv $@+ $@
git.7 git.html: git.txt core-intro.txt
clean:
- rm -f *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 howto-index.txt howto/*.html doc.dep
- rm -f $(cmds_txt) *.made
+ $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 howto-index.txt howto/*.html doc.dep
+ $(RM) $(cmds_txt) *.made
%.html : %.txt
- rm -f $@+ $@
+ $(RM) $@+ $@
$(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \
$(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $<
mv $@+ $@
%.1 %.5 %.7 : %.xml
+ $(RM) $@
xmlto -m callouts.xsl man $<
%.xml : %.txt
- rm -f $@+ $@
+ $(RM) $@+ $@
$(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \
$(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $<
mv $@+ $@
user-manual.xml: user-manual.txt user-manual.conf
$(ASCIIDOC) -b docbook -d book $<
-XSLT = http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl
+XSLT = docbook.xsl
XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css
user-manual.html: user-manual.xml
xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
- rm -f $@+ $@
+ $(RM) $@+ $@
sh ./howto-index.sh $(wildcard howto/*.txt) >$@+
mv $@+ $@
WEBDOC_DEST = /pub/software/scm/git/docs
$(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
- rm -f $@+ $@
+ $(RM) $@+ $@
sed -e '1,/^$$/d' $< | $(ASCIIDOC) -b xhtml11 - >$@+
mv $@+ $@
* Documentation updates
* User manual updates
-
-
* Documentation updates
* User manual updates
-
-
- user-manual has better cross references.
- gitweb installation/deployment procedure is now documented.
-
description was given by the caller.
Also contains various documentation updates.
-
--- /dev/null
+GIT v1.5.3 Release Notes
+========================
+
+Updates since v1.5.2
+--------------------
+
+* The commit walkers other than http are officially deprecated,
+ but still supported for now.
+
+* The submodule support has Porcelain layer.
+
+* There are a handful pack-objects changes to help you cope better
+ with repositories with pathologically large blobs in them.
+
+* For people who need to import from Perforce, a front-end for
+ fast-import is in contrib/fast-import/.
+
+* Comes with git-gui 0.8.0.
+
+* Comes with updated gitk.
+
+* New commands and options.
+
+ - "git log --date=<format>" can use more formats: iso8601, rfc2822.
+
+ - The hunk header output from "git diff" family can be customized
+ with the attributes mechanism. See gitattributes(5) for details.
+
+ - "git stash" allows you to quickly save away your work in
+ progress and replay it later on an updated state.
+
+ - "git rebase" learned an "interactive" mode that let you
+ pick and reorder which commits to rebuild.
+
+ - "git fsck" can save its findings in $GIT_DIR/lost-found, without a
+ separate invocation of "git lost-found" command. The blobs stored by
+ lost-found are stored in plain format to allow you to grep in them.
+
+ - $GIT_WORK_TREE environment variable can be used together with
+ $GIT_DIR to work in a subdirectory of a working tree that is
+ not located at "$GIT_DIR/..".
+
+ - Giving "--file=<file>" option to "git config" is the same as
+ running the command with GIT_CONFIG=<file> environment.
+
+ - "git log" learned a new option "--follow", to follow
+ renaming history of a single file.
+
+ - "git-filter-branch" lets you rewrite the revision history of
+ specified branches. You can specify a number of filters to
+ modify the commits, files and trees.
+
+ - "git-cvsserver" learned new options (--base-path, --export-all,
+ --strict-paths) inspired by git-daemon.
+
+ - "git daemon --base-path-relaxed" can help migrating a repository URL
+ that did not use to use --base-path to use --base-path.
+
+ - "git-commit" can use "-t templatefile" option and commit.template
+ configuration variable to prime the commit message given to you in the
+ editor.
+
+ - "git-submodule" command helps you manage the projects from
+ the superproject that contain them.
+
+ - In addition to core.compression configuration option,
+ core.loosecompression and pack.compression options can
+ independently tweak zlib compression levels used for loose
+ and packed objects.
+
+ - "git-ls-tree -l" shows size of blobs pointed at by the
+ tree entries, similar to "/bin/ls -l".
+
+ - "git-rev-list" learned --regexp-ignore-case and
+ --extended-regexp options to tweak its matching logic used
+ for --grep fitering.
+
+ - "git-describe --contains" is a handier way to call more
+ obscure command "git-name-rev --tags".
+
+ - "git gc --aggressive" tells the command to spend more cycles
+ to optimize the repository harder.
+
+ - "git repack" learned a "window-memory" limit which
+ dynamically reduces the window size to stay within the
+ specified memory usage.
+
+ - "git repack" can be told to split resulting packs to avoid
+ exceeding limit specified with "--max-pack-size".
+
+ - "git fsck" gained --verbose option. This is really really
+ verbose but it might help you identify exact commit that is
+ corrupt in your repository.
+
+ - "git format-patch" learned --numbered-files option. This
+ may be useful for MH users.
+
+ - "git format-patch" learned format.subjectprefix configuration
+ variable, which serves the same purpose as "--subject-prefix"
+ option.
+
+ - "git tag -n -l" shows tag annotations while listing tags.
+
+ - "git cvsimport" can optionally use the separate-remote layout.
+
+ - "git blame" can be told to see through commits that change
+ whitespaces and indentation levels with "-w" option.
+
+ - "git send-email" can be told not to thread the messages when
+ sending out more than one patches.
+
+ - "git config" learned NUL terminated output format via -z to
+ help scripts.
+
+ - "git init -q" makes the command quieter.
+
+* Updated behavior of existing commands.
+
+ - "gitweb" can offer multiple snapshot formats.
+
+ ***NOTE*** Unfortunately, this changes the format of the
+ $feature{snapshot}{default} entry in the per-site
+ configuration file 'gitweb_config.perl'. It used to be a
+ three-element tuple that describe a single format; with the
+ new configuration item format, you only have to say the name
+ of the format ('tgz', 'tbz2' or 'zip'). Please update the
+ your configuration file accordingly.
+
+ - "git diff" (but not the plumbing level "git diff-tree") now
+ recursively descends into trees by default.
+
+ - The editor to use with many interactive commands can be
+ overridden with GIT_EDITOR environment variable, or if it
+ does not exist, with core.editor configuration variable. As
+ before, if you have neither, environment variables VISUAL
+ and EDITOR are consulted in this order, and then finally we
+ fall back on "vi".
+
+ - "git rm --cached" does not complain when removing a newly
+ added file from the index anymore.
+
+ - Options to "git log" to affect how --grep/--author options look for
+ given strings now have shorter abbreviations. -i is for ignore case,
+ and -E is for extended regexp.
+
+ - "git svn dcommit" retains local merge information.
+
+ - "git config" to set values also honors type flags like --bool
+ and --int.
+
+ - core.quotepath configuration can be used to make textual git
+ output to emit most of the characters in the path literally.
+
+ - "git mergetool" chooses its backend more wisely, taking
+ notice of its environment such as use of X, Gnome/KDE, etc.
+
+ - "gitweb" shows merge commits a lot nicer than before. The
+ default view uses more compact --cc format, while the UI
+ allows to choose normal diff with any parent.
+
+ - snapshot files "gitweb" creates from a repository at
+ $path/$project/.git are more useful. We use $project part
+ in the filename, which we used to discard.
+
+ - "git cvsimport" creates lightweight tags; there is no
+ interesting information we can record in an annotated tag,
+ and the handcrafted ones the old code created was not
+ properly formed anyway.
+
+ - "git-push" pretends that you immediately fetched back from
+ the remote by updating corresponding remote tracking
+ branches if you have any.
+
+ - The diffstat given after a merge (or a pull) honors the
+ color.diff configuration.
+
+ - "git commit --amend" is now compatible with various message source
+ options such as -m/-C/-c/-F.
+
+ - "git-apply --whitespace=strip" removes blank lines added at
+ the end of the file.
+
+ - "git-fetch" over git native protocols with "-v" option shows
+ connection status, and the IP address of the other end, to
+ help diagnosing problems.
+
+ - We used to have core.legacyheaders configuration, when
+ set to false, allowed git to write loose objects in a format
+ that mimicks the format used by objects stored in packs. It
+ turns out that this was not so useful. Although we will
+ continue to read objects written in that format, we do not
+ honor that configuration anymore and create loose objects in
+ the legacy/traditional format.
+
+ - "--find-copies-harder" option to diff family can now be
+ spelled as "-C -C" for brevity.
+
+ - "git-mailsplit" (hence "git-am") can read from Maildir
+ formatted mailboxes.
+
+ - "git-cvsserver" does not barf upon seeing "cvs login"
+ request.
+
+ - "pack-objects" honors "delta" attribute set in
+ .gitattributes. It does not attempt to deltify blobs that
+ come from paths with delta attribute set to false.
+
+ - "new-workdir" script (in contrib) can now be used with a
+ bare repository.
+
+ - "git-mergetool" learned to use gvimdiff.
+
+ - "gitview" (in contrib) has a better blame interface.
+
+ - "git log" and friends did not handle a commit log message
+ that is larger than 16kB; they do now.
+
+ - "--pretty=oneline" output format for "git log" and friends
+ deals with "malformed" commit log messages that have more
+ than one lines in the first paragraph better. We used to
+ show the first line, cutting the title at mid-sentence; we
+ concatenate them into a single line and treat the result as
+ "oneline".
+
+ - "git p4import" has been demoted to contrib status. For
+ a superior option, checkout the git-p4 front end to
+ git-fast-import (also in contrib). The man page and p4
+ rpm have been removed as well.
+
+ - "git mailinfo" (hence "am") now tries to see if the message
+ is in utf-8 first, instead of assuming iso-8859-1, if
+ incoming e-mail does not say what encoding it is in.
+
+* Builds
+
+ - old-style function definitions (most notably, a function
+ without parameter defined with "func()", not "func(void)")
+ have been eradicated.
+
+* Performance Tweaks
+
+ - git-pack-objects avoids re-deltification cost by caching
+ small enough delta results it creates while looking for the
+ best delta candidates.
+
+ - git-pack-objects learned a new heuristcs to prefer delta
+ that is shallower in depth over the smallest delta
+ possible. This improves both overall packfile access
+ performance and packfile density.
+
+ - diff-delta code that is used for packing has been improved
+ to work better on big files.
+
+ - when there are more than one pack files in the repository,
+ the runtime used to try finding an object always from the
+ newest packfile; it now tries the same packfile as we found
+ the object requested the last time, which exploits the
+ locality of references.
+
+ - verifying pack contents done by "git fsck --full" got boost
+ by carefully choosing the order to verify objects in them.
+
+
+Fixes since v1.5.2
+------------------
+
+All of the fixes in v1.5.2 maintenance series are included in
+this release, unless otherwise noted.
+
+* Bugfixes
+
+ - "gitweb" had trouble handling non UTF-8 text with older
+ Encode.pm Perl module.
+
+--
+exec >/var/tmp/1
+O=v1.5.3-rc4
+echo O=`git describe refs/heads/master`
+git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
commit message (or just use the option "-s" when
committing) to confirm that you agree to the Developer's
Certificate of Origin
+ - make sure that you have tests for the bug you are fixing
+ - make sure that the test suite passes after your commit
Patch:
- if you change, add, or remove a command line option or
make some other user interface change, the associated
documentation should be updated as well.
+ - if your name is not writable in ASCII, make sure that
+ you send off a message in the correct encoding.
Long version:
$ git fetch http://kernel.org/pub/scm/git/git.git master:test-apply
$ git checkout test-apply
$ git reset --hard
- $ git applymbox a.patch
+ $ git am a.patch
If it does not apply correctly, there can be various reasons.
does not have much to do with your MUA. Please rebase the
patch appropriately.
-* Your MUA corrupted your patch; applymbox would complain that
+* Your MUA corrupted your patch; "am" would complain that
the patch does not apply. Look at .dotest/ subdirectory and
see what 'patch' file contains and check for the common
corruption patterns mentioned above.
--- a/pico/pico.c
+++ b/pico/pico.c
@@ -219,7 +219,9 @@ PICO *pm;
- switch(pico_all_done){ /* prepare for/handle final events */
- case COMP_EXIT : /* already confirmed */
- packheader();
+ switch(pico_all_done){ /* prepare for/handle final events */
+ case COMP_EXIT : /* already confirmed */
+ packheader();
+#if 0
- stripwhitespace();
+ stripwhitespace();
+#endif
- c |= COMP_EXIT;
- break;
-
+ c |= COMP_EXIT;
+ break;
+
(Daniel Barkalow)
[gitlink-inlinemacro]
<a href="{target}.html">{target}{0?({0})}</a>
endif::backend-xhtml11[]
-
-
git-add mainporcelain
git-am mainporcelain
git-annotate ancillaryinterrogators
-git-applymbox ancillaryinterrogators
-git-applypatch purehelpers
git-apply plumbingmanipulators
git-archimport foreignscminterface
git-archive mainporcelain
git-check-ref-format purehelpers
git-cherry ancillaryinterrogators
git-cherry-pick mainporcelain
+git-citool mainporcelain
git-clean mainporcelain
git-clone mainporcelain
git-commit mainporcelain
git-fast-import ancillarymanipulators
git-fetch mainporcelain
git-fetch-pack synchingrepositories
+git-filter-branch ancillarymanipulators
git-fmt-merge-msg purehelpers
git-for-each-ref plumbinginterrogators
git-format-patch mainporcelain
git-gc mainporcelain
git-get-tar-commit-id ancillaryinterrogators
git-grep mainporcelain
+git-gui mainporcelain
git-hash-object plumbingmanipulators
git-http-fetch synchelpers
git-http-push synchelpers
git-sh-setup purehelpers
git-ssh-fetch synchingrepositories
git-ssh-upload synchingrepositories
+git-stash mainporcelain
git-status mainporcelain
git-stripspace purehelpers
+git-submodule mainporcelain
git-svn foreignscminterface
git-svnimport foreignscminterface
git-symbolic-ref plumbingmanipulators
# Proxy settings
[core]
- gitProxy="ssh" for "ssh://kernel.org/"
+ gitProxy="ssh" for "kernel.org"
gitProxy=default-proxy ; for the rest
Variables
the working copy are ignored; useful on broken filesystems like FAT.
See gitlink:git-update-index[1]. True by default.
+core.quotepath::
+ The commands that output paths (e.g. `ls-files`,
+ `diff`), when not given the `-z` option, will quote
+ "unusual" characters in the pathname by enclosing the
+ pathname in a double-quote pair and with backslashes the
+ same way strings in C source code are quoted. If this
+ variable is set to false, the bytes higher than 0x80 are
+ not quoted but output as verbatim. Note that double
+ quote, backslash and control characters are always
+ quoted without `-z` regardless of the setting of this
+ variable.
+
core.autocrlf::
If true, makes git convert `CRLF` at the end of lines in text files to
`LF` when reading from the filesystem, and convert in reverse when
false), while all other repositories are assumed to be bare (bare
= true).
+core.worktree::
+ 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).
+ This can be overriden by the GIT_WORK_TREE environment
+ variable and the '--work-tree' command line option.
+
core.logAllRefUpdates::
Updates to a ref <ref> is logged to the file
"$GIT_DIR/logs/<ref>", by appending the new and old
and might match multiple refs in the .git/refs/ tree. True by default.
core.compression::
+ An integer -1..9, indicating a default compression level.
+ -1 is the zlib default. 0 means no compression,
+ and 1..9 are various speed/size tradeoffs, 9 being slowest.
+
+core.loosecompression::
An integer -1..9, indicating the compression level for objects that
- are not in a pack file. -1 is the zlib and git default. 0 means no
+ are not in a pack file. -1 is the zlib default. 0 means no
compression, and 1..9 are various speed/size tradeoffs, 9 being
- slowest.
-
-core.legacyheaders::
- A boolean which
- changes the format of loose objects so that they are more
- efficient to pack and to send out of the repository over git
- native protocol, since v1.4.2. However, loose objects
- written in the new format cannot be read by git older than
- that version; people fetching from your repository using
- older versions of git over dumb transports (e.g. http)
- will also be affected.
-+
-To let git use the new loose object format, you have to
-set core.legacyheaders to false.
+ slowest. If not set, defaults to core.compression. If that is
+ not set, defaults to 0 (best speed).
core.packedGitWindowSize::
Number of bytes of a pack file to map into memory in a
of files which are not meant to be tracked. See
gitlink:gitignore[5].
+core.editor::
+ Commands such as `commit` and `tag` that lets you edit
+ messages by lauching an editor uses the value of this
+ variable when it is set, and the environment variable
+ `GIT_EDITOR` is not set. The order of preference is
+ `GIT_EDITOR` environment, `core.editor`, `VISUAL` and
+ `EDITOR` environment variables and then finally `vi`.
+
+core.pager::
+ The command that git will use to paginate output. Can be overridden
+ with the `GIT_PAGER` environment variable.
+
alias.*::
Command aliases for the gitlink:git[1] command wrapper - e.g.
after defining "alias.last = cat-file commit HEAD", the invocation
or `untracked` (files which are not tracked by git). The values of
these variables may be specified as in color.branch.<slot>.
+commit.template::
+ Specify a file to use as the template for new commit messages.
+
diff.renameLimit::
The number of files to consider when performing the copy/rename
detection; equivalent to the git diff option '-l'.
`.patch`. Use this variable to change that suffix (make sure to
include the dot if you want it).
+gc.aggressiveWindow::
+ The window size parameter used in the delta compression
+ algorithm used by 'git gc --aggressive'. This defaults
+ to 10.
+
gc.packrefs::
`git gc` does not run `git pack-refs` in a bare repository by
default so that older dumb-transport clients can still fetch
kept for this many days when `git rerere gc` is run.
The default is 15 days. See gitlink:git-rerere[1].
+rerere.enabled::
+ Activate recording of resolved conflicts, so that identical
+ conflict hunks can be resolved automatically, should they
+ be encountered again. See gitlink:git-rerere[1].
+
gitcvs.enabled::
Whether the cvs server interface is enabled for this repository.
See gitlink:git-cvsserver[1].
merge.tool::
Controls which merge resolution program is used by
gitlink:git-mergetool[l]. Valid values are: "kdiff3", "tkdiff",
- "meld", "xxdiff", "emerge", "vimdiff", and "opendiff"
+ "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and "opendiff".
merge.verbosity::
Controls the amount of output shown by the recursive merge
message if conflicts were detected. Level 1 outputs only
conflicts, 2 outputs conflicts and file changes. Level 5 and
above outputs debugging information. The default is level 2.
+ Can be overriden by 'GIT_MERGE_VERBOSITY' environment variable.
merge.<driver>.name::
Defines a human readable name for a custom low-level
The maximum delta depth used by gitlink:git-pack-objects[1] when no
maximum depth is given on the command line. Defaults to 50.
+pack.windowMemory::
+ The window memory size limit used by gitlink:git-pack-objects[1]
+ when no limit is given on the command line. The value can be
+ suffixed with "k", "m", or "g". Defaults to 0, meaning no
+ limit.
+
+pack.compression::
+ An integer -1..9, indicating the compression level for objects
+ in a pack file. -1 is the zlib default. 0 means no
+ compression, and 1..9 are various speed/size tradeoffs, 9 being
+ slowest. If not set, defaults to core.compression. If that is
+ not set, defaults to -1.
+
+pack.deltaCacheSize::
+ The maxium memory in bytes used for caching deltas in
+ gitlink:git-pack-objects[1].
+ A value of 0 means no limit. Defaults to 0.
+
+pack.deltaCacheLimit::
+ The maxium size of a delta, that is cached in
+ gitlink:git-pack-objects[1]. Defaults to 1000.
+
pull.octopus::
The default merge strategy to use when pulling multiple branches
at once.
transfer.unpackLimit::
When `fetch.unpackLimit` or `receive.unpackLimit` are
not set, the value of this variable is used instead.
-
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sadly, many merges aren't trivial. If there are files that have
-been added.moved or removed, or if both branches have modified the
+been added, moved or removed, or if both branches have modified the
same file, you will be left with an index tree that contains "merge
entries" in it. Such an index tree can 'NOT' be written out to a tree
object, and you will have to resolve any such merge clashes using
often the best way of explaining what is going on.
In normal life, most people wouldn't use the "core" git programs
-directly, but rather script around them to make them more palatable.
+directly, but rather script around them to make them more palatable.
Understanding the core git stuff may help some people get those scripts
done, though, and it may also be instructive in helping people
understand what it is that the higher-level helper scripts are actually
-doing.
+doing.
The core git is often called "plumbing", with the prettier user
interfaces on top of it called "porcelain". You may not want to use the
out empty, and the only thing you need to do is find yourself a
subdirectory that you want to use as a working tree - either an empty
one for a totally new project, or an existing working tree that you want
-to import into git.
+to import into git.
For our first example, we're going to start a totally new repository from
scratch, with no pre-existing files, and we'll call it `git-tutorial`.
and see two files:
----------------
-.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
+.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
.git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962
----------------
you've only *told* git about them.
However, since git knows about them, you can now start using some of the
-most basic git commands to manipulate the files or look at their status.
+most basic git commands to manipulate the files or look at their status.
In particular, let's not even check in the two files into git yet, we'll
start off by adding another line to `hello` first:
Remember how we did the `git-update-index` on file `hello` and then we
changed `hello` afterward, and could compare the new state of `hello` with the
-state we saved in the index file?
+state we saved in the index file?
Further, remember how I said that `git-write-tree` writes the contents
of the *index* file to the tree, and thus what we just committed was in
between a committed *tree* and either the index file or the working
tree. In other words, `git-diff-index` wants a tree to be diffed
against, and before we did the commit, we couldn't do that, because we
-didn't have anything to diff against.
+didn't have anything to diff against.
But now we can do
----------------
(where `-p` has the same meaning as it did in `git-diff-files`), and it
-will show us the same difference, but for a totally different reason.
+will show us the same difference, but for a totally different reason.
Now we're comparing the working tree not against the index file,
but against the tree we just wrote. It just so happens that those two
are obviously the same, so we get the same result.
instead compare against just the index cache contents, and ignore the
current working tree state entirely. Since we just wrote the index
file to HEAD, doing `git-diff-index \--cached -p HEAD` should thus return
-an empty set of differences, and that's exactly what it does.
+an empty set of differences, and that's exactly what it does.
[NOTE]
================
----------------
and you will see exactly what has changed in the repository over its
-short history.
+short history.
[NOTE]
The `\--root` flag is a flag to `git-diff-tree` to tell it to
the working tree that it describes" may not be technically 100%
accurate, but it's a good model for all normal use.
-This has two implications:
+This has two implications:
- if you grow bored with the tutorial repository you created (or you've
made a mistake and want to start all over), you can just do simple
the checked out files or even an index file, and will *only* contain the
actual core git files. Such a repository usually doesn't even have the
`.git` subdirectory, but has all the git files directly in the
-repository.
+repository.
To create your own local live copy of such a "raw" git repository, you'd
first create your own subdirectory for the project, and then copy the
$ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
----------------
-followed by
+followed by
----------------
$ git-read-tree HEAD
`-a` flag means "check out all files" (if you have a stale copy or an
older version of a checked out tree you may also need to add the `-f`
flag first, to tell git-checkout-index to *force* overwriting of any old
-files).
+files).
Again, this can all be simplified with
which will end up doing all of the above for you.
You have now successfully copied somebody else's (mine) remote
-repository, and checked it out.
+repository, and checked it out.
Creating a new branch
Branches in git are really nothing more than pointers into the git
object database from within the `.git/refs/` subdirectory, and as we
already discussed, the `HEAD` branch is nothing but a symlink to one of
-these object pointers.
+these object pointers.
You can at any time create a new branch by just picking an arbitrary
point in the project history, and just writing the SHA1 name of that
object into a file under `.git/refs/heads/`. You can use any filename you
want (and indeed, subdirectories), but the convention is that the
"normal" branch is called `master`. That's just a convention, though,
-and nothing enforces it.
+and nothing enforces it.
To show that as an example, let's go back to the git-tutorial repository we
used earlier, and create a branch in it. You do that by simply just
------------
will create a new branch based at the current `HEAD` position, and switch
-to it.
+to it.
[NOTE]
================================================
$ git branch <branchname> [startingpoint]
------------
-which will simply _create_ the branch, but will not do anything further.
+which will simply _create_ the branch, but will not do anything further.
You can then later -- once you decide that you want to actually develop
on that branch -- switch to that branch with a regular `git checkout`
with the branchname as the argument.
will show you graphically both of your branches (that's what the `\--all`
means: normally it will just show you your current `HEAD`) and their
histories. You can also see exactly how they came to be from a common
-source.
+source.
Anyway, let's exit `gitk` (`^Q` or the File menu), and decide that we want
to merge the work we did on the `mybranch` branch into the `master`
file, which had no differences in the `mybranch` branch), and say:
----------------
- Auto-merging hello
- CONFLICT (content): Merge conflict in hello
+ Auto-merging hello
+ CONFLICT (content): Merge conflict in hello
Automatic merge failed; fix up by hand
----------------
propagation to other publicly visible machines:
------------
-$ git push master.kernel.org:/pub/scm/git/git.git/
+$ git push master.kernel.org:/pub/scm/git/git.git/
------------
The output format from "git-diff-index", "git-diff-tree" and
"git-diff-files" are very similar.
-These commands all compare two sets of things; what is
+These commands all compare two sets of things; what is
compared differs:
git-diff-index <tree-ish>::
If there is need for such substitution then the whole
pathname is put in double quotes.
+The similarity index is the percentage of unchanged lines, and
+the dissimilarity index is the percentage of changed lines. It
+is a rounded down integer, followed by a percent sign. The
+similarity index value of 100% is thus reserved for two equal
+files, while 100% dissimilarity means that no line from the old
+file made it into the new one.
+
combined diff format
--------------------
--- a/describe.c
+++ b/describe.c
@@@ -98,20 -98,12 +98,20 @@@
- return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
+ return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
}
-
+
- static void describe(char *arg)
-static void describe(struct commit *cmit, int last_one)
++static void describe(char *arg, int last_one)
{
+ unsigned char sha1[20];
+ struct commit *cmit;
- struct commit_list *list;
- static int initialized = 0;
- struct commit_name *n;
-
+ struct commit_list *list;
+ static int initialized = 0;
+ struct commit_name *n;
+
+ if (get_sha1(arg, sha1) < 0)
+ usage(describe_usage);
+ cmit = lookup_commit_reference(sha1);
+ if (!cmit)
+ usage(describe_usage);
+
- if (!initialized) {
- initialized = 1;
- for_each_ref(get_name);
+ if (!initialized) {
+ initialized = 1;
+ for_each_ref(get_name);
------------
1. It is preceded with a "git diff" header, that looks like
two unresolved merge parents with the working tree file
(i.e. file1 is stage 2 aka "our version", file2 is stage 3 aka
"their version").
-
-u::
Synonym for "-p".
+-U<n>::
+ Shorthand for "--unified=<n>".
+
+--unified=<n>::
+ Generate diffs with <n> lines of context instead of
+ the usual three. Implies "-p".
+
--raw::
Generate the raw format.
Synonym for "-p --stat".
-z::
- \0 line termination on output
+ NUL-line termination on output. This affects the --raw
+ output field terminator. Also output from commands such
+ as "git-log" will be delimited with NUL between commits.
--name-only::
Show only names of changed files.
Detect renames.
-C::
- Detect copies as well as renames.
+ Detect copies as well as renames. See also `--find-copies-harder`.
--diff-filter=[ACDMRTUXB*]::
Select only files that are Added (`A`), Copied (`C`),
that matches other criteria, nothing is selected.
--find-copies-harder::
- For performance reasons, by default, -C option finds copies only
- if the original file of the copy was modified in the same
+ For performance reasons, by default, `-C` option finds copies only
+ if the original file of the copy was modified in the same
changeset. This flag makes the command
inspect unmodified files as candidates for the source of
copy. This is a very expensive operation for large
- projects, so use it with caution.
+ projects, so use it with caution. Giving more than one
+ `-C` option has the same effect.
-l<num>::
-M and -C options require O(n^2) processing time where n
--quiet::
Disable all output of the program. Implies --exit-code.
+--ext-diff::
+ Allow an external diff helper to be executed. If you set an
+ external diff driver with gitlink:gitattributes(5), you need
+ to use this option with gitlink:git-log(1) and friends.
+
+--no-ext-diff::
+ Disallow external diff drivers.
+
For more detailed explanation on these common options, see also
link:diffcore.html[diffcore documentation].
is controlled by giving the pathname parameters to the
git-diff-* commands on the command line. The pathspec is used
to limit the world diff operates in. It removes the filepairs
-outside the specified set of pathnames. E.g. If the input set
+outside the specified set of pathnames. E.g. If the input set
of filepairs included:
------------------------------------------------
*.c
t
------------------------------------------------
-
-/*\r
- CSS stylesheet for XHTML produced by DocBook XSL stylesheets.\r
- Tested with XSL stylesheets 1.61.2, 1.67.2\r
-*/\r
-\r
-span.strong {\r
- font-weight: bold;\r
-}\r
-\r
-body blockquote {\r
- margin-top: .75em;\r
- line-height: 1.5;\r
- margin-bottom: .75em;\r
-}\r
-\r
-html body {\r
- margin: 1em 5% 1em 5%;\r
- line-height: 1.2;\r
-}\r
-\r
-body div {\r
- margin: 0;\r
-}\r
-\r
-h1, h2, h3, h4, h5, h6,\r
-div.toc p b,\r
-div.list-of-figures p b,\r
-div.list-of-tables p b,\r
-div.abstract p.title\r
-{\r
- color: #527bbd;\r
- font-family: tahoma, verdana, sans-serif;\r
-}\r
-\r
-div.toc p:first-child,\r
-div.list-of-figures p:first-child,\r
-div.list-of-tables p:first-child,\r
-div.example p.title\r
-{\r
- margin-bottom: 0.2em;\r
-}\r
-\r
-body h1 {\r
- margin: .0em 0 0 -4%;\r
- line-height: 1.3;\r
- border-bottom: 2px solid silver;\r
-}\r
-\r
-body h2 {\r
- margin: 0.5em 0 0 -4%;\r
- line-height: 1.3;\r
- border-bottom: 2px solid silver;\r
-}\r
-\r
-body h3 {\r
- margin: .8em 0 0 -3%;\r
- line-height: 1.3;\r
-}\r
-\r
-body h4 {\r
- margin: .8em 0 0 -3%;\r
- line-height: 1.3;\r
-}\r
-\r
-body h5 {\r
- margin: .8em 0 0 -2%;\r
- line-height: 1.3;\r
-}\r
-\r
-body h6 {\r
- margin: .8em 0 0 -1%;\r
- line-height: 1.3;\r
-}\r
-\r
-body hr {\r
- border: none; /* Broken on IE6 */\r
-}\r
-div.footnotes hr {\r
- border: 1px solid silver;\r
-}\r
-\r
-div.navheader th, div.navheader td, div.navfooter td {\r
- font-family: sans-serif;\r
- font-size: 0.9em;\r
- font-weight: bold;\r
- color: #527bbd;\r
-}\r
-div.navheader img, div.navfooter img {\r
- border-style: none;\r
-}\r
-div.navheader a, div.navfooter a {\r
- font-weight: normal;\r
-}\r
-div.navfooter hr {\r
- border: 1px solid silver;\r
-}\r
-\r
-body td {\r
- line-height: 1.2\r
-}\r
-\r
-body th {\r
- line-height: 1.2;\r
-}\r
-\r
-ol {\r
- line-height: 1.2;\r
-}\r
-\r
-ul, body dir, body menu {\r
- line-height: 1.2;\r
-}\r
-\r
-html {\r
- margin: 0; \r
- padding: 0;\r
-}\r
-\r
-body h1, body h2, body h3, body h4, body h5, body h6 {\r
- margin-left: 0\r
-} \r
-\r
-body pre {\r
- margin: 0.5em 10% 0.5em 1em;\r
- line-height: 1.0;\r
- color: navy;\r
-}\r
-\r
-tt.literal, code.literal {\r
- color: navy;\r
-}\r
-\r
-div.literallayout p {\r
- padding: 0em;\r
- margin: 0em;\r
-}\r
-\r
-div.literallayout {\r
- font-family: monospace;\r
-# margin: 0.5em 10% 0.5em 1em;\r
- margin: 0em;\r
- color: navy;\r
- border: 1px solid silver;\r
- background: #f4f4f4;\r
- padding: 0.5em;\r
-}\r
-\r
-.programlisting, .screen {\r
- border: 1px solid silver;\r
- background: #f4f4f4;\r
- margin: 0.5em 10% 0.5em 0;\r
- padding: 0.5em 1em;\r
-}\r
-\r
-div.sidebar {\r
- background: #ffffee;\r
- margin: 1.0em 10% 0.5em 0;\r
- padding: 0.5em 1em;\r
- border: 1px solid silver;\r
-}\r
-div.sidebar * { padding: 0; }\r
-div.sidebar div { margin: 0; }\r
-div.sidebar p.title {\r
- font-family: sans-serif;\r
- margin-top: 0.5em;\r
- margin-bottom: 0.2em;\r
-}\r
-\r
-div.bibliomixed {\r
- margin: 0.5em 5% 0.5em 1em;\r
-}\r
-\r
-div.glossary dt {\r
- font-weight: bold;\r
-}\r
-div.glossary dd p {\r
- margin-top: 0.2em;\r
-}\r
-\r
-dl {\r
- margin: .8em 0;\r
- line-height: 1.2;\r
-}\r
-\r
-dt {\r
- margin-top: 0.5em;\r
-}\r
-\r
-dt span.term {\r
- font-style: italic;\r
-}\r
-\r
-div.variablelist dd p {\r
- margin-top: 0;\r
-}\r
-\r
-div.itemizedlist li, div.orderedlist li {\r
- margin-left: -0.8em;\r
- margin-top: 0.5em;\r
-}\r
-\r
-ul, ol {\r
- list-style-position: outside;\r
-}\r
-\r
-div.sidebar ul, div.sidebar ol {\r
- margin-left: 2.8em;\r
-}\r
-\r
-div.itemizedlist p.title,\r
-div.orderedlist p.title,\r
-div.variablelist p.title\r
-{\r
- margin-bottom: -0.8em;\r
-}\r
-\r
-div.revhistory table {\r
- border-collapse: collapse;\r
- border: none;\r
-}\r
-div.revhistory th {\r
- border: none;\r
- color: #527bbd;\r
- font-family: tahoma, verdana, sans-serif;\r
-}\r
-div.revhistory td {\r
- border: 1px solid silver;\r
-}\r
-\r
-/* Keep TOC and index lines close together. */\r
-div.toc dl, div.toc dt,\r
-div.list-of-figures dl, div.list-of-figures dt,\r
-div.list-of-tables dl, div.list-of-tables dt,\r
-div.indexdiv dl, div.indexdiv dt\r
-{\r
- line-height: normal;\r
- margin-top: 0;\r
- margin-bottom: 0;\r
-}\r
-\r
-/*\r
- Table styling does not work because of overriding attributes in\r
- generated HTML.\r
-*/\r
-div.table table,\r
-div.informaltable table\r
-{\r
- margin-left: 0;\r
- margin-right: 5%;\r
- margin-bottom: 0.8em;\r
-}\r
-div.informaltable table\r
-{\r
- margin-top: 0.4em\r
-}\r
-div.table thead,\r
-div.table tfoot,\r
-div.table tbody,\r
-div.informaltable thead,\r
-div.informaltable tfoot,\r
-div.informaltable tbody\r
-{\r
- /* No effect in IE6. */\r
- border-top: 2px solid #527bbd;\r
- border-bottom: 2px solid #527bbd;\r
-}\r
-div.table thead, div.table tfoot,\r
-div.informaltable thead, div.informaltable tfoot\r
-{\r
- font-weight: bold;\r
-}\r
-\r
-div.mediaobject img {\r
- border: 1px solid silver;\r
- margin-bottom: 0.8em;\r
-}\r
-div.figure p.title,\r
-div.table p.title\r
-{\r
- margin-top: 1em;\r
- margin-bottom: 0.4em;\r
-}\r
-\r
-@media print {\r
- div.navheader, div.navfooter { display: none; }\r
-}\r
+/*
+ CSS stylesheet for XHTML produced by DocBook XSL stylesheets.
+ Tested with XSL stylesheets 1.61.2, 1.67.2
+*/
+
+span.strong {
+ font-weight: bold;
+}
+
+body blockquote {
+ margin-top: .75em;
+ line-height: 1.5;
+ margin-bottom: .75em;
+}
+
+html body {
+ margin: 1em 5% 1em 5%;
+ line-height: 1.2;
+}
+
+body div {
+ margin: 0;
+}
+
+h1, h2, h3, h4, h5, h6,
+div.toc p b,
+div.list-of-figures p b,
+div.list-of-tables p b,
+div.abstract p.title
+{
+ color: #527bbd;
+ font-family: tahoma, verdana, sans-serif;
+}
+
+div.toc p:first-child,
+div.list-of-figures p:first-child,
+div.list-of-tables p:first-child,
+div.example p.title
+{
+ margin-bottom: 0.2em;
+}
+
+body h1 {
+ margin: .0em 0 0 -4%;
+ line-height: 1.3;
+ border-bottom: 2px solid silver;
+}
+
+body h2 {
+ margin: 0.5em 0 0 -4%;
+ line-height: 1.3;
+ border-bottom: 2px solid silver;
+}
+
+body h3 {
+ margin: .8em 0 0 -3%;
+ line-height: 1.3;
+}
+
+body h4 {
+ margin: .8em 0 0 -3%;
+ line-height: 1.3;
+}
+
+body h5 {
+ margin: .8em 0 0 -2%;
+ line-height: 1.3;
+}
+
+body h6 {
+ margin: .8em 0 0 -1%;
+ line-height: 1.3;
+}
+
+body hr {
+ border: none; /* Broken on IE6 */
+}
+div.footnotes hr {
+ border: 1px solid silver;
+}
+
+div.navheader th, div.navheader td, div.navfooter td {
+ font-family: sans-serif;
+ font-size: 0.9em;
+ font-weight: bold;
+ color: #527bbd;
+}
+div.navheader img, div.navfooter img {
+ border-style: none;
+}
+div.navheader a, div.navfooter a {
+ font-weight: normal;
+}
+div.navfooter hr {
+ border: 1px solid silver;
+}
+
+body td {
+ line-height: 1.2
+}
+
+body th {
+ line-height: 1.2;
+}
+
+ol {
+ line-height: 1.2;
+}
+
+ul, body dir, body menu {
+ line-height: 1.2;
+}
+
+html {
+ margin: 0;
+ padding: 0;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6 {
+ margin-left: 0
+}
+
+body pre {
+ margin: 0.5em 10% 0.5em 1em;
+ line-height: 1.0;
+ color: navy;
+}
+
+tt.literal, code.literal {
+ color: navy;
+}
+
+div.literallayout p {
+ padding: 0em;
+ margin: 0em;
+}
+
+div.literallayout {
+ font-family: monospace;
+# margin: 0.5em 10% 0.5em 1em;
+ margin: 0em;
+ color: navy;
+ border: 1px solid silver;
+ background: #f4f4f4;
+ padding: 0.5em;
+}
+
+.programlisting, .screen {
+ border: 1px solid silver;
+ background: #f4f4f4;
+ margin: 0.5em 10% 0.5em 0;
+ padding: 0.5em 1em;
+}
+
+div.sidebar {
+ background: #ffffee;
+ margin: 1.0em 10% 0.5em 0;
+ padding: 0.5em 1em;
+ border: 1px solid silver;
+}
+div.sidebar * { padding: 0; }
+div.sidebar div { margin: 0; }
+div.sidebar p.title {
+ font-family: sans-serif;
+ margin-top: 0.5em;
+ margin-bottom: 0.2em;
+}
+
+div.bibliomixed {
+ margin: 0.5em 5% 0.5em 1em;
+}
+
+div.glossary dt {
+ font-weight: bold;
+}
+div.glossary dd p {
+ margin-top: 0.2em;
+}
+
+dl {
+ margin: .8em 0;
+ line-height: 1.2;
+}
+
+dt {
+ margin-top: 0.5em;
+}
+
+dt span.term {
+ font-style: italic;
+}
+
+div.variablelist dd p {
+ margin-top: 0;
+}
+
+div.itemizedlist li, div.orderedlist li {
+ margin-left: -0.8em;
+ margin-top: 0.5em;
+}
+
+ul, ol {
+ list-style-position: outside;
+}
+
+div.sidebar ul, div.sidebar ol {
+ margin-left: 2.8em;
+}
+
+div.itemizedlist p.title,
+div.orderedlist p.title,
+div.variablelist p.title
+{
+ margin-bottom: -0.8em;
+}
+
+div.revhistory table {
+ border-collapse: collapse;
+ border: none;
+}
+div.revhistory th {
+ border: none;
+ color: #527bbd;
+ font-family: tahoma, verdana, sans-serif;
+}
+div.revhistory td {
+ border: 1px solid silver;
+}
+
+/* Keep TOC and index lines close together. */
+div.toc dl, div.toc dt,
+div.list-of-figures dl, div.list-of-figures dt,
+div.list-of-tables dl, div.list-of-tables dt,
+div.indexdiv dl, div.indexdiv dt
+{
+ line-height: normal;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+/*
+ Table styling does not work because of overriding attributes in
+ generated HTML.
+*/
+div.table table,
+div.informaltable table
+{
+ margin-left: 0;
+ margin-right: 5%;
+ margin-bottom: 0.8em;
+}
+div.informaltable table
+{
+ margin-top: 0.4em
+}
+div.table thead,
+div.table tfoot,
+div.table tbody,
+div.informaltable thead,
+div.informaltable tfoot,
+div.informaltable tbody
+{
+ /* No effect in IE6. */
+ border-top: 2px solid #527bbd;
+ border-bottom: 2px solid #527bbd;
+}
+div.table thead, div.table tfoot,
+div.informaltable thead, div.informaltable tfoot
+{
+ font-weight: bold;
+}
+
+div.mediaobject img {
+ border: 1px solid silver;
+ margin-bottom: 0.8em;
+}
+div.figure p.title,
+div.table p.title
+{
+ margin-top: 1em;
+ margin-bottom: 0.4em;
+}
+
+@media print {
+ div.navheader, div.navfooter { display: none; }
+}
--- /dev/null
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version='1.0'>
+ <xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl"/>
+ <xsl:output method="html" encoding="UTF-8" indent="no" />
+</xsl:stylesheet>
Deepen the history of a 'shallow' repository created by
`git clone` with `--depth=<depth>` option (see gitlink:git-clone[1])
by the specified number of commits.
-
NAME
----
-git-add - Add file contents to the changeset to be committed next
+git-add - Add file contents to the index
SYNOPSIS
--------
DESCRIPTION
-----------
-All the changed file contents to be committed together in a single set
-of changes must be "added" with the 'add' command before using the
-'commit' command. This is not only for adding new files. Even modified
-files must be added to the set of changes about to be committed.
+This command adds the current content of new or modified files to the
+index, thus staging that content for inclusion in the next commit.
-This command can be performed multiple times before a commit. The added
-content corresponds to the state of specified file(s) at the time the
-'add' command is used. This means the 'commit' command will not consider
-subsequent changes to already added content if it is not added again before
-the commit.
+The "index" holds a snapshot of the content of the working tree, and it
+is this snapshot that is taken as the contents of the next commit. Thus
+after making any changes to the working directory, and before running
+the commit command, you must use the 'add' command to add any new or
+modified files to the index.
-The 'git status' command can be used to obtain a summary of what is included
-for the next commit.
+This command can be performed multiple times before a commit. It only
+adds the content of the specified file(s) at the time the add command is
+run; if you want subsequent changes included in the next commit, then
+you must run 'git add' again to add the new content to the index.
-This command can be used to add ignored files with `-f` (force)
-option, but they have to be
-explicitly and exactly specified from the command line. File globbing
-and recursive behaviour do not add ignored files.
+The 'git status' command can be used to obtain a summary of which
+files have changes that are staged for the next commit.
+
+The 'add' command can be used to add ignored files with `-f` (force)
+option, but they have to be explicitly and exactly specified from the
+command line. File globbing and recursive behaviour do not add ignored
+files.
Please see gitlink:git-commit[1] for alternative ways to add content to a
commit.
GIT
---
Part of the gitlink:git[7] suite
-
'git-am' [--signoff] [--dotest=<dir>] [--keep] [--utf8 | --no-utf8]
[--3way] [--interactive] [--binary]
[--whitespace=<option>] [-C<n>] [-p<n>]
- <mbox>...
+ <mbox>|<Maildir>...
'git-am' [--skip | --resolved]
DESCRIPTION
OPTIONS
-------
-<mbox>...::
+<mbox>|<Maildir>...::
The list of mailbox files to read patches from. If you do not
- supply this argument, reads from the standard input.
+ supply this argument, reads from the standard input. If you supply
+ directories, they'll be treated as Maildirs.
-s, --signoff::
Add `Signed-off-by:` line to the commit message, using
When initially invoking it, you give it names of the mailboxes
to crunch. Upon seeing the first patch that does not apply, it
-aborts in the middle, just like 'git-applymbox' does. You can
-recover from this in one of two ways:
+aborts in the middle,. You can recover from this in one of two ways:
. skip the current patch by re-running the command with '--skip'
option.
SEE ALSO
--------
-gitlink:git-applymbox[1], gitlink:git-applypatch[1], gitlink:git-apply[1].
+gitlink:git-apply[1].
Author
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
+++ /dev/null
-git-applymbox(1)
-================
-
-NAME
-----
-git-applymbox - Apply a series of patches in a mailbox
-
-
-SYNOPSIS
---------
-'git-applymbox' [-u] [-k] [-q] [-m] ( -c .dotest/<num> | <mbox> ) [ <signoff> ]
-
-DESCRIPTION
------------
-Splits mail messages in a mailbox into commit log message,
-authorship information and patches, and applies them to the
-current branch.
-
-
-OPTIONS
--------
--q::
- Apply patches interactively. The user will be given
- opportunity to edit the log message and the patch before
- attempting to apply it.
-
--k::
- Usually the program 'cleans up' the Subject: header line
- to extract the title line for the commit log message,
- among which (1) remove 'Re:' or 're:', (2) leading
- whitespaces, (3) '[' up to ']', typically '[PATCH]', and
- then prepends "[PATCH] ". This flag forbids this
- munging, and is most useful when used to read back 'git
- format-patch -k' output.
-
--m::
- Patches are applied with `git-apply` command, and unless
- it cleanly applies without fuzz, the processing fails.
- With this flag, if a tree that the patch applies cleanly
- is found in a repository, the patch is applied to the
- tree and then a 3-way merge between the resulting tree
- and the current tree.
-
--u::
- Pass `-u` flag to `git-mailinfo` (see gitlink:git-mailinfo[1]).
- The proposed commit log message taken from the e-mail
- are re-coded into UTF-8 encoding (configuration variable
- `i18n.commitencoding` can be used to specify project's
- preferred encoding if it is not UTF-8). This used to be
- optional but now it is the default.
-+
-Note that the patch is always used as-is without charset
-conversion, even with this flag.
-
--n::
- Pass `-n` flag to `git-mailinfo` (see
- gitlink:git-mailinfo[1]).
-
--c .dotest/<num>::
- When the patch contained in an e-mail does not cleanly
- apply, the command exits with an error message. The
- patch and extracted message are found in .dotest/, and
- you could re-run 'git applymbox' with '-c .dotest/<num>'
- flag to restart the process after inspecting and fixing
- them.
-
-<mbox>::
- The name of the file that contains the e-mail messages
- with patches. This file should be in the UNIX mailbox
- format. See 'SubmittingPatches' document to learn about
- the formatting convention for e-mail submission.
-
-<signoff>::
- The name of the file that contains your "Signed-off-by"
- line. See 'SubmittingPatches' document to learn what
- "Signed-off-by" line means. You can also just say
- 'yes', 'true', 'me', or 'please' to use an automatically
- generated "Signed-off-by" line based on your committer
- identity.
-
-
-SEE ALSO
---------
-gitlink:git-am[1], gitlink:git-applypatch[1].
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
-
+++ /dev/null
-git-applypatch(1)
-=================
-
-NAME
-----
-git-applypatch - Apply one patch extracted from an e-mail
-
-
-SYNOPSIS
---------
-'git-applypatch' <msg> <patch> <info> [<signoff>]
-
-DESCRIPTION
------------
-This is usually not what an end user wants to run directly. See
-gitlink:git-am[1] instead.
-
-Takes three files <msg>, <patch>, and <info> prepared from an
-e-mail message by 'git-mailinfo', and creates a commit. It is
-usually not necessary to use this command directly.
-
-This command can run `applypatch-msg`, `pre-applypatch`, and
-`post-applypatch` hooks. See link:hooks.html[hooks] for more
-information.
-
-
-OPTIONS
--------
-<msg>::
- Commit log message (sans the first line, which comes
- from e-mail Subject stored in <info>).
-
-<patch>::
- The patch to apply.
-
-<info>::
- Author and subject information extracted from e-mail,
- used on "author" line and as the first line of the
- commit log message.
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
-
Imports a project from one or more Arch repositories. It will follow branches
and repositories within the namespaces defined by the <archive/branch>
parameters supplied. If it cannot find the remote branch a merge comes from
-it will just import it as a regular commit. If it can find it, it will mark it
-as a merge whenever possible (see discussion below).
+it will just import it as a regular commit. If it can find it, it will mark it
+as a merge whenever possible (see discussion below).
-The script expects you to provide the key roots where it can start the import
-from an 'initial import' or 'tag' type of Arch commit. It will follow and
-import new branches within the provided roots.
+The script expects you to provide the key roots where it can start the import
+from an 'initial import' or 'tag' type of Arch commit. It will follow and
+import new branches within the provided roots.
-It expects to be dealing with one project only. If it sees
-branches that have different roots, it will refuse to run. In that case,
-edit your <archive/branch> parameters to define clearly the scope of the
-import.
+It expects to be dealing with one project only. If it sees
+branches that have different roots, it will refuse to run. In that case,
+edit your <archive/branch> parameters to define clearly the scope of the
+import.
-`git-archimport` uses `tla` extensively in the background to access the
+`git-archimport` uses `tla` extensively in the background to access the
Arch repository.
Make sure you have a recent version of `tla` available in the path. `tla` must
-know about the repositories you pass to `git-archimport`.
+know about the repositories you pass to `git-archimport`.
-For the initial import `git-archimport` expects to find itself in an empty
-directory. To follow the development of a project that uses Arch, rerun
-`git-archimport` with the same parameters as the initial import to perform
+For the initial import `git-archimport` expects to find itself in an empty
+directory. To follow the development of a project that uses Arch, rerun
+`git-archimport` with the same parameters as the initial import to perform
incremental imports.
While git-archimport will try to create sensible branch names for the
MERGES
------
-Patch merge data from Arch is used to mark merges in git as well. git
+Patch merge data from Arch is used to mark merges in git as well. git
does not care much about tracking patches, and only considers a merge when a
branch incorporates all the commits since the point they forked. The end result
-is that git will have a good idea of how far branches have diverged. So the
+is that git will have a good idea of how far branches have diverged. So the
import process does lose some patch-trading metadata.
-Fortunately, when you try and merge branches imported from Arch,
-git will find a good merge base, and it has a good chance of identifying
-patches that have been traded out-of-sequence between the branches.
+Fortunately, when you try and merge branches imported from Arch,
+git will find a good merge base, and it has a good chance of identifying
+patches that have been traded out-of-sequence between the branches.
OPTIONS
-------
Display usage.
-v::
- Verbose output.
+ Verbose output.
-T::
- Many tags. Will create a tag for every commit, reflecting the commit
+ Many tags. Will create a tag for every commit, reflecting the commit
name in the Arch repository.
-f::
<archive/branch>::
- Archive/branch identifier in a format that `tla log` understands.
+ Archive/branch identifier in a format that `tla log` understands.
Author
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
-'git bisect' <subcommand> <options>
+'git bisect' <subcommand> <options>
DESCRIPTION
-----------
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
[verse]
-'git-blame' [-c] [-b] [--root] [-s] [-l] [-t] [-f] [-n] [-p] [--incremental] [-L n,m]
+'git-blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [--incremental] [-L n,m]
[-S <revs-file>] [-M] [-C] [-C] [--since=<date>]
[<rev> | --contents <file>] [--] <file>
-s::
Suppress author name and timestamp from the output.
+-w::
+ Ignore whitespace when comparing parent's version and
+ child's to find where the lines came from.
+
+
THE PORCELAIN FORMAT
--------------------
With a `-d` or `-D` option, `<branchname>` will be deleted. You may
specify more than one branch for deletion. If the branch currently
-has a ref log then the ref log will also be deleted. Use -r together with -d
+has a reflog then the reflog will also be deleted. Use -r together with -d
to delete remote-tracking branches.
Delete a branch irrespective of its index status.
-l::
- Create the branch's ref log. This activates recording of
- all changes to made the branch ref, enabling use of date
- based sha1 expressions such as "<branchname>@{yesterday}".
+ Create the branch's reflog. This activates recording of
+ all changes made to the branch ref, enabling use of date
+ based sha1 expressions such as "<branchname>@\{yesterday}".
-f::
Force the creation of a new branch even if it means deleting
------------
$ git clone git://git.kernel.org/.../git.git my.git
$ cd my.git
-$ git branch -d -r todo html man <1>
-$ git branch -D test <2>
+$ git branch -d -r origin/todo origin/html origin/man <1>
+$ git branch -D test <2>
------------
+
-<1> delete remote-tracking branches "todo", "html", "man"
-<2> delete "test" branch even if the "master" branch does not have all
+<1> Delete remote-tracking branches "todo", "html", "man"
+<2> Delete "test" branch even if the "master" branch does not have all
commits from test branch.
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
configuration variable.
-l::
- Create the new branch's ref log. This activates recording of
- all changes to made the branch ref, enabling use of date
- based sha1 expressions such as "<branchname>@{yesterday}".
+ Create the new branch's reflog. This activates recording of
+ all changes made to the branch ref, enabling use of date
+ based sha1 expressions such as "<branchname>@\{yesterday}".
-m::
If you have local modifications to one or more files that
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
--- /dev/null
+git-citool(1)
+=============
+
+NAME
+----
+git-citool - Graphical alternative to git-commit
+
+SYNOPSIS
+--------
+'git citool'
+
+DESCRIPTION
+-----------
+A Tcl/Tk based graphical interface to review modified files, stage
+them into the index, enter a commit message and record the new
+commit onto the current branch. This interface is an alternative
+to the less interactive gitlink:git-commit[1] program.
+
+git-citool is actually a standard alias for 'git gui citool'.
+See gitlink:git-gui[1] for more details.
+
+Author
+------
+Written by Shawn O. Pearce <spearce@spearce.org>.
+
+Documentation
+--------------
+Documentation by Shawn O. Pearce <spearce@spearce.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
Operate quietly. This flag is passed to "rsync" and
"git-fetch-pack" commands when given.
+--no-checkout::
-n::
No checkout of HEAD is performed after the clone is complete.
as patches.
<repository>::
- The (possibly remote) repository to clone from. It can
- be any URL git-fetch supports.
+ The (possibly remote) repository to clone from. See the
+ <<URLS,URLS>> section below for more information on specifying
+ repositories.
<directory>::
The name of a new directory to clone into. The "humanish"
for "host.xz:foo/.git"). Cloning into an existing directory
is not allowed.
+include::urls.txt[]
+
Examples
--------
GIT
---
Part of the gitlink:git[7] suite
-
-p <parent commit>::
Each '-p' indicates the id of a parent commit object.
-
+
Commit Information
------------------
name = "Your Name"
email = "your@email.address.xz"
-A commit comment is read from stdin (max 999 chars). If a changelog
+A commit comment is read from stdin. If a changelog
entry is not provided via "<" redirection, "git-commit-tree" will just wait
for one to be entered and terminated with ^D.
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
[verse]
-'git-commit' [-a | --interactive] [-s] [-v]
+'git-commit' [-a | --interactive] [-s] [-v] [-u]
[(-c | -C) <commit> | -F <file> | -m <msg> | --amend]
[--no-verify] [-e] [--author <author>]
[--] [[-i | -o ]<file>...]
DESCRIPTION
-----------
-Use 'git commit' when you want to record your changes into the repository
-along with a log message describing what the commit is about. All changes
-to be committed must be explicitly identified using one of the following
-methods:
+Use 'git commit' to store the current contents of the index in a new
+commit along with a log message describing the changes you have made.
+The content to be added can be specified in several ways:
1. by using gitlink:git-add[1] to incrementally "add" changes to the
- next commit before using the 'commit' command (Note: even modified
+ index before using the 'commit' command (Note: even modified
files must be "added");
-2. by using gitlink:git-rm[1] to identify content removal for the next
- commit, again before using the 'commit' command;
+2. by using gitlink:git-rm[1] to remove files from the working tree
+ and the index, again before using the 'commit' command;
-3. by directly listing files containing changes to be committed as arguments
- to the 'commit' command, in which cases only those files alone will be
- considered for the commit;
+3. by listing files as arguments to the 'commit' command, in which
+ case the commit will ignore changes staged in the index, and instead
+ record the current content of the listed files;
-4. by using the -a switch with the 'commit' command to automatically "add"
- changes from all known files i.e. files that have already been committed
- before, and to automatically "rm" files that have been
- removed from the working tree, and perform the actual commit.
+4. by using the -a switch with the 'commit' command to automatically
+ "add" changes from all known files (i.e. all files that are already
+ listed in the index) and to automatically "rm" files in the index
+ that have been removed from the working tree, and then perform the
+ actual commit;
5. by using the --interactive switch with the 'commit' command to decide one
by one which files should be part of the commit, before finalizing the
Override the author name used in the commit. Use
`A U Thor <author@example.com>` format.
--m <msg>::
+-m <msg>|--message=<msg>::
Use the given <msg> as the commit message.
+-t <file>|--template=<file>::
+ Use the contents of the given file as the initial version
+ of the commit message. The editor is invoked and you can
+ make subsequent changes. If a message is specified using
+ the `-m` or `-F` options, this option has no effect. This
+ overrides the `commit.template` configuration variable.
+
-s|--signoff::
Add Signed-off-by line at the end of the commit message.
as well. This is usually not what you want unless you
are concluding a conflicted merge.
+-u|--untracked-files::
+ Show all untracked files, also those in uninteresting
+ directories, in the "Untracked files:" section of commit
+ message template. Without this option only its name and
+ a trailing slash are displayed for each untracked
+ directory.
+
+-v|--verbose::
+ Show unified diff between the HEAD commit and what
+ would be committed at the bottom of the commit message
+ template. Note that this diff output doesn't have its
+ lines prefixed with '#'.
+
-q|--quiet::
Suppress commit summary message.
include::i18n.txt[]
-ENVIRONMENT VARIABLES
----------------------
-The command specified by either the VISUAL or EDITOR environment
-variables is used to edit the commit log message.
+ENVIRONMENT AND CONFIGURATION VARIABLES
+---------------------------------------
+The editor used to edit the commit log message will be chosen from the
+GIT_EDITOR environment variable, the core.editor configuration variable, the
+VISUAL environment variable, or the EDITOR environment variable (in that
+order).
HOOKS
-----
SYNOPSIS
--------
[verse]
-'git-config' [--system | --global] name [value [value_regex]]
-'git-config' [--system | --global] --add name value
-'git-config' [--system | --global] --replace-all name [value [value_regex]]
-'git-config' [--system | --global] [type] --get name [value_regex]
-'git-config' [--system | --global] [type] --get-all name [value_regex]
-'git-config' [--system | --global] [type] --get-regexp name_regex [value_regex]
-'git-config' [--system | --global] --unset name [value_regex]
-'git-config' [--system | --global] --unset-all name [value_regex]
-'git-config' [--system | --global] --rename-section old_name new_name
-'git-config' [--system | --global] --remove-section name
-'git-config' [--system | --global] -l | --list
+'git-config' [<file-option>] [type] [-z|--null] name [value [value_regex]]
+'git-config' [<file-option>] [type] --add name value
+'git-config' [<file-option>] [type] --replace-all name [value [value_regex]]
+'git-config' [<file-option>] [type] [-z|--null] --get name [value_regex]
+'git-config' [<file-option>] [type] [-z|--null] --get-all name [value_regex]
+'git-config' [<file-option>] [type] [-z|--null] --get-regexp name_regex [value_regex]
+'git-config' [<file-option>] --unset name [value_regex]
+'git-config' [<file-option>] --unset-all name [value_regex]
+'git-config' [<file-option>] --rename-section old_name new_name
+'git-config' [<file-option>] --remove-section name
+'git-config' [<file-option>] [-z|--null] -l | --list
DESCRIPTION
-----------
The type specifier can be either '--int' or '--bool', which will make
'git-config' ensure that the variable(s) are of the given type and
convert the value to the canonical form (simple decimal number for int,
-a "true" or "false" string for bool). Type specifiers currently only
-take effect for reading operations. If no type specifier is passed,
+a "true" or "false" string for bool). If no type specifier is passed,
no checks or transformations are performed on the value.
+The file-option can be one of '--system', '--global' or '--file'
+which specify where the values will be read from or written to.
+The default is to assume the config file of the current repository,
+.git/config unless defined otherwise with GIT_DIR and GIT_CONFIG
+(see <<FILES>>).
+
This command will fail if:
-. The .git/config file is invalid,
-. Can not write to .git/config,
+. The config file is invalid,
+. Can not write to the config file,
. no section was provided,
. the section or key is invalid,
. you try to unset an option which does not exist,
+
See also <<FILES>>.
+-f config-file, --file config-file::
+ Use the given config file instead of the one specified by GIT_CONFIG.
+
--remove-section::
Remove the given section from the configuration file.
in the config file will cause the value to be multiplied
by 1024, 1048576, or 1073741824 prior to output.
+-z, --null::
+ For all options that output values and/or keys, always
+ end values with with the null character (instead of a
+ newline). Use newline instead as a delimiter between
+ key and value. This allows for secure parsing of the
+ output without getting confused e.g. by values that
+ contain line breaks.
+
[[FILES]]
FILES
-----
-There are three files where git-config will search for configuration
-options:
+If not set explicitely with '--file', there are three files where
+git-config will search for configuration options:
.git/config::
Repository specific configuration file. (The filename is
; Proxy settings
[core]
- gitproxy="ssh" for "ssh://kernel.org/"
gitproxy="proxy-command" for kernel.org
- gitproxy="myprotocol-command" for "my://"
gitproxy=default-proxy ; for all the rest
you can set the filemode to true with
To add a new proxy, without altering any of the existing ones, use
------------
-% git config core.gitproxy '"proxy" for example.com'
+% git config core.gitproxy '"proxy-command" for example.com'
------------
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
-'git-cvsexportcommit' [-h] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
DESCRIPTION
-----------
Exports a commit from GIT to a CVS checkout, making it easier
-to merge patches from a git repository into a CVS repository.
+to merge patches from a git repository into a CVS repository.
-Execute it from the root of the CVS working copy. GIT_DIR must be defined.
+Execute it from the root of the CVS working copy. GIT_DIR must be defined.
See examples below.
-It does its best to do the safe thing, it will check that the files are
-unchanged and up to date in the CVS checkout, and it will not autocommit
+It does its best to do the safe thing, it will check that the files are
+unchanged and up to date in the CVS checkout, and it will not autocommit
by default.
Supports file additions, removals, and commits that affect binary files.
If the commit is a merge commit, you must tell git-cvsexportcommit what parent
-should the changeset be done against.
+should the changeset be done against.
OPTIONS
-------
Force the parent commit, even if it is not a direct parent.
-m::
- Prepend the commit message with the provided prefix.
+ Prepend the commit message with the provided prefix.
Useful for patch series and the like.
+-u::
+ Update affected files from cvs repository before attempting export.
+
-v::
Verbose.
$ export GIT_DIR=~/project/.git
$ cd ~/project_cvs_checkout
$ git-cvsexportcommit -v <commit-sha1>
-$ cvs commit -F .mgs <files>
+$ cvs commit -F .mgs <files>
------------
Merge pending patches into CVS automatically -- only if you really know what you are doing::
GIT
---
Part of the gitlink:git[7] suite
-
[-A <author-conv-file>] [-p <options-for-cvsps>] [-P <file>]
[-C <git_repository>] [-z <fuzz>] [-i] [-k] [-u] [-s <subst>]
[-a] [-m] [-M <regex>] [-S <regex>] [-L <commitlimit>]
- [<CVS_module>]
+ [-r <remote>] [<CVS_module>]
DESCRIPTION
At least version 2.1 is required.
You should *never* do any work of your own on the branches that are
-created by git-cvsimport. The initial import will create and populate a
+created by git-cvsimport. By default initial import will create and populate a
"master" branch from the CVS repository's main branch which you're free
to work with; after that, you need to 'git merge' incremental imports, or
-any CVS branches, yourself.
+any CVS branches, yourself. It is advisable to specify a named remote via
+-r to separate and protect the incoming branches.
+
OPTIONS
-------
-d <CVSROOT>::
The root of the CVS archive. May be local (a simple path) or remote;
- currently, only the :local:, :ext: and :pserver: access methods
+ currently, only the :local:, :ext: and :pserver: access methods
are supported. If not given, git-cvsimport will try to read it
from `CVS/Root`. If no such file exists, it checks for the
`CVSROOT` environment variable.
The git repository to import to. If the directory doesn't
exist, it will be created. Default is the current directory.
+-r <remote>::
+ The git remote to import this CVS repository into.
+ Moves all CVS branches into remotes/<remote>/<branch>
+ akin to the git-clone --use-separate-remote option.
+
-o <branch-for-HEAD>::
- The 'HEAD' branch from CVS is imported to the 'origin' branch within
- the git repository, as 'HEAD' already has a special meaning for git.
- Use this option if you want to import into a different branch.
+ When no remote is specified (via -r) the 'HEAD' branch
+ from CVS is imported to the 'origin' branch within the git
+ repository, as 'HEAD' already has a special meaning for git.
+ When a remote is specified the 'HEAD' branch is named
+ remotes/<remote>/master mirroring git-clone behaviour.
+ Use this option if you want to import into a different
+ branch.
+
Use '-o master' for continuing an import that was initially done by
the old cvs2git tool.
-k::
Kill keywords: will extract files with '-kk' from the CVS archive
to avoid noisy changesets. Highly recommended, but off by default
- to preserve compatibility with early imported trees.
+ to preserve compatibility with early imported trees.
-u::
Convert underscores in tag and branch names to dots.
Instead of calling cvsps, read the provided cvsps output file. Useful
for debugging or when cvsps is being handled outside cvsimport.
--m::
+-m::
Attempt to detect merges based on the commit message. This option
- will enable default regexes that try to capture the name source
- branch name from the commit message.
+ will enable default regexes that try to capture the name source
+ branch name from the commit message.
-M <regex>::
Attempt to detect merges based on the commit message with a custom
regex. It can be used with '-m' to also see the default regexes.
- You must escape forward slashes.
+ You must escape forward slashes.
-S <regex>::
Skip paths matching the regex.
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
+
+SSH:
+
[verse]
export CVS_SERVER=git-cvsserver
'cvs' -d :ext:user@server/path/repo.git co <HEAD_name>
+pserver (/etc/inetd.conf):
+
+[verse]
+cvspserver stream tcp nowait nobody /usr/bin/git-cvsserver git-cvsserver pserver
+
+Usage:
+
+[verse]
+'git-cvsserver' [options] [pserver|server] [<directory> ...]
+
+OPTIONS
+-------
+
+All these options obviously only make sense if enforced by the server side.
+They have been implemented to resemble the gitlink:git-daemon[1] options as
+closely as possible.
+
+--base-path <path>::
+Prepend 'path' to requested CVSROOT
+
+--strict-paths::
+Don't allow recursing into subdirectories
+
+--export-all::
+Don't check for `gitcvs.enabled` in config. You also have to specify a list
+of allowed directories (see below) if you want to use this option.
+
+--version, -V::
+Print version information and exit
+
+--help, -h, -H::
+Print usage information and exit
+
+<directory>::
+You can specify a list of allowed directories. If no directories
+are given, all are allowed. This is an additional restriction, gitcvs
+access still needs to be enabled by the `gitcvs.enabled` config option
+unless '--export-all' was given, too.
+
+
DESCRIPTION
-----------
'git://example.com/hello.git', `git-daemon` will interpret the path
as '/srv/git/hello.git'.
+--base-path-relaxed::
+ If --base-path is enabled and repo lookup fails, with this option
+ `git-daemon` will attempt to lookup without prefixing the base path.
+ This is useful for switching to --base-path usage, while still
+ allowing the old paths.
+
--interpolated-path=pathtemplate::
To support virtual hosting, an interpolated path template can be
used to dynamically construct alternate paths. The template
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
-'git-describe' [--all] [--tags] [--abbrev=<n>] <committish>...
+'git-describe' [--all] [--tags] [--contains] [--abbrev=<n>] <committish>...
DESCRIPTION
-----------
Instead of using only the annotated tags, use any tag
found in `.git/refs/tags`.
+--contains::
+ Instead of finding the tag that predates the commit, find
+ the tag that comes after the commit, and thus contains it.
+ Automatically implies --tags.
+
--abbrev=<n>::
Instead of using the default 8 hexadecimal digits as the
abbreviated object name, use <n> digits.
GIT
---
Part of the gitlink:git[7] suite
-
branch" respectively. With these options, diffs for
merged entries are not shown.
+
-The default is to diff against our branch (-2) and the
+The default is to diff against our branch (-2) and the
cleanly resolved paths. The option -0 can be given to
omit diff output for unmerged entries and just show "Unmerged".
GIT
---
Part of the gitlink:git[7] suite
-
nicer for the case where you just want to check where you are.
So doing a "git-diff-index --cached" is basically very useful when you are
-asking yourself "what have I already marked for being committed, and
+asking yourself "what have I already marked for being committed, and
what's the difference to a previous tree".
Non-cached Mode
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
$ git diff HEAD <3>
------------
+
-<1> changes in the working tree not yet staged for the next commit.
-<2> changes between the index and your last commit; what you
+<1> Changes in the working tree not yet staged for the next commit.
+<2> Changes between the index and your last commit; what you
would be committing if you run "git commit" without "-a" option.
-<3> changes in the working tree since your last commit; what you
+<3> Changes in the working tree since your last commit; what you
would be committing if you run "git commit -a"
Comparing with arbitrary commits::
$ git diff HEAD^ HEAD <3>
------------
+
-<1> instead of using the tip of the current branch, compare with the
+<1> Instead of using the tip of the current branch, compare with the
tip of "test" branch.
-<2> instead of comparing with the tip of "test" branch, compare with
+<2> Instead of comparing with the tip of "test" branch, compare with
the tip of the current branch, but limit the comparison to the
file "test".
-<3> compare the version before the last commit and the last commit.
+<3> Compare the version before the last commit and the last commit.
Limiting the diff output::
+
------------
$ git diff --diff-filter=MRC <1>
-$ git diff --name-status -r <2>
+$ git diff --name-status <2>
$ git diff arch/i386 include/asm-i386 <3>
------------
+
-<1> show only modification, rename and copy, but not addition
+<1> Show only modification, rename and copy, but not addition
nor deletion.
-<2> show only names and the nature of change, but not actual
-diff output. --name-status disables usual patch generation
-which in turn also disables recursive behavior, so without -r
-you would only see the directory name if there is a change in a
-file in a subdirectory.
-<3> limit diff output to named subtrees.
+<2> Show only names and the nature of change, but not actual
+diff output.
+<3> Limit diff output to named subtrees.
Munging the diff output::
+
$ git diff -R <2>
------------
+
-<1> spend extra cycles to find renames, copies and complete
+<1> Spend extra cycles to find renames, copies and complete
rewrites (very expensive).
-<2> output diff in reverse.
+<2> Output diff in reverse.
Author
GIT
---
Part of the gitlink:git[7] suite
-
data
('from' SP <committish> LF)?
('merge' SP <committish> LF)?
- (filemodify | filedelete | filedeleteall)*
+ (filemodify | filedelete | filecopy | filerename | filedeleteall)*
LF
....
and are not interpreted by Git. Currently they must be encoded in
UTF-8, as fast-import does not permit other encodings to be specified.
-Zero or more `filemodify`, `filedelete` and `filedeleteall` commands
+Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`
+and `filedeleteall` commands
may be included to update the contents of the branch prior to
creating the commit. These commands may be supplied in any order.
However it is recommended that a `filedeleteall` command preceed
-all `filemodify` commands in the same commit, as `filedeleteall`
+all `filemodify`, `filecopy` and `filerename` commands in the same
+commit, as `filedeleteall`
wipes the branch clean (see below).
`author`
be removed from the branch.
See `filemodify` above for a detailed description of `<path>`.
+`filecopy`
+^^^^^^^^^^^^
+Recursively copies an existing file or subdirectory to a different
+location within the branch. The existing file or directory must
+exist. If the destination exists it will be completely replaced
+by the content copied from the source.
+
+....
+ 'C' SP <path> SP <path> LF
+....
+
+here the first `<path>` is the source location and the second
+`<path>` is the destination. See `filemodify` above for a detailed
+description of what `<path>` may look like. To use a source path
+that contains SP the path must be quoted.
+
+A `filecopy` command takes effect immediately. Once the source
+location has been copied to the destination any future commands
+applied to the source location will not impact the destination of
+the copy.
+
+`filerename`
+^^^^^^^^^^^^
+Renames an existing file or subdirectory to a different location
+within the branch. The existing file or directory must exist. If
+the destination exists it will be replaced by the source directory.
+
+....
+ 'R' SP <path> SP <path> LF
+....
+
+here the first `<path>` is the source location and the second
+`<path>` is the destination. See `filemodify` above for a detailed
+description of what `<path>` may look like. To use a source path
+that contains SP the path must be quoted.
+
+A `filerename` command takes effect immediately. Once the source
+location has been renamed to the destination any future commands
+applied to the source location will create new files there and not
+impact the destination of the rename.
+
+Note that a `filerename` is the same as a `filecopy` followed by a
+`filedelete` of the source location. There is a slight performance
+advantage to using `filerename`, but the advantage is so small
+that it is never worth trying to convert a delete/add pair in
+source material into a rename for fast-import. This `filerename`
+command is provided just to simplify frontends that already have
+rename information and don't want bother with decomposing it into a
+`filecopy` followed by a `filedelete`.
+
`filedeleteall`
^^^^^^^^^^^^^^^
Included in a `commit` command to remove all files (and also all
GIT
---
Part of the gitlink:git[7] suite
-
include::pull-fetch-param.txt[]
-include::urls.txt[]
+include::urls-remotes.txt[]
SEE ALSO
--------
--- /dev/null
+git-filter-branch(1)
+====================
+
+NAME
+----
+git-filter-branch - Rewrite branches
+
+SYNOPSIS
+--------
+[verse]
+'git-filter-branch' [--env-filter <command>] [--tree-filter <command>]
+ [--index-filter <command>] [--parent-filter <command>]
+ [--msg-filter <command>] [--commit-filter <command>]
+ [--tag-name-filter <command>] [--subdirectory-filter <directory>]
+ [-d <directory>] [-f | --force] [<rev-list options>...]
+
+DESCRIPTION
+-----------
+Lets you rewrite git revision history by creating a new branch from
+your current branch, applying custom filters on each revision.
+Those filters can modify each tree (e.g. removing a file or running
+a perl rewrite on all files) or information about each commit.
+Otherwise, all information (including original commit times or merge
+information) will be preserved.
+
+The command takes the new branch name as a mandatory argument and
+the filters as optional arguments. If you specify no filters, the
+commits will be recommitted without any changes, which would normally
+have no effect. Nevertheless, this may be useful in the future for
+compensating for some git bugs or such, therefore such a usage is
+permitted.
+
+*WARNING*! The rewritten history will have different object names for all
+the objects and will not converge with the original branch. You will not
+be able to easily push and distribute the rewritten branch on top of the
+original branch. Please do not use this command if you do not know the
+full implications, and avoid using it anyway, if a simple single commit
+would suffice to fix your problem.
+
+Always verify that the rewritten version is correct: The original refs,
+if different from the rewritten ones, will be stored in the namespace
+'refs/original/'.
+
+Note that since this operation is extensively I/O expensive, it might
+be a good idea to redirect the temporary directory off-disk, e.g. on
+tmpfs. Reportedly the speedup is very noticeable.
+
+
+Filters
+~~~~~~~
+
+The filters are applied in the order as listed below. The <command>
+argument is always evaluated in shell using the 'eval' command (with the
+notable exception of the commit filter, for technical reasons).
+Prior to that, the $GIT_COMMIT environment variable will be set to contain
+the id of the commit being rewritten. Also, GIT_AUTHOR_NAME,
+GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL,
+and GIT_COMMITTER_DATE are set according to the current commit.
+
+A 'map' function is available that takes an "original sha1 id" argument
+and outputs a "rewritten sha1 id" if the commit has been already
+rewritten, and "original sha1 id" otherwise; the 'map' function can
+return several ids on separate lines if your commit filter emitted
+multiple commits.
+
+
+OPTIONS
+-------
+
+--env-filter <command>::
+ This is the filter for modifying the environment in which
+ the commit will be performed. Specifically, you might want
+ to rewrite the author/committer name/email/time environment
+ variables (see gitlink:git-commit[1] for details). Do not forget
+ to re-export the variables.
+
+--tree-filter <command>::
+ This is the filter for rewriting the tree and its contents.
+ The argument is evaluated in shell with the working
+ directory set to the root of the checked out tree. The new tree
+ is then used as-is (new files are auto-added, disappeared files
+ are auto-removed - neither .gitignore files nor any other ignore
+ rules *HAVE ANY EFFECT*!).
+
+--index-filter <command>::
+ This is the filter for rewriting the index. It is similar to the
+ tree filter but does not check out the tree, which makes it much
+ faster. For hairy cases, see gitlink:git-update-index[1].
+
+--parent-filter <command>::
+ This is the filter for rewriting the commit's parent list.
+ It will receive the parent string on stdin and shall output
+ the new parent string on stdout. The parent string is in
+ a format accepted by gitlink:git-commit-tree[1]: empty for
+ the initial commit, "-p parent" for a normal commit and
+ "-p parent1 -p parent2 -p parent3 ..." for a merge commit.
+
+--msg-filter <command>::
+ This is the filter for rewriting the commit messages.
+ The argument is evaluated in the shell with the original
+ commit message on standard input; its standard output is
+ used as the new commit message.
+
+--commit-filter <command>::
+ This is the filter for performing the commit.
+ If this filter is specified, it will be called instead of the
+ gitlink:git-commit-tree[1] command, with arguments of the form
+ "<TREE_ID> [-p <PARENT_COMMIT_ID>]..." and the log message on
+ stdin. The commit id is expected on stdout.
++
+As a special extension, the commit filter may emit multiple
+commit ids; in that case, ancestors of the original commit will
+have all of them as parents.
+
+--tag-name-filter <command>::
+ This is the filter for rewriting tag names. When passed,
+ it will be called for every tag ref that points to a rewritten
+ object (or to a tag object which points to a rewritten object).
+ The original tag name is passed via standard input, and the new
+ tag name is expected on standard output.
++
+The original tags are not deleted, but can be overwritten;
+use "--tag-name-filter=cat" to simply update the tags. In this
+case, be very careful and make sure you have the old tags
+backed up in case the conversion has run afoul.
++
+Note that there is currently no support for proper rewriting of
+tag objects; in layman terms, if the tag has a message or signature
+attached, the rewritten tag won't have it. Sorry. (It is by
+definition impossible to preserve signatures at any rate.)
+
+--subdirectory-filter <directory>::
+ Only look at the history which touches the given subdirectory.
+ The result will contain that directory (and only that) as its
+ project root.
+
+-d <directory>::
+ Use this option to set the path to the temporary directory used for
+ rewriting. When applying a tree filter, the command needs to
+ temporary checkout the tree to some directory, which may consume
+ considerable space in case of large projects. By default it
+ does this in the '.git-rewrite/' directory but you can override
+ that choice by this parameter.
+
+-f\|--force::
+ `git filter-branch` refuses to start with an existing temporary
+ directory or when there are already refs starting with
+ 'refs/original/', unless forced.
+
+<rev-list-options>::
+ When options are given after the new branch name, they will
+ be passed to gitlink:git-rev-list[1]. Only commits in the resulting
+ output will be filtered, although the filtered commits can still
+ reference parents which are outside of that set.
+
+
+Examples
+--------
+
+Suppose you want to remove a file (containing confidential information
+or copyright violation) from all commits:
+
+-------------------------------------------------------
+git filter-branch --tree-filter 'rm filename' HEAD
+-------------------------------------------------------
+
+A significantly faster version:
+
+--------------------------------------------------------------------------
+git filter-branch --index-filter 'git update-index --remove filename' HEAD
+--------------------------------------------------------------------------
+
+Now, you will get the rewritten history saved in the branch 'newbranch'
+(your current branch is left untouched).
+
+To set a commit (which typically is at the tip of another
+history) to be the parent of the current initial commit, in
+order to paste the other history behind the current history:
+
+-------------------------------------------------------------------
+git filter-branch --parent-filter 'sed "s/^\$/-p <graft-id>/"' HEAD
+-------------------------------------------------------------------
+
+(if the parent string is empty - therefore we are dealing with the
+initial commit - add graftcommit as a parent). Note that this assumes
+history with a single root (that is, no merge without common ancestors
+happened). If this is not the case, use:
+
+--------------------------------------------------------------------------
+git filter-branch --parent-filter \
+ 'cat; test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>"' HEAD
+--------------------------------------------------------------------------
+
+or even simpler:
+
+-----------------------------------------------
+echo "$commit-id $graft-id" >> .git/info/grafts
+git filter-branch $graft-id..HEAD
+-----------------------------------------------
+
+To remove commits authored by "Darl McBribe" from the history:
+
+------------------------------------------------------------------------------
+git filter-branch --commit-filter '
+ if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ];
+ then
+ shift;
+ while [ -n "$1" ];
+ do
+ shift;
+ echo "$1";
+ shift;
+ done;
+ else
+ git commit-tree "$@";
+ fi' HEAD
+------------------------------------------------------------------------------
+
+The shift magic first throws away the tree id and then the -p
+parameters. Note that this handles merges properly! In case Darl
+committed a merge between P1 and P2, it will be propagated properly
+and all children of the merge will become merge commits with P1,P2
+as their parents instead of the merge commit.
+
+To restrict rewriting to only part of the history, specify a revision
+range in addition to the new branch name. The new branch name will
+point to the top-most revision that a 'git rev-list' of this range
+will print.
+
+Note that the changes introduced by the commits, and not reverted by
+subsequent commits, will still be in the rewritten branch. If you want
+to throw out _changes_ together with the commits, you should use the
+interactive mode of gitlink:git-rebase[1].
+
+Consider this history:
+
+------------------
+ D--E--F--G--H
+ / /
+A--B-----C
+------------------
+
+To rewrite only commits D,E,F,G,H, but leave A, B and C alone, use:
+
+--------------------------------
+git filter-branch ... C..H
+--------------------------------
+
+To rewrite commits E,F,G,H, use one of these:
+
+----------------------------------------
+git filter-branch ... C..H --not D
+git filter-branch ... D..H --not C
+----------------------------------------
+
+To move the whole tree into a subdirectory, or remove it from there:
+
+---------------------------------------------------------------
+git filter-branch --index-filter \
+ 'git ls-files -s | sed "s-\t-&newsubdir/-" |
+ GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
+ git update-index --index-info &&
+ mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
+---------------------------------------------------------------
+
+
+Author
+------
+Written by Petr "Pasky" Baudis <pasky@suse.cz>,
+and the git list <git@vger.kernel.org>
+
+Documentation
+--------------
+Documentation by Petr Baudis and the git list.
+
+GIT
+---
+Part of the gitlink:git[7] suite
GIT
---
Part of the gitlink:git[7] suite
-
[verse]
'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--thread]
[--attach[=<boundary>] | --inline[=<boundary>]]
- [-s | --signoff] [<common diff options>] [--start-number <n>]
+ [-s | --signoff] [<common diff options>]
+ [--start-number <n>] [--numbered-files]
[--in-reply-to=Message-Id] [--suffix=.<sfx>]
[--ignore-if-in-upstream]
[--subject-prefix=Subject-Prefix]
The output of this command is convenient for e-mail submission or
for use with gitlink:git-am[1].
-Each output file is numbered sequentially from 1, and uses the
+By default, each output file is numbered sequentially from 1, and uses the
first line of the commit message (massaged for pathname safety) as
-the filename. The names of the output files are printed to standard
+the filename. With the --numbered-files option, the output file names
+will only be numbers, without the first line of the commit appended.
+The names of the output files are printed to standard
output, unless the --stdout option is specified.
If -o is specified, output files are created in <dir>. Otherwise
--start-number <n>::
Start numbering the patches at <n> instead of 1.
+--numbered-files::
+ Output file names will be a simple number sequence
+ without the default first line of the commit appended.
+ Mutually exclusive with the --stdout option.
+
-k|--keep-subject::
Do not strip/add '[PATCH]' from the first line of the
commit log message.
CONFIGURATION
-------------
You can specify extra mail header lines to be added to each
-message in the repository configuration. Also you can specify
-the default suffix different from the built-in one:
+message in the repository configuration. You can also specify
+new defaults for the subject prefix and file suffix.
------------
[format]
headers = "Organization: git-foo\n"
+ subjectprefix = CHANGE
suffix = .txt
------------
GIT
---
Part of the gitlink:git[7] suite
-
--------
[verse]
'git-fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
- [--full] [--strict] [<object>*]
+ [--full] [--strict] [--verbose] [--lost-found] [<object>*]
DESCRIPTION
-----------
objects that triggers this check, but it is recommended
to check new projects with this flag.
+--verbose::
+ Be chatty.
+
+--lost-found::
+ Write dangling objects into .git/lost-found/commit/ or
+ .git/lost-found/other/, depending on type. If the object is
+ a blob, the contents are written into the file, rather than
+ its object name.
+
It tests SHA1 and general object sanity, and it does full tracking of
the resulting reachability and everything else. It prints out any
corruption it finds (missing or bad objects), and if you use the
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
-'git-gc' [--prune]
+'git-gc' [--prune] [--aggressive]
DESCRIPTION
-----------
repository at the same time (e.g. never use this option
in a cron script).
+--aggressive::
+ Usually 'git-gc' runs very quickly while providing good disk
+ space utilization and performance. This option will cause
+ git-gc to more aggressively optimize the repository at the expense
+ of taking much more time. The effects of this optimization are
+ persistent, so this option only needs to be used occasionally; every
+ few hundred changesets or so.
Configuration
-------------
is not run in bare repositories by default, to allow older dumb-transport
clients fetch from the repository, but this will change in the future.
+The optional configuration variable 'gc.aggressiveWindow' controls how
+much time is spent optimizing the delta compression of the objects in
+the repository when the --aggressive option is specified. The larger
+the value, the more time is spent optimizing the delta compression. See
+the documentation for the --window' option in gitlink:git-repack[1] for
+more details. This defaults to 10.
+
See Also
--------
gitlink:git-prune[1]
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
--- /dev/null
+git-gui(1)
+==========
+
+NAME
+----
+git-gui - A portable graphical interface to Git
+
+SYNOPSIS
+--------
+'git gui' [<command>] [arguments]
+
+DESCRIPTION
+-----------
+A Tcl/Tk based graphical user interface to Git. git-gui focuses
+on allowing users to make changes to their repository by making
+new commits, amending existing ones, creating branches, performing
+local merges, and fetching/pushing to remote repositories.
+
+Unlike gitlink:gitk[1], git-gui focuses on commit generation
+and single file annotation, and does not show project history.
+It does however supply menu actions to start a gitk session from
+within git-gui.
+
+git-gui is known to work on all popular UNIX systems, Mac OS X,
+and Windows (under both Cygwin and MSYS). To the extent possible
+OS specific user interface guidelines are followed, making git-gui
+a fairly native interface for users.
+
+COMMANDS
+--------
+blame::
+ Start a blame viewer on the specified file on the given
+ version (or working directory if not specified).
+
+browser::
+ Start a tree browser showing all files in the specified
+ commit (or 'HEAD' by default). Files selected through the
+ browser are opened in the blame viewer.
+
+citool::
+ Start git-gui and arrange to make exactly one commit before
+ exiting and returning to the shell. The interface is limited
+ to only commit actions, slightly reducing the application's
+ startup time and simplifying the menubar.
+
+version::
+ Display the currently running version of git-gui.
+
+
+Examples
+--------
+git gui blame Makefile::
+
+ Show the contents of the file 'Makefile' in the current
+ working directory, and provide annotations for both the
+ original author of each line, and who moved the line to its
+ current location. The uncommitted file is annotated, and
+ uncommitted changes (if any) are explicitly attributed to
+ 'Not Yet Committed'.
+
+git gui blame v0.99.8 Makefile::
+
+ Show the contents of 'Makefile' in revision 'v0.99.8'
+ and provide annotations for each line. Unlike the above
+ example the file is read from the object database and not
+ the working directory.
+
+git gui citool::
+
+ Make one commit and return to the shell when it is complete.
+
+git citool::
+
+ Same as 'git gui citool' (above).
+
+git gui browser maint::
+
+ Show a browser for the tree of the 'maint' branch. Files
+ selected in the browser can be viewed with the internal
+ blame viewer.
+
+See Also
+--------
+'gitk(1)'::
+ The git repository browser. Shows branches, commit history
+ and file differences. gitk is the utility started by
+ git-gui's Repository Visualize actions.
+
+Other
+-----
+git-gui is actually maintained as an independent project, but stable
+versions are distributed as part of the Git suite for the convience
+of end users.
+
+A git-gui development repository can be obtained from:
+
+ git clone git://repo.or.cz/git-gui.git
+
+or
+
+ git clone http://repo.or.cz/r/git-gui.git
+
+or browsed online at http://repo.or.cz/w/git-gui.git/[].
+
+Author
+------
+Written by Shawn O. Pearce <spearce@spearce.org>.
+
+Documentation
+--------------
+Documentation by Shawn O. Pearce <spearce@spearce.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
object database. Reports its object ID to its standard output.
This is used by "git-cvsimport" to update the index
without modifying files in the work tree. When <type> is not
-specified, it defaults to "blob".
+specified, it defaults to "blob".
OPTIONS
-------
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
A '<ref>' specification can be either a single pattern, or a pair
of such patterns separated by a colon ":" (this means that a ref name
-cannot have a colon in it). A single pattern '<name>' is just a
+cannot have a colon in it). A single pattern '<name>' is just a
shorthand for '<name>:<name>'.
Each pattern pair consists of the source side (before the colon)
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
-'git-init-db' [--template=<template_directory>] [--shared[=<permissions>]]
+'git-init-db' [-q | --quiet] [--template=<template_directory>] [--shared[=<permissions>]]
DESCRIPTION
This is a synonym for gitlink:git-init[1]. Please refer to the
documentation of that command.
-
SYNOPSIS
--------
-'git-init' [--template=<template_directory>] [--shared[=<permissions>]]
+'git-init' [-q | --quiet] [--template=<template_directory>] [--shared[=<permissions>]]
OPTIONS
--
+-q, \--quiet::
+
+Only print error and warning messages, all other output will be suppressed.
+
--template=<template_directory>::
Provide the directory from which templates will be used. The default template
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
DESCRIPTION
-----------
+THIS COMMAND IS DEPRECATED.
+
Duplicates another git repository on a local system.
OPTIONS
GIT
---
Part of the gitlink:git[7] suite
-
See also gitlink:git-reflog[1].
--decorate::
- Print out the ref names of any commits that are shown.
+ Print out the ref names of any commits that are shown.
+
+--full-diff::
+ Without this flag, "git log -p <paths>..." shows commits that
+ touch the specified paths, and diffs about the same specified
+ paths. With this, the full diff is shown for commits that touch
+ the specified paths; this means that "<paths>..." limits only
+ commits, and doesn't limit diff for those commits.
+
+--follow::
+ Continue listing the history of a file beyond renames.
<paths>...::
Show only commits that affect the specified paths.
in the "release" branch, along with the list of paths
each commit modifies.
+git log --follow builtin-rev-list.c::
+
+ Shows the commits that changed builtin-rev-list.c, including
+ those commits that occurred before the file was given its
+ present name.
+
Discussion
----------
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
[verse]
-'git-ls-tree' [-d] [-r] [-t] [-z]
+'git-ls-tree' [-d] [-r] [-t] [-l] [-z]
[--name-only] [--name-status] [--full-name] [--abbrev=[<n>]]
<tree-ish> [paths...]
Show tree entries even when going to recurse them. Has no effect
if '-r' was not passed. '-d' implies '-t'.
+-l::
+--long::
+ Show object size of blob (file) entries.
+
-z::
\0 line termination on output.
When the `-z` option is not used, TAB, LF, and backslash characters
in pathnames are represented as `\t`, `\n`, and `\\`, respectively.
+When the `-l` option is used, format changes to
+
+ <mode> SP <type> SP <object> SP <object size> TAB <file>
+
+Object size identified by <object> is given in bytes, and right-justified
+with minimum width of 7 characters. Object size is given only for blobs
+(file) entries; for other entries `-` character is used in place of size.
+
Author
------
GIT
---
Part of the gitlink:git[7] suite
-
Reading a single e-mail message from the standard input, and
writes the commit log message in <msg> file, and the patches in
<patch> file. The author name, e-mail and e-mail subject are
-written out to the standard output to be used by git-applypatch
+written out to the standard output to be used by git-am
to create a commit. It is usually not necessary to use this
command directly. See gitlink:git-am[1] instead.
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
-'git-mailsplit' [-b] [-f<nn>] [-d<prec>] -o<directory> [--] [<mbox>...]
+'git-mailsplit' [-b] [-f<nn>] [-d<prec>] -o<directory> [--] [<mbox>|<Maildir>...]
DESCRIPTION
-----------
-Splits a mbox file into a list of files: "0001" "0002" .. in the specified
-directory so you can process them further from there.
+Splits a mbox file or a Maildir into a list of files: "0001" "0002" .. in the
+specified directory so you can process them further from there.
+
+IMPORTANT: Maildir splitting relies upon filenames being sorted to output
+patches in the correct order.
OPTIONS
-------
Mbox file to split. If not given, the mbox is read from
the standard input.
+<Maildir>::
+ Root of the Maildir to split. This directory should contain the cur, tmp
+ and new subdirectories.
+
<directory>::
Directory in which to place the individual messages.
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
This is modified MM in the branch B. # merge2
This is modified MM in the branch B. # current contents
-or
+or
torvalds@ppc970:~/merge-test> git-merge-index cat AA MM
cat: : No such file or directory
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
[verse]
-'git-merge' [-n] [--no-commit] [--squash] [-s <strategy>]...
+'git-merge' [-n] [--summary] [--no-commit] [--squash] [-s <strategy>]...
[-m <msg>] <remote> <remote>...
DESCRIPTION
would want to start over, you can recover with
gitlink:git-reset[1].
+CONFIGURATION
+-------------
+
+merge.summary::
+ Whether to include summaries of merged commits in newly
+ created merge commit. False by default.
+
+merge.verbosity::
+ Controls the amount of output shown by the recursive merge
+ strategy. Level 0 outputs nothing except a final error
+ message if conflicts were detected. Level 1 outputs only
+ conflicts, 2 outputs conflicts and file changes. Level 5 and
+ above outputs debugging information. The default is level 2.
+ Can be overriden by 'GIT_MERGE_VERBOSITY' environment variable.
+
HOW MERGE WORKS
---------------
When things cleanly merge, these things happen:
-1. the results are updated both in the index file and in your
- working tree,
-2. index file is written out as a tree,
-3. the tree gets committed, and
-4. the `HEAD` pointer gets advanced.
+1. The results are updated both in the index file and in your
+ working tree;
+2. Index file is written out as a tree;
+3. The tree gets committed; and
+4. The `HEAD` pointer gets advanced.
Because of 2., we require that the original state of the index
file to match exactly the current `HEAD` commit; otherwise we
-t or --tool=<tool>::
Use the merge resolution program specified by <tool>.
Valid merge tools are:
- kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, and opendiff
+ kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, and opendiff
+
If a merge resolution program is not specified, 'git mergetool'
will use the configuration variable merge.tool. If the
GIT
---
Part of the gitlink:git[7] suite
-
Tag Format
----------
-A tag signature file has a very simple fixed format: three lines of
+A tag signature file has a very simple fixed format: four lines of
object <sha1>
type <typename>
tag <tagname>
+ tagger <tagger>
-followed by some 'optional' free-form signature that git itself
-doesn't care about, but that can be verified with gpg or similar.
-
-The size of the full object is artificially limited to 8kB. (Just
-because I'm a lazy bastard, and if you can't fit a signature in that
-size, you're doing something wrong)
+followed by some 'optional' free-form message (some tags created
+by older git may not have `tagger` line). The message, when
+exists, is separated by a blank line from the header. The
+message part may contain a signature that git itself doesn't
+care about, but that can be verified with gpg.
Author
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
Read from stdin, append "(<rev_name>)" to all sha1's of nameable
commits, and pass to stdout
+--name-only::
+ Instead of printing both the SHA-1 and the name, print only
+ the name. If given with --tags the usual tag prefix of
+ "tags/" is also ommitted from the name, matching the output
+ of gitlink::git-describe[1] more closely. This option
+ cannot be combined with --stdin.
+
EXAMPLE
-------
GIT
---
Part of the gitlink:git[7] suite
-
+++ /dev/null
-git-p4import(1)
-===============
-
-NAME
-----
-git-p4import - Import a Perforce repository into git
-
-
-SYNOPSIS
---------
-[verse]
-`git-p4import` [-q|-v] [--notags] [--authors <file>] [-t <timezone>]
- <//p4repo/path> <branch>
-`git-p4import` --stitch <//p4repo/path>
-`git-p4import`
-
-
-DESCRIPTION
------------
-Import a Perforce repository into an existing git repository. When
-a <//p4repo/path> and <branch> are specified a new branch with the
-given name will be created and the initial import will begin.
-
-Once the initial import is complete you can do an incremental import
-of new commits from the Perforce repository. You do this by checking
-out the appropriate git branch and then running `git-p4import` without
-any options.
-
-The standard p4 client is used to communicate with the Perforce
-repository; it must be configured correctly in order for `git-p4import`
-to operate (see below).
-
-
-OPTIONS
--------
--q::
- Do not display any progress information.
-
--v::
- Give extra progress information.
-
-\--authors::
- Specify an authors file containing a mapping of Perforce user
- ids to full names and email addresses (see Notes below).
-
-\--notags::
- Do not create a tag for each imported commit.
-
-\--stitch::
- Import the contents of the given perforce branch into the
- currently checked out git branch.
-
-\--log::
- Store debugging information in the specified file.
-
--t::
- Specify that the remote repository is in the specified timezone.
- Timezone must be in the format "US/Pacific" or "Europe/London"
- etc. You only need to specify this once, it will be saved in
- the git config file for the repository.
-
-<//p4repo/path>::
- The Perforce path that will be imported into the specified branch.
-
-<branch>::
- The new branch that will be created to hold the Perforce imports.
-
-
-P4 Client
----------
-You must make the `p4` client command available in your $PATH and
-configure it to communicate with the target Perforce repository.
-Typically this means you must set the "$P4PORT" and "$P4CLIENT"
-environment variables.
-
-You must also configure a `p4` client "view" which maps the Perforce
-branch into the top level of your git repository, for example:
-
-------------
-Client: myhost
-
-Root: /home/sean/import
-
-Options: noallwrite clobber nocompress unlocked modtime rmdir
-
-View:
- //public/jam/... //myhost/jam/...
-------------
-
-With the above `p4` client setup, you could import the "jam"
-perforce branch into a branch named "jammy", like so:
-
-------------
-$ mkdir -p /home/sean/import/jam
-$ cd /home/sean/import/jam
-$ git init
-$ git p4import //public/jam jammy
-------------
-
-
-Multiple Branches
------------------
-Note that by creating multiple "views" you can use `git-p4import`
-to import additional branches into the same git repository.
-However, the `p4` client has a limitation in that it silently
-ignores all but the last "view" that maps into the same local
-directory. So the following will *not* work:
-
-------------
-View:
- //public/jam/... //myhost/jam/...
- //public/other/... //myhost/jam/...
- //public/guest/... //myhost/jam/...
-------------
-
-If you want more than one Perforce branch to be imported into the
-same directory you must employ a workaround. A simple option is
-to adjust your `p4` client before each import to only include a
-single view.
-
-Another option is to create multiple symlinks locally which all
-point to the same directory in your git repository and then use
-one per "view" instead of listing the actual directory.
-
-
-Tags
-----
-A git tag of the form p4/xx is created for every change imported from
-the Perforce repository where xx is the Perforce changeset number.
-Therefore after the import you can use git to access any commit by its
-Perforce number, e.g. git show p4/327.
-
-The tag associated with the HEAD commit is also how `git-p4import`
-determines if there are new changes to incrementally import from the
-Perforce repository.
-
-If you import from a repository with many thousands of changes
-you will have an equal number of p4/xxxx git tags. Git tags can
-be expensive in terms of disk space and repository operations.
-If you don't need to perform further incremental imports, you
-may delete the tags.
-
-
-Notes
------
-You can interrupt the import (e.g. ctrl-c) at any time and restart it
-without worry.
-
-Author information is automatically determined by querying the
-Perforce "users" table using the id associated with each change.
-However, if you want to manually supply these mappings you can do
-so with the "--authors" option. It accepts a file containing a list
-of mappings with each line containing one mapping in the format:
-
-------------
- perforce_id = Full Name <email@address.com>
-------------
-
-
-Author
-------
-Written by Sean Estabrooks <seanlkml@sympatico.ca>
-
-
-GIT
----
-Part of the gitlink:git[7] suite
-
times to get to the necessary object.
The default value for --window is 10 and --depth is 50.
+--window-memory=[N]::
+ This option provides an additional limit on top of `--window`;
+ the window size will dynamically scale down so as to not take
+ up more than N bytes in memory. This is useful in
+ repositories with a mix of large and small objects to not run
+ out of memory with a large window, but still be able to take
+ advantage of the large window for the smaller objects. The
+ size can be suffixed with "k", "m", or "g".
+ `--window-memory=0` makes memory usage unlimited, which is the
+ default.
+
+--max-pack-size=<n>::
+ Maximum size of each output packfile, expressed in MiB.
+ If specified, multiple packfiles may be created.
+ The default is unlimited.
+
--incremental::
This flag causes an object already in a pack ignored
even if it appears in the standard input.
This flag tells the command not to reuse existing deltas
but compute them from scratch.
+--no-reuse-object::
+ This flag tells the command not to reuse existing object data at all,
+ including non deltified object, forcing recompression of everything.
+ This implies --no-reuse-delta. Useful only in the obscure case where
+ wholesale enforcement of a different compression level on the
+ packed data is desired.
+
+--compression=[N]::
+ Specifies compression level for newly-compressed data in the
+ generated pack. If not specified, pack compression level is
+ determined first by pack.compression, then by core.compression,
+ and defaults to -1, the zlib default, if neither is set.
+ Data copied from loose objects will be recompressed
+ if core.legacyheaders was true when they were created or if
+ the loose compression level (see core.loosecompression and
+ core.compression) is now a different value than the pack
+ compression level. Add --no-reuse-object if you want to force
+ a uniform compression level on all data no matter the source.
+
--delta-base-offset::
A packed archive can express base object of a delta as
either 20-byte object name or as an offset in the
GIT
---
Part of the gitlink:git[7] suite
-
'xargs rm' if you are in the root of the repository.
git-pack-redundant accepts a list of objects on standard input. Any objects
-given will be ignored when checking which packs are required. This makes the
+given will be ignored when checking which packs are required. This makes the
following command useful when wanting to remove packs which contain unreachable
objects.
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
include::pull-fetch-param.txt[]
-include::urls.txt[]
+include::urls-remotes.txt[]
include::merge-strategies.txt[]
GIT
---
Part of the gitlink:git[7] suite
-
+
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
+
-A parameter <ref> without a colon is equivalent to
-<ref>`:`<ref>, hence updates <ref> in the destination from <ref>
-in the source.
+A parameter <ref> without a colon pushes the <ref> from the source
+repository to the destination repository under the same name.
+
Pushing an empty <src> allows you to delete the <dst> ref from
the remote repository.
-f, \--force::
Usually, the command refuses to update a remote ref that is
- not a descendant of the local ref used to overwrite it.
+ 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.
-v::
Run verbosely.
-include::urls.txt[]
+include::urls-remotes.txt[]
+
+
+Examples
+--------
+
+git push origin master::
+ Find a ref that matches `master` in the source repository
+ (most likely, it would find `refs/heads/master`), and update
+ the same ref (e.g. `refs/heads/master`) in `origin` repository
+ with it.
+
+git push origin :experimental::
+ Find a ref that matches `experimental` in the `origin` repository
+ (e.g. `refs/heads/experimental`), and delete it.
+
+git push origin master:satellite/master::
+ Find a ref that matches `master` in the source repository
+ (most likely, it would find `refs/heads/master`), and update
+ the ref that matches `satellite/master` (most likely, it would
+ be `refs/remotes/satellite/master`) in `origin` repository with it.
Author
------
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
-'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
DESCRIPTION
trees that are not directly related to the current
working tree status into a temporary index file.
+--trivial::
+ Restrict three-way merge by `git-read-tree` to happen
+ only if there is no file-level merging required, instead
+ of resolving merge for trivial cases and leaving
+ conflicting files unresolved in the index.
+
--aggressive::
Usually a three-way merge by `git-read-tree` resolves
the merge for really trivial cases and leaves other
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
[verse]
-'git-rebase' [-v] [--merge] [-C<n>] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-i | --interactive] [-v | --verbose] [--merge] [-C<n>]
+ [-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>]
'git-rebase' --continue | --skip | --abort
DESCRIPTION
context exist they all must match. By default no context is
ever ignored.
+-i, \--interactive::
+ Make a list of the commits which are about to be rebased. Let the
+ user edit that list before rebasing.
+
+-p, \--preserve-merges::
+ Instead of ignoring merges, try to recreate them. This option
+ only works in interactive mode.
+
include::merge-strategies.txt[]
NOTES
You must be in the top directory of your project to start (or continue)
a rebase. Upon completion, <branch> will be the current branch.
-Author
+INTERACTIVE MODE
+----------------
+
+Rebasing interactively means that you have a chance to edit the commits
+which are rebased. You can reorder the commits, and you can
+remove them (weeding out bad or otherwise unwanted patches).
+
+The interactive mode is meant for this type of workflow:
+
+1. have a wonderful idea
+2. hack on the code
+3. prepare a series for submission
+4. submit
+
+where point 2. consists of several instances of
+
+a. regular use
+ 1. finish something worthy of a commit
+ 2. commit
+b. independent fixup
+ 1. realize that something does not work
+ 2. fix that
+ 3. commit it
+
+Sometimes the thing fixed in b.2. cannot be amended to the not-quite
+perfect commit it fixes, because that commit is buried deeply in a
+patch series. That is exactly what interactive rebase is for: use it
+after plenty of "a"s and "b"s, by rearranging and editing
+commits, and squashing multiple commits into one.
+
+Start it with the last commit you want to retain as-is:
+
+ git rebase -i <after-this-commit>
+
+An editor will be fired up with all the commits in your current branch
+(ignoring merge commits), which come after the given commit. You can
+reorder the commits in this list to your heart's content, and you can
+remove them. The list looks more or less like this:
+
+-------------------------------------------
+pick deadbee The oneline of this commit
+pick fa1afe1 The oneline of the next commit
+...
+-------------------------------------------
+
+The oneline descriptions are purely for your pleasure; `git-rebase` will
+not look at them but at the commit names ("deadbee" and "fa1afe1" in this
+example), so do not delete or edit the names.
+
+By replacing the command "pick" with the command "edit", you can tell
+`git-rebase` to stop after applying that commit, so that you can edit
+the files and/or the commit message, amend the commit, and continue
+rebasing.
+
+If you want to fold two or more commits into one, replace the command
+"pick" with "squash" for the second and subsequent commit. If the
+commits had different authors, it will attribute the squashed commit to
+the author of the last commit.
+
+In both cases, or when a "pick" does not succeed (because of merge
+errors), the loop will stop to let you fix things, and you can continue
+the loop with `git rebase --continue`.
+
+For example, if you want to reorder the last 5 commits, such that what
+was HEAD~4 becomes the new HEAD. To achieve that, you would call
+`git-rebase` like this:
+
+----------------------
+$ git rebase -i HEAD~5
+----------------------
+
+And move the first patch to the end of the list.
+
+You might want to preserve merges, if you have a history like this:
+
+------------------
+ X
+ \
+ A---M---B
+ /
+---o---O---P---Q
+------------------
+
+Suppose you want to rebase the side branch starting at "A" to "Q". Make
+sure that the current HEAD is "B", and call
+
+-----------------------------
+$ git rebase -i -p --onto Q O
+-----------------------------
+
+Authors
------
-Written by Junio C Hamano <junkio@cox.net>
+Written by Junio C Hamano <junkio@cox.net> and
+Johannes E. Schindelin <johannes.schindelin@gmx.de>
Documentation
--------------
GIT
---
Part of the gitlink:git[7] suite
-
The refname value is relative to $GIT_DIR; e.g. for the master
head this is "refs/heads/master". The two sha1 values before
each refname are the object names for the refname before and after
-the update. Refs to be created will have sha1-old equal to 0{40},
-while refs to be deleted will have sha1-new equal to 0{40}, otherwise
+the update. Refs to be created will have sha1-old equal to 0\{40},
+while refs to be deleted will have sha1-new equal to 0\{40}, otherwise
sha1-old and sha1-new should be valid objects in the repository.
This hook is called before any refname is updated and before any
head this is "refs/heads/master". The two sha1 arguments are
the object names for the refname before and after the update.
Note that the hook is called before the refname is updated,
-so either sha1-old is 0{40} (meaning there is no such ref yet),
+so either sha1-old is 0\{40} (meaning there is no such ref yet),
or it should match what is recorded in refname.
The hook should exit with non-zero status if it wants to disallow
head this is "refs/heads/master". The two sha1 values before
each refname are the object names for the refname before and after
the update. Refs that were created will have sha1-old equal to
-0{40}, while refs that were deleted will have sha1-new equal to
-0{40}, otherwise sha1-old and sha1-new should be valid objects in
+0\{40}, while refs that were deleted will have sha1-new equal to
+0\{40}, otherwise sha1-old and sha1-new should be valid objects in
the repository.
Using this hook, it is easy to generate mails describing the updates
OPTIONS
-------
+--stale-fix::
+ This revamps the logic -- the definition of "broken commit"
+ becomes: a commit that is not reachable from any of the refs and
+ there is a missing object among the commit, tree, or blob
+ objects reachable from it that is not reachable from any of the
+ refs.
++
+This computation involves traversing all the reachable objects, i.e. it
+has the same cost as 'git prune'. Fortunately, once this is run, we
+should not have to ever worry about missing objects, because the current
+prune and pack-objects know about reflogs and protect objects referred by
+them.
+
--expire=<time>::
Entries older than this time are pruned. Without the
option it is taken from configuration `gc.reflogExpire`,
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
to be applied that many times to get to the necessary object.
The default value for --window is 10 and --depth is 50.
+--window-memory=[N]::
+ This option provides an additional limit on top of `--window`;
+ the window size will dynamically scale down so as to not take
+ up more than N bytes in memory. This is useful in
+ repositories with a mix of large and small objects to not run
+ out of memory with a large window, but still be able to take
+ advantage of the large window for the smaller objects. The
+ size can be suffixed with "k", "m", or "g".
+ `--window-memory=0` makes memory usage unlimited, which is the
+ default.
+
+--max-pack-size=<n>::
+ Maximum size of each output packfile, expressed in MiB.
+ If specified, multiple packfiles may be created.
+ The default is unlimited.
+
Configuration
-------------
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
results and applying the previously recorded hand resolution.
[NOTE]
-You need to create `$GIT_DIR/rr-cache` directory to enable this
+You need to set the config variable rerere.enabled to enable this
command.
resolve when it is not. `git-commit` also invokes `git-rerere`
when recording a merge result. What this means is that you do
not have to do anything special yourself (Note: you still have
-to create `$GIT_DIR/rr-cache` directory to enable this command).
+to set the config variable rerere.enabled to enable this command).
In our example, when you did the test merge, the manual
resolution is recorded, and it will be reused when you do the
<1> This is most often done when you remembered what you
just committed is incomplete, or you misspelled your commit
message, or both. Leaves working tree as it was before "reset".
-<2> make corrections to working tree files.
+<2> Make corrections to working tree files.
<3> "reset" copies the old head to .git/ORIG_HEAD; redo the
commit by starting with its log message. If you do not need to
edit the message further, you can give -C option instead.
$ git pull git://info.example.com/ nitfol <4>
------------
+
-<1> you are happily working on something, and find the changes
+<1> You are happily working on something, and find the changes
in these files are in good order. You do not want to see them
when you run "git diff", because you plan to work on other files
and changes with these files are distracting.
-<2> somebody asks you to pull, and the changes sounds worthy of merging.
-<3> however, you already dirtied the index (i.e. your index does
+<2> Somebody asks you to pull, and the changes sounds worthy of merging.
+<3> However, you already dirtied the index (i.e. your index does
not match the HEAD commit). But you know the pull you are going
to make does not affect frotz.c nor filfre.c, so you revert the
index changes for these two files. Your changes in working tree
remain there.
-<4> then you can pull and merge, leaving frotz.c and filfre.c
+<4> Then you can pull and merge, leaving frotz.c and filfre.c
changes still in the working tree.
Undo a merge or pull::
$ git reset --hard ORIG_HEAD <4>
------------
+
-<1> try to update from the upstream resulted in a lot of
+<1> Try to update from the upstream resulted in a lot of
conflicts; you were not ready to spend a lot of time merging
right now, so you decide to do that later.
<2> "pull" has not made merge commit, so "git reset --hard"
which is a synonym for "git reset --hard HEAD" clears the mess
from the index file and the working tree.
-<3> merge a topic branch into the current branch, which resulted
+<3> Merge a topic branch into the current branch, which resulted
in a fast forward.
-<4> but you decided that the topic branch is not ready for public
+<4> But you decided that the topic branch is not ready for public
consumption yet. "pull" or "merge" always leaves the original
tip of the current branch in ORIG_HEAD, so resetting hard to it
brings your index file and the working tree back to that state,
[ \--sparse ]
[ \--no-merges ]
[ \--remove-empty ]
+ [ \--full-history ]
[ \--not ]
[ \--all ]
[ \--stdin ]
[ \--topo-order ]
[ \--parents ]
+ [ \--timestamp ]
[ \--left-right ]
[ \--cherry-pick ]
[ \--encoding[=<encoding>] ]
[ \--(author|committer|grep)=<pattern> ]
- [ \--date={local|relative|default} ]
+ [ \--regexp-ignore-case | \-i ]
+ [ \--extended-regexp | \-E ]
+ [ \--date={local|relative|default|iso|rfc|short} ]
[ [\--objects | \--objects-edge] [ \--unpacked ] ]
[ \--pretty | \--header ]
[ \--bisect ]
[ \--merge ]
[ \--reverse ]
[ \--walk-reflogs ]
+ [ \--no-walk ] [ \--do-walk ]
<commit>... [ \-- <paths>... ]
DESCRIPTION
Synonym for `--date=relative`.
---date={relative,local,default}::
+--date={relative,local,default,iso,rfc}::
Only takes effect for dates shown in human-readable format, such
as when using "--pretty".
+
`--date=local` shows timestamps in user's local timezone.
+
+`--date=iso` (or `--date=iso8601`) shows timestamps in ISO 8601 format.
++
+`--date=rfc` (or `--date=rfc2822`) shows timestamps in RFC 2822
+format, often found in E-mail messages.
++
+`--date=short` shows only date but not time, in `YYYY-MM-DD` fomat.
++
`--date=default` shows timestamps in the original timezone
(either committer's or author's).
Print the parents of the commit.
+--timestamp::
+ Print the raw commit timestamp.
+
--left-right::
Mark which side of a symmetric diff a commit is reachable from.
Limit the commits output to ones with log message that
matches the specified pattern (regular expression).
+-i, --regexp-ignore-case::
+
+ Match the regexp limiting patterns without regard to letters case.
+
+-E, --extended-regexp::
+
+ Consider the limiting patterns to be extended regular expressions
+ instead of the default basic regular expressions.
+
--remove-empty::
Stop when a given path disappears from the tree.
+--full-history::
+
+ Show also parts of history irrelevant to current state of a given
+ path. This turns off history simplification, which removed merges
+ which didn't change anything at all at some child. It will still actually
+ simplify away merges that didn't change anything at all into either
+ child.
+
--no-merges::
Do not print commits with more than one parent.
+
With '\--pretty' format other than oneline (for obvious reasons),
this causes the output to have two extra lines of information
-taken from the reflog. By default, 'commit@{Nth}' notation is
+taken from the reflog. By default, 'commit@\{Nth}' notation is
used in the output. When the starting commit is specified as
-'commit@{now}', output also uses 'commit@{timestamp}' notation
+'commit@{now}', output also uses 'commit@\{timestamp}' notation
instead. Under '\--pretty=oneline', the commit message is
prefixed with this information on the same line.
Only useful with '--objects'; print the object IDs that are not
in packs.
+--no-walk::
+
+ Only show the given revs, but do not traverse their ancestors.
+
+--do-walk::
+
+ Overrides a previous --no-walk.
+
include::pretty-formats.txt[]
--git-dir::
Show `$GIT_DIR` if defined else show the path to the .git directory.
+--is-inside-git-dir::
+ When the current working directory is below the repository
+ directory print "true", otherwise "false".
+
+--is-inside-work-tree::
+ When the current working directory is inside the work tree of the
+ repository print "true", otherwise "false".
+
+--is-bare-repository::
+ When the repository is bare print "true", otherwise "false".
+
--short, --short=number::
Instead of outputting the full SHA1 values of object names try to
abbreviate them to a shorter unique name. When no length is specified
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
Remove files from the working tree and from the index. The
files have to be identical to the tip of the branch, and no
updates to its contents must have been placed in the staging
-area (aka index).
+area (aka index). When --cached is given, the staged content has to
+match either the tip of the branch *or* the file on disk.
OPTIONS
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
value; if that is unspecified, default to --chain-reply-to.
--compose::
- Use $EDITOR to edit an introductory message for the
- patch series.
+ Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an
+ introductory message for the patch series.
--from::
Specify the sender of the emails. This will default to
Only necessary if --compose is also set. If --compose
is not set, this will be prompted for.
---no-signed-off-by-cc::
- Do not add emails found in Signed-off-by: or Cc: lines to the
- cc list.
+--signed-off-by-cc, --no-signed-off-by-cc::
+ If this is set, add emails found in Signed-off-by: or Cc: lines to the
+ cc list.
+ Default is the value of 'sendemail.signedoffbycc' configuration value;
+ if that is unspecified, default to --signed-off-by-cc.
--quiet::
Make git-send-email less verbose. One line per email should be
`localhost` otherwise.
--subject::
- Specify the initial subject of the email thread.
+ Specify the initial subject of the email thread.
Only necessary if --compose is also set. If --compose
is not set, this will be prompted for.
---suppress-from::
- Do not add the From: address to the cc: list, if it shows up in a From:
- line.
+--suppress-from, --no-suppress-from::
+ If this is set, do not add the From: address to the cc: list, if it
+ shows up in a From: line.
+ Default is the value of 'sendemail.suppressfrom' configuration value;
+ if that is unspecified, default to --no-supress-from.
+
+--thread, --no-thread::
+ If this is set, the In-Reply-To header will be set on each email sent.
+ If disabled with "--no-thread", no emails will have the In-Reply-To
+ header set.
+ Default is the value of the 'sendemail.thread' configuration value;
+ if that is unspecified, default to --thread.
--dry-run::
Do everything except actually send the emails.
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
DESCRIPTION
-----------
+THIS COMMAND IS DEPRECATED.
+
Pulls from a remote repository over ssh connection, invoking
git-ssh-upload on the other end. It functions identically to
git-ssh-upload, aside from which end you run it on.
GIT
---
Part of the gitlink:git[7] suite
-
DESCRIPTION
-----------
+THIS COMMAND IS DEPRECATED.
+
Pushes from a remote repository over ssh connection, invoking
git-ssh-fetch on the other end. It functions identically to
git-ssh-fetch, aside from which end you run it on.
GIT
---
Part of the gitlink:git[7] suite
-
--- /dev/null
+git-stash(1)
+============
+
+NAME
+----
+git-stash - Stash the changes in a dirty working directory away
+
+SYNOPSIS
+--------
+[verse]
+'git-stash' (list | show [<stash>] | apply [<stash>] | clear)
+'git-stash' [save] [message...]
+
+DESCRIPTION
+-----------
+
+Use 'git-stash' when you want to record the current state of the
+working directory and the index, but want to go back to a clean
+working directory. The command saves your local modifications away
+and reverts the working directory to match the `HEAD` commit.
+
+The modifications stashed away by this command can be listed with
+`git-stash list`, inspected with `git-stash show`, and restored
+(potentially on top of a different commit) with `git-stash apply`.
+Calling git-stash without any arguments is equivalent to `git-stash
+save`. A stash is by default listed as "WIP on 'branchname' ...", but
+you can give a more descriptive message on the command line when
+you create one.
+
+The latest stash you created is stored in `$GIT_DIR/refs/stash`; older
+stashes are found in the reflog of this reference and can be named using
+the usual reflog syntax (e.g. `stash@\{1}` is the most recently
+created stash, `stash@\{2}` is the one before it, `stash@\{2.hours.ago}`
+is also possible).
+
+OPTIONS
+-------
+
+save::
+
+ Save your local modifications to a new 'stash', and run `git-reset
+ --hard` to revert them. This is the default action when no
+ subcommand is given.
+
+list::
+
+ List the stashes that you currently have. Each 'stash' is listed
+ with its name (e.g. `stash@\{0}` is the latest stash, `stash@\{1} is
+ the one before, etc.), the name of the branch that was current when the
+ stash was made, and a short description of the commit the stash was
+ based on.
++
+----------------------------------------------------------------
+stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
+stash@{1}: On master: 9cc0589... Add git-stash
+----------------------------------------------------------------
+
+show [<stash>]::
+
+ Show the changes recorded in the stash as a diff between the the
+ stashed state and its original parent. When no `<stash>` is given,
+ shows the latest one. By default, the command shows the diffstat, but
+ it will accept any format known to `git-diff` (e.g., `git-stash show
+ -p stash@\{2}` to view the second most recent stash in patch form).
+
+apply [<stash>]::
+
+ Restore the changes recorded in the stash on top of the current
+ working tree state. When no `<stash>` is given, applies the latest
+ one. The working directory must match the index.
++
+This operation can fail with conflicts; you need to resolve them
+by hand in the working tree.
+
+clear::
+ Remove all the stashed states. Note that those states will then
+ be subject to pruning, and may be difficult or impossible to recover.
+
+
+DISCUSSION
+----------
+
+A stash is represented as a commit whose tree records the state of the
+working directory, and its first parent is the commit at `HEAD` when
+the stash was created. The tree of the second parent records the
+state of the index when the stash is made, and it is made a child of
+the `HEAD` commit. The ancestry graph looks like this:
+
+ .----W
+ / /
+ -----H----I
+
+where `H` is the `HEAD` commit, `I` is a commit that records the state
+of the index, and `W` is a commit that records the state of the working
+tree.
+
+
+EXAMPLES
+--------
+
+Pulling into a dirty tree::
+
+When you are in the middle of something, you learn that there are
+upstream changes that are possibly relevant to what you are
+doing. When your local changes do not conflict with the changes in
+the upstream, a simple `git pull` will let you move forward.
++
+However, there are cases in which your local changes do conflict with
+the upstream changes, and `git pull` refuses to overwrite your
+changes. In such a case, you can stash your changes away,
+perform a pull, and then unstash, like this:
++
+----------------------------------------------------------------
+$ git pull
+...
+file foobar not up to date, cannot merge.
+$ git stash
+$ git pull
+$ git stash apply
+----------------------------------------------------------------
+
+Interrupted workflow::
+
+When you are in the middle of something, your boss comes in and
+demands that you fix something immediately. Traditionally, you would
+make a commit to a temporary branch to store your changes away, and
+return to your original branch to make the emergency fix, like this:
++
+----------------------------------------------------------------
+... hack hack hack ...
+$ git checkout -b my_wip
+$ git commit -a -m "WIP"
+$ git checkout master
+$ edit emergency fix
+$ git commit -a -m "Fix in a hurry"
+$ git checkout my_wip
+$ git reset --soft HEAD^
+... continue hacking ...
+----------------------------------------------------------------
++
+You can use `git-stash` to simplify the above, like this:
++
+----------------------------------------------------------------
+... hack hack hack ...
+$ git stash
+$ edit emergency fix
+$ git commit -a -m "Fix in a hurry"
+$ git stash apply
+... continue hacking ...
+----------------------------------------------------------------
+
+SEE ALSO
+--------
+gitlink:git-checkout[1],
+gitlink:git-commit[1],
+gitlink:git-reflog[1],
+gitlink:git-reset[1]
+
+AUTHOR
+------
+Written by Nanako Shiraishi <nanako3@bluebottle.com>
+
+GIT
+---
+Part of the gitlink:git[7] suite
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
--- /dev/null
+git-submodule(1)
+================
+
+NAME
+----
+git-submodule - Initialize, update or inspect submodules
+
+
+SYNOPSIS
+--------
+[verse]
+'git-submodule' [--quiet] [-b branch] add <repository> [<path>]
+'git-submodule' [--quiet] [--cached] [status|init|update] [--] [<path>...]
+
+
+COMMANDS
+--------
+add::
+ Add the given repository as a submodule at the given path
+ to the changeset to be committed next. In particular, the
+ repository is cloned at the specified path, added to the
+ changeset and registered in .gitmodules. If no path is
+ specified, the path is deduced from the repository specification.
+
+status::
+ Show the status of the submodules. This will print the SHA-1 of the
+ currently checked out commit for each submodule, along with the
+ submodule path and the output of gitlink:git-describe[1] for the
+ SHA-1. Each SHA-1 will be prefixed with `-` if the submodule is not
+ initialized and `+` if the currently checked out submodule commit
+ does not match the SHA-1 found in the index of the containing
+ repository. This command is the default command for git-submodule.
+
+init::
+ Initialize the submodules, i.e. register in .git/config each submodule
+ name and url found in .gitmodules. The key used in .git/config is
+ `submodule.$name.url`. This command does not alter existing information
+ in .git/config.
+
+update::
+ Update the registered submodules, i.e. clone missing submodules and
+ checkout the commit specified in the index of the containing repository.
+ This will make the submodules HEAD be detached.
+
+
+OPTIONS
+-------
+-q, --quiet::
+ Only print error messages.
+
+-b, --branch::
+ Branch of repository to add as submodule.
+
+--cached::
+ Display the SHA-1 stored in the index, not the SHA-1 of the currently
+ checked out submodule commit. This option is only valid for the
+ status command.
+
+<path>::
+ Path to submodule(s). When specified this will restrict the command
+ to only operate on the submodules found at the specified paths.
+
+FILES
+-----
+When initializing submodules, a .gitmodules file in the top-level directory
+of the containing repository is used to find the url of each submodule.
+This file should be formatted in the same way as $GIR_DIR/config. The key
+to each submodule url is "submodule.$name.url".
+
+
+AUTHOR
+------
+Written by Lars Hjemli <hjemli@gmail.com>
+
+GIT
+---
+Part of the gitlink:git[7] suite
to the names of remotes if trunk/branches/tags are
specified. The prefix does not automatically include a
trailing slash, so be sure you include one in the
- argument if that is what you want. This is useful if
- you wish to track multiple projects that share a common
- repository.
+ argument if that is what you want. If --branches/-b is
+ specified, the prefix must include a trailing slash.
+ Setting a prefix is useful if you wish to track multiple
+ projects that share a common repository.
'fetch'::
Fetch unfetched revisions from the Subversion remote we are
GIT
---
Part of the gitlink:git[7] suite
-
[verse]
'git-tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <name> [<head>]
'git-tag' -d <name>...
-'git-tag' -l [<pattern>]
+'git-tag' [-n [<num>]] -l [<pattern>]
'git-tag' -v <name>
DESCRIPTION
`-v <tag>` verifies the gpg signature of the tag.
-`-l <pattern>` lists tags that match the given pattern (or all
-if no pattern is given).
+`-l <pattern>` lists tags with names that match the given pattern
+(or all if no pattern is given).
OPTIONS
-------
-v::
Verify the gpg signature of given the tag
+-n <num>::
+ <num> specifies how many lines from the annotation, if any,
+ are printed when using -l.
+ The default is not to print any annotation lines.
+
-l <pattern>::
- List tags that match the given pattern (or all if no pattern is given).
+ List tags with names that match the given pattern (or all if no pattern is given).
-m <msg>::
Use the given tag message (instead of prompting)
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
--unmerged::
If --refresh finds unmerged changes in the index, the default
- behavior is to error out. This option makes git-update-index
+ behavior is to error out. This option makes git-update-index
continue anyway.
--ignore-missing::
--cacheinfo <mode> <object> <path>::
Directly insert the specified info into the index.
-
+
--index-info::
Read index information from stdin.
--chmod=(+|-)x::
- Set the execute permissions on the updated files.
+ Set the execute permissions on the updated files.
--assume-unchanged, --no-assume-unchanged::
When these flags are specified, the object name recorded
<file>::
Files to act on.
Note that files beginning with '.' are discarded. This includes
- `./file` and `dir/./file`. If you don't want this, then use
+ `./file` and `dir/./file`. If you don't want this, then use
cleaner names.
The same applies to directories ending '/' and paths with '//'
GIT
---
Part of the gitlink:git[7] suite
-
SYNOPSIS
--------
-'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>])
+'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | [--no-deref] <ref> <newvalue> [<oldvalue>])
DESCRIPTION
-----------
filesystem to follow them, but will overwrite such a symlink to
somewhere else with a regular filename).
+If --no-deref is given, <ref> itself is overwritten, rather than
+the result of following the symbolic pointers.
+
In general, using
git-update-ref HEAD "$head"
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
GIT
---
Part of the gitlink:git[7] suite
-
--------
[verse]
'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate]
- [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]
+ [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]
+ [--help] COMMAND [ARGS]
DESCRIPTION
-----------
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.5.2/git.html[documentation for release 1.5.2]
+* link:v1.5.2.4/git.html[documentation for release 1.5.2.4]
* release notes for
+ link:RelNotes-1.5.2.4.txt[1.5.2.4],
+ link:RelNotes-1.5.2.3.txt[1.5.2.3],
+ link:RelNotes-1.5.2.2.txt[1.5.2.2],
+ link:RelNotes-1.5.2.1.txt[1.5.2.1],
link:RelNotes-1.5.2.txt[1.5.2].
* link:v1.5.1.6/git.html[documentation for release 1.5.1.6]
Set the path to the repository. This can also be controlled by
setting the GIT_DIR environment variable.
+--work-tree=<path>::
+ 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).
+ This can also be controlled by setting the GIT_WORK_TREE
+ environment variable and the core.worktree configuration
+ variable.
+
--bare::
Same as --git-dir=`pwd`.
specifies a path to use instead of the default `.git`
for the base of the repository.
+'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).
+ This can also be controlled by the '--work-tree' command line
+ option and the core.worktree configuration variable.
+
git Commits
~~~~~~~~~~~
'GIT_AUTHOR_NAME'::
other
~~~~~
+'GIT_MERGE_VERBOSITY'::
+ A number controlling the amount of output shown by
+ the recursive merge strategy. Overrides merge.verbosity.
+ See gitlink:git-merge[1]
+
'GIT_PAGER'::
- This environment variable overrides `$PAGER`.
+ This environment variable overrides `$PAGER`. If it is set
+ to an empty string or to the value "cat", git will not launch
+ a pager.
+
+'GIT_SSH'::
+ If this environment variable is set then gitlink:git-fetch[1]
+ and gitlink:git-push[1] will use this command instead
+ of `ssh` when they need to connect to a remote system.
+ The 'GIT_SSH' command will be given exactly two arguments:
+ the 'username@host' (or just 'host') from the URL and the
+ shell command to execute on that remote system.
++
+To pass options to the program that you want to list in GIT_SSH
+you will need to wrap the program and options into a shell script,
+then set GIT_SSH to refer to the shell script.
++
+Usually it is easier to configure any desired options through your
+personal `.ssh/config` file. Please consult your ssh documentation
+for further details.
+
+'GIT_FLUSH'::
+ If this environment variable is set to "1", then commands such
+ as git-blame (in incremental mode), git-rev-list, git-log,
+ git-whatchanged, etc., will force a flush of the output stream
+ after each commit-oriented 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
+ based on whether stdout appears to be redirected to a file or not.
'GIT_TRACE'::
If this variable is set to "1", "2" or "true" (comparison
GIT
---
Part of the gitlink:git[7] suite
-
-------
Certain operations by git can be influenced by assigning
-particular attributes to a path. Currently, three operations
-are attributes-aware.
+particular attributes to a path. Currently, the following
+operations are attributes-aware.
Checking-out and checking-in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~
The attribute `diff` affects if `git diff` generates textual
-patch for the path or just says `Binary files differ`.
+patch for the path or just says `Binary files differ`. It also
+can affect what line is shown on the hunk header `@@ -k,l +n,m @@`
+line.
Set::
Diff is shown using the specified custom diff driver.
The driver program is given its input using the same
calling convention as used for GIT_EXTERNAL_DIFF
- program.
+ program. This name is also used for custom hunk header
+ selection.
Defining a custom diff driver
See gitlink:git[7] for details.
+Defining a custom hunk-header
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Each group of changes (called "hunk") in the textual diff output
+is prefixed with a line of the form:
+
+ @@ -k,l +n,m @@ TEXT
+
+The text is called 'hunk header', and by default a line that
+begins with an alphabet, an underscore or a dollar sign is used,
+which matches what GNU `diff -p` output uses. This default
+selection however is not suited for some contents, and you can
+use customized pattern to make a selection.
+
+First in .gitattributes, you would assign the `diff` attribute
+for paths.
+
+------------------------
+*.tex diff=tex
+------------------------
+
+Then, you would define "diff.tex.funcname" configuration to
+specify a regular expression that matches a line that you would
+want to appear as the hunk header, like this:
+
+------------------------
+[diff "tex"]
+ funcname = "^\\(\\\\\\(sub\\)*section{.*\\)$"
+------------------------
+
+Note. A single level of backslashes are eaten by the
+configuration file parser, so you would need to double the
+backslashes; the pattern above picks a line that begins with a
+backslash, and zero or more occurences of `sub` followed by
+`section` followed by open brace, to the end of line.
+
+There are a few built-in patterns to make this easier, and `tex`
+is one of them, so you do not have to write the above in your
+configuration file (you still need to enable this with the
+attribute mechanism, via `.gitattributes`). Another built-in
+pattern is defined for `java` that defines a pattern suitable
+for program text in Java language.
+
+
Performing a three-way merge
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
and `bar` attributes should be given to this path, so it
leaves `foo` and `bar` unset. Attribute `baz` is set.
-3. Finally it examines `$GIT_DIR/info/gitattributes`. This file
+3. Finally it examines `$GIT_DIR/info/attributes`. This file
is used to override the in-tree settings. The first line is
a match, and `foo` is set, `bar` is reverted to unspecified
state, and `baz` is unset.
When deciding whether to ignore a path, git normally checks
`gitignore` patterns from multiple sources, with the following
-order of precedence:
+order of precedence, from highest to lowest (within one level of
+precedence, the last matching pattern decides the outcome):
- * Patterns read from the file specified by the configuration
- variable 'core.excludesfile'.
-
- * Patterns read from `$GIT_DIR/info/exclude`.
+ * Patterns read from the command line for those commands that support
+ them.
* Patterns read from a `.gitignore` file in the same directory
- as the path, or in any parent directory, ordered from the
- deepest such file to a file in the root of the repository.
+ as the path, or in any parent directory, with patterns in the
+ higher level files (up to the root) being overriden by those in
+ lower level files down to the directory containing the file.
These patterns match relative to the location of the
`.gitignore` file. A project normally includes such
`.gitignore` files in its repository, containing patterns for
files generated as part of the project build.
+ * Patterns read from `$GIT_DIR/info/exclude`.
+
+ * Patterns read from the file specified by the configuration
+ variable 'core.excludesfile'.
+
The underlying git plumbing tools, such as
gitlink:git-ls-files[1] and gitlink:git-read-tree[1], read
`gitignore` patterns specified by command-line options, or from
- An optional prefix '!' which negates the pattern; any
matching file excluded by a previous pattern will become
- included again.
+ included again. If a negated pattern matches, this will
+ override lower precedence patterns sources.
- If the pattern does not contain a slash '/', git treats it as
a shell glob pattern and checks for a match against the
GIT
---
Part of the gitlink:git[7] suite
-
--- /dev/null
+gitmodules(5)
+=============
+
+NAME
+----
+gitmodules - defining submodule properties
+
+SYNOPSIS
+--------
+gitmodules
+
+
+DESCRIPTION
+-----------
+
+The `.gitmodules` file, located in the top-level directory of a git
+working tree, is a text file with a syntax matching the requirements
+of gitlink:git-config[1].
+
+The file contains one subsection per submodule, and the subsection value
+is the name of the submodule. Each submodule section also contains the
+following required keys:
+
+submodule.<name>.path::
+ Defines the path, relative to the top-level directory of the git
+ working tree, where the submodule is expected to be checked out.
+ The path name must not end with a `/`. All submodule paths must
+ be unique within the .gitmodules file.
+
+submodule.<name>.url::
+ Defines an url from where the submodule repository can be cloned.
+
+
+EXAMPLES
+--------
+
+Consider the following .gitmodules file:
+
+ [submodule "libfoo"]
+ path = include/foo
+ url = git://foo.com/git/lib.git
+
+ [submodule "libbar"]
+ path = include/bar
+ url = git://bar.com/git/lib.git
+
+
+This defines two submodules, `libfoo` and `libbar`. These are expected to
+be checked out in the paths 'include/foo' and 'include/bar', and for both
+submodules an url is specified which can be used for cloning the submodules.
+
+SEE ALSO
+--------
+gitlink:git-submodule[1] gitlink:git-config[1]
+
+DOCUMENTATION
+-------------
+Documentation by Lars Hjemli <hjemli@gmail.com>
+
+GIT
+---
+Part of the gitlink:git[7] suite
applypatch-msg
--------------
-This hook is invoked by `git-applypatch` script, which is
-typically invoked by `git-applymbox`. It takes a single
+This hook is invoked by `git-am` script. It takes a single
parameter, the name of the file that holds the proposed commit
log message. Exiting with non-zero status causes
-`git-applypatch` to abort before applying the patch.
+`git-am` to abort before applying the patch.
The hook is allowed to edit the message file in place, and can
be used to normalize the message into some project standard
pre-applypatch
--------------
-This hook is invoked by `git-applypatch` script, which is
-typically invoked by `git-applymbox`. It takes no parameter,
+This hook is invoked by `git-am`. It takes no parameter,
and is invoked after the patch is applied, but before a commit
is made. Exiting with non-zero status causes the working tree
after application of the patch not committed.
post-applypatch
---------------
-This hook is invoked by `git-applypatch` script, which is
-typically invoked by `git-applymbox`. It takes no parameter,
+This hook is invoked by `git-am`. It takes no parameter,
and is invoked after the patch is applied and a commit is made.
This hook is meant primarily for notification, and cannot affect
-the outcome of `git-applypatch`.
+the outcome of `git-am`.
pre-commit
----------
On Sat, 13 Aug 2005, Linus Torvalds wrote:
-> That's correct. Same things apply: you can move a patch over, and create a
-> new one with a modified comment, but basically the _old_ commit will be
+> That's correct. Same things apply: you can move a patch over, and create a
+> new one with a modified comment, but basically the _old_ commit will be
> immutable.
Let me clarify.
You can entirely _drop_ old branches, so commits may be immutable, but
-nothing forces you to keep them. Of course, when you drop a commit, you'll
-always end up dropping all the commits that depended on it, and if you
-actually got somebody else to pull that commit you can't drop it from
+nothing forces you to keep them. Of course, when you drop a commit, you'll
+always end up dropping all the commits that depended on it, and if you
+actually got somebody else to pull that commit you can't drop it from
_their_ repository, but undoing things is not impossible.
For example, let's say that you've made a mess of things: you've committed
# for reference
git branch broken
- # Reset the main branch to three parents back: this
+ # Reset the main branch to three parents back: this
# effectively undoes the three top commits
git reset HEAD^^^
git checkout -f
to see that everything looks sensible.
-And then, you can just remove the broken branch if you decide you really
+And then, you can just remove the broken branch if you decide you really
don't want it:
# remove 'broken' branch
# Prune old objects if you're really really sure
git prune
-And yeah, I'm sure there are other ways of doing this. And as usual, the
-above is totally untested, and I just wrote it down in this email, so if
+And yeah, I'm sure there are other ways of doing this. And as usual, the
+above is totally untested, and I just wrote it down in this email, so if
I've done something wrong, you'll have to figure it out on your own ;)
Linus
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
-
-
> Dear diary, on Sun, Aug 14, 2005 at 09:57:13AM CEST, I got a letter
> where Junio C Hamano <junkio@cox.net> told me that...
>> Linus Torvalds <torvalds@osdl.org> writes:
->>
->> > Junio, maybe you want to talk about how you move patches from your "pu"
+>>
+>> > Junio, maybe you want to talk about how you move patches from your "pu"
>> > branch to the real branches.
->>
+>>
> Actually, wouldn't this be also precisely for what StGIT is intended to?
Exactly my feeling. I was sort of waiting for Catalin to speak
where *your "master" head
upstream --> #1 --> #2 --> #3
- used \
+ used \
to be \--> #A --> #2' --> #3' --> #B --> #C
*upstream head
$ git fetch upstream
This leaves the updated upstream head in .git/FETCH_HEAD but
-does not touch your .git/HEAD nor .git/refs/heads/master.
+does not touch your .git/HEAD nor .git/refs/heads/master.
You run "git rebase" now.
$ git rebase FETCH_HEAD master
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
-
-
- This is still crude and does not protect against simultaneous
make invocations stomping on each other. I would need to add
some locking mechanism for this.
-
nor tag anymore, so remove them:
------------------------------------------------
-$ rm -f .git/refs/tags/pu-anchor
+$ rm -f .git/refs/tags/pu-anchor
$ git branch -d revert-c99
------------------------------------------------
"master"
o---o
- \ "topic"
+ \ "topic"
o---o---o---o---o---o
At this point, "topic" contains something I know I want, but it
$ git checkout -b topicA master
... pick and apply pieces from P.diff to build
... commits on topicA branch.
-
+
o---o---o
/ "topicA"
o---o"master"
- \ "topic"
+ \ "topic"
o---o---o---o---o---o
Before doing each commit on "topicA" HEAD, I run "diff HEAD"
/o---o---o
|/ "topicA"
o---o"master"
- \ "topic"
+ \ "topic"
o---o---o---o---o---o
After I am done, I'd try a pretend-merge between "topicA" and
/o---o---o----------'
|/ "topicA"
o---o"master"
- \ "topic"
+ \ "topic"
o---o---o---o---o---o
The last diff better not to show anything other than cleanups
"topicB"
o---o---o---o---o
- /
+ /
/o---o---o
|/ "topicA"
o---o"master"
-
$ git ls-remote git://127.0.0.1/rule-the-world.git
If this does not work, find out why, and submit a patch to this document.
-
SUBDIRECTORY_OK=t
USAGE='<refname> <target directory>'
. git-sh-setup
-export GIT_DIR
+cd_to_toplevel
test -z "$mandir" && usage
-if ! git-rev-parse --verify "$head^0" >/dev/null; then
+if ! git rev-parse --verify "$head^0" >/dev/null; then
echo >&2 "head: $head does not exist in the current repository"
usage
fi
GIT_INDEX_FILE=`pwd`/.quick-doc.index
export GIT_INDEX_FILE
rm -f "$GIT_INDEX_FILE"
-git-read-tree $head
-git-checkout-index -a -f --prefix="$mandir"/
+trap 'rm -f "$GIT_INDEX_FILE"' 0
+
+git read-tree $head
+git checkout-index -a -f --prefix="$mandir"/
if test -n "$GZ"; then
cd "$mandir"
- for i in `git-ls-tree -r --name-only $head`
+ for i in `git ls-tree -r --name-only $head`
do
gzip < $i > $i.gz && rm $i
done
+--summary::
+ Show a diffstat at the end of the merge. The diffstat is also
+ controlled by the configuration option merge.diffstat.
+
-n, \--no-summary::
Do not show diffstat at the end of the merge.
If there is no `-s` option, a built-in list of strategies
is used instead (`git-merge-recursive` when merging a single
head, `git-merge-octopus` otherwise).
-
- '%aD': author date, RFC2822 style
- '%ar': author date, relative
- '%at': author date, UNIX timestamp
+- '%ai': author date, ISO 8601 format
- '%cn': committer name
- '%ce': committer email
- '%cd': committer date
- '%cD': committer date, RFC2822 style
- '%cr': committer date, relative
- '%ct': committer date, UNIX timestamp
+- '%ci': committer date, ISO 8601 format
- '%e': encoding
- '%s': subject
- '%b': body
- '%Creset': reset color
- '%m': left, right or boundary mark
- '%n': newline
-
--pretty[='<format>']::
- Pretty print the contents of the commit logs in a given format,
+ Pretty-print the contents of the commit logs in a given format,
where '<format>' can be one of 'oneline', 'short', 'medium',
'full', 'fuller', 'email', 'raw' and 'format:<string>'.
- When left out the format default to 'medium'.
+ When omitted, the format defaults to 'medium'.
+
+--abbrev-commit::
+ Instead of showing the full 40-byte hexadecimal commit object
+ name, show only handful hexdigits prefix. Non default number of
+ digits can be specified with "--abbrev=<n>" (which also modifies
+ diff output, if it is displayed).
++
+This should make "--pretty=oneline" a whole lot more readable for
+people using 80-column terminals.
--encoding[=<encoding>]::
The commit objects record the encoding used for the log message
command to re-code the commit log message in the encoding
preferred by the user. For non plumbing commands this
defaults to UTF-8.
-
+
Some short-cut notations are also supported.
+
-* `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`;
+* `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
This is similar to `info/grafts` but is internally used
and maintained by shallow clone mechanism. See `--depth`
option to gitlink:git-clone[1] and gitlink:git-fetch[1].
-
+--------------------------------+ |
main | offset | |
index | object name 00XXXXXXXXXXXXXXXX | |
-table +--------------------------------+ |
+table +--------------------------------+ |
| offset | |
| object name 00XXXXXXXXXXXXXXXX | |
+--------------------------------+ |
| +--------------------------------+
| | idxfile checksum |
| +--------------------------------+
- .-------.
+ .-------.
|
Pack file entry: <+
packed object header:
1-byte size extension bit (MSB)
type (next 3 bit)
- size0 (lower 4-bit)
+ size0 (lower 4-bit)
n-byte sizeN (as long as MSB is set, each 7-bit)
size0..sizeN form 4+7+7+..+7 bit integer, size0
is the least significant part, and sizeN is the
is the size before compression).
If it is DELTA, then
20-byte base object name SHA1 (the size above is the
- size of the delta data that follows).
+ size of the delta data that follows).
delta data, deflated.
-------------------------------------
$ git config --get remote.origin.url
-/home/bob/myrepo
+/home/alice/project
-------------------------------------
(The complete configuration created by git-clone is visible using
--- /dev/null
+include::urls.txt[]
+
+REMOTES
+-------
+
+In addition to the above, as a short-hand, the name of a
+file in `$GIT_DIR/remotes` directory can be given; the
+named file should be in the following format:
+
+------------
+ URL: one of the above URL format
+ Push: <refspec>
+ Pull: <refspec>
+
+------------
+
+Then such a short-hand is specified in place of
+<repository> without <refspec> parameters on the command
+line, <refspec> specified on `Push:` lines or `Pull:`
+lines are used for `git-push` and `git-fetch`/`git-pull`,
+respectively. Multiple `Push:` and `Pull:` lines may
+be specified for additional branch mappings.
+
+Or, equivalently, in the `$GIT_DIR/config` (note the use
+of `fetch` instead of `Pull:`):
+
+------------
+ [remote "<remote>"]
+ url = <url>
+ push = <refspec>
+ fetch = <refspec>
+
+------------
+
+The name of a file in `$GIT_DIR/branches` directory can be
+specified as an older notation short-hand; the named
+file should contain a single line, a URL in one of the
+above formats, optionally followed by a hash `#` and the
+name of remote head (URL fragment notation).
+`$GIT_DIR/branches/<remote>` file that stores a <url>
+without the fragment is equivalent to have this in the
+corresponding file in the `$GIT_DIR/remotes/` directory.
+
+------------
+ URL: <url>
+ Pull: refs/heads/master:<remote>
+
+------------
+
+while having `<url>#<head>` is equivalent to
+
+------------
+ URL: <url>
+ Pull: refs/heads/<head>:<remote>
+------------
===============================================================
- /path/to/repo.git/
===============================================================
-
-REMOTES
--------
-
-In addition to the above, as a short-hand, the name of a
-file in `$GIT_DIR/remotes` directory can be given; the
-named file should be in the following format:
-
-------------
- URL: one of the above URL format
- Push: <refspec>
- Pull: <refspec>
-
-------------
-
-Then such a short-hand is specified in place of
-<repository> without <refspec> parameters on the command
-line, <refspec> specified on `Push:` lines or `Pull:`
-lines are used for `git-push` and `git-fetch`/`git-pull`,
-respectively. Multiple `Push:` and `Pull:` lines may
-be specified for additional branch mappings.
-
-Or, equivalently, in the `$GIT_DIR/config` (note the use
-of `fetch` instead of `Pull:`):
-
-------------
- [remote "<remote>"]
- url = <url>
- push = <refspec>
- fetch = <refspec>
-
-------------
-
-The name of a file in `$GIT_DIR/branches` directory can be
-specified as an older notation short-hand; the named
-file should contain a single line, a URL in one of the
-above formats, optionally followed by a hash `#` and the
-name of remote head (URL fragment notation).
-`$GIT_DIR/branches/<remote>` file that stores a <url>
-without the fragment is equivalent to have this in the
-corresponding file in the `$GIT_DIR/remotes/` directory.
-
-------------
- URL: <url>
- Pull: refs/heads/master:<remote>
-
-------------
-
-while having `<url>#<head>` is equivalent to
-
-------------
- URL: <url>
- Pull: refs/heads/<head>:<remote>
-------------
-Git User's Manual (for version 1.5.1 or newer)
+Git User's Manual (for version 1.5.3 or newer)
______________________________________________
Date: Sat Dec 2 22:22:25 2006 -0800
[XFRM]: Fix aevent structuring to be more complete.
-
+
aevents can not uniquely identify an SA. We break the ABI with this
patch, but consensus is that since it is not yet utilized by any
(known) application then it is fine (better do it now than later).
-
+
Signed-off-by: Jamal Hadi Salim <hadi@cyberus.ca>
Signed-off-by: David S. Miller <davem@davemloft.net>
--- a/Documentation/networking/xfrm_sync.txt
+++ b/Documentation/networking/xfrm_sync.txt
@@ -47,10 +47,13 @@ aevent_id structure looks like:
-
+
struct xfrm_aevent_id {
struct xfrm_usersa_id sa_id;
+ xfrm_address_t saddr;
Git is best thought of as a tool for storing the history of a
collection of files. It does this by storing compressed snapshots of
-the contents of a file heirarchy, together with "commits" which show
+the contents of a file hierarchy, together with "commits" which show
the relationships between these snapshots.
Git provides extremely flexible and fast tools for exploring the
-------------------------------------------------
As a special shortcut,
-
+
-------------------------------------------------
$ git commit -a
-------------------------------------------------
-------------------------------------------------
$ git diff --cached # difference between HEAD and the index; what
- # would be commited if you ran "commit" now.
+ # would be committed if you ran "commit" now.
$ git diff # difference between the index file and your
# working directory; changes that would not
# be included if you ran "commit" now.
$ git status # a brief per-file summary of the above.
-------------------------------------------------
+You can also use gitlink:git-gui[1] to create commits, view changes in
+the index and the working tree files, and individually select diff hunks
+for inclusion in the index (by right-clicking on the diff hunk and
+choosing "Stage Hunk For Commit").
+
[[creating-good-commit-messages]]
Creating good commit messages
-----------------------------
++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
-------------------------------------------------
-Recall that the commit which will be commited after we resolve this
+Recall that the commit which will be committed after we resolve this
conflict will have two parents instead of the usual one: one parent
will be HEAD, the tip of the current branch; the other will be the
tip of the other branch, which is stored temporarily in MERGE_HEAD.
$ git reset --hard HEAD
-------------------------------------------------
-Or, if you've already commited the merge that you want to throw away,
+Or, if you've already committed the merge that you want to throw away,
-------------------------------------------------
$ git reset --hard ORIG_HEAD
which will display the given version of the file.
+[[interrupted-work]]
+Temporarily setting aside work in progress
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+While you are in the middle of working on something complicated, you
+find an unrelated but obvious and trivial bug. You would like to fix it
+before continuing. You can use gitlink:git-stash[1] to save the current
+state of your work, and after fixing the bug (or, optionally after doing
+so on a different branch and then coming back), unstash the
+work-in-progress changes.
+
+------------------------------------------------
+$ git stash "work in progress for foo feature"
+------------------------------------------------
+
+This command will save your changes away to the `stash`, and
+reset your working tree and the index to match the tip of your
+current branch. Then you can make your fix as usual.
+
+------------------------------------------------
+... edit and test ...
+$ git commit -a -m "blorpl: typofix"
+------------------------------------------------
+
+After that, you can go back to what you were working on with
+`git stash apply`:
+
+------------------------------------------------
+$ git stash apply
+------------------------------------------------
+
+
[[ensuring-good-performance]]
Ensuring good performance
-------------------------
Fortunately, git also keeps a log, called a "reflog", of all the
previous values of each branch. So in this case you can still find the
-old history using, for example,
+old history using, for example,
-------------------------------------------------
$ git log master@{1}
reference pointing to it, for example, a new branch:
------------------------------------------------
-$ git branch recovered-branch 7281251ddd
+$ git branch recovered-branch 7281251ddd
------------------------------------------------
Other types of dangling objects (blobs and trees) are also possible, and
$ git pull origin master
-------------------------------------------------
-In fact, "origin" is normally the default repository to pull from,
-and the default branch is normally the HEAD of the remote repository,
-so often you can accomplish the above with just
+In fact, if you have "master" checked out, then by default "git pull"
+merges from the HEAD branch of the origin repository. So often you can
+accomplish the above with just a simple
-------------------------------------------------
$ git pull
-------------------------------------------------
-See the descriptions of the branch.<name>.remote and branch.<name>.merge
-options in gitlink:git-config[1] to learn how to control these defaults
-depending on the current branch. Also note that the --track option to
-gitlink:git-branch[1] and gitlink:git-checkout[1] can be used to
-automatically set the default remote branch to pull from at the time
-that a branch is created:
-
--------------------------------------------------
-$ git checkout --track -b maint origin/maint
--------------------------------------------------
+More generally, a branch that is created from a remote branch will pull
+by default from that branch. See the descriptions of the
+branch.<name>.remote and branch.<name>.merge options in
+gitlink:git-config[1], and the discussion of the --track option in
+gitlink:git-checkout[1], to learn how to control these defaults.
In addition to saving you keystrokes, "git pull" also helps you by
producing a default commit message documenting the branch and
you push
your personal repo ------------------> your public repo
- ^ |
+ ^ |
| |
| you pull | they pull
| |
\ \
a--b--c--m <-- mywork
................................................
-
+
However, if you prefer to keep the history in mywork a simple series of
commits without any merges, you may instead choose to use
gitlink:git-rebase[1]:
And browse through the list of patches in the mywork branch using gitk,
applying them (possibly in a different order) to mywork-new using
-cherry-pick, and possibly modifying them as you go using commit
---amend.
+cherry-pick, and possibly modifying them as you go using commit --amend.
+The git-gui[1] command may also help as it allows you to individually
+select diff hunks for inclusion in the index (by right-clicking on the
+diff hunk and choosing "Stage Hunk for Commit").
Another technique is to use git-format-patch to create a series of
patches, then reset the state to before the patches:
root objects together into one project by creating a commit object which
has two or more separate roots as its ultimate parents, that's probably
just going to confuse people. So aim for the notion of "one root object
-per project", even if git itself does not enforce that.
+per project", even if git itself does not enforce that.
A <<def_tag_object,"tag" object>> symbolically identifies and can be
used to sign other objects. It contains the identifier and type of
known tree object, or update/compare it with a live tree that is being
developed. If you blow the directory cache away entirely, you generally
haven't lost any information as long as you have the name of the tree
-that it described.
+that it described.
-At the same time, the index is at the same time also the
-staging area for creating new trees, and creating a new tree always
-involves a controlled modification of the index file. In particular,
-the index file can have the representation of an intermediate tree that
-has not yet been instantiated. So the index can be thought of as a
-write-back cache, which can contain dirty information that has not yet
-been written back to the backing store.
+At the same time, the index is also the staging area for creating
+new trees, and creating a new tree always involves a controlled
+modification of the index file. In particular, the index file can
+have the representation of an intermediate tree that has not yet been
+instantiated. So the index can be thought of as a write-back cache,
+which can contain dirty information that has not yet been written back
+to the backing store.
work *purely* on the index file (showing the current state of the
index), but most operations move data to and from the index file. Either
from the database or from the working directory. Thus there are four
-main combinations:
+main combinations:
[[working-directory-to-index]]
working directory -> index
leaving _some_ of the new objects in the object database, but just
dangling and useless.
-Anyway, once you are sure that you're not interested in any dangling
+Anyway, once you are sure that you're not interested in any dangling
state, you can just prune all unreachable objects:
------------------------------------------------
repository - it's kind of like doing a filesystem fsck recovery: you
don't want to do that while the filesystem is mounted.
-(The same is true of "git-fsck" itself, btw - but since
-git-fsck never actually *changes* the repository, it just reports
-on what it found, git-fsck itself is never "dangerous" to run.
-Running it while somebody is actually changing the repository can cause
-confusing and scary messages, but it won't actually do anything bad. In
-contrast, running "git prune" while somebody is actively changing the
+(The same is true of "git-fsck" itself, btw - but since
+git-fsck never actually *changes* the repository, it just reports
+on what it found, git-fsck itself is never "dangerous" to run.
+Running it while somebody is actually changing the repository can cause
+confusing and scary messages, but it won't actually do anything bad. In
+contrast, running "git prune" while somebody is actively changing the
repository is a *BAD* idea).
[[birdview-on-the-source-code]]
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.5.2.4.GIT
+DEF_VER=v1.5.3.GIT
LF='
'
interactive tools. None of the core git stuff needs the wrapper,
it's just a convenient shorthand and while it is documented in some
places, you can always replace "git commit" with "git-commit"
- instead.
+ instead.
But let's face it, most of us don't have GNU interactive tools, and
even if we had it, we wouldn't know what it does. I don't think it
- "zlib", the compression library. Git won't build without it.
- - "openssl". The git-rev-list program uses bignum support from
- openssl, and unless you specify otherwise, you'll also get the
- SHA1 library from here.
+ - "openssl". Unless you specify otherwise, you'll get the SHA1
+ library from here.
If you don't have openssl, you can use one of the SHA1 libraries
that come with git (git includes the one from Mozilla, and has
management over DAV. Similar to "curl" above, this is optional.
- "wish", the Tcl/Tk windowing shell is used in gitk to show the
- history graphically
+ history graphically, and in git-gui.
- "ssh" is used to push and pull over the net
will include them. Note that config.mak is not distributed;
the name is reserved for local settings.
- - To build and install documentation suite, you need to have the
- asciidoc/xmlto toolchain. Alternatively, pre-formatted
- documentation are available in "html" and "man" branches of the git
- repository itself. For example, you could:
+ - To build and install documentation suite, you need to have
+ the asciidoc/xmlto toolchain. Because not many people are
+ inclined to install the tools, the default build target
+ ("make all") does _not_ build them. The documentation is
+ written for AsciiDoc 7, but "make ASCIIDOC8=YesPlease doc"
+ will let you format with AsciiDoc 8.
+
+ Alternatively, pre-formatted documentation are available in
+ "html" and "man" branches of the git repository itself. For
+ example, you could:
$ mkdir manual && cd manual
$ git init
http://www.kernel.org/pub/software/scm/git/docs/
+ It has been reported that docbook-xsl version 1.72 and 1.73 are
+ buggy; 1.72 misformats manual pages for callouts, and 1.73 needs
+ the patch in contrib/patches/docbook-xsl-manpages-charmap.patch
# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
# MakeMaker (e.g. using ActiveState under Cygwin).
#
-# Define WITH_P4IMPORT to build and install Python git-p4import script.
-#
# Define NO_TCLTK if you do not want Tcl/Tk GUI.
#
# The TCL_PATH variable governs the location of the Tcl interpreter
prefix = $(HOME)
bindir = $(prefix)/bin
gitexecdir = $(bindir)
-sharedir = $(prefix)/share/
-template_dir = $(sharedir)/git-core/templates/
+sharedir = $(prefix)/share
+template_dir = $(sharedir)/git-core/templates
ifeq ($(prefix),/usr)
sysconfdir = /etc
else
sysconfdir = $(prefix)/etc
endif
+lib = lib
ETC_GITCONFIG = $(sysconfdir)/gitconfig
# DESTDIR=
CC = gcc
AR = ar
+RM = rm -f
TAR = tar
+FIND = find
INSTALL = install
RPMBUILD = rpmbuild
TCL_PATH = tclsh
git-fetch.sh \
git-ls-remote.sh \
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
- git-pull.sh git-rebase.sh \
+ git-pull.sh git-rebase.sh git-rebase--interactive.sh \
git-repack.sh git-request-pull.sh git-reset.sh \
git-sh-setup.sh \
git-tag.sh git-verify-tag.sh \
- git-applymbox.sh git-applypatch.sh git-am.sh \
+ git-am.sh \
git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
git-merge-resolve.sh git-merge-ours.sh \
- git-lost-found.sh git-quiltimport.sh
+ git-lost-found.sh git-quiltimport.sh git-submodule.sh \
+ git-filter-branch.sh \
+ git-stash.sh
SCRIPT_PERL = \
git-add--interactive.perl \
git-svnimport.perl git-cvsexportcommit.perl \
git-send-email.perl git-svn.perl
-SCRIPT_PYTHON = \
- git-p4import.py
-
-ifdef WITH_P4IMPORT
-SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
- $(patsubst %.perl,%,$(SCRIPT_PERL)) \
- $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
- git-status git-instaweb
-else
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
git-status git-instaweb
-endif
-
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS = \
git-convert-objects$X git-fetch-pack$X \
git-hash-object$X git-index-pack$X git-local-fetch$X \
git-fast-import$X \
- git-merge-base$X \
git-daemon$X \
git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \
git-peek-remote$X git-receive-pack$X \
ifndef PERL_PATH
PERL_PATH = /usr/bin/perl
endif
-ifndef PYTHON_PATH
- PYTHON_PATH = /usr/local/bin/python
-endif
export PERL_PATH
diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \
run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
- utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h mailmap.h
+ utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \
+ mailmap.h remote.h
DIFF_OBJS = \
diff.o diff-lib.o diffcore-break.o diffcore-order.o \
write_or_die.o trace.o list-objects.o grep.o match-trees.o \
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
- convert.o attr.o decorate.o progress.o mailmap.o symlinks.o
+ convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o
BUILTIN_OBJS = \
builtin-add.o \
builtin-pack-refs.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
-EXTLIBS = -lz
+EXTLIBS =
#
# Platform specific tweaks
NO_STRLCPY = YesPlease
NEEDS_LIBICONV=YesPlease
endif
+ifeq ($(uname_S),GNU)
+ # GNU/Hurd
+ NO_STRLCPY=YesPlease
+endif
ifeq ($(uname_S),IRIX64)
NO_IPV6=YesPlease
NO_SETENV=YesPlease
ifndef NO_CURL
ifdef CURLDIR
- # Try "-Wl,-rpath=$(CURLDIR)/lib" in such a case.
+ # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case.
BASIC_CFLAGS += -I$(CURLDIR)/include
- CURL_LIBCURL = -L$(CURLDIR)/lib $(CC_LD_DYNPATH)$(CURLDIR)/lib -lcurl
+ CURL_LIBCURL = -L$(CURLDIR)/$(lib) $(CC_LD_DYNPATH)$(CURLDIR)/$(lib) -lcurl
else
CURL_LIBCURL = -lcurl
endif
endif
endif
+ifdef ZLIB_PATH
+ BASIC_CFLAGS += -I$(ZLIB_PATH)/include
+ EXTLIBS += -L$(ZLIB_PATH)/$(lib) $(CC_LD_DYNPATH)$(ZLIB_PATH)/$(lib)
+endif
+EXTLIBS += -lz
+
ifndef NO_OPENSSL
OPENSSL_LIBSSL = -lssl
ifdef OPENSSLDIR
BASIC_CFLAGS += -I$(OPENSSLDIR)/include
- OPENSSL_LINK = -L$(OPENSSLDIR)/lib $(CC_LD_DYNPATH)$(OPENSSLDIR)/lib
+ OPENSSL_LINK = -L$(OPENSSLDIR)/$(lib) $(CC_LD_DYNPATH)$(OPENSSLDIR)/$(lib)
else
OPENSSL_LINK =
endif
ifdef NEEDS_LIBICONV
ifdef ICONVDIR
BASIC_CFLAGS += -I$(ICONVDIR)/include
- ICONV_LINK = -L$(ICONVDIR)/lib $(CC_LD_DYNPATH)$(ICONVDIR)/lib
+ ICONV_LINK = -L$(ICONVDIR)/$(lib) $(CC_LD_DYNPATH)$(ICONVDIR)/$(lib)
else
ICONV_LINK =
endif
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
-PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
LIBS = $(GITLIBS) $(EXTLIBS)
all:: $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS)
ifneq (,$X)
- $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), rm -f '$p';)
+ $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), $(RM) '$p';)
endif
all::
$(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
gitk-wish: gitk GIT-GUI-VARS
- $(QUIET_GEN)rm -f $@ $@+ && \
+ $(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \
chmod +x $@+ && \
mv -f $@+ $@
-git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS
- $(QUIET_LINK)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
- $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \
+git.o: git.c common-cmds.h GIT-CFLAGS
+ $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
+ $(ALL_CFLAGS) -c $(filter %.c,$^)
+
+git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
help.o: common-cmds.h
git-merge-subtree$X: git-merge-recursive$X
- $(QUIET_BUILT_IN)rm -f $@ && ln git-merge-recursive$X $@
+ $(QUIET_BUILT_IN)$(RM) $@ && ln git-merge-recursive$X $@
$(BUILT_INS): git$X
- $(QUIET_BUILT_IN)rm -f $@ && ln git$X $@
+ $(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@
+
+common-cmds.h: ./generate-cmdlist.sh
common-cmds.h: $(wildcard Documentation/git-*.txt)
$(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
- $(QUIET_GEN)rm -f $@ $@+ && \
+ $(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
-$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
- rm -f $@ $@+
- sed -e '1s|#!.*/python|#!$(PYTHON_PATH_SQ)|' \
- -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
- -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
- $@.py >$@+
- chmod +x $@+
- mv $@+ $@
-
perl/perl.mak: GIT-CFLAGS
$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
$(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
- $(QUIET_GEN)rm -f $@ $@+ && \
+ $(QUIET_GEN)$(RM) $@ $@+ && \
INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
sed -e '1{' \
-e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \
$(QUIET_GEN)cp $< $@+ && mv $@+ $@
gitweb/gitweb.cgi: gitweb/gitweb.perl
- $(QUIET_GEN)rm -f $@ $@+ && \
+ $(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
-e 's|++GIT_BINDIR++|$(bindir)|g' \
mv $@+ $@
git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
- $(QUIET_GEN)rm -f $@ $@+ && \
+ $(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
mv $@+ $@
configure: configure.ac
- $(QUIET_GEN)rm -f $@ $<+ && \
+ $(QUIET_GEN)$(RM) $@ $<+ && \
sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
$< > $<+ && \
autoconf -o $@ $<+ && \
- rm -f $<+
+ $(RM) $<+
# These can record GIT_VERSION
-git$X git.spec \
+git.o git.spec \
$(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
: GIT-VERSION-FILE
%.o: %.c GIT-CFLAGS
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+%.s: %.c GIT-CFLAGS
+ $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
%.o: %.S
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
$(DIFF_OBJS): diffcore.h
$(LIB_FILE): $(LIB_OBJS)
- $(QUIET_AR)rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
+ $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
xdiff/xmerge.o
xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
$(XDIFF_LIB): $(XDIFF_OBJS)
- $(QUIET_AR)rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
+ $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS)
perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS
$(MAKE) -C Documentation all
TAGS:
- rm -f TAGS
- find . -name '*.[hcS]' -print | xargs etags -a
+ $(RM) TAGS
+ $(FIND) . -name '*.[hcS]' -print | xargs etags -a
tags:
- rm -f tags
- find . -name '*.[hcS]' -print | xargs ctags -a
+ $(RM) tags
+ $(FIND) . -name '*.[hcS]' -print | xargs ctags -a
### Detect prefix changes
TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
### Testing rules
-TEST_PROGRAMS = test-chmtime$X test-genrandom$X
+TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X
all:: $(TEST_PROGRAMS)
test: all
$(MAKE) -C t/ all
-test-date$X: test-date.c date.o ctype.o
- $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o
+test-date$X: date.o ctype.o
-test-delta$X: test-delta.o diff-delta.o patch-delta.o $(GITLIBS)
- $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+test-delta$X: diff-delta.o patch-delta.o
-test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
- $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-
-test-sha1$X: test-sha1.o $(GITLIBS)
- $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-
-test-match-trees$X: test-match-trees.o $(GITLIBS)
- $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-
-test-chmtime$X: test-chmtime.c
- $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $<
-
-test-genrandom$X: test-genrandom.c
- $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $<
+test-%$X: test-%.o $(GITLIBS)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
check-sha1:: test-sha1$X
./test-sha1.sh
check: common-cmds.h
for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
-
+remove-dashes:
+ ./fixup-builtins $(BUILT_INS)
### Installation rules
cp '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
'$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X'; \
fi
- $(foreach p,$(BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;)
+ $(foreach p,$(BUILT_INS), $(RM) '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;)
ifneq (,$X)
- $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p';)
+ $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), $(RM) '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p';)
endif
install-doc:
mv $@+ $@
GIT_TARNAME=git-$(GIT_VERSION)
-dist: git.spec git-archive
+dist: git.spec git-archive configure
./git-archive --format=tar \
--prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar
@mkdir -p $(GIT_TARNAME)
- @cp git.spec $(GIT_TARNAME)
+ @cp git.spec configure $(GIT_TARNAME)
@echo $(GIT_VERSION) > $(GIT_TARNAME)/version
@$(MAKE) -C git-gui TARDIR=../$(GIT_TARNAME)/git-gui dist-version
$(TAR) rf $(GIT_TARNAME).tar \
$(GIT_TARNAME)/git.spec \
+ $(GIT_TARNAME)/configure \
$(GIT_TARNAME)/version \
$(GIT_TARNAME)/git-gui/version
- @rm -rf $(GIT_TARNAME)
+ @$(RM) -r $(GIT_TARNAME)
gzip -f -9 $(GIT_TARNAME).tar
rpm: dist
htmldocs = git-htmldocs-$(GIT_VERSION)
manpages = git-manpages-$(GIT_VERSION)
dist-doc:
- rm -fr .doc-tmp-dir
+ $(RM) -r .doc-tmp-dir
mkdir .doc-tmp-dir
$(MAKE) -C Documentation WEBDOC_DEST=../.doc-tmp-dir install-webdoc
cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar .
gzip -n -9 -f $(htmldocs).tar
:
- rm -fr .doc-tmp-dir
+ $(RM) -r .doc-tmp-dir
mkdir -p .doc-tmp-dir/man1 .doc-tmp-dir/man5 .doc-tmp-dir/man7
$(MAKE) -C Documentation DESTDIR=./ \
man1dir=../.doc-tmp-dir/man1 \
install
cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar .
gzip -n -9 -f $(manpages).tar
- rm -fr .doc-tmp-dir
+ $(RM) -r .doc-tmp-dir
### Cleaning rules
clean:
- rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
- test-chmtime$X test-genrandom$X $(LIB_FILE) $(XDIFF_LIB)
- rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X
- rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
- rm -rf autom4te.cache
- rm -f configure config.log config.mak.autogen config.mak.append config.status config.cache
- rm -rf $(GIT_TARNAME) .doc-tmp-dir
- rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
- rm -f $(htmldocs).tar.gz $(manpages).tar.gz
- rm -f gitweb/gitweb.cgi
+ $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
+ $(LIB_FILE) $(XDIFF_LIB)
+ $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
+ $(RM) $(TEST_PROGRAMS)
+ $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
+ $(RM) -r autom4te.cache
+ $(RM) configure config.log config.mak.autogen config.mak.append config.status config.cache
+ $(RM) -r $(GIT_TARNAME) .doc-tmp-dir
+ $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
+ $(RM) $(htmldocs).tar.gz $(manpages).tar.gz
+ $(RM) gitweb/gitweb.cgi
$(MAKE) -C Documentation/ clean
$(MAKE) -C perl clean
$(MAKE) -C templates/ clean
$(MAKE) -C t/ clean
ifndef NO_TCLTK
- rm -f gitk-wish
+ $(RM) gitk-wish
$(MAKE) -C git-gui clean
endif
- rm -f GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
+ $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
.PHONY: all install clean strip
.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags .FORCE-GIT-CFLAGS
-Documentation/RelNotes-1.5.2.4.txt
\ No newline at end of file
+Documentation/RelNotes-1.5.3.txt
\ No newline at end of file
} else {
if (verbose)
fprintf(stderr, "%.*s\n", path->len, path->buf);
- if (S_ISDIR(mode) || S_ISDIRLNK(mode)) {
+ if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
*header.typeflag = TYPEFLAG_DIR;
mode = (mode | 0777) & ~tar_umask;
} else if (S_ISLNK(mode)) {
memcpy(path.buf + baselen, filename, filenamelen);
path.len = baselen + filenamelen;
path.buf[path.len] = '\0';
- if (S_ISDIR(mode) || S_ISDIRLNK(mode)) {
+ if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
strbuf_append_string(&path, "/");
buffer = NULL;
size = 0;
goto out;
}
- if (S_ISDIR(mode) || S_ISDIRLNK(mode)) {
+ if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
method = 0;
attr2 = 16;
result = (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
void SHA1_Final(unsigned char *hash, SHA_CTX *c)
{
uint64_t bitlen;
- uint32_t bitlen_hi, bitlen_lo;
+ uint32_t bitlen_hi, bitlen_lo;
unsigned int i, offset, padlen;
unsigned char bits[8];
static const unsigned char padding[64] = { 0x80, };
bits[5] = bitlen_lo >> 16;
bits[6] = bitlen_lo >> 8;
bits[7] = bitlen_lo;
- SHA1_Update(c, bits, 8);
+ SHA1_Update(c, bits, 8);
for (i = 0; i < 5; i++) {
uint32_t v = c->hash[i];
.L_sha_K:
.word 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6
-
dir->nr = dst - dir->entries;
for (i = 0; i < specs; i++) {
- struct stat st;
- const char *match;
- if (seen[i])
- continue;
-
- match = pathspec[i];
- if (!match[0])
- continue;
-
- /* Existing file? We must have ignored it */
- if (!lstat(match, &st)) {
- struct dir_entry *ent;
-
- ent = dir_add_name(dir, match, strlen(match));
- ent->ignored = 1;
- if (S_ISDIR(st.st_mode))
- ent->ignored_dir = 1;
- continue;
- }
- die("pathspec '%s' did not match any files", match);
+ if (!seen[i] && !file_exists(pathspec[i]))
+ die("pathspec '%s' did not match any files",
+ pathspec[i]);
}
}
-static void fill_directory(struct dir_struct *dir, const char **pathspec)
+static void fill_directory(struct dir_struct *dir, const char **pathspec,
+ int ignored_too)
{
const char *path, *base;
int baselen;
/* Set up the default git porcelain excludes */
memset(dir, 0, sizeof(*dir));
- dir->exclude_per_dir = ".gitignore";
- path = git_path("info/exclude");
- if (!access(path, R_OK))
- add_excludes_from_file(dir, path);
- if (!access(excludes_file, R_OK))
- add_excludes_from_file(dir, excludes_file);
+ if (!ignored_too) {
+ dir->collect_ignored = 1;
+ dir->exclude_per_dir = ".gitignore";
+ path = git_path("info/exclude");
+ if (!access(path, R_OK))
+ add_excludes_from_file(dir, path);
+ if (excludes_file != NULL && !access(excludes_file, R_OK))
+ add_excludes_from_file(dir, excludes_file);
+ }
/*
* Calculate common prefix for the pathspec, and
}
pathspec = get_pathspec(prefix, argv + i);
- fill_directory(&dir, pathspec);
+ fill_directory(&dir, pathspec, ignored_too);
if (show_only) {
const char *sep = "", *eof = "";
for (i = 0; i < dir.nr; i++) {
- if (!ignored_too && dir.entries[i]->ignored)
- continue;
printf("%s%s", sep, dir.entries[i]->name);
sep = " ";
eof = "\n";
if (read_cache() < 0)
die("index file corrupt");
- if (!ignored_too) {
- int has_ignored = 0;
- for (i = 0; i < dir.nr; i++)
- if (dir.entries[i]->ignored)
- has_ignored = 1;
- if (has_ignored) {
- fprintf(stderr, ignore_warning);
- for (i = 0; i < dir.nr; i++) {
- if (!dir.entries[i]->ignored)
- continue;
- fprintf(stderr, "%s", dir.entries[i]->name);
- if (dir.entries[i]->ignored_dir)
- fprintf(stderr, " (directory)");
- fputc('\n', stderr);
- }
- fprintf(stderr,
- "Use -f if you really want to add them.\n");
- exit(1);
+ if (dir.ignored_nr) {
+ fprintf(stderr, ignore_warning);
+ for (i = 0; i < dir.ignored_nr; i++) {
+ fprintf(stderr, "%s\n", dir.ignored[i]->name);
}
+ fprintf(stderr, "Use -f if you really want to add them.\n");
+ exit(1);
}
for (i = 0; i < dir.nr; i++)
return cmd_blame(argc + 1, nargv, prefix);
}
-
} new_whitespace = warn_on_whitespace;
static int whitespace_error;
static int squelch_whitespace_errors = 5;
-static int applied_after_stripping;
+static int applied_after_fixing_ws;
static const char *patch_input_file;
static void parse_whitespace_option(const char *option)
if (add_nl_to_tail)
output[plen++] = '\n';
if (fixed)
- applied_after_stripping++;
+ applied_after_fixing_ws++;
return output + plen - buf;
}
char *new = xmalloc(size);
const char *oldlines, *newlines;
int oldsize = 0, newsize = 0;
+ int new_blank_lines_at_end = 0;
unsigned long leading, trailing;
int pos, lines;
char first;
int len = linelen(patch, size);
int plen;
+ int added_blank_line = 0;
if (!len)
break;
else if (first == '+')
first = '-';
}
+
switch (first) {
case '\n':
/* Newer GNU diff, empty context line */
break;
/* Fall-through for ' ' */
case '+':
- if (first != '+' || !no_add)
- newsize += apply_line(new + newsize, patch,
- plen);
+ if (first != '+' || !no_add) {
+ int added = apply_line(new + newsize, patch,
+ plen);
+ newsize += added;
+ if (first == '+' &&
+ added == 1 && new[newsize-1] == '\n')
+ added_blank_line = 1;
+ }
break;
case '@': case '\\':
/* Ignore it, we already handled it */
error("invalid start of line: '%c'", first);
return -1;
}
+ if (added_blank_line)
+ new_blank_lines_at_end++;
+ else
+ new_blank_lines_at_end = 0;
patch += len;
size -= len;
}
if (match_beginning && offset)
offset = -1;
if (offset >= 0) {
- int diff = newsize - oldsize;
- unsigned long size = desc->size + diff;
- unsigned long alloc = desc->alloc;
+ int diff;
+ unsigned long size, alloc;
+
+ if (new_whitespace == strip_whitespace &&
+ (desc->size - oldsize - offset == 0)) /* end of file? */
+ newsize -= new_blank_lines_at_end;
+
+ diff = newsize - oldsize;
+ size = desc->size + diff;
+ alloc = desc->alloc;
/* Warn if it was necessary to reduce the number
* of context lines.
squelched == 1 ? "" : "s");
}
if (new_whitespace == error_on_whitespace)
- die("%d line%s add%s trailing whitespaces.",
+ die("%d line%s add%s whitespace errors.",
whitespace_error,
whitespace_error == 1 ? "" : "s",
whitespace_error == 1 ? "s" : "");
- if (applied_after_stripping)
+ if (applied_after_fixing_ws)
fprintf(stderr, "warning: %d line%s applied after"
- " stripping trailing whitespaces.\n",
- applied_after_stripping,
- applied_after_stripping == 1 ? "" : "s");
+ " fixing whitespace errors.\n",
+ applied_after_fixing_ws,
+ applied_after_fixing_ws == 1 ? "" : "s");
else if (whitespace_error)
- fprintf(stderr, "warning: %d line%s add%s trailing"
- " whitespaces.\n",
+ fprintf(stderr, "warning: %d line%s add%s whitespace errors.\n",
whitespace_error,
whitespace_error == 1 ? "" : "s",
whitespace_error == 1 ? "s" : "");
}
url = xstrdup(remote);
- pid = git_connect(fd, url, exec);
+ pid = git_connect(fd, url, exec, 0);
if (pid < 0)
return pid;
#include "mailmap.h"
static char blame_usage[] =
-"git-blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
+"git-blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
" -c Use the same output mode as git-annotate (Default: off)\n"
" -b Show blank SHA-1 for boundary commits (Default: off)\n"
" -l Show long commit SHA1 (Default: off)\n"
" -n, --show-number Show original linenumber (Default: off)\n"
" -s Suppress author name and timestamp (Default: off)\n"
" -p, --porcelain Show in a format designed for machine consumption\n"
+" -w Ignore whitespace differences\n"
" -L n,m Process only line range n,m, counting from 1\n"
" -M, -C Find line movements within and across files\n"
" --incremental Show blame entries as we find them, incrementally\n"
static int blank_boundary;
static int incremental;
static int cmd_is_annotate;
+static int xdl_opts = XDF_NEED_MINIMAL;
static struct path_list mailmap;
#ifndef DEBUG
xdemitconf_t xecfg;
xdemitcb_t ecb;
- xpp.flags = XDF_NEED_MINIMAL;
+ xpp.flags = xdl_opts;
+ memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = context;
- xecfg.flags = 0;
ecb.outf = xdiff_outf;
ecb.priv = &state;
memset(&state, 0, sizeof(state));
printf("boundary\n");
}
write_filename_info(suspect->path);
+ maybe_flush_or_die(stdout, "stdout");
}
}
*/
static int lineno_width(int lines)
{
- int i, width;
+ int i, width;
- for (width = 1, i = 10; i <= lines + 1; width++)
- i *= 10;
- return width;
+ for (width = 1, i = 10; i <= lines + 1; width++)
+ i *= 10;
+ return width;
}
/*
output_option |= OUTPUT_LONG_OBJECT_NAME;
else if (!strcmp("-s", arg))
output_option |= OUTPUT_NO_AUTHOR;
+ else if (!strcmp("-w", arg))
+ xdl_opts |= XDF_IGNORE_WHITESPACE;
else if (!strcmp("-S", arg) && ++i < argc)
revs_file = argv[i];
else if (!prefixcmp(arg, "-M")) {
/*
* Builtin "git branch"
*
- * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
* Based on git-branch.sh by Junio C Hamano.
*/
#include "refs.h"
#include "commit.h"
#include "builtin.h"
+#include "remote.h"
static const char builtin_branch_usage[] =
"git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]";
static const char *head;
static unsigned char head_sha1[20];
-static int branch_track_remotes;
+static int branch_track = 1;
static int branch_use_color;
static char branch_colors[][COLOR_MAXLEN] = {
die("bad config variable '%s'", var);
}
-int git_branch_config(const char *var, const char *value)
+static int git_branch_config(const char *var, const char *value)
{
if (!strcmp(var, "color.branch")) {
branch_use_color = git_config_colorbool(var, value);
return 0;
}
if (!strcmp(var, "branch.autosetupmerge"))
- branch_track_remotes = git_config_bool(var, value);
+ branch_track = git_config_bool(var, value);
return git_default_config(var, value);
}
-const char *branch_get_color(enum color_branch ix)
+static const char *branch_get_color(enum color_branch ix)
{
if (branch_use_color)
return branch_colors[ix];
unsigned char sha1[20];
char *name = NULL;
const char *fmt, *remote;
+ char section[PATH_MAX];
int i;
int ret = 0;
error("Error deleting %sbranch '%s'", remote,
argv[i]);
ret = 1;
- } else
+ } else {
printf("Deleted %sbranch %s.\n", remote, argv[i]);
-
+ snprintf(section, sizeof(section), "branch.%s",
+ argv[i]);
+ if (git_config_rename_section(section, NULL) < 0)
+ warning("Update of config-file failed");
+ }
}
if (name)
char c;
int color;
struct commit *commit;
- char subject[256];
switch (item->kind) {
case REF_LOCAL_BRANCH:
}
if (verbose) {
+ char *subject = NULL;
+ unsigned long subject_len = 0;
+ const char *sub = " **** invalid ref ****";
+
commit = lookup_commit(item->sha1);
- if (commit && !parse_commit(commit))
+ if (commit && !parse_commit(commit)) {
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
- subject, sizeof(subject), 0,
+ &subject, &subject_len, 0,
NULL, NULL, 0);
- else
- strcpy(subject, " **** invalid ref ****");
+ sub = subject;
+ }
printf("%c %s%-*s%s %s %s\n", c, branch_get_color(color),
maxwidth, item->name,
branch_get_color(COLOR_BRANCH_RESET),
- find_unique_abbrev(item->sha1, abbrev), subject);
+ find_unique_abbrev(item->sha1, abbrev), sub);
+ if (subject)
+ free(subject);
} else {
printf("%c %s%s%s\n", c, branch_get_color(color), item->name,
branch_get_color(COLOR_BRANCH_RESET));
free_ref_list(&ref_list);
}
-static char *config_repo;
-static char *config_remote;
-static const char *start_ref;
+struct tracking {
+ struct refspec spec;
+ char *src;
+ const char *remote;
+ int matches;
+};
-static int get_remote_branch_name(const char *value)
+static int find_tracked_branch(struct remote *remote, void *priv)
{
- const char *colon;
- const char *end;
-
- if (*value == '+')
- value++;
-
- colon = strchr(value, ':');
- if (!colon)
- return 0;
-
- end = value + strlen(value);
-
- /*
- * Try an exact match first. I.e. handle the case where the
- * value is "$anything:refs/foo/bar/baz" and start_ref is exactly
- * "refs/foo/bar/baz". Then the name at the remote is $anything.
- */
- if (!strcmp(colon + 1, start_ref)) {
- /* Truncate the value before the colon. */
- nfasprintf(&config_repo, "%.*s", colon - value, value);
- return 1;
+ struct tracking *tracking = priv;
+
+ if (!remote_find_tracking(remote, &tracking->spec)) {
+ if (++tracking->matches == 1) {
+ tracking->src = tracking->spec.src;
+ tracking->remote = remote->name;
+ } else {
+ free(tracking->spec.src);
+ if (tracking->src) {
+ free(tracking->src);
+ tracking->src = NULL;
+ }
+ }
+ tracking->spec.src = NULL;
}
- /*
- * Is this a wildcard match?
- */
- if ((end - 2 <= value) || end[-2] != '/' || end[-1] != '*' ||
- (colon - 2 <= value) || colon[-2] != '/' || colon[-1] != '*')
- return 0;
-
- /*
- * Value is "refs/foo/bar/<asterisk>:refs/baz/boa/<asterisk>"
- * and start_ref begins with "refs/baz/boa/"; the name at the
- * remote is refs/foo/bar/ with the remaining part of the
- * start_ref. The length of the prefix on the RHS is (end -
- * colon - 2), including the slash immediately before the
- * asterisk.
- */
- if ((strlen(start_ref) < end - colon - 2) ||
- memcmp(start_ref, colon + 1, end - colon - 2))
- return 0; /* does not match prefix */
-
- /* Replace the asterisk with the remote branch name. */
- nfasprintf(&config_repo, "%.*s%s",
- (colon - 1) - value, value,
- start_ref + (end - colon - 2));
- return 1;
-}
-
-static int get_remote_config(const char *key, const char *value)
-{
- const char *var;
- if (prefixcmp(key, "remote."))
- return 0;
-
- var = strrchr(key, '.');
- if (var == key + 6 || strcmp(var, ".fetch"))
- return 0;
- /*
- * Ok, we are looking at key == "remote.$foo.fetch";
- */
- if (get_remote_branch_name(value))
- nfasprintf(&config_remote, "%.*s", var - (key + 7), key + 7);
-
return 0;
}
-static void set_branch_merge(const char *name, const char *config_remote,
- const char *config_repo)
+
+/*
+ * This is called when new_ref is branched off of orig_ref, and tries
+ * to infer the settings for branch.<new_ref>.{remote,merge} from the
+ * config.
+ */
+static int setup_tracking(const char *new_ref, const char *orig_ref)
{
char key[1024];
- if (sizeof(key) <=
- snprintf(key, sizeof(key), "branch.%s.remote", name))
- die("what a long branch name you have!");
- git_config_set(key, config_remote);
-
- /*
- * We do not have to check if we have enough space for
- * the 'merge' key, since it's shorter than the
- * previous 'remote' key, which we already checked.
- */
- snprintf(key, sizeof(key), "branch.%s.merge", name);
- git_config_set(key, config_repo);
-}
+ struct tracking tracking;
-static void set_branch_defaults(const char *name, const char *real_ref)
-{
- /*
- * name is the name of new branch under refs/heads;
- * real_ref is typically refs/remotes/$foo/$bar, where
- * $foo is the remote name (there typically are no slashes)
- * and $bar is the branch name we map from the remote
- * (it could have slashes).
- */
- start_ref = real_ref;
- git_config(get_remote_config);
- if (!config_repo && !config_remote &&
- !prefixcmp(real_ref, "refs/heads/")) {
- set_branch_merge(name, ".", real_ref);
- printf("Branch %s set up to track local branch %s.\n",
- name, real_ref);
- }
+ if (strlen(new_ref) > 1024 - 7 - 7 - 1)
+ return error("Tracking not set up: name too long: %s",
+ new_ref);
- if (config_repo && config_remote) {
- set_branch_merge(name, config_remote, config_repo);
+ memset(&tracking, 0, sizeof(tracking));
+ tracking.spec.dst = (char *)orig_ref;
+ if (for_each_remote(find_tracked_branch, &tracking) ||
+ !tracking.matches)
+ return 1;
+
+ if (tracking.matches > 1)
+ return error("Not tracking: ambiguous information for ref %s",
+ orig_ref);
+
+ if (tracking.matches == 1) {
+ sprintf(key, "branch.%s.remote", new_ref);
+ git_config_set(key, tracking.remote ? tracking.remote : ".");
+ sprintf(key, "branch.%s.merge", new_ref);
+ git_config_set(key, tracking.src);
+ free(tracking.src);
printf("Branch %s set up to track remote branch %s.\n",
- name, real_ref);
+ new_ref, orig_ref);
}
- if (config_repo)
- free(config_repo);
- if (config_remote)
- free(config_remote);
+ return 0;
}
static void create_branch(const char *name, const char *start_name,
die("Not a valid branch point: '%s'.", start_name);
hashcpy(sha1, commit->object.sha1);
- lock = lock_any_ref_for_update(ref, NULL);
+ lock = lock_any_ref_for_update(ref, NULL, 0);
if (!lock)
die("Failed to lock ref for update: %s.", strerror(errno));
automatically merges from there. So far, this is only done for
remotes registered via .git/config. */
if (real_ref && track)
- set_branch_defaults(name, real_ref);
+ setup_tracking(name, real_ref);
if (write_ref_sha1(lock, sha1, msg) < 0)
die("Failed to write ref: %s.", strerror(errno));
int i;
git_config(git_branch_config);
- track = branch_track_remotes;
+ track = branch_track;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
+#include "builtin.h"
#include "cache.h"
#include "object.h"
#include "commit.h"
* of "-a" causing problems (not possible in the above example,
* but get used to it in scripting!).
*/
+#include "builtin.h"
#include "cache.h"
#include "strbuf.h"
#include "quote.h"
#include "cache.h"
static const char git_config_set_usage[] =
-"git-config [ --global | --system ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
+"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
static char *key;
static regex_t *key_regexp;
static int do_all;
static int do_not_match;
static int seen;
+static char delim = '=';
+static char key_delim = ' ';
+static char term = '\n';
static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
static int show_all_config(const char *key_, const char *value_)
{
if (value_)
- printf("%s=%s\n", key_, value_);
+ printf("%s%c%s%c", key_, delim, value_, term);
else
- printf("%s\n", key_);
+ printf("%s%c", key_, term);
return 0;
}
if (show_keys) {
if (value_)
- printf("%s ", key_);
+ printf("%s%c", key_, key_delim);
else
printf("%s", key_);
}
key_, vptr);
}
else
- printf("%s\n", vptr);
+ printf("%s%c", vptr, term);
return 0;
}
return ret;
}
+char *normalize_value(const char *key, const char *value)
+{
+ char *normalized;
+
+ if (!value)
+ return NULL;
+
+ if (type == T_RAW)
+ normalized = xstrdup(value);
+ else {
+ normalized = xmalloc(64);
+ if (type == T_INT) {
+ int v = git_config_int(key, value);
+ sprintf(normalized, "%d", v);
+ }
+ else if (type == T_BOOL)
+ sprintf(normalized, "%s",
+ git_config_bool(key, value) ? "true" : "false");
+ }
+
+ return normalized;
+}
+
int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = 0;
+ char* value;
setup_git_directory_gently(&nongit);
while (1 < argc) {
char *home = getenv("HOME");
if (home) {
char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
- setenv("GIT_CONFIG", user_config, 1);
+ setenv(CONFIG_ENVIRONMENT, user_config, 1);
free(user_config);
} else {
die("$HOME not set");
}
}
else if (!strcmp(argv[1], "--system"))
- setenv("GIT_CONFIG", ETC_GITCONFIG, 1);
+ setenv(CONFIG_ENVIRONMENT, ETC_GITCONFIG, 1);
+ else if (!strcmp(argv[1], "--file") || !strcmp(argv[1], "-f")) {
+ if (argc < 3)
+ usage(git_config_set_usage);
+ setenv(CONFIG_ENVIRONMENT, argv[2], 1);
+ argc--;
+ argv++;
+ }
+ else if (!strcmp(argv[1], "--null") || !strcmp(argv[1], "-z")) {
+ term = '\0';
+ delim = '\n';
+ key_delim = '\n';
+ }
else if (!strcmp(argv[1], "--rename-section")) {
int ret;
if (argc != 4)
use_key_regexp = 1;
do_all = 1;
return get_value(argv[2], NULL);
- } else
-
- return git_config_set(argv[1], argv[2]);
+ } else {
+ value = normalize_value(argv[1], argv[2]);
+ return git_config_set(argv[1], value);
+ }
case 4:
if (!strcmp(argv[1], "--unset"))
return git_config_set_multivar(argv[2], NULL, argv[3], 0);
use_key_regexp = 1;
do_all = 1;
return get_value(argv[2], argv[3]);
- } else if (!strcmp(argv[1], "--add"))
- return git_config_set_multivar(argv[2], argv[3], "^$", 0);
- else if (!strcmp(argv[1], "--replace-all"))
-
- return git_config_set_multivar(argv[2], argv[3], NULL, 1);
- else
-
- return git_config_set_multivar(argv[1], argv[2], argv[3], 0);
+ } else if (!strcmp(argv[1], "--add")) {
+ value = normalize_value(argv[2], argv[3]);
+ return git_config_set_multivar(argv[2], value, "^$", 0);
+ } else if (!strcmp(argv[1], "--replace-all")) {
+ value = normalize_value(argv[2], argv[3]);
+ return git_config_set_multivar(argv[2], value, NULL, 1);
+ } else {
+ value = normalize_value(argv[1], argv[2]);
+ return git_config_set_multivar(argv[1], value, argv[3], 0);
+ }
case 5:
- if (!strcmp(argv[1], "--replace-all"))
- return git_config_set_multivar(argv[2], argv[3], argv[4], 1);
+ if (!strcmp(argv[1], "--replace-all")) {
+ value = normalize_value(argv[2], argv[3]);
+ return git_config_set_multivar(argv[2], value, argv[4], 1);
+ }
case 1:
default:
usage(git_config_set_usage);
for (p = packed_git; p; p = p->next) {
if (!p->pack_local)
continue;
+ if (open_pack_index(p))
+ continue;
packed += p->num_objects;
num_pack++;
}
#include "tag.h"
#include "refs.h"
#include "builtin.h"
+#include "exec_cmd.h"
#define SEEN (1u<<0)
#define MAX_TAGS (FLAG_BITS - 1)
int cmd_describe(int argc, const char **argv, const char *prefix)
{
int i;
+ int contains = 0;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (*arg != '-')
break;
+ else if (!strcmp(arg, "--contains"))
+ contains = 1;
else if (!strcmp(arg, "--debug"))
debug = 1;
else if (!strcmp(arg, "--all"))
save_commit_buffer = 0;
+ if (contains) {
+ const char **args = xmalloc((4 + argc - i) * sizeof(char*));
+ args[0] = "name-rev";
+ args[1] = "--name-only";
+ args[2] = "--tags";
+ memcpy(args + 3, argv + i, (argc - i) * sizeof(char*));
+ args[3 + argc - i] = NULL;
+ return cmd_name_rev(3 + argc - i, args, prefix);
+ }
+
if (argc <= i)
describe("HEAD", 1);
else
argc = setup_revisions(argc, argv, &rev, NULL);
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
-
+
if (!strcmp(arg, "--cached"))
cached = 1;
else
die("diff_setup_done failed");
}
rev.diffopt.allow_external = 1;
+ rev.diffopt.recursive = 1;
/* Do we have --cached and not have a pending object, then
* default to HEAD by hand. Eek.
+#include "builtin.h"
#include "cache.h"
#include "refs.h"
#include "commit.h"
if (!rla)
rla = "(reflog update)";
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
- lock = lock_any_ref_for_update(refname, oldval);
+ lock = lock_any_ref_for_update(refname, oldval, 0);
if (!lock)
return 1;
if (write_ref_sha1(lock, sha1, msg) < 0)
+#include "builtin.h"
#include "cache.h"
#include "refs.h"
#include "object.h"
return sort;
}
-int cmd_for_each_ref(int ac, const char **av, char *prefix)
+int cmd_for_each_ref(int ac, const char **av, const char *prefix)
{
int i, num_refs;
const char *format = NULL;
+#include "builtin.h"
#include "cache.h"
#include "commit.h"
#include "tree.h"
static int keep_cache_objects;
static unsigned char head_sha1[20];
static int errors_found;
+static int write_lost_and_found;
+static int verbose;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
if (!obj->used) {
printf("dangling %s %s\n", typename(obj->type),
sha1_to_hex(obj->sha1));
+ if (write_lost_and_found) {
+ char *filename = git_path("lost-found/%s/%s",
+ obj->type == OBJ_COMMIT ? "commit" : "other",
+ sha1_to_hex(obj->sha1));
+ FILE *f;
+
+ if (safe_create_leading_directories(filename)) {
+ error("Could not create lost-found");
+ return;
+ }
+ if (!(f = fopen(filename, "w")))
+ die("Could not open %s", filename);
+ if (obj->type == OBJ_BLOB) {
+ enum object_type type;
+ unsigned long size;
+ char *buf = read_sha1_file(obj->sha1,
+ &type, &size);
+ if (buf) {
+ fwrite(buf, size, 1, f);
+ free(buf);
+ }
+ } else
+ fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
+ fclose(f);
+ }
return;
}
static void check_object(struct object *obj)
{
+ if (verbose)
+ fprintf(stderr, "Checking %s\n", sha1_to_hex(obj->sha1));
+
if (obj->flags & REACHABLE)
check_reachable_object(obj);
else
/* Look up all the requirements, warn about missing objects.. */
max = get_max_object_index();
+ if (verbose)
+ fprintf(stderr, "Checking connectivity (%d objects)\n", max);
+
for (i = 0; i < max; i++) {
struct object *obj = get_indexed_object(i);
const char *o_name;
const unsigned char *o_sha1;
+ if (verbose)
+ fprintf(stderr, "Checking tree %s\n",
+ sha1_to_hex(item->object.sha1));
+
init_tree_desc(&desc, item->buffer, item->size);
o_mode = 0;
case S_IFREG | 0644:
case S_IFLNK:
case S_IFDIR:
- case S_IFDIRLNK:
+ case S_IFGITLINK:
break;
/*
* This is nonstandard, but we had a few of these
char *buffer = commit->buffer;
unsigned char tree_sha1[20], sha1[20];
+ if (verbose)
+ fprintf(stderr, "Checking commit %s\n",
+ sha1_to_hex(commit->object.sha1));
+
if (memcmp(buffer, "tree ", 5))
return objerror(&commit->object, "invalid format - expected 'tree' line");
if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n')
if (!commit->parents && show_root)
printf("root %s\n", sha1_to_hex(commit->object.sha1));
if (!commit->date)
- printf("bad commit date in %s\n",
+ printf("bad commit date in %s\n",
sha1_to_hex(commit->object.sha1));
return 0;
}
{
struct object *tagged = tag->tagged;
+ if (verbose)
+ fprintf(stderr, "Checking tag %s\n",
+ sha1_to_hex(tag->object.sha1));
+
if (!tagged) {
return objerror(&tag->object, "could not load tagged object");
}
if (!dir)
return;
+ if (verbose)
+ fprintf(stderr, "Checking directory %s\n", path);
+
while ((de = readdir(dir)) != NULL) {
char name[100];
unsigned char sha1[20];
{
struct object *obj;
+ if (verbose)
+ fprintf(stderr, "Checking reflog %s->%s\n",
+ sha1_to_hex(osha1), sha1_to_hex(nsha1));
+
if (!is_null_sha1(osha1)) {
obj = lookup_object(osha1);
if (obj) {
static void fsck_object_dir(const char *path)
{
int i;
+
+ if (verbose)
+ fprintf(stderr, "Checking object directory\n");
+
for (i = 0; i < 256; i++) {
static char dir[4096];
sprintf(dir, "%s/%02x", path, i);
int null_is_error = 0;
const char *head_points_at = resolve_ref("HEAD", sha1, 0, &flag);
+ if (verbose)
+ fprintf(stderr, "Checking HEAD link\n");
+
if (!head_points_at)
return error("Invalid HEAD");
if (!strcmp(head_points_at, "HEAD"))
int i;
int err = 0;
+ if (verbose)
+ fprintf(stderr, "Checking cache tree\n");
+
if (0 <= it->entry_count) {
struct object *obj = parse_object(it->sha1);
if (!obj) {
static const char fsck_usage[] =
"git-fsck [--tags] [--root] [[--unreachable] [--cache] [--full] "
-"[--strict] <head-sha1>*]";
+"[--strict] [--verbose] <head-sha1>*]";
-int cmd_fsck(int argc, char **argv, const char *prefix)
+int cmd_fsck(int argc, const char **argv, const char *prefix)
{
int i, heads;
check_strict = 1;
continue;
}
+ if (!strcmp(arg, "--verbose")) {
+ verbose = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--lost-found")) {
+ check_full = 1;
+ include_reflogs = 0;
+ write_lost_and_found = 1;
+ continue;
+ }
if (*arg == '-')
usage(fsck_usage);
}
verify_pack(p, 0);
for (p = packed_git; p; p = p->next) {
- uint32_t i, num = p->num_objects;
+ uint32_t i, num;
+ if (open_pack_index(p))
+ continue;
+ num = p->num_objects;
for (i = 0; i < num; i++)
fsck_sha1(nth_packed_object_sha1(p, i));
}
heads = 0;
for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
+ const char *arg = argv[i];
if (*arg == '-')
continue;
struct object *obj;
mode = ntohl(active_cache[i]->ce_mode);
- if (S_ISDIRLNK(mode))
+ if (S_ISGITLINK(mode))
continue;
blob = lookup_blob(active_cache[i]->sha1);
if (!blob)
* Copyright (c) 2006 Shawn O. Pearce
*/
+#include "builtin.h"
#include "cache.h"
#include "run-command.h"
#define FAILED_RUN "failed to run %s"
-static const char builtin_gc_usage[] = "git-gc [--prune]";
+static const char builtin_gc_usage[] = "git-gc [--prune] [--aggressive]";
-static int pack_refs = -1;
+static int pack_refs = 1;
+static int aggressive_window = -1;
-static const char *argv_pack_refs[] = {"pack-refs", "--prune", NULL};
+#define MAX_ADD 10
+static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
-static const char *argv_repack[] = {"repack", "-a", "-d", "-l", NULL};
+static const char *argv_repack[MAX_ADD] = {"repack", "-a", "-d", "-l", NULL};
static const char *argv_prune[] = {"prune", NULL};
static const char *argv_rerere[] = {"rerere", "gc", NULL};
pack_refs = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "gc.aggressivewindow")) {
+ aggressive_window = git_config_int(var, value);
+ return 0;
+ }
return git_default_config(var, value);
}
+static void append_option(const char **cmd, const char *opt, int max_length)
+{
+ int i;
+
+ for (i = 0; cmd[i]; i++)
+ ;
+
+ if (i + 2 >= max_length)
+ die("Too many options specified");
+ cmd[i++] = opt;
+ cmd[i] = NULL;
+}
+
int cmd_gc(int argc, const char **argv, const char *prefix)
{
int i;
int prune = 0;
+ char buf[80];
git_config(gc_config);
prune = 1;
continue;
}
+ if (!strcmp(arg, "--aggressive")) {
+ append_option(argv_repack, "-f", MAX_ADD);
+ if (aggressive_window > 0) {
+ sprintf(buf, "--window=%d", aggressive_window);
+ append_option(argv_repack, buf, MAX_ADD);
+ }
+ continue;
+ }
/* perhaps other parameters later... */
break;
}
#include "builtin.h"
#ifndef DEFAULT_GIT_TEMPLATE_DIR
-#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates/"
+#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
#endif
#ifdef NO_TRUSTABLE_FILEMODE
return fdo;
}
status = copy_fd(fdi, fdo);
- close(fdo);
+ if (close(fdo) != 0)
+ return error("%s: write error: %s", dst, strerror(errno));
if (!status && adjust_shared_perm(dst))
return -1;
}
git_config_set("core.filemode", filemode ? "true" : "false");
- if (is_bare_repository()) {
+ if (is_bare_repository())
git_config_set("core.bare", "true");
- }
else {
+ const char *work_tree = get_git_work_tree();
git_config_set("core.bare", "false");
/* allow template config file to override the default */
if (log_all_ref_updates == -1)
git_config_set("core.logallrefupdates", "true");
+ if (work_tree != git_work_tree_cfg)
+ git_config_set("core.worktree", work_tree);
}
return reinit;
}
static const char init_db_usage[] =
-"git-init [--template=<template-directory>] [--shared]";
+"git-init [-q | --quiet] [--template=<template-directory>] [--shared]";
/*
* If you want to, you can share the DB area with any number of branches.
const char *template_dir = NULL;
char *path;
int len, i, reinit;
+ int quiet = 0;
for (i = 1; i < argc; i++, argv++) {
const char *arg = argv[1];
shared_repository = PERM_GROUP;
else if (!prefixcmp(arg, "--shared="))
shared_repository = git_config_perm("arg", arg+9);
+ else if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet"))
+ quiet = 1;
else
usage(init_db_usage);
}
+ git_work_tree_cfg = xcalloc(PATH_MAX, 1);
+ if (!getcwd(git_work_tree_cfg, PATH_MAX))
+ die ("Cannot access current working directory.");
+ if (access(get_git_work_tree(), X_OK))
+ die ("Cannot access work tree '%s'", get_git_work_tree());
+
/*
* Set up the default .git directory contents
*/
git_config_set("receive.denyNonFastforwards", "true");
}
- printf("%s%s Git repository in %s/\n",
- reinit ? "Reinitialized existing" : "Initialized empty",
- shared_repository ? " shared" : "",
- git_dir);
+ if (!quiet)
+ printf("%s%s Git repository in %s/\n",
+ reinit ? "Reinitialized existing" : "Initialized empty",
+ shared_repository ? " shared" : "",
+ git_dir);
return 0;
}
#include "refs.h"
static int default_show_root = 1;
+static const char *fmt_patch_subject_prefix = "PATCH";
/* this is in builtin-diff.c */
void add_head(struct rev_info *revs);
rev->commit_format = CMIT_FMT_DEFAULT;
rev->verbose_header = 1;
rev->show_root_diff = default_show_root;
+ rev->subject_prefix = fmt_patch_subject_prefix;
argc = setup_revisions(argc, argv, rev, "HEAD");
if (rev->diffopt.pickaxe || rev->diffopt.filter)
rev->always_show_header = 0;
+ if (rev->diffopt.follow_renames) {
+ rev->always_show_header = 0;
+ if (rev->diffopt.nr_paths != 1)
+ usage("git logs can only follow renames on one pathname at a time");
+ }
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
- if (!prefixcmp(arg, "--encoding=")) {
- arg += 11;
- if (strcmp(arg, "none"))
- git_log_output_encoding = xstrdup(arg);
- else
- git_log_output_encoding = "";
- } else if (!strcmp(arg, "--decorate")) {
+ if (!strcmp(arg, "--decorate")) {
if (!decorate)
for_each_ref(add_ref_decoration, NULL);
decorate = 1;
static int git_log_config(const char *var, const char *value)
{
+ if (!strcmp(var, "format.subjectprefix")) {
+ if (!value)
+ die("format.subjectprefix without value");
+ fmt_patch_subject_prefix = xstrdup(value);
+ return 0;
+ }
if (!strcmp(var, "log.showroot")) {
default_show_root = git_config_bool(var, value);
return 0;
if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
return 0;
}
+
return git_log_config(var, value);
}
static FILE *realstdout = NULL;
static const char *output_directory = NULL;
-static int reopen_stdout(struct commit *commit, int nr, int keep_subject)
+static int reopen_stdout(struct commit *commit, int nr, int keep_subject,
+ int numbered_files)
{
char filename[PATH_MAX];
char *sol;
filename[len++] = '/';
}
- sprintf(filename + len, "%04d", nr);
- len = strlen(filename);
-
- sol = strstr(commit->buffer, "\n\n");
- if (sol) {
- int j, space = 1;
-
- sol += 2;
- /* strip [PATCH] or [PATCH blabla] */
- if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
- char *eos = strchr(sol + 6, ']');
- if (eos) {
- while (isspace(*eos))
- eos++;
- sol = eos;
- }
- }
+ if (numbered_files) {
+ sprintf(filename + len, "%d", nr);
+ len = strlen(filename);
+
+ } else {
+ sprintf(filename + len, "%04d", nr);
+ len = strlen(filename);
- for (j = 0;
- j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
- len < sizeof(filename) - suffix_len &&
- sol[j] && sol[j] != '\n';
- j++) {
- if (istitlechar(sol[j])) {
- if (space) {
- filename[len++] = '-';
- space = 0;
+ sol = strstr(commit->buffer, "\n\n");
+ if (sol) {
+ int j, space = 1;
+
+ sol += 2;
+ /* strip [PATCH] or [PATCH blabla] */
+ if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
+ char *eos = strchr(sol + 6, ']');
+ if (eos) {
+ while (isspace(*eos))
+ eos++;
+ sol = eos;
}
- filename[len++] = sol[j];
- if (sol[j] == '.')
- while (sol[j + 1] == '.')
- j++;
- } else
- space = 1;
+ }
+
+ for (j = 0;
+ j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
+ len < sizeof(filename) - suffix_len &&
+ sol[j] && sol[j] != '\n';
+ j++) {
+ if (istitlechar(sol[j])) {
+ if (space) {
+ filename[len++] = '-';
+ space = 0;
+ }
+ filename[len++] = sol[j];
+ if (sol[j] == '.')
+ while (sol[j + 1] == '.')
+ j++;
+ } else
+ space = 1;
+ }
+ while (filename[len - 1] == '.'
+ || filename[len - 1] == '-')
+ len--;
+ filename[len] = 0;
}
- while (filename[len - 1] == '.' || filename[len - 1] == '-')
- len--;
- filename[len] = 0;
+ if (len + suffix_len >= sizeof(filename))
+ return error("Patch pathname too long");
+ strcpy(filename + len, fmt_patch_suffix);
}
- if (len + suffix_len >= sizeof(filename))
- return error("Patch pathname too long");
- strcpy(filename + len, fmt_patch_suffix);
+
fprintf(realstdout, "%s\n", filename);
if (freopen(filename, "w", stdout) == NULL)
return error("Cannot open patch file %s",filename);
- return 0;
+ return 0;
}
static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix)
int numbered = 0;
int start_number = -1;
int keep_subject = 0;
+ int numbered_files = 0; /* _just_ numbers */
int subject_prefix = 0;
int ignore_if_in_upstream = 0;
int thread = 0;
rev.diffopt.msg_sep = "";
rev.diffopt.recursive = 1;
+ rev.subject_prefix = fmt_patch_subject_prefix;
rev.extra_headers = extra_headers;
/*
numbered = 1;
else if (!prefixcmp(argv[i], "--start-number="))
start_number = strtol(argv[i] + 15, NULL, 10);
+ else if (!strcmp(argv[i], "--numbered-files"))
+ numbered_files = 1;
else if (!strcmp(argv[i], "--start-number")) {
i++;
if (i == argc)
die ("-n and -k are mutually exclusive.");
if (keep_subject && subject_prefix)
die ("--subject-prefix and -k are mutually exclusive.");
+ if (numbered_files && use_stdout)
+ die ("--numbered-files and --stdout are mutually exclusive.");
argc = setup_revisions(argc, argv, &rev, "HEAD");
if (argc > 1)
get_patch_ids(&rev, &ids, prefix);
if (!use_stdout)
- realstdout = fdopen(dup(1), "w");
+ realstdout = xfdopen(xdup(1), "w");
prepare_revision_walk(&rev);
while ((commit = get_revision(&rev)) != NULL) {
rev.message_id = message_id;
}
if (!use_stdout)
- if (reopen_stdout(commit, rev.nr, keep_subject))
+ if (reopen_stdout(commit, rev.nr, keep_subject,
+ numbered_files))
die("Failed to create output files");
shown = log_tree_commit(&rev, commit);
free(commit->buffer);
sign = '-';
if (verbose) {
- static char buf[16384];
+ char *buf = NULL;
+ unsigned long buflen = 0;
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
- buf, sizeof(buf), 0, NULL, NULL, 0);
+ &buf, &buflen, 0, NULL, NULL, 0);
printf("%c %s %s\n", sign,
sha1_to_hex(commit->object.sha1), buf);
+ free(buf);
}
else {
printf("%c %s\n", sign,
if (0 <= pos)
continue; /* exact match */
pos = -pos - 1;
- if (pos < active_nr) {
+ if (pos < active_nr) {
ce = active_cache[pos];
if (ce_namelen(ce) == len &&
!memcmp(ce->name, ent->name, len))
break;
}
- if (require_work_tree &&
- (is_bare_repository() || is_inside_git_dir()))
- die("This operation must be run in a work tree");
+ if (require_work_tree && !is_inside_work_tree()) {
+ const char *work_tree = get_git_work_tree();
+ if (!work_tree || chdir(work_tree))
+ die("This operation must be run in a work tree");
+ }
pathspec = get_pathspec(prefix, argv + i);
#define LS_TREE_ONLY 2
#define LS_SHOW_TREES 4
#define LS_NAME_ONLY 8
+#define LS_SHOW_SIZE 16
static int abbrev;
static int ls_options;
static const char **pathspec;
static const char *ls_tree_prefix;
static const char ls_tree_usage[] =
- "git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]";
+ "git-ls-tree [-d] [-r] [-t] [-l] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]";
static int show_recursive(const char *base, int baselen, const char *pathname)
{
{
int retval = 0;
const char *type = blob_type;
+ unsigned long size;
- if (S_ISDIRLNK(mode)) {
+ if (S_ISGITLINK(mode)) {
/*
* Maybe we want to have some recursive version here?
*
(baselen < chomp_prefix || memcmp(ls_tree_prefix, base, chomp_prefix)))
return 0;
- if (!(ls_options & LS_NAME_ONLY))
- printf("%06o %s %s\t", mode, type,
- abbrev ? find_unique_abbrev(sha1,abbrev)
- : sha1_to_hex(sha1));
+ if (!(ls_options & LS_NAME_ONLY)) {
+ if (ls_options & LS_SHOW_SIZE) {
+ if (!strcmp(type, blob_type)) {
+ sha1_object_info(sha1, &size);
+ printf("%06o %s %s %7lu\t", mode, type,
+ abbrev ? find_unique_abbrev(sha1, abbrev)
+ : sha1_to_hex(sha1),
+ size);
+ } else
+ printf("%06o %s %s %7c\t", mode, type,
+ abbrev ? find_unique_abbrev(sha1, abbrev)
+ : sha1_to_hex(sha1),
+ '-');
+ } else
+ printf("%06o %s %s\t", mode, type,
+ abbrev ? find_unique_abbrev(sha1, abbrev)
+ : sha1_to_hex(sha1));
+ }
write_name_quoted(base + chomp_prefix, baselen - chomp_prefix,
pathname,
line_termination, stdout);
case 't':
ls_options |= LS_SHOW_TREES;
break;
+ case 'l':
+ ls_options |= LS_SHOW_SIZE;
+ break;
case '-':
if (!strcmp(argv[1]+2, "name-only") ||
!strcmp(argv[1]+2, "name-status")) {
ls_options |= LS_NAME_ONLY;
break;
}
+ if (!strcmp(argv[1]+2, "long")) {
+ ls_options |= LS_SHOW_SIZE;
+ break;
+ }
if (!strcmp(argv[1]+2, "full-name")) {
chomp_prefix = 0;
break;
static char *cleanup_subject(char *subject)
{
- if (keep_subject)
- return subject;
for (;;) {
char *p;
int len, remove;
if (addlen >= sz - len)
addlen = sz - len - 1;
memcpy(line + len, continuation, addlen);
+ line[len] = '\n';
len += addlen;
}
}
return 0;
}
+/*
+ * When there is no known charset, guess.
+ *
+ * Right now we assume that if the target is UTF-8 (the default),
+ * and it already looks like UTF-8 (which includes US-ASCII as its
+ * subset, of course) then that is what it is and there is nothing
+ * to do.
+ *
+ * Otherwise, we default to assuming it is Latin1 for historical
+ * reasons.
+ */
+static const char *guess_charset(const char *line, const char *target_charset)
+{
+ if (is_encoding_utf8(target_charset)) {
+ if (is_utf8(line))
+ return NULL;
+ }
+ return "latin1";
+}
+
static void convert_to_utf8(char *line, const char *charset)
{
- static const char latin_one[] = "latin1";
- const char *input_charset = *charset ? charset : latin_one;
- char *out = reencode_string(line, metainfo_charset, input_charset);
+ char *out;
+
+ if (!charset || !*charset) {
+ charset = guess_charset(line, metainfo_charset);
+ if (!charset)
+ return;
+ }
+ if (!strcmp(metainfo_charset, charset))
+ return;
+ out = reencode_string(line, metainfo_charset, charset);
if (!out)
die("cannot convert from %s to %s\n",
- input_charset, metainfo_charset);
+ charset, metainfo_charset);
strcpy(line, out);
free(out);
}
return;
}
+static void output_header_lines(FILE *fout, const char *hdr, char *data)
+{
+ while (1) {
+ char *ep = strchr(data, '\n');
+ int len;
+ if (!ep)
+ len = strlen(data);
+ else
+ len = ep - data;
+ fprintf(fout, "%s: %.*s\n", hdr, len, data);
+ if (!ep)
+ break;
+ data = ep + 1;
+ }
+}
+
static void handle_info(void)
{
char *sub;
continue;
if (!memcmp(header[i], "Subject", 7)) {
- sub = cleanup_subject(hdr);
- cleanup_space(sub);
- fprintf(fout, "Subject: %s\n", sub);
+ if (keep_subject)
+ sub = hdr;
+ else {
+ sub = cleanup_subject(hdr);
+ cleanup_space(sub);
+ }
+ output_header_lines(fout, "Subject", sub);
} else if (!memcmp(header[i], "From", 4)) {
handle_from(hdr);
fprintf(fout, "Author: %s\n", name);
fprintf(fout, "\n");
}
-int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
- const char *msg, const char *patch)
+static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
+ const char *msg, const char *patch)
{
keep_subject = ks;
metainfo_charset = encoding;
*/
#include "cache.h"
#include "builtin.h"
+#include "path-list.h"
static const char git_mailsplit_usage[] =
-"git-mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> <mbox>...";
+"git-mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> <mbox>|<Maildir>...";
static int is_from_line(const char *line, int len)
{
exit(1);
}
-int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip)
+static int populate_maildir_list(struct path_list *list, const char *path)
{
- char *name = xmalloc(strlen(dir) + 2 + 3 * sizeof(skip));
+ DIR *dir;
+ struct dirent *dent;
+
+ if ((dir = opendir(path)) == NULL) {
+ error("cannot opendir %s (%s)", path, strerror(errno));
+ return -1;
+ }
+
+ while ((dent = readdir(dir)) != NULL) {
+ if (dent->d_name[0] == '.')
+ continue;
+ path_list_insert(dent->d_name, list);
+ }
+
+ closedir(dir);
+
+ return 0;
+}
+
+static int split_maildir(const char *maildir, const char *dir,
+ int nr_prec, int skip)
+{
+ char file[PATH_MAX];
+ char curdir[PATH_MAX];
+ char name[PATH_MAX];
int ret = -1;
+ int i;
+ struct path_list list = {NULL, 0, 0, 1};
- while (*mbox) {
- const char *file = *mbox++;
- FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
- int file_done = 0;
+ snprintf(curdir, sizeof(curdir), "%s/cur", maildir);
+ if (populate_maildir_list(&list, curdir) < 0)
+ goto out;
- if ( !f ) {
- error("cannot open mbox %s", file);
+ for (i = 0; i < list.nr; i++) {
+ FILE *f;
+ snprintf(file, sizeof(file), "%s/%s", curdir, list.items[i].path);
+ f = fopen(file, "r");
+ if (!f) {
+ error("cannot open mail %s (%s)", file, strerror(errno));
goto out;
}
if (fgets(buf, sizeof(buf), f) == NULL) {
- if (f == stdin)
- break; /* empty stdin is OK */
- error("cannot read mbox %s", file);
+ error("cannot read mail %s (%s)", file, strerror(errno));
goto out;
}
- while (!file_done) {
- sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
- file_done = split_one(f, name, allow_bare);
+ sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+ split_one(f, name, 1);
+
+ fclose(f);
+ }
+
+ path_list_clear(&list, 1);
+
+ ret = skip;
+out:
+ return ret;
+}
+
+static int split_mbox(const char *file, const char *dir, int allow_bare,
+ int nr_prec, int skip)
+{
+ char name[PATH_MAX];
+ int ret = -1;
+
+ FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
+ int file_done = 0;
+
+ if (!f) {
+ error("cannot open mbox %s", file);
+ goto out;
+ }
+
+ if (fgets(buf, sizeof(buf), f) == NULL) {
+ /* empty stdin is OK */
+ if (f != stdin) {
+ error("cannot read mbox %s", file);
+ goto out;
}
+ file_done = 1;
+ }
- if (f != stdin)
- fclose(f);
+ while (!file_done) {
+ sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+ file_done = split_one(f, name, allow_bare);
}
+
+ if (f != stdin)
+ fclose(f);
+
ret = skip;
out:
- free(name);
return ret;
}
+
int cmd_mailsplit(int argc, const char **argv, const char *prefix)
{
- int nr = 0, nr_prec = 4, ret;
+ int nr = 0, nr_prec = 4, num = 0;
int allow_bare = 0;
const char *dir = NULL;
const char **argp;
argp = stdin_only;
}
- ret = split_mbox(argp, dir, allow_bare, nr_prec, nr);
- if (ret != -1)
- printf("%d\n", ret);
+ while (*argp) {
+ const char *arg = *argp++;
+ struct stat argstat;
+ int ret = 0;
+
+ if (arg[0] == '-' && arg[1] == 0) {
+ ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
+ if (ret < 0) {
+ error("cannot split patches from stdin");
+ return 1;
+ }
+ num += (ret - nr);
+ nr = ret;
+ continue;
+ }
+
+ if (stat(arg, &argstat) == -1) {
+ error("cannot stat %s (%s)", arg, strerror(errno));
+ return 1;
+ }
+
+ if (S_ISDIR(argstat.st_mode))
+ ret = split_maildir(arg, dir, nr_prec, nr);
+ else
+ ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
+
+ if (ret < 0) {
+ error("cannot split patches from %s", arg);
+ return 1;
+ }
+ num += (ret - nr);
+ nr = ret;
+ }
+
+ printf("%d\n", num);
- return ret == -1;
+ return 0;
}
+#include "builtin.h"
#include "cache.h"
#include "commit.h"
+#include "builtin.h"
#include "cache.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
static const char merge_file_usage[] =
"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2";
-int cmd_merge_file(int argc, char **argv, char **envp)
+int cmd_merge_file(int argc, const char **argv, const char *prefix)
{
- char *names[3];
+ const char *names[3];
mmfile_t mmfs[3];
mmbuffer_t result = {NULL, 0};
xpparam_t xpp = {XDF_NEED_MINIMAL};
for (; i < 3; i++)
names[i] = argv[i + 1];
- for (i = 0; i < 3; i++)
+ for (i = 0; i < 3; i++) {
if (read_mmfile(mmfs + i, argv[i + 1]))
return -1;
+ if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
+ return error("Cannot merge binary files: %s\n",
+ argv[i + 1]);
+ }
ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
&xpp, XDL_MERGE_ZEALOUS, &result);
free(mmfs[i].ptr);
if (ret >= 0) {
- char *filename = argv[1];
+ const char *filename = argv[1];
FILE *f = to_stdout ? stdout : fopen(filename, "wb");
if (!f)
struct name_ref_data {
int tags_only;
+ int name_only;
const char *ref_filter;
};
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;
{
struct object_array revs = { 0, 0, NULL };
int as_is = 0, all = 0, transform_stdin = 0;
- struct name_ref_data data = { 0, NULL };
+ struct name_ref_data data = { 0, 0, NULL };
git_config(git_default_config);
if (!strcmp(*argv, "--")) {
as_is = 1;
continue;
+ } else if (!strcmp(*argv, "--name-only")) {
+ data.name_only = 1;
+ continue;
} else if (!strcmp(*argv, "--tags")) {
data.tags_only = 1;
continue;
struct object * obj = get_indexed_object(i);
if (!obj)
continue;
- printf("%s %s\n", sha1_to_hex(obj->sha1), get_rev_name(obj));
+ if (!data.name_only)
+ printf("%s ", sha1_to_hex(obj->sha1));
+ printf("%s\n", get_rev_name(obj));
}
} else {
int i;
- for (i = 0; i < revs.nr; i++)
- printf("%s %s\n",
- revs.objects[i].name,
- get_rev_name(revs.objects[i].item));
+ for (i = 0; i < revs.nr; i++) {
+ if (!data.name_only)
+ printf("%s ", revs.objects[i].name);
+ printf("%s\n", get_rev_name(revs.objects[i].item));
+ }
}
return 0;
}
-
#include "builtin.h"
#include "cache.h"
+#include "attr.h"
#include "object.h"
#include "blob.h"
#include "commit.h"
static const char pack_usage[] = "\
git-pack-objects [{ -q | --progress | --all-progress }] \n\
- [--local] [--incremental] [--window=N] [--depth=N] \n\
- [--no-reuse-delta] [--delta-base-offset] [--non-empty] \n\
- [--revs [--unpacked | --all]*] [--reflog] [--stdout | base-name] \n\
- [<ref-list | <object-list]";
+ [--max-pack-size=N] [--local] [--incremental] \n\
+ [--window=N] [--window-memory=N] [--depth=N] \n\
+ [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\
+ [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
+ [--stdout | base-name] [<ref-list | <object-list]";
struct object_entry {
- unsigned char sha1[20];
- uint32_t crc32; /* crc of raw pack data for this object */
- off_t offset; /* offset into the final pack file */
+ struct pack_idx_entry idx;
unsigned long size; /* uncompressed size */
- unsigned int hash; /* name hint hash */
- unsigned int depth; /* delta depth */
struct packed_git *in_pack; /* already in pack */
off_t in_pack_offset;
struct object_entry *delta; /* delta base object */
struct object_entry *delta_sibling; /* other deltified objects who
* uses the same base as me
*/
+ void *delta_data; /* cached delta (uncompressed) */
unsigned long delta_size; /* delta data size (uncompressed) */
+ unsigned int hash; /* name hint hash */
enum object_type type;
enum object_type in_pack_type; /* could be delta */
unsigned char in_pack_header_size;
unsigned char preferred_base; /* we do not pack this, but is available
- * to be used as the base objectto delta
+ * to be used as the base object to delta
* objects against.
*/
+ unsigned char no_try_delta;
};
/*
* nice "minimum seek" order.
*/
static struct object_entry *objects;
-static uint32_t nr_objects, nr_alloc, nr_result;
+static struct object_entry **written_list;
+static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
static int non_empty;
-static int no_reuse_delta;
+static int no_reuse_delta, no_reuse_object;
static int local;
static int incremental;
static int allow_ofs_delta;
static const char *pack_tmp_name, *idx_tmp_name;
static char tmpname[PATH_MAX];
-static unsigned char pack_file_sha1[20];
+static const char *base_name;
static int progress = 1;
static int window = 10;
+static uint32_t pack_size_limit;
static int depth = 50;
static int pack_to_stdout;
static int num_preferred_base;
static struct progress progress_state;
+static int pack_compression_level = Z_DEFAULT_COMPRESSION;
+static int pack_compression_seen;
+
+static unsigned long delta_cache_size = 0;
+static unsigned long max_delta_cache_size = 0;
+static unsigned long cache_max_small_delta_size = 1000;
+
+static unsigned long window_memory_usage = 0;
+static unsigned long window_memory_limit = 0;
/*
* The object names in objects array are hashed with this hashtable,
{
unsigned long othersize, delta_size;
enum object_type type;
- void *otherbuf = read_sha1_file(entry->delta->sha1, &type, &othersize);
+ void *otherbuf = read_sha1_file(entry->delta->idx.sha1, &type, &othersize);
void *delta_buf;
if (!otherbuf)
- die("unable to read %s", sha1_to_hex(entry->delta->sha1));
+ die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1));
delta_buf = diff_delta(otherbuf, othersize,
buf, size, &delta_size, 0);
if (!delta_buf || delta_size != entry->delta_size)
- die("delta size changed");
+ die("delta size changed");
free(buf);
free(otherbuf);
return delta_buf;
}
}
-static int check_loose_inflate(unsigned char *data, unsigned long len, unsigned long expect)
-{
- z_stream stream;
- unsigned char fakebuf[4096];
- int st;
-
- memset(&stream, 0, sizeof(stream));
- stream.next_in = data;
- stream.avail_in = len;
- stream.next_out = fakebuf;
- stream.avail_out = sizeof(fakebuf);
- inflateInit(&stream);
-
- while (1) {
- st = inflate(&stream, Z_FINISH);
- if (st == Z_STREAM_END || st == Z_OK) {
- st = (stream.total_out == expect &&
- stream.total_in == len) ? 0 : -1;
- break;
- }
- if (st != Z_BUF_ERROR) {
- st = -1;
- break;
- }
- stream.next_out = fakebuf;
- stream.avail_out = sizeof(fakebuf);
- }
- inflateEnd(&stream);
- return st;
-}
-
-static int revalidate_loose_object(struct object_entry *entry,
- unsigned char *map,
- unsigned long mapsize)
-{
- /* we already know this is a loose object with new type header. */
- enum object_type type;
- unsigned long size, used;
-
- if (pack_to_stdout)
- return 0;
-
- used = unpack_object_header_gently(map, mapsize, &type, &size);
- if (!used)
- return -1;
- map += used;
- mapsize -= used;
- return check_loose_inflate(map, mapsize, size);
-}
-
static unsigned long write_object(struct sha1file *f,
- struct object_entry *entry)
+ struct object_entry *entry,
+ off_t write_offset)
{
unsigned long size;
enum object_type type;
void *buf;
unsigned char header[10];
+ unsigned char dheader[10];
unsigned hdrlen;
off_t datalen;
enum object_type obj_type;
int to_reuse = 0;
+ /* write limit if limited packsize and not first object */
+ unsigned long limit = pack_size_limit && nr_written ?
+ pack_size_limit - write_offset : 0;
+ /* no if no delta */
+ int usable_delta = !entry->delta ? 0 :
+ /* yes if unlimited packfile */
+ !pack_size_limit ? 1 :
+ /* no if base written to previous pack */
+ entry->delta->idx.offset == (off_t)-1 ? 0 :
+ /* otherwise double-check written to this
+ * pack, like we do below
+ */
+ entry->delta->idx.offset ? 1 : 0;
if (!pack_to_stdout)
crc32_begin(f);
obj_type = entry->type;
- if (! entry->in_pack)
+ if (no_reuse_object)
+ to_reuse = 0; /* explicit */
+ else if (!entry->in_pack)
to_reuse = 0; /* can't reuse what we don't have */
else if (obj_type == OBJ_REF_DELTA || obj_type == OBJ_OFS_DELTA)
- to_reuse = 1; /* check_object() decided it for us */
+ /* check_object() decided it for us ... */
+ to_reuse = usable_delta;
+ /* ... but pack split may override that */
else if (obj_type != entry->in_pack_type)
to_reuse = 0; /* pack has delta which is unusable */
else if (entry->delta)
* and we do not need to deltify it.
*/
- if (!entry->in_pack && !entry->delta) {
- unsigned char *map;
- unsigned long mapsize;
- map = map_sha1_file(entry->sha1, &mapsize);
- if (map && !legacy_loose_object(map)) {
- /* We can copy straight into the pack file */
- if (revalidate_loose_object(entry, map, mapsize))
- die("corrupt loose object %s",
- sha1_to_hex(entry->sha1));
- sha1write(f, map, mapsize);
- munmap(map, mapsize);
- written++;
- reused++;
- return mapsize;
- }
- if (map)
- munmap(map, mapsize);
- }
-
if (!to_reuse) {
- buf = read_sha1_file(entry->sha1, &type, &size);
- if (!buf)
- die("unable to read %s", sha1_to_hex(entry->sha1));
- if (size != entry->size)
- die("object %s size inconsistency (%lu vs %lu)",
- sha1_to_hex(entry->sha1), size, entry->size);
- if (entry->delta) {
+ z_stream stream;
+ unsigned long maxsize;
+ void *out;
+ if (!usable_delta) {
+ buf = read_sha1_file(entry->idx.sha1, &obj_type, &size);
+ if (!buf)
+ die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+ } else if (entry->delta_data) {
+ size = entry->delta_size;
+ buf = entry->delta_data;
+ entry->delta_data = NULL;
+ obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ } else {
+ buf = read_sha1_file(entry->idx.sha1, &type, &size);
+ if (!buf)
+ die("unable to read %s", sha1_to_hex(entry->idx.sha1));
buf = delta_against(buf, size, entry);
size = entry->delta_size;
- obj_type = (allow_ofs_delta && entry->delta->offset) ?
+ obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
OBJ_OFS_DELTA : OBJ_REF_DELTA;
}
+ /* compress the data to store and put compressed length in datalen */
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, pack_compression_level);
+ maxsize = deflateBound(&stream, size);
+ out = xmalloc(maxsize);
+ /* Compress it */
+ stream.next_in = buf;
+ stream.avail_in = size;
+ stream.next_out = out;
+ stream.avail_out = maxsize;
+ while (deflate(&stream, Z_FINISH) == Z_OK)
+ /* nothing */;
+ deflateEnd(&stream);
+ datalen = stream.total_out;
+ deflateEnd(&stream);
/*
* The object header is a byte of 'type' followed by zero or
* more bytes of length.
*/
hdrlen = encode_header(obj_type, size, header);
- sha1write(f, header, hdrlen);
if (obj_type == OBJ_OFS_DELTA) {
/*
* encoding of the relative offset for the delta
* base from this object's position in the pack.
*/
- off_t ofs = entry->offset - entry->delta->offset;
- unsigned pos = sizeof(header) - 1;
- header[pos] = ofs & 127;
+ off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+ unsigned pos = sizeof(dheader) - 1;
+ dheader[pos] = ofs & 127;
while (ofs >>= 7)
- header[--pos] = 128 | (--ofs & 127);
- sha1write(f, header + pos, sizeof(header) - pos);
- hdrlen += sizeof(header) - pos;
+ dheader[--pos] = 128 | (--ofs & 127);
+ if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+ free(out);
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, dheader + pos, sizeof(dheader) - pos);
+ hdrlen += sizeof(dheader) - pos;
} else if (obj_type == OBJ_REF_DELTA) {
/*
* Deltas with a base reference contain
* an additional 20 bytes for the base sha1.
*/
- sha1write(f, entry->delta->sha1, 20);
+ if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+ free(out);
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, entry->delta->idx.sha1, 20);
hdrlen += 20;
+ } else {
+ if (limit && hdrlen + datalen + 20 >= limit) {
+ free(out);
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
}
- datalen = sha1write_compressed(f, buf, size);
+ sha1write(f, out, datalen);
+ free(out);
free(buf);
}
else {
off_t offset;
if (entry->delta) {
- obj_type = (allow_ofs_delta && entry->delta->offset) ?
+ obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
OBJ_OFS_DELTA : OBJ_REF_DELTA;
reused_delta++;
}
hdrlen = encode_header(obj_type, entry->size, header);
- sha1write(f, header, hdrlen);
- if (obj_type == OBJ_OFS_DELTA) {
- off_t ofs = entry->offset - entry->delta->offset;
- unsigned pos = sizeof(header) - 1;
- header[pos] = ofs & 127;
- while (ofs >>= 7)
- header[--pos] = 128 | (--ofs & 127);
- sha1write(f, header + pos, sizeof(header) - pos);
- hdrlen += sizeof(header) - pos;
- } else if (obj_type == OBJ_REF_DELTA) {
- sha1write(f, entry->delta->sha1, 20);
- hdrlen += 20;
- }
-
offset = entry->in_pack_offset;
revidx = find_packed_object(p, offset);
datalen = revidx[1].offset - offset;
if (!pack_to_stdout && p->index_version > 1 &&
check_pack_crc(p, &w_curs, offset, datalen, revidx->nr))
- die("bad packed object CRC for %s", sha1_to_hex(entry->sha1));
+ die("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
offset += entry->in_pack_header_size;
datalen -= entry->in_pack_header_size;
+ if (obj_type == OBJ_OFS_DELTA) {
+ off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+ unsigned pos = sizeof(dheader) - 1;
+ dheader[pos] = ofs & 127;
+ while (ofs >>= 7)
+ dheader[--pos] = 128 | (--ofs & 127);
+ if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit)
+ return 0;
+ sha1write(f, header, hdrlen);
+ sha1write(f, dheader + pos, sizeof(dheader) - pos);
+ hdrlen += sizeof(dheader) - pos;
+ } else if (obj_type == OBJ_REF_DELTA) {
+ if (limit && hdrlen + 20 + datalen + 20 >= limit)
+ return 0;
+ sha1write(f, header, hdrlen);
+ sha1write(f, entry->delta->idx.sha1, 20);
+ hdrlen += 20;
+ } else {
+ if (limit && hdrlen + datalen + 20 >= limit)
+ return 0;
+ sha1write(f, header, hdrlen);
+ }
+
if (!pack_to_stdout && p->index_version == 1 &&
check_pack_inflate(p, &w_curs, offset, datalen, entry->size))
- die("corrupt packed object for %s", sha1_to_hex(entry->sha1));
+ die("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
copy_pack_data(f, p, &w_curs, offset, datalen);
unuse_pack(&w_curs);
reused++;
}
- if (entry->delta)
+ if (usable_delta)
written_delta++;
written++;
if (!pack_to_stdout)
- entry->crc32 = crc32_end(f);
+ entry->idx.crc32 = crc32_end(f);
return hdrlen + datalen;
}
unsigned long size;
/* offset is non zero if object is written already. */
- if (e->offset || e->preferred_base)
+ if (e->idx.offset || e->preferred_base)
return offset;
/* if we are deltified, write out base object first. */
- if (e->delta)
+ if (e->delta) {
offset = write_one(f, e->delta, offset);
+ if (!offset)
+ return 0;
+ }
- e->offset = offset;
- size = write_object(f, e);
+ e->idx.offset = offset;
+ size = write_object(f, e, offset);
+ if (!size) {
+ e->idx.offset = 0;
+ return 0;
+ }
+ written_list[nr_written++] = e;
/* make sure off_t is sufficiently large not to wrap */
if (offset > offset + size)
return mkstemp(tmpname);
}
-static off_t write_pack_file(void)
+/* forward declaration for write_pack_file */
+static int adjust_perm(const char *path, mode_t mode);
+
+static void write_pack_file(void)
{
- uint32_t i;
+ uint32_t i = 0, j;
struct sha1file *f;
- off_t offset, last_obj_offset = 0;
+ off_t offset, offset_one, last_obj_offset = 0;
struct pack_header hdr;
- int do_progress = progress;
-
- if (pack_to_stdout) {
- f = sha1fd(1, "<stdout>");
- do_progress >>= 1;
- } else {
- int fd = open_object_dir_tmp("tmp_pack_XXXXXX");
- if (fd < 0)
- die("unable to create %s: %s\n", tmpname, strerror(errno));
- pack_tmp_name = xstrdup(tmpname);
- f = sha1fd(fd, pack_tmp_name);
- }
+ int do_progress = progress >> pack_to_stdout;
+ uint32_t nr_remaining = nr_result;
if (do_progress)
start_progress(&progress_state, "Writing %u objects...", "", nr_result);
+ written_list = xmalloc(nr_objects * sizeof(struct object_entry *));
- hdr.hdr_signature = htonl(PACK_SIGNATURE);
- hdr.hdr_version = htonl(PACK_VERSION);
- hdr.hdr_entries = htonl(nr_result);
- sha1write(f, &hdr, sizeof(hdr));
- offset = sizeof(hdr);
- if (!nr_result)
- goto done;
- for (i = 0; i < nr_objects; i++) {
- last_obj_offset = offset;
- offset = write_one(f, objects + i, offset);
- if (do_progress)
- display_progress(&progress_state, written);
- }
- if (do_progress)
- stop_progress(&progress_state);
- done:
- if (written != nr_result)
- die("wrote %u objects while expecting %u", written, nr_result);
- sha1close(f, pack_file_sha1, 1);
-
- return last_obj_offset;
-}
-
-static int sha1_sort(const void *_a, const void *_b)
-{
- const struct object_entry *a = *(struct object_entry **)_a;
- const struct object_entry *b = *(struct object_entry **)_b;
- return hashcmp(a->sha1, b->sha1);
-}
-
-static uint32_t index_default_version = 1;
-static uint32_t index_off32_limit = 0x7fffffff;
-
-static void write_index_file(off_t last_obj_offset, unsigned char *sha1)
-{
- struct sha1file *f;
- struct object_entry **sorted_by_sha, **list, **last;
- uint32_t array[256];
- uint32_t i, index_version;
- SHA_CTX ctx;
-
- int fd = open_object_dir_tmp("tmp_idx_XXXXXX");
- if (fd < 0)
- die("unable to create %s: %s\n", tmpname, strerror(errno));
- idx_tmp_name = xstrdup(tmpname);
- f = sha1fd(fd, idx_tmp_name);
-
- if (nr_result) {
- uint32_t j = 0;
- sorted_by_sha =
- xcalloc(nr_result, sizeof(struct object_entry *));
- for (i = 0; i < nr_objects; i++)
- if (!objects[i].preferred_base)
- sorted_by_sha[j++] = objects + i;
- if (j != nr_result)
- die("listed %u objects while expecting %u", j, nr_result);
- qsort(sorted_by_sha, nr_result, sizeof(*sorted_by_sha), sha1_sort);
- list = sorted_by_sha;
- last = sorted_by_sha + nr_result;
- } else
- sorted_by_sha = list = last = NULL;
-
- /* if last object's offset is >= 2^31 we should use index V2 */
- index_version = (last_obj_offset >> 31) ? 2 : index_default_version;
+ do {
+ unsigned char sha1[20];
+
+ if (pack_to_stdout) {
+ f = sha1fd(1, "<stdout>");
+ } else {
+ int fd = open_object_dir_tmp("tmp_pack_XXXXXX");
+ if (fd < 0)
+ die("unable to create %s: %s\n", tmpname, strerror(errno));
+ pack_tmp_name = xstrdup(tmpname);
+ f = sha1fd(fd, pack_tmp_name);
+ }
- /* index versions 2 and above need a header */
- if (index_version >= 2) {
- struct pack_idx_header hdr;
- hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
- hdr.idx_version = htonl(index_version);
+ hdr.hdr_signature = htonl(PACK_SIGNATURE);
+ hdr.hdr_version = htonl(PACK_VERSION);
+ hdr.hdr_entries = htonl(nr_remaining);
sha1write(f, &hdr, sizeof(hdr));
- }
-
- /*
- * Write the first-level table (the list is sorted,
- * but we use a 256-entry lookup to be able to avoid
- * having to do eight extra binary search iterations).
- */
- for (i = 0; i < 256; i++) {
- struct object_entry **next = list;
- while (next < last) {
- struct object_entry *entry = *next;
- if (entry->sha1[0] != i)
+ offset = sizeof(hdr);
+ nr_written = 0;
+ for (; i < nr_objects; i++) {
+ last_obj_offset = offset;
+ offset_one = write_one(f, objects + i, offset);
+ if (!offset_one)
break;
- next++;
+ offset = offset_one;
+ if (do_progress)
+ display_progress(&progress_state, written);
}
- array[i] = htonl(next - sorted_by_sha);
- list = next;
- }
- sha1write(f, array, 256 * 4);
-
- /* Compute the SHA1 hash of sorted object names. */
- SHA1_Init(&ctx);
-
- /* Write the actual SHA1 entries. */
- list = sorted_by_sha;
- for (i = 0; i < nr_result; i++) {
- struct object_entry *entry = *list++;
- if (index_version < 2) {
- uint32_t offset = htonl(entry->offset);
- sha1write(f, &offset, 4);
- }
- sha1write(f, entry->sha1, 20);
- SHA1_Update(&ctx, entry->sha1, 20);
- }
- if (index_version >= 2) {
- unsigned int nr_large_offset = 0;
-
- /* write the crc32 table */
- list = sorted_by_sha;
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *entry = *list++;
- uint32_t crc32_val = htonl(entry->crc32);
- sha1write(f, &crc32_val, 4);
+ /*
+ * Did we write the wrong # entries in the header?
+ * If so, rewrite it like in fast-import
+ */
+ if (pack_to_stdout || nr_written == nr_remaining) {
+ sha1close(f, sha1, 1);
+ } else {
+ sha1close(f, sha1, 0);
+ fixup_pack_header_footer(f->fd, sha1, pack_tmp_name, nr_written);
+ close(f->fd);
}
- /* write the 32-bit offset table */
- list = sorted_by_sha;
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *entry = *list++;
- uint32_t offset = (entry->offset <= index_off32_limit) ?
- entry->offset : (0x80000000 | nr_large_offset++);
- offset = htonl(offset);
- sha1write(f, &offset, 4);
+ if (!pack_to_stdout) {
+ mode_t mode = umask(0);
+
+ umask(mode);
+ mode = 0444 & ~mode;
+
+ idx_tmp_name = write_idx_file(NULL,
+ (struct pack_idx_entry **) written_list, nr_written, sha1);
+ snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
+ base_name, sha1_to_hex(sha1));
+ if (adjust_perm(pack_tmp_name, mode))
+ die("unable to make temporary pack file readable: %s",
+ strerror(errno));
+ if (rename(pack_tmp_name, tmpname))
+ die("unable to rename temporary pack file: %s",
+ strerror(errno));
+ snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
+ base_name, sha1_to_hex(sha1));
+ if (adjust_perm(idx_tmp_name, mode))
+ die("unable to make temporary index file readable: %s",
+ strerror(errno));
+ if (rename(idx_tmp_name, tmpname))
+ die("unable to rename temporary index file: %s",
+ strerror(errno));
+ puts(sha1_to_hex(sha1));
}
- /* write the large offset table */
- list = sorted_by_sha;
- while (nr_large_offset) {
- struct object_entry *entry = *list++;
- uint64_t offset = entry->offset;
- if (offset > index_off32_limit) {
- uint32_t split[2];
- split[0] = htonl(offset >> 32);
- split[1] = htonl(offset & 0xffffffff);
- sha1write(f, split, 8);
- nr_large_offset--;
- }
+ /* mark written objects as written to previous pack */
+ for (j = 0; j < nr_written; j++) {
+ written_list[j]->idx.offset = (off_t)-1;
}
- }
+ nr_remaining -= nr_written;
+ } while (nr_remaining && i < nr_objects);
- sha1write(f, pack_file_sha1, 20);
- sha1close(f, NULL, 1);
- free(sorted_by_sha);
- SHA1_Final(sha1, &ctx);
+ free(written_list);
+ if (do_progress)
+ stop_progress(&progress_state);
+ if (written != nr_result)
+ die("wrote %u objects while expecting %u", written, nr_result);
+ /*
+ * We have scanned through [0 ... i). Since we have written
+ * the correct number of objects, the remaining [i ... nr_objects)
+ * items must be either already written (due to out-of-order delta base)
+ * or a preferred base. Count those which are neither and complain if any.
+ */
+ for (j = 0; i < nr_objects; i++) {
+ struct object_entry *e = objects + i;
+ j += !e->idx.offset && !e->preferred_base;
+ }
+ if (j)
+ die("wrote %u objects as expected but %u unwritten", written, j);
}
static int locate_object_entry_hash(const unsigned char *sha1)
memcpy(&ui, sha1, sizeof(unsigned int));
i = ui % object_ix_hashsz;
while (0 < object_ix[i]) {
- if (!hashcmp(sha1, objects[object_ix[i] - 1].sha1))
+ if (!hashcmp(sha1, objects[object_ix[i] - 1].idx.sha1))
return i;
if (++i == object_ix_hashsz)
i = 0;
object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz);
memset(object_ix, 0, sizeof(int) * object_ix_hashsz);
for (i = 0, oe = objects; i < nr_objects; i++, oe++) {
- int ix = locate_object_entry_hash(oe->sha1);
+ int ix = locate_object_entry_hash(oe->idx.sha1);
if (0 <= ix)
continue;
ix = -1 - ix;
unsigned char c;
unsigned hash = 0;
+ if (!name)
+ return 0;
+
/*
* This effectively just creates a sortable number from the
* last sixteen non-whitespace characters. Last characters
return hash;
}
+static void setup_delta_attr_check(struct git_attr_check *check)
+{
+ static struct git_attr *attr_delta;
+
+ if (!attr_delta)
+ attr_delta = git_attr("delta", 5);
+
+ check[0].attr = attr_delta;
+}
+
+static int no_try_delta(const char *path)
+{
+ struct git_attr_check check[1];
+
+ setup_delta_attr_check(check);
+ if (git_checkattr(path, ARRAY_SIZE(check), check))
+ return 0;
+ if (ATTR_FALSE(check->value))
+ return 1;
+ return 0;
+}
+
static int add_object_entry(const unsigned char *sha1, enum object_type type,
- unsigned hash, int exclude)
+ const char *name, int exclude)
{
struct object_entry *entry;
struct packed_git *p, *found_pack = NULL;
off_t found_offset = 0;
int ix;
+ unsigned hash = name_hash(name);
ix = nr_objects ? locate_object_entry_hash(sha1) : -1;
if (ix >= 0) {
entry = objects + nr_objects++;
memset(entry, 0, sizeof(*entry));
- hashcpy(entry->sha1, sha1);
+ hashcpy(entry->idx.sha1, sha1);
entry->hash = hash;
if (type)
entry->type = type;
if (progress)
display_progress(&progress_state, nr_objects);
+ if (name && no_try_delta(name))
+ entry->no_try_delta = 1;
+
return 1;
}
if (cmp < 0)
return;
if (name[cmplen] != '/') {
- unsigned hash = name_hash(fullname);
add_object_entry(entry.sha1,
S_ISDIR(entry.mode) ? OBJ_TREE : OBJ_BLOB,
- hash, 1);
+ fullname, 1);
return;
}
if (S_ISDIR(entry.mode)) {
return 0;
}
-static void add_preferred_base_object(const char *name, unsigned hash)
+static void add_preferred_base_object(const char *name)
{
struct pbase_tree *it;
int cmplen;
+ unsigned hash = name_hash(name);
if (!num_preferred_base || check_pbase_path(hash))
return;
cmplen = name_cmp_len(name);
for (it = pbase_tree; it; it = it->next) {
if (cmplen == 0) {
- add_object_entry(it->pcache.sha1, OBJ_TREE, 0, 1);
+ add_object_entry(it->pcache.sha1, OBJ_TREE, NULL, 1);
}
else {
struct tree_desc tree;
buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
/*
- * We want in_pack_type even if we do not reuse delta.
- * There is no point not reusing non-delta representations.
+ * We want in_pack_type even if we do not reuse delta
+ * since non-delta representations could still be reused.
*/
used = unpack_object_header_gently(buf, avail,
&entry->in_pack_type,
ofs += 1;
if (!ofs || MSB(ofs, 7))
die("delta base offset overflow in pack for %s",
- sha1_to_hex(entry->sha1));
+ sha1_to_hex(entry->idx.sha1));
c = buf[used_0++];
ofs = (ofs << 7) + (c & 127);
}
if (ofs >= entry->in_pack_offset)
die("delta base offset out of bound for %s",
- sha1_to_hex(entry->sha1));
+ sha1_to_hex(entry->idx.sha1));
ofs = entry->in_pack_offset - ofs;
if (!no_reuse_delta && !entry->preferred_base)
base_ref = find_packed_object_name(p, ofs);
unuse_pack(&w_curs);
}
- entry->type = sha1_object_info(entry->sha1, &entry->size);
+ entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
if (entry->type < 0)
die("unable to get type of object %s",
- sha1_to_hex(entry->sha1));
+ sha1_to_hex(entry->idx.sha1));
}
static int pack_offset_sort(const void *_a, const void *_b)
/* avoid filesystem trashing with loose objects */
if (!a->in_pack && !b->in_pack)
- return hashcmp(a->sha1, b->sha1);
+ return hashcmp(a->idx.sha1, b->idx.sha1);
if (a->in_pack < b->in_pack)
return -1;
struct object_entry *entry;
void *data;
struct delta_index *index;
+ unsigned depth;
};
+static int delta_cacheable(struct unpacked *trg, struct unpacked *src,
+ unsigned long src_size, unsigned long trg_size,
+ unsigned long delta_size)
+{
+ if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size)
+ return 0;
+
+ if (delta_size < cache_max_small_delta_size)
+ return 1;
+
+ /* cache delta, if objects are large enough compared to delta size */
+ if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10))
+ return 1;
+
+ return 0;
+}
+
/*
* We search for deltas _backwards_ in a list sorted by type and
* by size, so that we see progressively smaller and smaller files.
struct object_entry *trg_entry = trg->entry;
struct object_entry *src_entry = src->entry;
unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz;
+ unsigned ref_depth;
enum object_type type;
void *delta_buf;
return 0;
/* Let's not bust the allowed depth. */
- if (src_entry->depth >= max_depth)
+ if (src->depth >= max_depth)
return 0;
/* Now some size filtering heuristics. */
trg_size = trg_entry->size;
- max_size = trg_size/2 - 20;
- max_size = max_size * (max_depth - src_entry->depth) / max_depth;
+ if (!trg_entry->delta) {
+ max_size = trg_size/2 - 20;
+ ref_depth = 1;
+ } else {
+ max_size = trg_entry->delta_size;
+ ref_depth = trg->depth;
+ }
+ max_size = max_size * (max_depth - src->depth) /
+ (max_depth - ref_depth + 1);
if (max_size == 0)
return 0;
- if (trg_entry->delta && trg_entry->delta_size <= max_size)
- max_size = trg_entry->delta_size-1;
src_size = src_entry->size;
sizediff = src_size < trg_size ? trg_size - src_size : 0;
if (sizediff >= max_size)
return 0;
+ if (trg_size < src_size / 32)
+ return 0;
/* Load data if not already done */
if (!trg->data) {
- trg->data = read_sha1_file(trg_entry->sha1, &type, &sz);
+ trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
if (sz != trg_size)
die("object %s inconsistent object length (%lu vs %lu)",
- sha1_to_hex(trg_entry->sha1), sz, trg_size);
+ sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
+ window_memory_usage += sz;
}
if (!src->data) {
- src->data = read_sha1_file(src_entry->sha1, &type, &sz);
+ src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
if (sz != src_size)
die("object %s inconsistent object length (%lu vs %lu)",
- sha1_to_hex(src_entry->sha1), sz, src_size);
+ sha1_to_hex(src_entry->idx.sha1), sz, src_size);
+ window_memory_usage += sz;
}
if (!src->index) {
src->index = create_delta_index(src->data, src_size);
- if (!src->index)
- die("out of memory");
+ if (!src->index) {
+ static int warned = 0;
+ if (!warned++)
+ warning("suboptimal pack - out of memory");
+ return 0;
+ }
+ window_memory_usage += sizeof_delta_index(src->index);
}
delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
if (!delta_buf)
return 0;
+ if (trg_entry->delta_data) {
+ /* Prefer only shallower same-sized deltas. */
+ if (delta_size == trg_entry->delta_size &&
+ src->depth + 1 >= trg->depth) {
+ free(delta_buf);
+ return 0;
+ }
+ delta_cache_size -= trg_entry->delta_size;
+ free(trg_entry->delta_data);
+ trg_entry->delta_data = NULL;
+ }
trg_entry->delta = src_entry;
trg_entry->delta_size = delta_size;
- trg_entry->depth = src_entry->depth + 1;
- free(delta_buf);
+ trg->depth = src->depth + 1;
+
+ if (delta_cacheable(src, trg, src_size, trg_size, delta_size)) {
+ trg_entry->delta_data = xrealloc(delta_buf, delta_size);
+ delta_cache_size += trg_entry->delta_size;
+ } else
+ free(delta_buf);
return 1;
}
return m;
}
+static void free_unpacked(struct unpacked *n)
+{
+ window_memory_usage -= sizeof_delta_index(n->index);
+ free_delta_index(n->index);
+ n->index = NULL;
+ if (n->data) {
+ free(n->data);
+ n->data = NULL;
+ window_memory_usage -= n->entry->size;
+ }
+ n->entry = NULL;
+ n->depth = 0;
+}
+
static void find_deltas(struct object_entry **list, int window, int depth)
{
- uint32_t i = nr_objects, idx = 0, processed = 0;
+ uint32_t i = nr_objects, idx = 0, count = 0, processed = 0;
unsigned int array_size = window * sizeof(struct unpacked);
struct unpacked *array;
int max_depth;
if (entry->size < 50)
continue;
- free_delta_index(n->index);
- n->index = NULL;
- free(n->data);
- n->data = NULL;
+
+ if (entry->no_try_delta)
+ continue;
+
+ free_unpacked(n);
n->entry = entry;
+ while (window_memory_limit &&
+ window_memory_usage > window_memory_limit &&
+ count > 1) {
+ uint32_t tail = (idx + window - count) % window;
+ free_unpacked(array + tail);
+ count--;
+ }
+
/*
* If the current object is at pack edge, take the depth the
* objects that depend on the current object into account
* depth, leaving it in the window is pointless. we
* should evict it first.
*/
- if (entry->delta && depth <= entry->depth)
+ if (entry->delta && depth <= n->depth)
continue;
next:
idx++;
+ if (count + 1 < window)
+ count++;
if (idx >= window)
idx = 0;
} while (i > 0);
window = git_config_int(k, v);
return 0;
}
- if(!strcmp(k, "pack.depth")) {
+ if (!strcmp(k, "pack.windowmemory")) {
+ window_memory_limit = git_config_ulong(k, v);
+ return 0;
+ }
+ if (!strcmp(k, "pack.depth")) {
depth = git_config_int(k, v);
return 0;
}
+ if (!strcmp(k, "pack.compression")) {
+ int level = git_config_int(k, v);
+ if (level == -1)
+ level = Z_DEFAULT_COMPRESSION;
+ else if (level < 0 || level > Z_BEST_COMPRESSION)
+ die("bad pack compression level %d", level);
+ pack_compression_level = level;
+ pack_compression_seen = 1;
+ return 0;
+ }
+ if (!strcmp(k, "pack.deltacachesize")) {
+ max_delta_cache_size = git_config_int(k, v);
+ return 0;
+ }
+ if (!strcmp(k, "pack.deltacachelimit")) {
+ cache_max_small_delta_size = git_config_int(k, v);
+ return 0;
+ }
return git_default_config(k, v);
}
{
char line[40 + 1 + PATH_MAX + 2];
unsigned char sha1[20];
- unsigned hash;
for (;;) {
if (!fgets(line, sizeof(line), stdin)) {
if (get_sha1_hex(line, sha1))
die("expected sha1, got garbage:\n %s", line);
- hash = name_hash(line+41);
- add_preferred_base_object(line+41, hash);
- add_object_entry(sha1, 0, hash, 0);
+ add_preferred_base_object(line+41);
+ add_object_entry(sha1, 0, line+41, 0);
}
}
static void show_commit(struct commit *commit)
{
- add_object_entry(commit->object.sha1, OBJ_COMMIT, 0, 0);
+ add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
}
static void show_object(struct object_array_entry *p)
{
- unsigned hash = name_hash(p->name);
- add_preferred_base_object(p->name, hash);
- add_object_entry(p->item->sha1, p->item->type, hash, 0);
+ add_preferred_base_object(p->name);
+ add_object_entry(p->item->sha1, p->item->type, p->name, 0);
}
static void show_edge(struct commit *commit)
int use_internal_rev_list = 0;
int thin = 0;
uint32_t i;
- off_t last_obj_offset;
- const char *base_name = NULL;
const char **rp_av;
int rp_ac_alloc = 64;
int rp_ac;
rp_ac = 2;
git_config(git_pack_config);
+ if (!pack_compression_seen && core_compression_seen)
+ pack_compression_level = core_compression_level;
progress = isatty(2);
for (i = 1; i < argc; i++) {
incremental = 1;
continue;
}
+ if (!prefixcmp(arg, "--compression=")) {
+ char *end;
+ int level = strtoul(arg+14, &end, 0);
+ if (!arg[14] || *end)
+ usage(pack_usage);
+ if (level == -1)
+ level = Z_DEFAULT_COMPRESSION;
+ else if (level < 0 || level > Z_BEST_COMPRESSION)
+ die("bad pack compression level %d", level);
+ pack_compression_level = level;
+ continue;
+ }
+ if (!prefixcmp(arg, "--max-pack-size=")) {
+ char *end;
+ pack_size_limit = strtoul(arg+16, &end, 0) * 1024 * 1024;
+ if (!arg[16] || *end)
+ usage(pack_usage);
+ continue;
+ }
if (!prefixcmp(arg, "--window=")) {
char *end;
window = strtoul(arg+9, &end, 0);
usage(pack_usage);
continue;
}
+ if (!prefixcmp(arg, "--window-memory=")) {
+ if (!git_parse_ulong(arg+16, &window_memory_limit))
+ usage(pack_usage);
+ continue;
+ }
if (!prefixcmp(arg, "--depth=")) {
char *end;
depth = strtoul(arg+8, &end, 0);
no_reuse_delta = 1;
continue;
}
+ if (!strcmp("--no-reuse-object", arg)) {
+ no_reuse_object = no_reuse_delta = 1;
+ continue;
+ }
if (!strcmp("--delta-base-offset", arg)) {
allow_ofs_delta = 1;
continue;
}
if (!prefixcmp(arg, "--index-version=")) {
char *c;
- index_default_version = strtoul(arg + 16, &c, 10);
- if (index_default_version > 2)
+ pack_idx_default_version = strtoul(arg + 16, &c, 10);
+ if (pack_idx_default_version > 2)
die("bad %s", arg);
if (*c == ',')
- index_off32_limit = strtoul(c+1, &c, 0);
- if (*c || index_off32_limit & 0x80000000)
+ pack_idx_off32_limit = strtoul(c+1, &c, 0);
+ if (*c || pack_idx_off32_limit & 0x80000000)
die("bad %s", arg);
continue;
}
if (pack_to_stdout != !base_name)
usage(pack_usage);
+ if (pack_to_stdout && pack_size_limit)
+ die("--max-pack-size cannot be used to build a pack for transfer.");
+
if (!pack_to_stdout && thin)
die("--thin cannot be used to build an indexable pack.");
fprintf(stderr, "Result has %u objects.\n", nr_result);
if (nr_result)
prepare_pack(window, depth);
- last_obj_offset = write_pack_file();
- if (!pack_to_stdout) {
- unsigned char object_list_sha1[20];
- mode_t mode = umask(0);
-
- umask(mode);
- mode = 0444 & ~mode;
-
- write_index_file(last_obj_offset, object_list_sha1);
- snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
- base_name, sha1_to_hex(object_list_sha1));
- if (adjust_perm(pack_tmp_name, mode))
- die("unable to make temporary pack file readable: %s",
- strerror(errno));
- if (rename(pack_tmp_name, tmpname))
- die("unable to rename temporary pack file: %s",
- strerror(errno));
- snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
- base_name, sha1_to_hex(object_list_sha1));
- if (adjust_perm(idx_tmp_name, mode))
- die("unable to make temporary index file readable: %s",
- strerror(errno));
- if (rename(idx_tmp_name, tmpname))
- die("unable to rename temporary index file: %s",
- strerror(errno));
- puts(sha1_to_hex(object_list_sha1));
- }
+ write_pack_file();
if (progress)
fprintf(stderr, "Total %u (delta %u), reused %u (delta %u)\n",
written, written_delta, reused, reused_delta);
+#include "builtin.h"
#include "cache.h"
#include "refs.h"
#include "object.h"
char name[FLEX_ARRAY];
};
+#define PACK_REFS_PRUNE 0x0001
+#define PACK_REFS_ALL 0x0002
+
struct pack_refs_cb_data {
- int prune;
- int all;
+ unsigned int flags;
struct ref_to_prune *ref_to_prune;
FILE *refs_file;
};
is_tag_ref = !prefixcmp(path, "refs/tags/");
/* ALWAYS pack refs that were already packed or are tags */
- if (!cb->all && !is_tag_ref && !(flags & REF_ISPACKED))
+ 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);
}
}
- if (cb->prune && !do_not_prune(flags)) {
+ 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);
static struct lock_file packed;
-int cmd_pack_refs(int argc, const char **argv, const char *prefix)
+static int pack_refs(unsigned int flags)
{
- int fd, i;
+ 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"), 1);
+ cbdata.refs_file = fdopen(fd, "w");
+ if (!cbdata.refs_file)
+ die("unable to create ref-pack file structure (%s)",
+ strerror(errno));
+
+ /* perhaps other traits later as well */
+ fprintf(cbdata.refs_file, "# pack-refs with: 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("failed to write ref-pack file (%s)", strerror(errno));
+ if (commit_lock_file(&packed) < 0)
+ die("unable to overwrite old ref-pack file (%s)", strerror(errno));
+ if (cbdata.flags & PACK_REFS_PRUNE)
+ prune_refs(cbdata.ref_to_prune);
+ return 0;
+}
- cbdata.prune = 1;
+int cmd_pack_refs(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ unsigned int flags;
+
+ flags = PACK_REFS_PRUNE;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--prune")) {
- cbdata.prune = 1; /* now the default */
+ flags |= PACK_REFS_PRUNE; /* now the default */
continue;
}
if (!strcmp(arg, "--no-prune")) {
- cbdata.prune = 0;
+ flags &= ~PACK_REFS_PRUNE;
continue;
}
if (!strcmp(arg, "--all")) {
- cbdata.all = 1;
+ flags |= PACK_REFS_ALL;
continue;
}
/* perhaps other parameters later... */
if (i != argc)
usage(builtin_pack_refs_usage);
- fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1);
- cbdata.refs_file = fdopen(fd, "w");
- if (!cbdata.refs_file)
- die("unable to create ref-pack file structure (%s)",
- strerror(errno));
-
- /* perhaps other traits later as well */
- fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
-
- for_each_ref(handle_one_ref, &cbdata);
- fflush(cbdata.refs_file);
- fsync(fd);
- fclose(cbdata.refs_file);
- if (commit_lock_file(&packed) < 0)
- die("unable to overwrite old ref-pack file (%s)", strerror(errno));
- if (cbdata.prune)
- prune_refs(cbdata.ref_to_prune);
- return 0;
+ return pack_refs(flags);
}
#include "refs.h"
#include "run-command.h"
#include "builtin.h"
-
-#define MAX_URI (16)
+#include "remote.h"
static const char push_usage[] = "git-push [--all] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]";
-static int all, tags, force, thin = 1, verbose;
+static int all, force, thin = 1, verbose;
static const char *receivepack;
-#define BUF_SIZE (2084)
-static char buffer[BUF_SIZE];
-
static const char **refspec;
static int refspec_nr;
refspec_nr = nr;
}
-static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data)
-{
- /* Ignore the "refs/" at the beginning of the refname */
- ref += 5;
-
- if (!prefixcmp(ref, "tags/"))
- add_refspec(xstrdup(ref));
- return 0;
-}
-
-static void expand_refspecs(void)
-{
- if (all) {
- if (refspec_nr)
- die("cannot mix '--all' and a refspec");
-
- /*
- * No need to expand "--all" - we'll just use
- * the "--all" flag to send-pack
- */
- return;
- }
- if (!tags)
- return;
- for_each_ref(expand_one_ref, NULL);
-}
-
-struct wildcard_cb {
- const char *from_prefix;
- int from_prefix_len;
- const char *to_prefix;
- int to_prefix_len;
- int force;
-};
-
-static int expand_wildcard_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct wildcard_cb *cb = cb_data;
- int len = strlen(ref);
- char *expanded, *newref;
-
- if (len < cb->from_prefix_len ||
- memcmp(cb->from_prefix, ref, cb->from_prefix_len))
- return 0;
- expanded = xmalloc(len * 2 + cb->force +
- (cb->to_prefix_len - cb->from_prefix_len) + 2);
- newref = expanded + cb->force;
- if (cb->force)
- expanded[0] = '+';
- memcpy(newref, ref, len);
- newref[len] = ':';
- memcpy(newref + len + 1, cb->to_prefix, cb->to_prefix_len);
- strcpy(newref + len + 1 + cb->to_prefix_len,
- ref + cb->from_prefix_len);
- add_refspec(expanded);
- return 0;
-}
-
-static int wildcard_ref(const char *ref)
-{
- int len;
- const char *colon;
- struct wildcard_cb cb;
-
- memset(&cb, 0, sizeof(cb));
- if (ref[0] == '+') {
- cb.force = 1;
- ref++;
- }
- len = strlen(ref);
- colon = strchr(ref, ':');
- if (! (colon && ref < colon &&
- colon[-2] == '/' && colon[-1] == '*' &&
- /* "<mine>/<asterisk>:<yours>/<asterisk>" is at least 7 bytes */
- 7 <= len &&
- ref[len-2] == '/' && ref[len-1] == '*') )
- return 0 ;
- cb.from_prefix = ref;
- cb.from_prefix_len = colon - ref - 1;
- cb.to_prefix = colon + 1;
- cb.to_prefix_len = len - (colon - ref) - 2;
- for_each_ref(expand_wildcard_ref, &cb);
- return 1;
-}
-
static void set_refspecs(const char **refs, int nr)
{
- if (nr) {
- int i;
- for (i = 0; i < nr; i++) {
- const char *ref = refs[i];
- if (!strcmp("tag", ref)) {
- char *tag;
- int len;
- if (nr <= ++i)
- die("tag shorthand without <tag>");
- len = strlen(refs[i]) + 11;
- tag = xmalloc(len);
- strcpy(tag, "refs/tags/");
- strcat(tag, refs[i]);
- ref = tag;
- }
- else if (wildcard_ref(ref))
- continue;
- add_refspec(ref);
- }
- }
- expand_refspecs();
-}
-
-static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
-{
- int n = 0;
- FILE *f = fopen(git_path("remotes/%s", repo), "r");
- int has_explicit_refspec = refspec_nr || all || tags;
-
- if (!f)
- return -1;
- while (fgets(buffer, BUF_SIZE, f)) {
- int is_refspec;
- char *s, *p;
-
- if (!prefixcmp(buffer, "URL:")) {
- is_refspec = 0;
- s = buffer + 4;
- } else if (!prefixcmp(buffer, "Push:")) {
- is_refspec = 1;
- s = buffer + 5;
- } else
- continue;
-
- /* Remove whitespace at the head.. */
- while (isspace(*s))
- s++;
- if (!*s)
- continue;
-
- /* ..and at the end */
- p = s + strlen(s);
- while (isspace(p[-1]))
- *--p = 0;
-
- if (!is_refspec) {
- if (n < MAX_URI)
- uri[n++] = xstrdup(s);
- else
- error("more than %d URL's specified, ignoring the rest", MAX_URI);
- }
- else if (is_refspec && !has_explicit_refspec) {
- if (!wildcard_ref(s))
- add_refspec(xstrdup(s));
- }
- }
- fclose(f);
- if (!n)
- die("remote '%s' has no URL", repo);
- return n;
-}
-
-static const char **config_uri;
-static const char *config_repo;
-static int config_repo_len;
-static int config_current_uri;
-static int config_get_refspecs;
-static int config_get_receivepack;
-
-static int get_remote_config(const char* key, const char* value)
-{
- if (!prefixcmp(key, "remote.") &&
- !strncmp(key + 7, config_repo, config_repo_len)) {
- if (!strcmp(key + 7 + config_repo_len, ".url")) {
- if (config_current_uri < MAX_URI)
- config_uri[config_current_uri++] = xstrdup(value);
- else
- error("more than %d URL's specified, ignoring the rest", MAX_URI);
- }
- else if (config_get_refspecs &&
- !strcmp(key + 7 + config_repo_len, ".push")) {
- if (!wildcard_ref(value))
- add_refspec(xstrdup(value));
- }
- else if (config_get_receivepack &&
- !strcmp(key + 7 + config_repo_len, ".receivepack")) {
- if (!receivepack) {
- char *rp = xmalloc(strlen(value) + 16);
- sprintf(rp, "--receive-pack=%s", value);
- receivepack = rp;
- } else
- error("more than one receivepack given, using the first");
- }
- }
- return 0;
-}
-
-static int get_config_remotes_uri(const char *repo, const char *uri[MAX_URI])
-{
- config_repo_len = strlen(repo);
- config_repo = repo;
- config_current_uri = 0;
- config_uri = uri;
- config_get_refspecs = !(refspec_nr || all || tags);
- config_get_receivepack = (receivepack == NULL);
-
- git_config(get_remote_config);
- return config_current_uri;
-}
-
-static int get_branches_uri(const char *repo, const char *uri[MAX_URI])
-{
- const char *slash = strchr(repo, '/');
- int n = slash ? slash - repo : 1000;
- FILE *f = fopen(git_path("branches/%.*s", n, repo), "r");
- char *s, *p;
- int len;
-
- if (!f)
- return 0;
- s = fgets(buffer, BUF_SIZE, f);
- fclose(f);
- if (!s)
- return 0;
- while (isspace(*s))
- s++;
- if (!*s)
- return 0;
- p = s + strlen(s);
- 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);
- uri[0] = p;
- return 1;
-}
-
-/*
- * Read remotes and branches file, fill the push target URI
- * list. If there is no command line refspecs, read Push: lines
- * to set up the *refspec list as well.
- * return the number of push target URIs
- */
-static int read_config(const char *repo, const char *uri[MAX_URI])
-{
- int n;
-
- if (*repo != '/') {
- n = get_remotes_uri(repo, uri);
- if (n > 0)
- return n;
-
- n = get_config_remotes_uri(repo, uri);
- if (n > 0)
- return n;
-
- n = get_branches_uri(repo, uri);
- if (n > 0)
- return n;
+ int i;
+ for (i = 0; i < nr; i++) {
+ const char *ref = refs[i];
+ if (!strcmp("tag", ref)) {
+ char *tag;
+ int len;
+ if (nr <= ++i)
+ die("tag shorthand without <tag>");
+ len = strlen(refs[i]) + 11;
+ tag = xmalloc(len);
+ strcpy(tag, "refs/tags/");
+ strcat(tag, refs[i]);
+ ref = tag;
+ }
+ add_refspec(ref);
}
-
- uri[0] = repo;
- return 1;
}
static int do_push(const char *repo)
{
- const char *uri[MAX_URI];
- int i, n, errs;
+ int i, errs;
int common_argc;
const char **argv;
int argc;
+ struct remote *remote = remote_get(repo);
- n = read_config(repo, uri);
- if (n <= 0)
+ if (!remote)
die("bad repository '%s'", repo);
+ if (remote->receivepack) {
+ char *rp = xmalloc(strlen(remote->receivepack) + 16);
+ sprintf(rp, "--receive-pack=%s", remote->receivepack);
+ receivepack = rp;
+ }
+ if (!refspec && !all && remote->push_refspec_nr) {
+ refspec = remote->push_refspec;
+ refspec_nr = remote->push_refspec_nr;
+ }
+
argv = xmalloc((refspec_nr + 10) * sizeof(char *));
argv[0] = "dummy-send-pack";
argc = 1;
common_argc = argc;
errs = 0;
- for (i = 0; i < n; i++) {
+ for (i = 0; i < remote->uri_nr; i++) {
int err;
int dest_argc = common_argc;
int dest_refspec_nr = refspec_nr;
const char **dest_refspec = refspec;
- const char *dest = uri[i];
+ const char *dest = remote->uri[i];
const char *sender = "send-pack";
if (!prefixcmp(dest, "http://") ||
!prefixcmp(dest, "https://"))
sender = "http-push";
- else if (thin)
- argv[dest_argc++] = "--thin";
+ else {
+ char *rem = xmalloc(strlen(remote->name) + 10);
+ sprintf(rem, "--remote=%s", remote->name);
+ argv[dest_argc++] = rem;
+ if (thin)
+ argv[dest_argc++] = "--thin";
+ }
argv[0] = sender;
argv[dest_argc++] = dest;
while (dest_refspec_nr--)
if (!err)
continue;
- error("failed to push to '%s'", uri[i]);
+ error("failed to push to '%s'", remote->uri[i]);
switch (err) {
case -ERR_RUN_COMMAND_FORK:
error("unable to fork for %s", sender);
int cmd_push(int argc, const char **argv, const char *prefix)
{
int i;
- const char *repo = "origin"; /* default repository */
+ const char *repo = NULL; /* default repository */
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
continue;
}
if (!strcmp(arg, "--tags")) {
- tags = 1;
+ add_refspec("refs/tags/*");
continue;
}
if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) {
usage(push_usage);
}
set_refspecs(argv + i, argc - i);
+ if (all && refspec)
+ usage(push_usage);
+
return do_push(repo);
}
}
-static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])";
static struct lock_file lock_file;
memset(&opts, 0, sizeof(opts));
opts.head_idx = -1;
- setup_git_directory();
git_config(git_default_config);
newfd = hold_locked_index(&lock_file, 1);
/* we take the lock for the ref itself to prevent it from
* getting updated.
*/
- lock = lock_any_ref_for_update(ref, sha1);
+ lock = lock_any_ref_for_update(ref, sha1, 0);
if (!lock)
return error("cannot lock ref '%s'", ref);
log_file = xstrdup(git_path("logs/%s", ref));
+#include "builtin.h"
#include "cache.h"
#include "path-list.h"
#include "xdiff/xdiff.h"
static int cutoff_noresolve = 15;
static int cutoff_resolve = 60;
+/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
+static int rerere_enabled = -1;
+
static char *merge_rr_path;
static const char *rr_path(const char *name, const char *file)
write_in_full(out_fd, path, length) != length)
die("unable to write rerere record");
}
- close(out_fd);
+ if (close(out_fd) != 0)
+ die("unable to write rerere record");
return commit_lock_file(&write_lock);
}
int i;
if (read_cache() < 0)
return error("Could not read index");
- for (i = 0; i + 2 < active_nr; i++) {
- struct cache_entry *e1 = active_cache[i];
- struct cache_entry *e2 = active_cache[i+1];
- struct cache_entry *e3 = active_cache[i+2];
- if (ce_stage(e1) == 1 &&
- ce_stage(e2) == 2 &&
+ for (i = 0; i+1 < active_nr; i++) {
+ struct cache_entry *e2 = active_cache[i];
+ struct cache_entry *e3 = active_cache[i+1];
+ if (ce_stage(e2) == 2 &&
ce_stage(e3) == 3 &&
- ce_same_name(e1, e2) && ce_same_name(e1, e3) &&
- S_ISREG(ntohl(e1->ce_mode)) &&
+ ce_same_name(e2, e3) &&
S_ISREG(ntohl(e2->ce_mode)) &&
S_ISREG(ntohl(e3->ce_mode))) {
- path_list_insert((const char *)e1->name, conflict);
- i += 2;
+ path_list_insert((const char *)e2->name, conflict);
+ i++; /* skip over both #2 and #3 */
}
}
return 0;
printf("--- a/%s\n+++ b/%s\n", label1, label2);
fflush(stdout);
xpp.flags = XDF_NEED_MINIMAL;
+ memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
- xecfg.flags = 0;
ecb.outf = outf;
xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb);
cutoff_resolve = git_config_int(var, value);
else if (!strcmp(var, "gc.rerereunresolved"))
cutoff_noresolve = git_config_int(var, value);
+ else if (!strcmp(var, "rerere.enabled"))
+ rerere_enabled = git_config_bool(var, value);
else
return git_default_config(var, value);
return 0;
}
-int cmd_rerere(int argc, const char **argv, const char *prefix)
+static int is_rerere_enabled(void)
{
- struct path_list merge_rr = { NULL, 0, 0, 1 };
- int i, fd = -1;
struct stat st;
+ const char *rr_cache;
+ int rr_cache_exists;
- if (stat(git_path("rr-cache"), &st) || !S_ISDIR(st.st_mode))
+ if (!rerere_enabled)
return 0;
+ rr_cache = git_path("rr-cache");
+ rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode);
+ if (rerere_enabled < 0)
+ return rr_cache_exists;
+
+ if (!rr_cache_exists &&
+ (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache)))
+ die("Could not create directory %s", rr_cache);
+ return 1;
+}
+
+int cmd_rerere(int argc, const char **argv, const char *prefix)
+{
+ struct path_list merge_rr = { NULL, 0, 0, 1 };
+ int i, fd = -1;
+
git_config(git_rerere_config);
+ if (!is_rerere_enabled())
+ return 0;
merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
return do_plain_rerere(&merge_rr, fd);
else if (!strcmp(argv[1], "clear")) {
for (i = 0; i < merge_rr.nr; i++) {
+ struct stat st;
const char *name = (const char *)merge_rr.items[i].util;
if (!stat(git_path("rr-cache/%s", name), &st) &&
S_ISDIR(st.st_mode) &&
path_list_clear(&merge_rr, 1);
return 0;
}
-
if (revs.parents) {
struct commit_list *parents = commit->parents;
while (parents) {
- struct object *o = &(parents->item->object);
+ printf(" %s", sha1_to_hex(parents->item->object.sha1));
parents = parents->next;
- if (o->flags & TMP_MARK)
- continue;
- printf(" %s", sha1_to_hex(o->sha1));
- o->flags |= TMP_MARK;
}
- /* TMP_MARK is a general purpose flag that can
- * be used locally, but the user should clean
- * things up after it is done with them.
- */
- for (parents = commit->parents;
- parents;
- parents = parents->next)
- parents->item->object.flags &= ~TMP_MARK;
}
if (revs.commit_format == CMIT_FMT_ONELINE)
putchar(' ');
putchar('\n');
if (revs.verbose_header) {
- static char pretty_header[16384];
+ char *buf = NULL;
+ unsigned long buflen = 0;
pretty_print_commit(revs.commit_format, commit, ~0,
- pretty_header, sizeof(pretty_header),
+ &buf, &buflen,
revs.abbrev, NULL, NULL, revs.date_mode);
- printf("%s%c", pretty_header, hdr_termination);
+ printf("%s%c", buf, hdr_termination);
+ free(buf);
}
- fflush(stdout);
+ maybe_flush_or_die(stdout, "stdout");
if (commit->parents) {
free_commit_list(commit->parents);
commit->parents = NULL;
}
if (!strcmp(arg, "--show-cdup")) {
const char *pfx = prefix;
+ if (!is_inside_work_tree()) {
+ const char *work_tree =
+ get_git_work_tree();
+ if (work_tree)
+ printf("%s\n", work_tree);
+ continue;
+ }
while (pfx) {
pfx = strchr(pfx, '/');
if (pfx) {
: "false");
continue;
}
+ if (!strcmp(arg, "--is-inside-work-tree")) {
+ printf("%s\n", is_inside_work_tree() ? "true"
+ : "false");
+ continue;
+ }
+ if (!strcmp(arg, "--is-bare-repository")) {
+ printf("%s\n", is_bare_repository() ? "true"
+ : "false");
+ continue;
+ }
if (!prefixcmp(arg, "--since=")) {
show_datestring("--max-age=", arg+8);
continue;
static int edit;
static int replay;
-enum { REVERT, CHERRY_PICK } action;
+static enum { REVERT, CHERRY_PICK } action;
static int no_commit;
static struct commit *commit;
static int needed_deref;
if (argc < 2)
usage(usage_str);
- for (i = 1; i < argc - 1; i++) {
+ for (i = 1; i < argc; i++) {
arg = argv[i];
+ if (arg[0] != '-')
+ break;
if (!strcmp(arg, "-n") || !strcmp(arg, "--no-commit"))
no_commit = 1;
else if (!strcmp(arg, "-e") || !strcmp(arg, "--edit"))
else if (strcmp(arg, "-r"))
usage(usage_str);
}
-
+ if (i != argc - 1)
+ usage(usage_str);
arg = argv[argc - 1];
if (get_sha1(arg, sha1))
die ("Cannot find '%s'", arg);
return result;
}
-char *get_encoding(const char *message)
+static char *get_encoding(const char *message)
{
const char *p = message, *eol;
return NULL;
}
-struct lock_file msg_file;
+static struct lock_file msg_file;
static int msg_fd;
static void add_to_msg(const char *string)
return ret;
}
-static int check_local_mod(unsigned char *head)
+static int check_local_mod(unsigned char *head, int index_only)
{
/* items in list are already sorted in the cache order,
* so we could do this a lot more efficiently by using
const char *name = list.name[i];
unsigned char sha1[20];
unsigned mode;
+ int local_changes = 0;
+ int staged_changes = 0;
pos = cache_name_pos(name, strlen(name));
if (pos < 0)
continue;
}
if (ce_match_stat(ce, &st, 0))
- errs = error("'%s' has local modifications "
- "(hint: try -f)", ce->name);
+ local_changes = 1;
if (no_head
|| get_tree_entry(head, name, sha1, &mode)
|| ce->ce_mode != create_ce_mode(mode)
|| hashcmp(ce->sha1, sha1))
- errs = error("'%s' has changes staged in the index "
- "(hint: try -f)", name);
+ staged_changes = 1;
+
+ if (local_changes && staged_changes)
+ errs = error("'%s' has staged content different "
+ "from both the file and the HEAD\n"
+ "(use -f to force removal)", name);
+ else if (!index_only) {
+ /* It's not dangerous to git-rm --cached a
+ * file if the index matches the file or the
+ * HEAD, since it means the deleted content is
+ * still available somewhere.
+ */
+ 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);
+ if (local_changes)
+ errs = error("'%s' has local modifications\n"
+ "(use --cached to keep the file, "
+ "or -f to force removal)", name);
+ }
}
return errs;
}
unsigned char sha1[20];
if (get_sha1("HEAD", sha1))
hashclr(sha1);
- if (check_local_mod(sha1))
+ if (check_local_mod(sha1, index_only))
exit(1);
}
+#include "builtin.h"
#include "cache.h"
#include "wt-status.h"
static void show_one_commit(struct commit *commit, int no_name)
{
- char pretty[256], *cp;
+ char *pretty = NULL;
+ const char *pretty_str = "(unavailable)";
+ unsigned long pretty_len = 0;
struct commit_name *name = commit->util;
- if (commit->object.parsed)
+
+ if (commit->object.parsed) {
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
- pretty, sizeof(pretty), 0, NULL, NULL, 0);
- else
- strcpy(pretty, "(unavailable)");
- if (!prefixcmp(pretty, "[PATCH] "))
- cp = pretty + 8;
- else
- cp = pretty;
+ &pretty, &pretty_len,
+ 0, NULL, NULL, 0);
+ pretty_str = pretty;
+ }
+ if (!prefixcmp(pretty_str, "[PATCH] "))
+ pretty_str += 8;
if (!no_name) {
if (name && name->head_name) {
printf("[%s] ",
find_unique_abbrev(commit->object.sha1, 7));
}
- puts(cp);
+ puts(pretty_str);
+ free(pretty);
}
static char *ref_name[MAX_REVS + 1];
+#include "builtin.h"
#include "cache.h"
#include "refs.h"
#include "object.h"
#include "builtin.h"
+#include "cache.h"
/*
- * Remove empty lines from the beginning and end.
+ * Returns the length of a line, without trailing spaces.
*
- * Turn multiple consecutive empty lines into just one
- * empty line. Return true if it is an incomplete line.
+ * If the line ends with newline, it will be removed too.
*/
-static int cleanup(char *line)
+static size_t cleanup(char *line, size_t len)
{
- int len = strlen(line);
+ if (len) {
+ if (line[len - 1] == '\n')
+ len--;
- if (len && line[len-1] == '\n') {
- if (len == 1)
- return 0;
- do {
- unsigned char c = line[len-2];
+ while (len) {
+ unsigned char c = line[len - 1];
if (!isspace(c))
break;
- line[len-2] = '\n';
len--;
- line[len] = 0;
- } while (len > 1);
- return 0;
+ }
}
- return 1;
+ return len;
}
-void stripspace(FILE *in, FILE *out)
+/*
+ * Remove empty lines from the beginning and end
+ * and also trailing spaces from every line.
+ *
+ * Note that the buffer will not be NUL-terminated.
+ *
+ * Turn multiple consecutive empty lines between paragraphs
+ * into just one empty line.
+ *
+ * If the input has only empty lines and spaces,
+ * no output will be produced.
+ *
+ * If last line has a newline at the end, it will be removed.
+ *
+ * Enable skip_comments to skip every line starting with "#".
+ */
+size_t stripspace(char *buffer, size_t length, int skip_comments)
{
int empties = -1;
- int incomplete = 0;
- char line[1024];
+ size_t i, j, len, newlen;
+ char *eol;
- while (fgets(line, sizeof(line), in)) {
- incomplete = cleanup(line);
+ for (i = j = 0; i < length; i += len, j += newlen) {
+ eol = memchr(buffer + i, '\n', length - i);
+ len = eol ? eol - (buffer + i) + 1 : length - i;
+
+ if (skip_comments && len && buffer[i] == '#') {
+ newlen = 0;
+ continue;
+ }
+ newlen = cleanup(buffer + i, len);
/* Not just an empty line? */
- if (line[0] != '\n') {
+ if (newlen) {
+ if (empties != -1)
+ buffer[j++] = '\n';
if (empties > 0)
- fputc('\n', out);
+ buffer[j++] = '\n';
empties = 0;
- fputs(line, out);
+ memmove(buffer + j, buffer + i, newlen);
continue;
}
if (empties < 0)
continue;
empties++;
}
- if (incomplete)
- fputc('\n', out);
+
+ return j;
}
int cmd_stripspace(int argc, const char **argv, const char *prefix)
{
- stripspace(stdin, stdout);
+ char *buffer;
+ unsigned long size;
+
+ size = 1024;
+ buffer = xmalloc(size);
+ if (read_fd(0, &buffer, &size)) {
+ free(buffer);
+ die("could not read the input");
+ }
+
+ size = stripspace(buffer, size, 0);
+ write_or_die(1, buffer, size);
+ if (size)
+ putc('\n', stdout);
+
+ free(buffer);
return 0;
}
msg = argv[1];
if (!*msg)
die("Refusing to perform update with empty message");
- if (strchr(msg, '\n'))
- die("Refusing to perform update with \\n in message");
}
else if (!strcmp("--", arg)) {
argc--;
/* Exact match: file or existing gitlink */
if (pos >= 0) {
struct cache_entry *ce = active_cache[pos];
- if (S_ISDIRLNK(ntohl(ce->ce_mode))) {
+ if (S_ISGITLINK(ntohl(ce->ce_mode))) {
/* Do nothing to the index if there is no HEAD! */
if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
int pos = cache_name_pos(path, len);
struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
- if (ce && S_ISDIRLNK(ntohl(ce->ce_mode)))
+ if (ce && S_ISGITLINK(ntohl(ce->ce_mode)))
return error("%s is already a gitlink, not replacing", path);
return add_one_path(ce, path, len, st);
#include "builtin.h"
static const char git_update_ref_usage[] =
-"git-update-ref [-m <reason>] (-d <refname> <value> | <refname> <value> [<oldval>])";
+"git-update-ref [-m <reason>] (-d <refname> <value> | [--no-deref] <refname> <value> [<oldval>])";
int cmd_update_ref(int argc, const char **argv, const char *prefix)
{
const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
struct ref_lock *lock;
unsigned char sha1[20], oldsha1[20];
- int i, delete;
+ int i, delete, ref_flags;
delete = 0;
+ ref_flags = 0;
git_config(git_default_config);
for (i = 1; i < argc; i++) {
msg = argv[++i];
if (!*msg)
die("Refusing to perform update with empty message.");
- if (strchr(msg, '\n'))
- die("Refusing to perform update with \\n in message.");
continue;
}
if (!strcmp("-d", argv[i])) {
delete = 1;
continue;
}
+ if (!strcmp("--no-deref", argv[i])) {
+ ref_flags |= REF_NODEREF;
+ continue;
+ }
if (!refname) {
refname = argv[i];
continue;
if (oldval && *oldval && get_sha1(oldval, oldsha1))
die("%s: not a valid old SHA1", oldval);
- lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL);
+ lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, ref_flags);
if (!lock)
die("%s: cannot lock the ref", refname);
if (write_ref_sha1(lock, sha1, msg) < 0)
if (prefix) {
struct cache_tree *subtree =
cache_tree_find(active_cache_tree, prefix);
+ if (!subtree)
+ die("git-write-tree: prefix %s not found", prefix);
hashcpy(sha1, subtree->sha1);
}
else
extern const char git_usage_string[];
extern void help_unknown_cmd(const char *cmd);
-extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch);
-extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip);
-extern void stripspace(FILE *in, FILE *out);
+extern size_t stripspace(char *buffer, size_t length, int skip_comments);
extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
extern void prune_packed_objects(int);
mode = ntohl(ce->ce_mode);
entlen = pathlen - baselen;
}
- if (mode != S_IFDIRLNK && !missing_ok && !has_sha1_file(sha1))
+ if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1))
return error("invalid object %s", sha1_to_hex(sha1));
if (!ce->ce_mode)
if (0 <= it->entry_count) {
if (size < 20)
goto free_return;
- hashcpy(it->sha1, (unsigned char*)buf);
+ hashcpy(it->sha1, (const unsigned char*)buf);
buf += 20;
size -= 20;
}
* happens that everybody shares the same bit representation
* in the UNIX world (and apparently wider too..)
*/
-#define S_IFDIRLNK 0160000
-#define S_ISDIRLNK(m) (((m) & S_IFMT) == S_IFDIRLNK)
+#define S_IFGITLINK 0160000
+#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
/*
* Intensive research over the course of many years has shown that
{
if (S_ISLNK(mode))
return htonl(S_IFLNK);
- if (S_ISDIR(mode) || S_ISDIRLNK(mode))
- return htonl(S_IFDIRLNK);
+ if (S_ISDIR(mode) || S_ISGITLINK(mode))
+ return htonl(S_IFGITLINK);
return htonl(S_IFREG | ce_permissions(mode));
}
static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
}
#define canon_mode(mode) \
(S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
- S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFDIRLNK)
+ S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)
#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
};
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
#define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
extern int is_bare_repository_cfg;
extern int is_bare_repository(void);
extern int is_inside_git_dir(void);
+extern char *git_work_tree_cfg;
+extern int is_inside_work_tree(void);
extern const char *get_git_dir(void);
extern char *get_object_directory(void);
extern char *get_refs_directory(void);
extern char *get_index_file(void);
extern char *get_graft_file(void);
+extern int set_git_dir(const char *path);
+extern const char *get_git_work_tree(void);
#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
#define alloc_nr(x) (((x)+16)*3/2)
+/*
+ * Realloc the buffer pointed at by variable 'x' so that it can hold
+ * at least 'nr' entries; the number of entries currently allocated
+ * is 'alloc', using the standard growing factor alloc_nr() macro.
+ *
+ * DO NOT USE any expression with side-effect for 'x' or 'alloc'.
+ */
+#define ALLOC_GROW(x, nr, alloc) \
+ do { \
+ if ((nr) > alloc) { \
+ if (alloc_nr(alloc) < (nr)) \
+ alloc = (nr); \
+ else \
+ alloc = alloc_nr(alloc); \
+ x = xrealloc((x), alloc * sizeof(*(x))); \
+ } \
+ } while(0)
+
/* Initialize and use the cache information */
extern int read_index(struct index_state *);
extern int read_index_from(struct index_state *, const char *path);
extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, int);
extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
-extern int read_pipe(int fd, char** return_buf, unsigned long* return_size);
+extern int read_fd(int fd, char **return_buf, unsigned long *return_size);
extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
extern int delete_ref(const char *, const unsigned char *sha1);
/* Environment bits from configuration mechanism */
-extern int use_legacy_headers;
extern int trust_executable_bit;
+extern int quote_path_fully;
extern int has_symlinks;
extern int assume_unchanged;
extern int prefer_symlink_refs;
extern int shared_repository;
extern const char *apply_default_whitespace;
extern int zlib_compression_level;
+extern int core_compression_level;
+extern int core_compression_seen;
extern size_t packed_git_window_size;
extern size_t packed_git_limit;
extern size_t delta_base_cache_limit;
int adjust_shared_perm(const char *path);
int safe_create_leading_directories(char *path);
char *enter_repo(char *path, int strict);
+static inline int is_absolute_path(const char *path)
+{
+ return path[0] == '/';
+}
+const char *make_absolute_path(const char *path);
/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
extern int sha1_object_info(const unsigned char *, unsigned long *);
extern int has_sha1_pack(const unsigned char *sha1, const char **ignore);
extern int has_sha1_file(const unsigned char *sha1);
-extern void *map_sha1_file(const unsigned char *sha1, unsigned long *);
-extern int legacy_loose_object(unsigned char *);
extern int has_pack_file(const unsigned char *sha1);
extern int has_pack_index(const unsigned char *sha1);
unsigned long *size,
unsigned char *sha1_ret);
-enum date_mode { DATE_NORMAL = 0, DATE_RELATIVE, DATE_SHORT, DATE_LOCAL };
+enum date_mode {
+ DATE_NORMAL = 0,
+ DATE_RELATIVE,
+ DATE_SHORT,
+ DATE_LOCAL,
+ DATE_ISO8601,
+ DATE_RFC2822
+};
+
const char *show_date(unsigned long time, int timezone, enum date_mode mode);
-const char *show_rfc2822_date(unsigned long time, int timezone);
int parse_date(const char *date, char *buf, int bufsize);
void datestamp(char *buf, int bufsize);
unsigned long approxidate(const char *);
#define REF_HEADS (1u << 1)
#define REF_TAGS (1u << 2)
-extern pid_t git_connect(int fd[2], char *url, const char *prog);
+#define CONNECT_VERBOSE (1u << 0)
+extern pid_t git_connect(int fd[2], char *url, const char *prog, int flags);
extern int finish_connect(pid_t pid);
extern int path_match(const char *path, int nr, char **match);
-extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
- int nr_refspec, char **refspec, int all);
extern int get_ack(int fd, unsigned char *result_sha1);
extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags);
extern int server_supports(const char *feature);
extern void reprepare_packed_git(void);
extern void install_packed_git(struct packed_git *pack);
-extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
+extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
struct packed_git *packs);
extern void pack_report(void);
+extern int open_pack_index(struct packed_git *);
extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
extern void unuse_pack(struct pack_window **);
extern struct packed_git *add_packed_git(const char *, int, int);
-extern const unsigned char *nth_packed_object_sha1(const struct packed_git *, uint32_t);
+extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t);
extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
extern int git_default_config(const char *, const char *);
extern int git_config_from_file(config_fn_t fn, const char *);
extern int git_config(config_fn_t fn);
+extern int git_parse_long(const char *, long *);
+extern int git_parse_ulong(const char *, unsigned long *);
extern int git_config_int(const char *, const char *);
+extern unsigned long git_config_ulong(const char *, const char *);
extern int git_config_bool(const char *, const char *);
extern int git_config_set(const char *, const char *);
extern int git_config_set_multivar(const char *, const char *, const char *, int);
extern const char *git_commit_encoding;
extern const char *git_log_output_encoding;
+/* IO helper functions */
+extern void maybe_flush_or_die(FILE *, const char *);
extern int copy_fd(int ifd, int ofd);
extern int read_in_full(int fd, void *buf, size_t count);
extern int write_in_full(int fd, const void *buf, size_t count);
/* pager.c */
extern void setup_pager(void);
+extern char *pager_program;
extern int pager_in_use;
extern int pager_use_color;
parent_file.ptr = grab_blob(parent, &sz);
parent_file.size = sz;
xpp.flags = XDF_NEED_MINIMAL;
- xecfg.ctxlen = 0;
- xecfg.flags = 0;
+ memset(&xecfg, 0, sizeof(xecfg));
ecb.outf = xdiff_outf;
ecb.priv = &state;
memset(&state, 0, sizeof(state));
const char *commit_type = "commit";
-struct cmt_fmt_map {
+static struct cmt_fmt_map {
const char *n;
size_t cmp_len;
enum cmit_fmt v;
int register_commit_graft(struct commit_graft *graft, int ignore_dups)
{
int pos = commit_graft_pos(graft->sha1);
-
+
if (0 <= pos) {
if (ignore_dups)
free(graft);
return commit_list_insert(item, pp);
}
-
+
void sort_by_date(struct commit_list **list)
{
struct commit_list *ret = NULL;
return bp - buf;
}
+static unsigned long bound_rfc2047(unsigned long len, const char *encoding)
+{
+ /* upper bound of q encoded string of length 'len' */
+ unsigned long elen = strlen(encoding);
+
+ return len * 3 + elen + 100;
+}
+
static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
const char *line, enum date_mode dmode,
const char *encoding)
break;
case CMIT_FMT_EMAIL:
ret += sprintf(buf + ret, "Date: %s\n",
- show_rfc2822_date(time, tz));
+ show_date(time, tz, DATE_RFC2822));
break;
case CMIT_FMT_FULLER:
ret += sprintf(buf + ret, "%sDate: %s\n", what,
encoding = get_header(commit, "encoding");
use_encoding = encoding ? encoding : utf8;
if (!strcmp(use_encoding, output_encoding))
- out = xstrdup(commit->buffer);
+ if (encoding) /* we'll strip encoding header later */
+ out = xstrdup(commit->buffer);
+ else
+ return NULL; /* nothing to do */
else
out = reencode_string(commit->buffer,
output_encoding, use_encoding);
tz = -tz;
}
- interp_set_entry(table, 2, show_date(date, tz, 0));
- interp_set_entry(table, 3, show_rfc2822_date(date, tz));
- interp_set_entry(table, 4, show_date(date, tz, 1));
+ interp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));
+ interp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));
+ interp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));
+ interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
}
static long format_commit_message(const struct commit *commit,
- const char *msg, char *buf, unsigned long space)
+ const char *msg, char **buf_p, unsigned long *space_p)
{
struct interp table[] = {
{ "%H" }, /* commit hash */
{ "%aD" }, /* author date, RFC2822 style */
{ "%ar" }, /* author date, relative */
{ "%at" }, /* author date, UNIX timestamp */
+ { "%ai" }, /* author date, ISO 8601 */
{ "%cn" }, /* committer name */
{ "%ce" }, /* committer email */
{ "%cd" }, /* committer date */
{ "%cD" }, /* committer date, RFC2822 style */
{ "%cr" }, /* committer date, relative */
{ "%ct" }, /* committer date, UNIX timestamp */
+ { "%ci" }, /* committer date, ISO 8601 */
{ "%e" }, /* encoding */
{ "%s" }, /* subject */
{ "%b" }, /* body */
IPARENTS, IPARENTS_ABBREV,
IAUTHOR_NAME, IAUTHOR_EMAIL,
IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
- IAUTHOR_TIMESTAMP,
+ IAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,
ICOMMITTER_NAME, ICOMMITTER_EMAIL,
ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
+ ICOMMITTER_ISO8601,
IENCODING,
ISUBJECT,
IBODY,
if (!table[i].value)
interp_set_entry(table, i, "<unknown>");
- interpolate(buf, space, user_format, table, ARRAY_SIZE(table));
+ do {
+ char *buf = *buf_p;
+ unsigned long space = *space_p;
+
+ space = interpolate(buf, space, user_format,
+ table, ARRAY_SIZE(table));
+ if (!space)
+ break;
+ buf = xrealloc(buf, space);
+ *buf_p = buf;
+ *space_p = space;
+ } while (1);
interp_clear_table(table, ARRAY_SIZE(table));
- return strlen(buf);
+ return strlen(*buf_p);
+}
+
+static void pp_header(enum cmit_fmt fmt,
+ int abbrev,
+ enum date_mode dmode,
+ const char *encoding,
+ const struct commit *commit,
+ const char **msg_p,
+ unsigned long *len_p,
+ unsigned long *ofs_p,
+ char **buf_p,
+ unsigned long *space_p)
+{
+ int parents_shown = 0;
+
+ for (;;) {
+ const char *line = *msg_p;
+ char *dst;
+ int linelen = get_one_line(*msg_p, *len_p);
+ unsigned long len;
+
+ if (!linelen)
+ return;
+ *msg_p += linelen;
+ *len_p -= linelen;
+
+ if (linelen == 1)
+ /* End of header */
+ return;
+
+ ALLOC_GROW(*buf_p, linelen + *ofs_p + 20, *space_p);
+ dst = *buf_p + *ofs_p;
+
+ if (fmt == CMIT_FMT_RAW) {
+ memcpy(dst, line, linelen);
+ *ofs_p += linelen;
+ continue;
+ }
+
+ if (!memcmp(line, "parent ", 7)) {
+ if (linelen != 48)
+ die("bad parent line in commit");
+ continue;
+ }
+
+ if (!parents_shown) {
+ struct commit_list *parent;
+ int num;
+ for (parent = commit->parents, num = 0;
+ parent;
+ parent = parent->next, num++)
+ ;
+ /* with enough slop */
+ num = *ofs_p + num * 50 + 20;
+ ALLOC_GROW(*buf_p, num, *space_p);
+ dst = *buf_p + *ofs_p;
+ *ofs_p += add_merge_info(fmt, dst, commit, abbrev);
+ parents_shown = 1;
+ }
+
+ /*
+ * MEDIUM == DEFAULT shows only author with dates.
+ * FULL shows both authors but not dates.
+ * FULLER shows both authors and dates.
+ */
+ if (!memcmp(line, "author ", 7)) {
+ len = linelen;
+ if (fmt == CMIT_FMT_EMAIL)
+ len = bound_rfc2047(linelen, encoding);
+ ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
+ dst = *buf_p + *ofs_p;
+ *ofs_p += add_user_info("Author", fmt, dst,
+ line + 7, dmode, encoding);
+ }
+
+ if (!memcmp(line, "committer ", 10) &&
+ (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
+ len = linelen;
+ if (fmt == CMIT_FMT_EMAIL)
+ len = bound_rfc2047(linelen, encoding);
+ ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
+ dst = *buf_p + *ofs_p;
+ *ofs_p += add_user_info("Commit", fmt, dst,
+ line + 10, dmode, encoding);
+ }
+ }
+}
+
+static void pp_title_line(enum cmit_fmt fmt,
+ const char **msg_p,
+ unsigned long *len_p,
+ unsigned long *ofs_p,
+ char **buf_p,
+ unsigned long *space_p,
+ int indent,
+ const char *subject,
+ const char *after_subject,
+ const char *encoding,
+ int plain_non_ascii)
+{
+ char *title;
+ unsigned long title_alloc, title_len;
+ unsigned long len;
+
+ title_len = 0;
+ title_alloc = 80;
+ title = xmalloc(title_alloc);
+ for (;;) {
+ const char *line = *msg_p;
+ int linelen = get_one_line(line, *len_p);
+ *msg_p += linelen;
+ *len_p -= linelen;
+
+ if (!linelen || is_empty_line(line, &linelen))
+ break;
+
+ if (title_alloc <= title_len + linelen + 2) {
+ title_alloc = title_len + linelen + 80;
+ title = xrealloc(title, title_alloc);
+ }
+ len = 0;
+ if (title_len) {
+ if (fmt == CMIT_FMT_EMAIL) {
+ len++;
+ title[title_len++] = '\n';
+ }
+ len++;
+ title[title_len++] = ' ';
+ }
+ memcpy(title + title_len, line, linelen);
+ title_len += linelen;
+ }
+
+ /* Enough slop for the MIME header and rfc2047 */
+ len = bound_rfc2047(title_len, encoding)+ 1000;
+ if (subject)
+ len += strlen(subject);
+ if (after_subject)
+ len += strlen(after_subject);
+ if (encoding)
+ len += strlen(encoding);
+ ALLOC_GROW(*buf_p, title_len + *ofs_p + len, *space_p);
+
+ if (subject) {
+ len = strlen(subject);
+ memcpy(*buf_p + *ofs_p, subject, len);
+ *ofs_p += len;
+ *ofs_p += add_rfc2047(*buf_p + *ofs_p,
+ title, title_len, encoding);
+ } else {
+ memcpy(*buf_p + *ofs_p, title, title_len);
+ *ofs_p += title_len;
+ }
+ (*buf_p)[(*ofs_p)++] = '\n';
+ if (plain_non_ascii) {
+ const char *header_fmt =
+ "MIME-Version: 1.0\n"
+ "Content-Type: text/plain; charset=%s\n"
+ "Content-Transfer-Encoding: 8bit\n";
+ *ofs_p += snprintf(*buf_p + *ofs_p,
+ *space_p - *ofs_p,
+ header_fmt, encoding);
+ }
+ if (after_subject) {
+ len = strlen(after_subject);
+ memcpy(*buf_p + *ofs_p, after_subject, len);
+ *ofs_p += len;
+ }
+ free(title);
+ if (fmt == CMIT_FMT_EMAIL) {
+ ALLOC_GROW(*buf_p, *ofs_p + 20, *space_p);
+ (*buf_p)[(*ofs_p)++] = '\n';
+ }
+}
+
+static void pp_remainder(enum cmit_fmt fmt,
+ const char **msg_p,
+ unsigned long *len_p,
+ unsigned long *ofs_p,
+ char **buf_p,
+ unsigned long *space_p,
+ int indent)
+{
+ int first = 1;
+ for (;;) {
+ const char *line = *msg_p;
+ int linelen = get_one_line(line, *len_p);
+ *msg_p += linelen;
+ *len_p -= linelen;
+
+ if (!linelen)
+ break;
+
+ if (is_empty_line(line, &linelen)) {
+ if (first)
+ continue;
+ if (fmt == CMIT_FMT_SHORT)
+ break;
+ }
+ first = 0;
+
+ ALLOC_GROW(*buf_p, *ofs_p + linelen + indent + 20, *space_p);
+ if (indent) {
+ memset(*buf_p + *ofs_p, ' ', indent);
+ *ofs_p += indent;
+ }
+ memcpy(*buf_p + *ofs_p, line, linelen);
+ *ofs_p += linelen;
+ (*buf_p)[(*ofs_p)++] = '\n';
+ }
}
unsigned long pretty_print_commit(enum cmit_fmt fmt,
const struct commit *commit,
unsigned long len,
- char *buf, unsigned long space,
+ char **buf_p, unsigned long *space_p,
int abbrev, const char *subject,
const char *after_subject,
enum date_mode dmode)
{
- int hdr = 1, body = 0, seen_title = 0;
unsigned long offset = 0;
+ unsigned long beginning_of_body;
int indent = 4;
- int parents_shown = 0;
const char *msg = commit->buffer;
int plain_non_ascii = 0;
char *reencoded;
const char *encoding;
+ char *buf;
if (fmt == CMIT_FMT_USERFORMAT)
- return format_commit_message(commit, msg, buf, space);
+ return format_commit_message(commit, msg, buf_p, space_p);
encoding = (git_log_output_encoding
? git_log_output_encoding
if (!encoding)
encoding = "utf-8";
reencoded = logmsg_reencode(commit, encoding);
- if (reencoded)
+ if (reencoded) {
msg = reencoded;
+ len = strlen(reencoded);
+ }
if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
indent = 0;
}
}
+ pp_header(fmt, abbrev, dmode, encoding,
+ commit, &msg, &len,
+ &offset, buf_p, space_p);
+ if (fmt != CMIT_FMT_ONELINE && !subject) {
+ ALLOC_GROW(*buf_p, offset + 20, *space_p);
+ (*buf_p)[offset++] = '\n';
+ }
+
+ /* Skip excess blank lines at the beginning of body, if any... */
for (;;) {
- const char *line = msg;
int linelen = get_one_line(msg, len);
-
+ int ll = linelen;
if (!linelen)
break;
-
- /*
- * We want some slop for indentation and a possible
- * final "...". Thus the "+ 20".
- */
- if (offset + linelen + 20 > space) {
- memcpy(buf + offset, " ...\n", 8);
- offset += 8;
+ if (!is_empty_line(msg, &ll))
break;
- }
-
msg += linelen;
len -= linelen;
- if (hdr) {
- if (linelen == 1) {
- hdr = 0;
- if ((fmt != CMIT_FMT_ONELINE) && !subject)
- buf[offset++] = '\n';
- continue;
- }
- if (fmt == CMIT_FMT_RAW) {
- memcpy(buf + offset, line, linelen);
- offset += linelen;
- continue;
- }
- if (!memcmp(line, "parent ", 7)) {
- if (linelen != 48)
- die("bad parent line in commit");
- continue;
- }
-
- if (!parents_shown) {
- offset += add_merge_info(fmt, buf + offset,
- commit, abbrev);
- parents_shown = 1;
- continue;
- }
- /*
- * MEDIUM == DEFAULT shows only author with dates.
- * FULL shows both authors but not dates.
- * FULLER shows both authors and dates.
- */
- if (!memcmp(line, "author ", 7))
- offset += add_user_info("Author", fmt,
- buf + offset,
- line + 7,
- dmode,
- encoding);
- if (!memcmp(line, "committer ", 10) &&
- (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
- offset += add_user_info("Commit", fmt,
- buf + offset,
- line + 10,
- dmode,
- encoding);
- continue;
- }
+ }
- if (!subject)
- body = 1;
+ /* These formats treat the title line specially. */
+ if (fmt == CMIT_FMT_ONELINE
+ || fmt == CMIT_FMT_EMAIL)
+ pp_title_line(fmt, &msg, &len, &offset,
+ buf_p, space_p, indent,
+ subject, after_subject, encoding,
+ plain_non_ascii);
- if (is_empty_line(line, &linelen)) {
- if (!seen_title)
- continue;
- if (!body)
- continue;
- if (subject)
- continue;
- if (fmt == CMIT_FMT_SHORT)
- break;
- }
+ beginning_of_body = offset;
+ if (fmt != CMIT_FMT_ONELINE)
+ pp_remainder(fmt, &msg, &len, &offset,
+ buf_p, space_p, indent);
- seen_title = 1;
- if (subject) {
- int slen = strlen(subject);
- memcpy(buf + offset, subject, slen);
- offset += slen;
- offset += add_rfc2047(buf + offset, line, linelen,
- encoding);
- }
- else {
- memset(buf + offset, ' ', indent);
- memcpy(buf + offset + indent, line, linelen);
- offset += linelen + indent;
- }
- buf[offset++] = '\n';
- if (fmt == CMIT_FMT_ONELINE)
- break;
- if (subject && plain_non_ascii) {
- int sz;
- char header[512];
- const char *header_fmt =
- "MIME-Version: 1.0\n"
- "Content-Type: text/plain; charset=%s\n"
- "Content-Transfer-Encoding: 8bit\n";
- sz = snprintf(header, sizeof(header), header_fmt,
- encoding);
- if (sizeof(header) < sz)
- die("Encoding name %s too long", encoding);
- memcpy(buf + offset, header, sz);
- offset += sz;
- }
- if (after_subject) {
- int slen = strlen(after_subject);
- if (slen > space - offset - 1)
- slen = space - offset - 1;
- memcpy(buf + offset, after_subject, slen);
- offset += slen;
- after_subject = NULL;
- }
- subject = NULL;
- }
- while (offset && isspace(buf[offset-1]))
+ while (offset && isspace((*buf_p)[offset-1]))
offset--;
+
+ ALLOC_GROW(*buf_p, offset + 20, *space_p);
+ buf = *buf_p;
+
/* Make sure there is an EOLN for the non-oneline case */
if (fmt != CMIT_FMT_ONELINE)
buf[offset++] = '\n';
+
/*
- * make sure there is another EOLN to separate the headers from whatever
- * body the caller appends if we haven't already written a body
+ * The caller may append additional body text in e-mail
+ * format. Make sure we did not strip the blank line
+ * between the header and the body.
*/
- if (fmt == CMIT_FMT_EMAIL && !body)
+ if (fmt == CMIT_FMT_EMAIL && offset <= beginning_of_body)
buf[offset++] = '\n';
buf[offset] = '\0';
-
free(reencoded);
return offset;
}
return item;
}
-int count_parents(struct commit * commit)
-{
- int count;
- struct commit_list * parents = commit->parents;
- for (count = 0; parents; parents = parents->next,count++)
- ;
- return count;
-}
-
void topo_sort_default_setter(struct commit *c, void *data)
{
c->util = data;
next = next->next;
count++;
}
-
+
if (!count)
return;
/* allocate an array to help sort the list */
}
next=next->next;
}
- /*
+ /*
* find the tips
*
- * tips are nodes not reachable from any other node in the list
- *
+ * tips are nodes not reachable from any other node in the list
+ *
* the tips serve as a starting set for the work queue.
*/
next=*list;
if (pn) {
/*
- * parents are only enqueued for emission
+ * parents are only enqueued for emission
* when all their children have been emitted thereby
* guaranteeing topological order.
*/
};
extern enum cmit_fmt get_commit_format(const char *arg);
-extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
+extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char **buf_p, unsigned long *space_p, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
/** Removes the first commit from a list sorted by date, and adds all
* of its parents.
**/
-struct commit *pop_most_recent_commit(struct commit_list **list,
+struct commit *pop_most_recent_commit(struct commit_list **list,
unsigned int mark);
struct commit *pop_commit(struct commit_list **stack);
void clear_commit_marks(struct commit *commit, unsigned int mark);
-int count_parents(struct commit * commit);
-
/*
* Performs an in-place topological sort of list supplied.
*
free(start);
return 0;
}
-
static FILE *config_file;
static const char *config_file_name;
static int config_linenr;
+static int zlib_compression_seen;
+
static int get_next_char(void)
{
int c;
die("bad config file line %d in %s", config_linenr, config_file_name);
}
-int git_config_int(const char *name, const char *value)
+static unsigned long get_unit_factor(const char *end)
+{
+ if (!*end)
+ return 1;
+ else if (!strcasecmp(end, "k"))
+ return 1024;
+ else if (!strcasecmp(end, "m"))
+ return 1024 * 1024;
+ else if (!strcasecmp(end, "g"))
+ return 1024 * 1024 * 1024;
+ die("unknown unit: '%s'", end);
+}
+
+int git_parse_long(const char *value, long *ret)
+{
+ if (value && *value) {
+ char *end;
+ long val = strtol(value, &end, 0);
+ *ret = val * get_unit_factor(end);
+ return 1;
+ }
+ return 0;
+}
+
+int git_parse_ulong(const char *value, unsigned long *ret)
{
if (value && *value) {
char *end;
- int val = strtol(value, &end, 0);
- if (!*end)
- return val;
- if (!strcasecmp(end, "k"))
- return val * 1024;
- if (!strcasecmp(end, "m"))
- return val * 1024 * 1024;
- if (!strcasecmp(end, "g"))
- return val * 1024 * 1024 * 1024;
- }
- die("bad config value for '%s' in %s", name, config_file_name);
+ unsigned long val = strtoul(value, &end, 0);
+ *ret = val * get_unit_factor(end);
+ return 1;
+ }
+ return 0;
+}
+
+int git_config_int(const char *name, const char *value)
+{
+ long ret;
+ if (!git_parse_long(value, &ret))
+ die("bad config value for '%s' in %s", name, config_file_name);
+ return ret;
+}
+
+unsigned long git_config_ulong(const char *name, const char *value)
+{
+ unsigned long ret;
+ if (!git_parse_ulong(value, &ret))
+ die("bad config value for '%s' in %s", name, config_file_name);
+ return ret;
}
int git_config_bool(const char *name, const char *value)
return 0;
}
+ if (!strcmp(var, "core.quotepath")) {
+ quote_path_fully = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "core.symlinks")) {
has_symlinks = git_config_bool(var, value);
return 0;
return 0;
}
- if (!strcmp(var, "core.legacyheaders")) {
- use_legacy_headers = git_config_bool(var, value);
+ if (!strcmp(var, "core.loosecompression")) {
+ int level = git_config_int(var, value);
+ if (level == -1)
+ level = Z_DEFAULT_COMPRESSION;
+ else if (level < 0 || level > Z_BEST_COMPRESSION)
+ die("bad zlib compression level %d", level);
+ zlib_compression_level = level;
+ zlib_compression_seen = 1;
return 0;
}
level = Z_DEFAULT_COMPRESSION;
else if (level < 0 || level > Z_BEST_COMPRESSION)
die("bad zlib compression level %d", level);
- zlib_compression_level = level;
+ core_compression_level = level;
+ core_compression_seen = 1;
+ if (!zlib_compression_seen)
+ zlib_compression_level = level;
return 0;
}
return 0;
}
+ if (!strcmp(var, "core.pager")) {
+ pager_program = xstrdup(value);
+ return 0;
+ }
+
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
return 0;
}
-static int write_error()
+static int write_error(void)
{
fprintf(stderr, "Failed to write new configuration file\n");
size_t equal_offset = size, bracket_offset = size;
ssize_t offset;
- for (offset = offset_-2; offset > 0
+ for (offset = offset_-2; offset > 0
&& contents[offset] != '\n'; offset--)
switch (contents[offset]) {
case '=': equal_offset = offset; break;
int fd = -1, in_fd;
int ret;
char* config_filename;
- char* lock_file;
+ struct lock_file *lock = NULL;
const char* last_dot = strrchr(key, '.');
config_filename = getenv(CONFIG_ENVIRONMENT);
config_filename = git_path("config");
}
config_filename = xstrdup(config_filename);
- lock_file = xstrdup(mkpath("%s.lock", config_filename));
/*
* Since "key" actually contains the section name and the real
store.key[i] = 0;
/*
- * The lock_file serves a purpose in addition to locking: the new
+ * The lock serves a purpose in addition to locking: the new
* contents of .git/config will be written into it.
*/
- fd = open(lock_file, O_WRONLY | O_CREAT | O_EXCL, 0666);
- if (fd < 0 || adjust_shared_perm(lock_file)) {
+ lock = xcalloc(sizeof(struct lock_file), 1);
+ fd = hold_lock_file_for_update(lock, config_filename, 0);
+ if (fd < 0) {
fprintf(stderr, "could not lock config file\n");
free(store.key);
ret = -1;
goto write_err_out;
munmap(contents, contents_sz);
- unlink(config_filename);
}
- if (rename(lock_file, config_filename) < 0) {
- fprintf(stderr, "Could not rename the lock file?\n");
+ if (close(fd) || commit_lock_file(lock) < 0) {
+ fprintf(stderr, "Cannot commit config file!\n");
ret = 4;
goto out_free;
}
+ /* fd is closed, so don't try to close it below. */
+ fd = -1;
+ /*
+ * lock is committed, so don't try to roll it back below.
+ * NOTE: Since lockfile.c keeps a linked list of all created
+ * lock_file structures, it isn't safe to free(lock). It's
+ * better to just leave it hanging around.
+ */
+ lock = NULL;
ret = 0;
out_free:
if (0 <= fd)
close(fd);
+ if (lock)
+ rollback_lock_file(lock);
free(config_filename);
- if (lock_file) {
- unlink(lock_file);
- free(lock_file);
- }
return ret;
write_err_out:
free(config_filename);
return ret;
}
-
NO_STRLCPY=@NO_STRLCPY@
NO_SETENV=@NO_SETENV@
NO_ICONV=@NO_ICONV@
-
## Site configuration related to programs (before tests)
## --with-PACKAGE[=ARG] and --without-PACKAGE
#
+# Set lib to alternative name of lib directory (e.g. lib64)
+AC_ARG_WITH([lib],
+ [AS_HELP_STRING([--with-lib=ARG],
+ [ARG specifies alternative name for lib directory])],
+ [if test "$withval" = "no" -o "$withval" = "yes"; then \
+ AC_MSG_WARN([You should provide name for --with-lib=ARG]); \
+else \
+ GIT_CONF_APPEND_LINE(lib=$withval); \
+fi; \
+],[])
+#
# Define SHELL_PATH to provide path to shell.
GIT_ARG_SET_PATH(shell)
#
# Define PERL_PATH to provide path to Perl.
GIT_ARG_SET_PATH(perl)
#
+# Define ZLIB_PATH to provide path to zlib.
+GIT_ARG_SET_PATH(zlib)
+#
# Declare the with-tcltk/without-tcltk options.
AC_ARG_WITH(tcltk,
AS_HELP_STRING([--with-tcltk],[use Tcl/Tk GUI (default is YES)])
#include "quote.h"
#include "refs.h"
#include "run-command.h"
+#include "remote.h"
static char *server_capabilities;
continue;
if (nr_match && !path_match(name, nr_match, match))
continue;
- ref = xcalloc(1, sizeof(*ref) + len - 40);
+ ref = alloc_ref(len - 40);
hashcpy(ref->old_sha1, old_sha1);
memcpy(ref->name, buffer + 41, len - 40);
*list = ref;
return 0;
}
-struct refspec {
- char *src;
- char *dst;
- char force;
-};
-
-/*
- * A:B means fast forward remote B with local A.
- * +A:B means overwrite remote B with local A.
- * +A is a shorthand for +A:A.
- * A is a shorthand for A:A.
- * :B means delete remote B.
- */
-static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
-{
- int i;
- struct refspec *rs = xcalloc(sizeof(*rs), (nr_refspec + 1));
- for (i = 0; i < nr_refspec; i++) {
- char *sp, *dp, *ep;
- sp = refspec[i];
- if (*sp == '+') {
- rs[i].force = 1;
- sp++;
- }
- ep = strchr(sp, ':');
- if (ep) {
- dp = ep + 1;
- *ep = 0;
- }
- else
- dp = sp;
- rs[i].src = sp;
- rs[i].dst = dp;
- }
- rs[nr_refspec].src = rs[nr_refspec].dst = NULL;
- return rs;
-}
-
-static int count_refspec_match(const char *pattern,
- struct ref *refs,
- struct ref **matched_ref)
-{
- int patlen = strlen(pattern);
- struct ref *matched_weak = NULL;
- struct ref *matched = NULL;
- int weak_match = 0;
- int match = 0;
-
- for (weak_match = match = 0; refs; refs = refs->next) {
- char *name = refs->name;
- int namelen = strlen(name);
- int weak_match;
-
- if (namelen < patlen ||
- memcmp(name + namelen - patlen, pattern, patlen))
- continue;
- if (namelen != patlen && name[namelen - patlen - 1] != '/')
- continue;
-
- /* A match is "weak" if it is with refs outside
- * heads or tags, and did not specify the pattern
- * in full (e.g. "refs/remotes/origin/master") or at
- * least from the toplevel (e.g. "remotes/origin/master");
- * otherwise "git push $URL master" would result in
- * ambiguity between remotes/origin/master and heads/master
- * at the remote site.
- */
- if (namelen != patlen &&
- patlen != namelen - 5 &&
- prefixcmp(name, "refs/heads/") &&
- prefixcmp(name, "refs/tags/")) {
- /* We want to catch the case where only weak
- * matches are found and there are multiple
- * matches, and where more than one strong
- * matches are found, as ambiguous. One
- * strong match with zero or more weak matches
- * are acceptable as a unique match.
- */
- matched_weak = refs;
- weak_match++;
- }
- else {
- matched = refs;
- match++;
- }
- }
- if (!matched) {
- *matched_ref = matched_weak;
- return weak_match;
- }
- else {
- *matched_ref = matched;
- return match;
- }
-}
-
-static void link_dst_tail(struct ref *ref, struct ref ***tail)
-{
- **tail = ref;
- *tail = &ref->next;
- **tail = NULL;
-}
-
-static struct ref *try_explicit_object_name(const char *name)
-{
- unsigned char sha1[20];
- struct ref *ref;
- int len;
-
- if (!*name) {
- ref = xcalloc(1, sizeof(*ref) + 20);
- strcpy(ref->name, "(delete)");
- hashclr(ref->new_sha1);
- return ref;
- }
- if (get_sha1(name, sha1))
- return NULL;
- len = strlen(name) + 1;
- ref = xcalloc(1, sizeof(*ref) + len);
- memcpy(ref->name, name, len);
- hashcpy(ref->new_sha1, sha1);
- return ref;
-}
-
-static int match_explicit_refs(struct ref *src, struct ref *dst,
- struct ref ***dst_tail, struct refspec *rs)
-{
- int i, errs;
- for (i = errs = 0; rs[i].src; i++) {
- struct ref *matched_src, *matched_dst;
-
- matched_src = matched_dst = NULL;
- switch (count_refspec_match(rs[i].src, src, &matched_src)) {
- case 1:
- break;
- case 0:
- /* The source could be in the get_sha1() format
- * not a reference name. :refs/other is a
- * way to delete 'other' ref at the remote end.
- */
- matched_src = try_explicit_object_name(rs[i].src);
- if (matched_src)
- break;
- errs = 1;
- error("src refspec %s does not match any.",
- rs[i].src);
- break;
- default:
- errs = 1;
- error("src refspec %s matches more than one.",
- rs[i].src);
- break;
- }
- switch (count_refspec_match(rs[i].dst, dst, &matched_dst)) {
- case 1:
- break;
- case 0:
- if (!memcmp(rs[i].dst, "refs/", 5)) {
- int len = strlen(rs[i].dst) + 1;
- matched_dst = xcalloc(1, sizeof(*dst) + len);
- memcpy(matched_dst->name, rs[i].dst, len);
- link_dst_tail(matched_dst, dst_tail);
- }
- else if (!strcmp(rs[i].src, rs[i].dst) &&
- matched_src) {
- /* pushing "master:master" when
- * remote does not have master yet.
- */
- int len = strlen(matched_src->name) + 1;
- matched_dst = xcalloc(1, sizeof(*dst) + len);
- memcpy(matched_dst->name, matched_src->name,
- len);
- link_dst_tail(matched_dst, dst_tail);
- }
- else {
- errs = 1;
- error("dst refspec %s does not match any "
- "existing ref on the remote and does "
- "not start with refs/.", rs[i].dst);
- }
- break;
- default:
- errs = 1;
- error("dst refspec %s matches more than one.",
- rs[i].dst);
- break;
- }
- if (errs)
- continue;
- if (matched_dst->peer_ref) {
- errs = 1;
- error("dst ref %s receives from more than one src.",
- matched_dst->name);
- }
- else {
- matched_dst->peer_ref = matched_src;
- matched_dst->force = rs[i].force;
- }
- }
- return -errs;
-}
-
-static struct ref *find_ref_by_name(struct ref *list, const char *name)
-{
- for ( ; list; list = list->next)
- if (!strcmp(list->name, name))
- return list;
- return NULL;
-}
-
-int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
- int nr_refspec, char **refspec, int all)
-{
- struct refspec *rs = parse_ref_spec(nr_refspec, refspec);
-
- if (nr_refspec)
- return match_explicit_refs(src, dst, dst_tail, rs);
-
- /* pick the remainder */
- for ( ; src; src = src->next) {
- struct ref *dst_peer;
- if (src->peer_ref)
- continue;
- dst_peer = find_ref_by_name(dst, src->name);
- if ((dst_peer && dst_peer->peer_ref) || (!dst_peer && !all))
- continue;
- if (!dst_peer) {
- /* Create a new one and link it */
- int len = strlen(src->name) + 1;
- dst_peer = xcalloc(1, sizeof(*dst_peer) + len);
- memcpy(dst_peer->name, src->name, len);
- hashcpy(dst_peer->new_sha1, src->new_sha1);
- link_dst_tail(dst_peer, dst_tail);
- }
- dst_peer->peer_ref = src;
- }
- return 0;
-}
-
enum protocol {
PROTO_LOCAL = 1,
PROTO_SSH,
#ifndef NO_IPV6
+static const char *ai_name(const struct addrinfo *ai)
+{
+ static char addr[INET_ADDRSTRLEN];
+ if ( AF_INET == ai->ai_family ) {
+ struct sockaddr_in *in;
+ in = (struct sockaddr_in *)ai->ai_addr;
+ inet_ntop(ai->ai_family, &in->sin_addr, addr, sizeof(addr));
+ } else if ( AF_INET6 == ai->ai_family ) {
+ struct sockaddr_in6 *in;
+ in = (struct sockaddr_in6 *)ai->ai_addr;
+ inet_ntop(ai->ai_family, &in->sin6_addr, addr, sizeof(addr));
+ } else {
+ strcpy(addr, "(unknown)");
+ }
+ return addr;
+}
+
/*
* Returns a connected socket() fd, or else die()s.
*/
-static int git_tcp_connect_sock(char *host)
+static int git_tcp_connect_sock(char *host, int flags)
{
int sockfd = -1, saved_errno = 0;
char *colon, *end;
const char *port = STR(DEFAULT_GIT_PORT);
struct addrinfo hints, *ai0, *ai;
int gai;
+ int cnt = 0;
if (host[0] == '[') {
end = strchr(host + 1, ']');
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
+ if (flags & CONNECT_VERBOSE)
+ fprintf(stderr, "Looking up %s ... ", host);
+
gai = getaddrinfo(host, port, &hints, &ai);
if (gai)
die("Unable to look up %s (port %s) (%s)", host, port, gai_strerror(gai));
+ if (flags & CONNECT_VERBOSE)
+ fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
+
for (ai0 = ai; ai; ai = ai->ai_next) {
sockfd = socket(ai->ai_family,
ai->ai_socktype, ai->ai_protocol);
}
if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
saved_errno = errno;
+ fprintf(stderr, "%s[%d: %s]: errno=%s\n",
+ host,
+ cnt,
+ ai_name(ai),
+ strerror(saved_errno));
close(sockfd);
sockfd = -1;
continue;
}
+ if (flags & CONNECT_VERBOSE)
+ fprintf(stderr, "%s ", ai_name(ai));
break;
}
if (sockfd < 0)
die("unable to connect a socket (%s)", strerror(saved_errno));
+ if (flags & CONNECT_VERBOSE)
+ fprintf(stderr, "done.\n");
+
return sockfd;
}
/*
* Returns a connected socket() fd, or else die()s.
*/
-static int git_tcp_connect_sock(char *host)
+static int git_tcp_connect_sock(char *host, int flags)
{
int sockfd = -1, saved_errno = 0;
char *colon, *end;
struct sockaddr_in sa;
char **ap;
unsigned int nport;
+ int cnt;
if (host[0] == '[') {
end = strchr(host + 1, ']');
port = colon + 1;
}
+ if (flags & CONNECT_VERBOSE)
+ fprintf(stderr, "Looking up %s ... ", host);
+
he = gethostbyname(host);
if (!he)
die("Unable to look up %s (%s)", host, hstrerror(h_errno));
nport = se->s_port;
}
- for (ap = he->h_addr_list; *ap; ap++) {
+ if (flags & CONNECT_VERBOSE)
+ fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
+
+ for (cnt = 0, ap = he->h_addr_list; *ap; ap++, cnt++) {
sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
if (sockfd < 0) {
saved_errno = errno;
if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
saved_errno = errno;
+ fprintf(stderr, "%s[%d: %s]: errno=%s\n",
+ host,
+ cnt,
+ inet_ntoa(*(struct in_addr *)&sa.sin_addr),
+ strerror(saved_errno));
close(sockfd);
sockfd = -1;
continue;
}
+ if (flags & CONNECT_VERBOSE)
+ fprintf(stderr, "%s ",
+ inet_ntoa(*(struct in_addr *)&sa.sin_addr));
break;
}
if (sockfd < 0)
die("unable to connect a socket (%s)", strerror(saved_errno));
+ if (flags & CONNECT_VERBOSE)
+ fprintf(stderr, "done.\n");
+
return sockfd;
}
#endif /* NO_IPV6 */
-static void git_tcp_connect(int fd[2], char *host)
+static void git_tcp_connect(int fd[2], char *host, int flags)
{
- int sockfd = git_tcp_connect_sock(host);
+ int sockfd = git_tcp_connect_sock(host, flags);
fd[0] = sockfd;
fd[1] = dup(sockfd);
}
if (0 <= matchlen) {
/* core.gitproxy = none for kernel.org */
- if (matchlen == 4 &&
+ if (matchlen == 4 &&
!memcmp(value, "none", 4))
matchlen = 0;
git_proxy_command = xmalloc(matchlen + 1);
*
* Does not return a negative value on error; it just dies.
*/
-pid_t git_connect(int fd[2], char *url, const char *prog)
+pid_t git_connect(int fd[2], char *url, const char *prog, int flags)
{
char *host, *path = url;
char *end;
if (git_use_proxy(host))
git_proxy_connect(fd, host);
else
- git_tcp_connect(fd, host);
+ git_tcp_connect(fd, host, flags);
/*
* Separate original protocol components prog and path
* from extended components with a NUL byte.
unsetenv(ALTERNATE_DB_ENVIRONMENT);
unsetenv(DB_ENVIRONMENT);
unsetenv(GIT_DIR_ENVIRONMENT);
+ unsetenv(GIT_WORK_TREE_ENVIRONMENT);
unsetenv(GRAFT_ENVIRONMENT);
unsetenv(INDEX_ENVIRONMENT);
execlp("sh", "sh", "-c", command, NULL);
Cc: git@vger.kernel.org
Date: Sat, 27 Jan 2007 18:52:38 -0500
Message-ID: <20070127235238.GA28706@coredump.intra.peff.net>
-
esac
__gitcomp "$(__git_refs "$remote")" "" "${cur#*:}"
;;
+ +*)
+ __gitcomp "$(__git_refs)" + "${cur#+}"
+ ;;
*)
- __gitcomp "$(__git_refs2)"
+ __gitcomp "$(__git_refs)"
;;
esac
;;
__git_complete_file
}
+_git_stash ()
+{
+ __gitcomp 'list show apply clear'
+}
+
_git ()
{
local i c=1 command __git_dir
shortlog) _git_shortlog ;;
show) _git_show ;;
show-branch) _git_log ;;
+ stash) _git_stash ;;
whatchanged) _git_log ;;
*) COMPREPLY=() ;;
esac
complete -o default -o nospace -F _git_reset git-reset
complete -o default -o nospace -F _git_shortlog git-shortlog
complete -o default -o nospace -F _git_show git-show
+complete -o default -o nospace -F _git_stash git-stash
complete -o default -o nospace -F _git_log git-show-branch
complete -o default -o nospace -F _git_log git-whatchanged
INSTALL_ELC = $(INSTALL) -m 644
prefix ?= $(HOME)
emacsdir = $(prefix)/share/emacs/site-lisp
+RM ?= rm -f
all: $(ELC)
install: all
$(INSTALL) -d $(DESTDIR)$(emacsdir)
- $(INSTALL_ELC) $(ELC) $(DESTDIR)$(emacsdir)
+ $(INSTALL_ELC) $(ELC:.elc=.el) $(ELC) $(DESTDIR)$(emacsdir)
%.elc: %.el
$(EMACS) -batch -f batch-byte-compile $<
-clean:; rm -f $(ELC)
+clean:; $(RM) $(ELC)
(sort-lines nil (point-min) (point-max))
(save-buffer))
(when created
- (git-run-command nil nil "update-index" "--info-only" "--add" "--" (file-relative-name ignore-name)))
- (git-add-status-file (if created 'added 'modified) (file-relative-name ignore-name))))
+ (git-run-command nil nil "update-index" "--add" "--" (file-relative-name ignore-name)))
+ (git-update-status-files (list (file-relative-name ignore-name)) 'unknown)))
; propertize definition for XEmacs, stolen from erc-compat
(eval-when-compile
" " (git-escape-file-name (git-fileinfo->name info))
(git-rename-as-string info))))
-(defun git-parse-status (status)
- "Parse the output of git-diff-index in the current buffer."
- (goto-char (point-min))
- (while (re-search-forward
- ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMU]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
- nil t 1)
- (let ((old-perm (string-to-number (match-string 1) 8))
- (new-perm (string-to-number (match-string 2) 8))
- (state (or (match-string 4) (match-string 6)))
- (name (or (match-string 5) (match-string 7)))
- (new-name (match-string 8)))
- (if new-name ; copy or rename
- (if (eq ?C (string-to-char state))
- (ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name))
- (ewoc-enter-last status (git-create-fileinfo 'deleted name 0 0 'rename new-name))
- (ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)))
- (ewoc-enter-last status (git-create-fileinfo (git-state-code state) name old-perm new-perm))))))
+(defun git-insert-fileinfo (status info &optional refresh)
+ "Insert INFO in the status buffer, optionally refreshing an existing one."
+ (let ((node (and refresh
+ (git-find-status-file status (git-fileinfo->name info)))))
+ (setf (git-fileinfo->needs-refresh info) t)
+ (when node ;preserve the marked flag
+ (setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node))))
+ (if node (setf (ewoc-data node) info) (ewoc-enter-last status info))))
+
+(defun git-run-diff-index (status files)
+ "Run git-diff-index on FILES and parse the results into STATUS.
+Return the list of files that haven't been handled."
+ (let ((refresh files))
+ (with-temp-buffer
+ (apply #'git-run-command t nil "diff-index" "-z" "-M" "HEAD" "--" files)
+ (goto-char (point-min))
+ (while (re-search-forward
+ ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMU]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
+ nil t 1)
+ (let ((old-perm (string-to-number (match-string 1) 8))
+ (new-perm (string-to-number (match-string 2) 8))
+ (state (or (match-string 4) (match-string 6)))
+ (name (or (match-string 5) (match-string 7)))
+ (new-name (match-string 8)))
+ (if new-name ; copy or rename
+ (if (eq ?C (string-to-char state))
+ (git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) refresh)
+ (git-insert-fileinfo status (git-create-fileinfo 'deleted name 0 0 'rename new-name) refresh)
+ (git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)) refresh)
+ (git-insert-fileinfo status (git-create-fileinfo (git-state-code state) name old-perm new-perm) refresh))
+ (setq files (delete name files))
+ (when new-name (setq files (delete new-name files)))))))
+ files)
(defun git-find-status-file (status file)
"Find a given file in the status ewoc and return its node."
(setq node (ewoc-next status node)))
node))
-(defun git-parse-ls-files (status default-state &optional skip-existing)
- "Parse the output of git-ls-files in the current buffer."
- (goto-char (point-min))
- (let (infolist)
- (while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1)
- (let ((state (match-string 1))
- (name (match-string 2)))
- (unless (and skip-existing (git-find-status-file status name))
- (push (git-create-fileinfo (or (git-state-code state) default-state) name) infolist))))
- (dolist (info (nreverse infolist))
- (ewoc-enter-last status info))))
-
-(defun git-parse-ls-unmerged (status)
- "Parse the output of git-ls-files -u in the current buffer."
- (goto-char (point-min))
- (let (files)
- (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
- (let ((node (git-find-status-file status (match-string 1))))
- (when node (push (ewoc-data node) files))))
- (git-set-files-state files 'unmerged)))
-
-(defun git-add-status-file (state name)
- "Add a new file to the status list (if not existing already) and return its node."
+(defun git-run-ls-files (status files default-state &rest options)
+ "Run git-ls-files on FILES and parse the results into STATUS.
+Return the list of files that haven't been handled."
+ (let ((refresh files))
+ (with-temp-buffer
+ (apply #'git-run-command t nil "ls-files" "-z" "-t" (append options (list "--") files))
+ (goto-char (point-min))
+ (while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1)
+ (let ((state (match-string 1))
+ (name (match-string 2)))
+ (git-insert-fileinfo status (git-create-fileinfo (or (git-state-code state) default-state) name) refresh)
+ (setq files (delete name files))))))
+ files)
+
+(defun git-run-ls-unmerged (status files)
+ "Run git-ls-files -u on FILES and parse the results into STATUS."
+ (with-temp-buffer
+ (apply #'git-run-command t nil "ls-files" "-z" "-u" "--" files)
+ (goto-char (point-min))
+ (let (unmerged-files)
+ (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
+ (let ((node (git-find-status-file status (match-string 1))))
+ (when node (push (ewoc-data node) unmerged-files))))
+ (git-set-files-state unmerged-files 'unmerged))))
+
+(defun git-get-exclude-files ()
+ "Get the list of exclude files to pass to git-ls-files."
+ (let (files
+ (config (git-config "core.excludesfile")))
+ (when (file-readable-p ".git/info/exclude")
+ (push ".git/info/exclude" files))
+ (when (and config (file-readable-p config))
+ (push config files))
+ files))
+
+(defun git-update-status-files (files &optional default-state)
+ "Update the status of FILES from the index."
(unless git-status (error "Not in git-status buffer."))
- (or (git-find-status-file git-status name)
- (ewoc-enter-last git-status (git-create-fileinfo state name))))
+ (let* ((status git-status)
+ (remaining-files
+ (if (git-empty-db-p) ; we need some special handling for an empty db
+ (git-run-ls-files status files 'added "-c")
+ (git-run-diff-index status files))))
+ (git-run-ls-unmerged status files)
+ (when (or (not files) remaining-files)
+ (let ((exclude-files (git-get-exclude-files)))
+ (setq remaining-files (apply #'git-run-ls-files status remaining-files 'unknown "-o"
+ (concat "--exclude-per-directory=" git-per-dir-ignore-file)
+ (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
+ ; mark remaining files with the default state (or remove them if nil)
+ (when remaining-files
+ (if default-state
+ (ewoc-map (lambda (info)
+ (when (member (git-fileinfo->name info) remaining-files)
+ (git-set-files-state (list info) default-state))
+ nil)
+ status)
+ (ewoc-filter status
+ (lambda (info files)
+ (not (member (git-fileinfo->name info) files)))
+ remaining-files)))
+ (git-refresh-files)
+ (git-refresh-ewoc-hf status)))
(defun git-marked-files ()
"Return a list of all marked files, or if none a list containing just the file at cursor position."
(condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
(with-current-buffer buffer (erase-buffer))
(git-set-files-state files 'uptodate)
- (when (file-directory-p ".git/rr-cache")
- (git-run-command nil nil "rerere"))
+ (git-run-command nil nil "rerere")
(git-refresh-files)
(git-refresh-ewoc-hf git-status)
(message "Committed %s." commit)
(defun git-add-file ()
"Add marked file(s) to the index cache."
(interactive)
- (let ((files (git-marked-files-state 'unknown)))
+ (let ((files (git-get-filenames (git-marked-files-state 'unknown))))
(unless files
- (push (ewoc-data
- (git-add-status-file 'added (file-relative-name
- (read-file-name "File to add: " nil nil t))))
- files))
- (apply #'git-run-command nil nil "update-index" "--info-only" "--add" "--" (git-get-filenames files))
- (git-set-files-state files 'added)
- (git-refresh-files)))
+ (push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
+ (apply #'git-run-command nil nil "update-index" "--add" "--" files)
+ (git-update-status-files files 'uptodate)))
(defun git-ignore-file ()
"Add marked file(s) to the ignore list."
(interactive)
- (let ((files (git-marked-files-state 'unknown)))
+ (let ((files (git-get-filenames (git-marked-files-state 'unknown))))
(unless files
- (push (ewoc-data
- (git-add-status-file 'unknown (file-relative-name
- (read-file-name "File to ignore: " nil nil t))))
- files))
- (dolist (info files) (git-append-to-ignore (git-fileinfo->name info)))
- (git-set-files-state files 'ignored)
- (git-refresh-files)))
+ (push (file-relative-name (read-file-name "File to ignore: " nil nil t)) files))
+ (dolist (f files) (git-append-to-ignore f))
+ (git-update-status-files files 'ignored)))
(defun git-remove-file ()
"Remove the marked file(s)."
(interactive)
- (let ((files (git-marked-files-state 'added 'modified 'unknown 'uptodate)))
+ (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate))))
(unless files
- (push (ewoc-data
- (git-add-status-file 'unknown (file-relative-name
- (read-file-name "File to remove: " nil nil t))))
- files))
+ (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files))
(if (yes-or-no-p
(format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" "")))
(progn
- (dolist (info files)
- (let ((name (git-fileinfo->name info)))
- (when (file-exists-p name) (delete-file name))))
- (apply #'git-run-command nil nil "update-index" "--info-only" "--remove" "--" (git-get-filenames files))
- ; remove unknown files from the list, set the others to deleted
- (ewoc-filter git-status
- (lambda (info files)
- (not (and (memq info files) (eq (git-fileinfo->state info) 'unknown))))
- files)
- (git-set-files-state files 'deleted)
- (git-refresh-files)
- (unless (ewoc-nth git-status 0) ; refresh header if list is empty
- (git-refresh-ewoc-hf git-status)))
+ (dolist (name files)
+ (when (file-exists-p name) (delete-file name)))
+ (apply #'git-run-command nil nil "update-index" "--remove" "--" files)
+ (git-update-status-files files nil))
(message "Aborting"))))
(defun git-revert-file ()
(format "Revert %d file%s? " (length files) (if (> (length files) 1) "s" ""))))
(dolist (info files)
(case (git-fileinfo->state info)
- ('added (push info added))
- ('deleted (push info modified))
- ('unmerged (push info modified))
- ('modified (push info modified))))
+ ('added (push (git-fileinfo->name info) added))
+ ('deleted (push (git-fileinfo->name info) modified))
+ ('unmerged (push (git-fileinfo->name info) modified))
+ ('modified (push (git-fileinfo->name info) modified))))
(when added
- (apply #'git-run-command nil nil "update-index" "--force-remove" "--" (git-get-filenames added))
- (git-set-files-state added 'unknown))
+ (apply #'git-run-command nil nil "update-index" "--force-remove" "--" added))
(when modified
- (apply #'git-run-command nil nil "checkout" "HEAD" (git-get-filenames modified))
- (git-set-files-state modified 'uptodate))
- (git-refresh-files))))
+ (apply #'git-run-command nil nil "checkout" "HEAD" modified))
+ (git-update-status-files (append added modified) 'uptodate))))
(defun git-resolve-file ()
"Resolve conflicts in marked file(s)."
(interactive)
- (let ((files (git-marked-files-state 'unmerged)))
+ (let ((files (git-get-filenames (git-marked-files-state 'unmerged))))
(when files
- (apply #'git-run-command nil nil "update-index" "--" (git-get-filenames files))
- (git-set-files-state files 'modified)
- (git-refresh-files))))
+ (apply #'git-run-command nil nil "update-index" "--" files)
+ (git-update-status-files files 'uptodate))))
(defun git-remove-handled ()
"Remove handled files from the status list."
(let ((info (ewoc-data (ewoc-locate git-status))))
(find-file (git-fileinfo->name info))
(when (eq 'unmerged (git-fileinfo->state info))
- (smerge-mode))))
+ (smerge-mode 1))))
(defun git-find-file-other-window ()
"Visit the current file in its own buffer in another window."
(pos (ewoc-locate status))
(cur-name (and pos (git-fileinfo->name (ewoc-data pos)))))
(unless status (error "Not in git-status buffer."))
+ (git-run-command nil nil "update-index" "--refresh")
(git-clear-status status)
- (git-run-command nil nil "update-index" "--info-only" "--refresh")
- (if (git-empty-db-p)
- ; we need some special handling for an empty db
- (with-temp-buffer
- (git-run-command t nil "ls-files" "-z" "-t" "-c")
- (git-parse-ls-files status 'added))
- (with-temp-buffer
- (git-run-command t nil "diff-index" "-z" "-M" "HEAD")
- (git-parse-status status)))
- (with-temp-buffer
- (git-run-command t nil "ls-files" "-z" "-u")
- (git-parse-ls-unmerged status))
- (when (file-readable-p ".git/info/exclude")
- (with-temp-buffer
- (git-run-command t nil "ls-files" "-z" "-t" "-o"
- "--exclude-from=.git/info/exclude"
- (concat "--exclude-per-directory=" git-per-dir-ignore-file))
- (git-parse-ls-files status 'unknown)))
- (git-refresh-files)
- (git-refresh-ewoc-hf status)
+ (git-update-status-files nil)
; move point to the current file name if any
(let ((node (and cur-name (git-find-status-file status cur-name))))
(when node (ewoc-goto-node status node)))))
(match-string 2 str)
str)))
+(defun vc-git-symbolic-commit (commit)
+ "Translate COMMIT string into symbolic form.
+Returns nil if not possible."
+ (and commit
+ (with-temp-buffer
+ (and
+ (zerop
+ (call-process "git" nil '(t nil) nil "name-rev"
+ "--name-only" "--tags"
+ commit))
+ (goto-char (point-min))
+ (= (forward-line 2) 1)
+ (bolp)
+ (buffer-substring-no-properties (point-min) (1- (point-max)))))))
+
+(defun vc-git-previous-version (file rev)
+ "git-specific version of `vc-previous-version'."
+ (let ((default-directory (file-name-directory (expand-file-name file)))
+ (file (file-name-nondirectory file)))
+ (vc-git-symbolic-commit
+ (with-temp-buffer
+ (and
+ (zerop
+ (call-process "git" nil '(t nil) nil "rev-list"
+ "-2" rev "--" file))
+ (goto-char (point-max))
+ (bolp)
+ (zerop (forward-line -1))
+ (not (bobp))
+ (buffer-substring-no-properties
+ (point)
+ (1- (point-max))))))))
+
+(defun vc-git-next-version (file rev)
+ "git-specific version of `vc-next-version'."
+ (let* ((default-directory (file-name-directory
+ (expand-file-name file)))
+ (file (file-name-nondirectory file))
+ (current-rev
+ (with-temp-buffer
+ (and
+ (zerop
+ (call-process "git" nil '(t nil) nil "rev-list"
+ "-1" rev "--" file))
+ (goto-char (point-max))
+ (bolp)
+ (zerop (forward-line -1))
+ (bobp)
+ (buffer-substring-no-properties
+ (point)
+ (1- (point-max)))))))
+ (and current-rev
+ (vc-git-symbolic-commit
+ (with-temp-buffer
+ (and
+ (zerop
+ (call-process "git" nil '(t nil) nil "rev-list"
+ "HEAD" "--" file))
+ (goto-char (point-min))
+ (search-forward current-rev nil t)
+ (zerop (forward-line -1))
+ (buffer-substring-no-properties
+ (point)
+ (progn (forward-line 1) (1- (point))))))))))
+
(defun vc-git-revert (file &optional contents-done)
"Revert FILE to the version stored in the git repository."
(if contents-done
esac
test "true" != "$pack_refs" ||
-git-pack-refs --prune &&
-git-reflog expire --all &&
+git pack-refs --prune &&
+git reflog expire --all &&
git-repack -a -d -l &&
-$no_prune git-prune &&
-git-rerere gc || exit
+$no_prune git prune &&
+git rerere gc || exit
"$GIT_DIR/LAST_MERGE" || exit 1
}
-head=$(git-rev-parse --verify "$1"^0) &&
-merge=$(git-rev-parse --verify "$2"^0) &&
+head=$(git rev-parse --verify "$1"^0) &&
+merge=$(git rev-parse --verify "$2"^0) &&
merge_name="$2" &&
merge_msg="$3" || usage
echo $head > "$GIT_DIR"/ORIG_HEAD
echo $merge > "$GIT_DIR"/LAST_MERGE
-common=$(git-merge-base $head $merge)
+common=$(git merge-base $head $merge)
if [ -z "$common" ]; then
die "Unable to find common commit between" $merge $head
fi
exit 0
;;
"$head")
- echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $merge)"
- git-read-tree -u -m $head $merge || exit 1
- git-update-ref -m "resolve $merge_name: Fast forward" \
+ echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $merge)"
+ git read-tree -u -m $head $merge || exit 1
+ git update-ref -m "resolve $merge_name: Fast forward" \
HEAD "$merge" "$head"
- git-diff-tree -p $head $merge | git-apply --stat
+ git diff-tree -p $head $merge | git apply --stat
dropheads
exit 0
;;
# Find an optimum merge base if there are more than one candidates.
LF='
'
-common=$(git-merge-base -a $head $merge)
+common=$(git merge-base -a $head $merge)
case "$common" in
?*"$LF"?*)
echo "Trying to find the optimum merge base."
for c in $common
do
rm -f $G
- GIT_INDEX_FILE=$G git-read-tree -m $c $head $merge \
+ GIT_INDEX_FILE=$G git read-tree -m $c $head $merge \
2>/dev/null || continue
# Count the paths that are unmerged.
- cnt=`GIT_INDEX_FILE=$G git-ls-files --unmerged | wc -l`
+ cnt=`GIT_INDEX_FILE=$G git ls-files --unmerged | wc -l`
if test $best_cnt -le 0 -o $cnt -le $best_cnt
then
best=$c
esac
echo "Trying to merge $merge into $head using $common."
-git-update-index --refresh 2>/dev/null
-git-read-tree -u -m $common $head $merge || exit 1
-result_tree=$(git-write-tree 2> /dev/null)
+git update-index --refresh 2>/dev/null
+git read-tree -u -m $common $head $merge || exit 1
+result_tree=$(git write-tree 2> /dev/null)
if [ $? -ne 0 ]; then
echo "Simple merge failed, trying Automatic merge"
git-merge-index -o git-merge-one-file -a
echo $merge > "$GIT_DIR"/MERGE_HEAD
die "Automatic merge failed, fix up by hand"
fi
- result_tree=$(git-write-tree) || exit 1
+ result_tree=$(git write-tree) || exit 1
fi
-result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree -p $head -p $merge)
+result_commit=$(echo "$merge_msg" | git commit-tree $result_tree -p $head -p $merge)
echo "Committed merge $result_commit"
-git-update-ref -m "resolve $merge_name: In-index merge" \
+git update-ref -m "resolve $merge_name: In-index merge" \
HEAD "$result_commit" "$head"
-git-diff-tree -p $head $result_commit | git-apply --stat
+git diff-tree -p $head $result_commit | git apply --stat
dropheads
--- /dev/null
+#!/usr/bin/env python
+#
+# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
+#
+# Author: Simon Hausmann <simon@lst.de>
+# Copyright: 2007 Simon Hausmann <simon@lst.de>
+# 2007 Trolltech ASA
+# License: MIT <http://www.opensource.org/licenses/mit-license.php>
+#
+
+import optparse, sys, os, marshal, popen2, subprocess, shelve
+import tempfile, getopt, sha, os.path, time, platform
+import re
+
+from sets import Set;
+
+verbose = False
+
+def die(msg):
+ if verbose:
+ raise Exception(msg)
+ else:
+ sys.stderr.write(msg + "\n")
+ sys.exit(1)
+
+def write_pipe(c, str):
+ if verbose:
+ sys.stderr.write('Writing pipe: %s\n' % c)
+
+ pipe = os.popen(c, 'w')
+ val = pipe.write(str)
+ if pipe.close():
+ die('Command failed: %s' % c)
+
+ return val
+
+def read_pipe(c, ignore_error=False):
+ if verbose:
+ sys.stderr.write('Reading pipe: %s\n' % c)
+
+ pipe = os.popen(c, 'rb')
+ val = pipe.read()
+ if pipe.close() and not ignore_error:
+ die('Command failed: %s' % c)
+
+ return val
+
+
+def read_pipe_lines(c):
+ if verbose:
+ sys.stderr.write('Reading pipe: %s\n' % c)
+ ## todo: check return status
+ pipe = os.popen(c, 'rb')
+ val = pipe.readlines()
+ if pipe.close():
+ die('Command failed: %s' % c)
+
+ return val
+
+def system(cmd):
+ if verbose:
+ sys.stderr.write("executing %s\n" % cmd)
+ if os.system(cmd) != 0:
+ die("command failed: %s" % cmd)
+
+def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
+ cmd = "p4 -G %s" % cmd
+ if verbose:
+ sys.stderr.write("Opening pipe: %s\n" % cmd)
+
+ # Use a temporary file to avoid deadlocks without
+ # subprocess.communicate(), which would put another copy
+ # of stdout into memory.
+ stdin_file = None
+ if stdin is not None:
+ stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
+ stdin_file.write(stdin)
+ stdin_file.flush()
+ stdin_file.seek(0)
+
+ p4 = subprocess.Popen(cmd, shell=True,
+ stdin=stdin_file,
+ stdout=subprocess.PIPE)
+
+ result = []
+ try:
+ while True:
+ entry = marshal.load(p4.stdout)
+ result.append(entry)
+ except EOFError:
+ pass
+ exitCode = p4.wait()
+ if exitCode != 0:
+ entry = {}
+ entry["p4ExitCode"] = exitCode
+ result.append(entry)
+
+ return result
+
+def p4Cmd(cmd):
+ list = p4CmdList(cmd)
+ result = {}
+ for entry in list:
+ result.update(entry)
+ return result;
+
+def p4Where(depotPath):
+ if not depotPath.endswith("/"):
+ depotPath += "/"
+ output = p4Cmd("where %s..." % depotPath)
+ if output["code"] == "error":
+ return ""
+ clientPath = ""
+ if "path" in output:
+ clientPath = output.get("path")
+ elif "data" in output:
+ data = output.get("data")
+ lastSpace = data.rfind(" ")
+ clientPath = data[lastSpace + 1:]
+
+ if clientPath.endswith("..."):
+ clientPath = clientPath[:-3]
+ return clientPath
+
+def currentGitBranch():
+ return read_pipe("git name-rev HEAD").split(" ")[1].strip()
+
+def isValidGitDir(path):
+ if (os.path.exists(path + "/HEAD")
+ and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
+ return True;
+ return False
+
+def parseRevision(ref):
+ return read_pipe("git rev-parse %s" % ref).strip()
+
+def extractLogMessageFromGitCommit(commit):
+ logMessage = ""
+
+ ## fixme: title is first line of commit, not 1st paragraph.
+ foundTitle = False
+ for log in read_pipe_lines("git cat-file commit %s" % commit):
+ if not foundTitle:
+ if len(log) == 1:
+ foundTitle = True
+ continue
+
+ logMessage += log
+ return logMessage
+
+def extractSettingsGitLog(log):
+ values = {}
+ for line in log.split("\n"):
+ line = line.strip()
+ m = re.search (r"^ *\[git-p4: (.*)\]$", line)
+ if not m:
+ continue
+
+ assignments = m.group(1).split (':')
+ for a in assignments:
+ vals = a.split ('=')
+ key = vals[0].strip()
+ val = ('='.join (vals[1:])).strip()
+ if val.endswith ('\"') and val.startswith('"'):
+ val = val[1:-1]
+
+ values[key] = val
+
+ paths = values.get("depot-paths")
+ if not paths:
+ paths = values.get("depot-path")
+ if paths:
+ values['depot-paths'] = paths.split(',')
+ return values
+
+def gitBranchExists(branch):
+ proc = subprocess.Popen(["git", "rev-parse", branch],
+ stderr=subprocess.PIPE, stdout=subprocess.PIPE);
+ return proc.wait() == 0;
+
+def gitConfig(key):
+ return read_pipe("git config %s" % key, ignore_error=True).strip()
+
+def p4BranchesInGit(branchesAreInRemotes = True):
+ branches = {}
+
+ cmdline = "git rev-parse --symbolic "
+ if branchesAreInRemotes:
+ cmdline += " --remotes"
+ else:
+ cmdline += " --branches"
+
+ for line in read_pipe_lines(cmdline):
+ line = line.strip()
+
+ ## only import to p4/
+ if not line.startswith('p4/') or line == "p4/HEAD":
+ continue
+ branch = line
+
+ # strip off p4
+ branch = re.sub ("^p4/", "", line)
+
+ branches[branch] = parseRevision(line)
+ return branches
+
+def findUpstreamBranchPoint(head = "HEAD"):
+ branches = p4BranchesInGit()
+ # map from depot-path to branch name
+ branchByDepotPath = {}
+ for branch in branches.keys():
+ tip = branches[branch]
+ log = extractLogMessageFromGitCommit(tip)
+ settings = extractSettingsGitLog(log)
+ if settings.has_key("depot-paths"):
+ paths = ",".join(settings["depot-paths"])
+ branchByDepotPath[paths] = "remotes/p4/" + branch
+
+ settings = None
+ parent = 0
+ while parent < 65535:
+ commit = head + "~%s" % parent
+ log = extractLogMessageFromGitCommit(commit)
+ settings = extractSettingsGitLog(log)
+ if settings.has_key("depot-paths"):
+ paths = ",".join(settings["depot-paths"])
+ if branchByDepotPath.has_key(paths):
+ return [branchByDepotPath[paths], settings]
+
+ parent = parent + 1
+
+ return ["", settings]
+
+class Command:
+ def __init__(self):
+ self.usage = "usage: %prog [options]"
+ self.needsGit = True
+
+class P4Debug(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = [
+ optparse.make_option("--verbose", dest="verbose", action="store_true",
+ default=False),
+ ]
+ self.description = "A tool to debug the output of p4 -G."
+ self.needsGit = False
+ self.verbose = False
+
+ def run(self, args):
+ j = 0
+ for output in p4CmdList(" ".join(args)):
+ print 'Element: %d' % j
+ j += 1
+ print output
+ return True
+
+class P4RollBack(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = [
+ optparse.make_option("--verbose", dest="verbose", action="store_true"),
+ optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
+ ]
+ self.description = "A tool to debug the multi-branch import. Don't use :)"
+ self.verbose = False
+ self.rollbackLocalBranches = False
+
+ def run(self, args):
+ if len(args) != 1:
+ return False
+ maxChange = int(args[0])
+
+ if "p4ExitCode" in p4Cmd("changes -m 1"):
+ die("Problems executing p4");
+
+ if self.rollbackLocalBranches:
+ refPrefix = "refs/heads/"
+ lines = read_pipe_lines("git rev-parse --symbolic --branches")
+ else:
+ refPrefix = "refs/remotes/"
+ lines = read_pipe_lines("git rev-parse --symbolic --remotes")
+
+ for line in lines:
+ if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
+ line = line.strip()
+ ref = refPrefix + line
+ log = extractLogMessageFromGitCommit(ref)
+ settings = extractSettingsGitLog(log)
+
+ depotPaths = settings['depot-paths']
+ change = settings['change']
+
+ changed = False
+
+ if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
+ for p in depotPaths]))) == 0:
+ print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
+ system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
+ continue
+
+ while change and int(change) > maxChange:
+ changed = True
+ if self.verbose:
+ print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
+ system("git update-ref %s \"%s^\"" % (ref, ref))
+ log = extractLogMessageFromGitCommit(ref)
+ settings = extractSettingsGitLog(log)
+
+
+ depotPaths = settings['depot-paths']
+ change = settings['change']
+
+ if changed:
+ print "%s rewound to %s" % (ref, change)
+
+ return True
+
+class P4Submit(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = [
+ optparse.make_option("--continue", action="store_false", dest="firstTime"),
+ optparse.make_option("--verbose", dest="verbose", action="store_true"),
+ optparse.make_option("--origin", dest="origin"),
+ optparse.make_option("--reset", action="store_true", dest="reset"),
+ optparse.make_option("--log-substitutions", dest="substFile"),
+ optparse.make_option("--dry-run", action="store_true"),
+ optparse.make_option("--direct", dest="directSubmit", action="store_true"),
+ optparse.make_option("--trust-me-like-a-fool", dest="trustMeLikeAFool", action="store_true"),
+ ]
+ self.description = "Submit changes from git to the perforce depot."
+ self.usage += " [name of git branch to submit into perforce depot]"
+ self.firstTime = True
+ self.reset = False
+ self.interactive = True
+ self.dryRun = False
+ self.substFile = ""
+ self.firstTime = True
+ self.origin = ""
+ self.directSubmit = False
+ self.trustMeLikeAFool = False
+ self.verbose = False
+ self.isWindows = (platform.system() == "Windows")
+
+ self.logSubstitutions = {}
+ self.logSubstitutions["<enter description here>"] = "%log%"
+ self.logSubstitutions["\tDetails:"] = "\tDetails: %log%"
+
+ def check(self):
+ if len(p4CmdList("opened ...")) > 0:
+ die("You have files opened with perforce! Close them before starting the sync.")
+
+ def start(self):
+ if len(self.config) > 0 and not self.reset:
+ die("Cannot start sync. Previous sync config found at %s\n"
+ "If you want to start submitting again from scratch "
+ "maybe you want to call git-p4 submit --reset" % self.configFile)
+
+ commits = []
+ if self.directSubmit:
+ commits.append("0")
+ else:
+ for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
+ commits.append(line.strip())
+ commits.reverse()
+
+ self.config["commits"] = commits
+
+ def prepareLogMessage(self, template, message):
+ result = ""
+
+ for line in template.split("\n"):
+ if line.startswith("#"):
+ result += line + "\n"
+ continue
+
+ substituted = False
+ for key in self.logSubstitutions.keys():
+ if line.find(key) != -1:
+ value = self.logSubstitutions[key]
+ value = value.replace("%log%", message)
+ if value != "@remove@":
+ result += line.replace(key, value) + "\n"
+ substituted = True
+ break
+
+ if not substituted:
+ result += line + "\n"
+
+ return result
+
+ def applyCommit(self, id):
+ if self.directSubmit:
+ print "Applying local change in working directory/index"
+ diff = self.diffStatus
+ else:
+ print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
+ diff = read_pipe_lines("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id))
+ filesToAdd = set()
+ filesToDelete = set()
+ editedFiles = set()
+ for line in diff:
+ modifier = line[0]
+ path = line[1:].strip()
+ if modifier == "M":
+ system("p4 edit \"%s\"" % path)
+ editedFiles.add(path)
+ elif modifier == "A":
+ filesToAdd.add(path)
+ if path in filesToDelete:
+ filesToDelete.remove(path)
+ elif modifier == "D":
+ filesToDelete.add(path)
+ if path in filesToAdd:
+ filesToAdd.remove(path)
+ else:
+ die("unknown modifier %s for %s" % (modifier, path))
+
+ if self.directSubmit:
+ diffcmd = "cat \"%s\"" % self.diffFile
+ else:
+ diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
+ patchcmd = diffcmd + " | git apply "
+ tryPatchCmd = patchcmd + "--check -"
+ applyPatchCmd = patchcmd + "--check --apply -"
+
+ if os.system(tryPatchCmd) != 0:
+ print "Unfortunately applying the change failed!"
+ print "What do you want to do?"
+ response = "x"
+ while response != "s" and response != "a" and response != "w":
+ response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
+ "and with .rej files / [w]rite the patch to a file (patch.txt) ")
+ if response == "s":
+ print "Skipping! Good luck with the next patches..."
+ return
+ elif response == "a":
+ os.system(applyPatchCmd)
+ if len(filesToAdd) > 0:
+ print "You may also want to call p4 add on the following files:"
+ print " ".join(filesToAdd)
+ if len(filesToDelete):
+ print "The following files should be scheduled for deletion with p4 delete:"
+ print " ".join(filesToDelete)
+ die("Please resolve and submit the conflict manually and "
+ + "continue afterwards with git-p4 submit --continue")
+ elif response == "w":
+ system(diffcmd + " > patch.txt")
+ print "Patch saved to patch.txt in %s !" % self.clientPath
+ die("Please resolve and submit the conflict manually and "
+ "continue afterwards with git-p4 submit --continue")
+
+ system(applyPatchCmd)
+
+ for f in filesToAdd:
+ system("p4 add \"%s\"" % f)
+ for f in filesToDelete:
+ system("p4 revert \"%s\"" % f)
+ system("p4 delete \"%s\"" % f)
+
+ logMessage = ""
+ if not self.directSubmit:
+ logMessage = extractLogMessageFromGitCommit(id)
+ logMessage = logMessage.replace("\n", "\n\t")
+ if self.isWindows:
+ logMessage = logMessage.replace("\n", "\r\n")
+ logMessage = logMessage.strip()
+
+ template = read_pipe("p4 change -o")
+
+ if self.interactive:
+ submitTemplate = self.prepareLogMessage(template, logMessage)
+ diff = read_pipe("p4 diff -du ...")
+
+ for newFile in filesToAdd:
+ diff += "==== new file ====\n"
+ diff += "--- /dev/null\n"
+ diff += "+++ %s\n" % newFile
+ f = open(newFile, "r")
+ for line in f.readlines():
+ diff += "+" + line
+ f.close()
+
+ separatorLine = "######## everything below this line is just the diff #######"
+ if platform.system() == "Windows":
+ separatorLine += "\r"
+ separatorLine += "\n"
+
+ response = "e"
+ if self.trustMeLikeAFool:
+ response = "y"
+
+ firstIteration = True
+ while response == "e":
+ if not firstIteration:
+ response = raw_input("Do you want to submit this change? [y]es/[e]dit/[n]o/[s]kip ")
+ firstIteration = False
+ if response == "e":
+ [handle, fileName] = tempfile.mkstemp()
+ tmpFile = os.fdopen(handle, "w+")
+ tmpFile.write(submitTemplate + separatorLine + diff)
+ tmpFile.close()
+ defaultEditor = "vi"
+ if platform.system() == "Windows":
+ defaultEditor = "notepad"
+ editor = os.environ.get("EDITOR", defaultEditor);
+ system(editor + " " + fileName)
+ tmpFile = open(fileName, "rb")
+ message = tmpFile.read()
+ tmpFile.close()
+ os.remove(fileName)
+ submitTemplate = message[:message.index(separatorLine)]
+ if self.isWindows:
+ submitTemplate = submitTemplate.replace("\r\n", "\n")
+
+ if response == "y" or response == "yes":
+ if self.dryRun:
+ print submitTemplate
+ raw_input("Press return to continue...")
+ else:
+ if self.directSubmit:
+ print "Submitting to git first"
+ os.chdir(self.oldWorkingDirectory)
+ write_pipe("git commit -a -F -", submitTemplate)
+ os.chdir(self.clientPath)
+
+ write_pipe("p4 submit -i", submitTemplate)
+ elif response == "s":
+ for f in editedFiles:
+ system("p4 revert \"%s\"" % f);
+ for f in filesToAdd:
+ system("p4 revert \"%s\"" % f);
+ system("rm %s" %f)
+ for f in filesToDelete:
+ system("p4 delete \"%s\"" % f);
+ return
+ else:
+ print "Not submitting!"
+ self.interactive = False
+ else:
+ fileName = "submit.txt"
+ file = open(fileName, "w+")
+ file.write(self.prepareLogMessage(template, logMessage))
+ file.close()
+ print ("Perforce submit template written as %s. "
+ + "Please review/edit and then use p4 submit -i < %s to submit directly!"
+ % (fileName, fileName))
+
+ def run(self, args):
+ if len(args) == 0:
+ self.master = currentGitBranch()
+ if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
+ die("Detecting current git branch failed!")
+ elif len(args) == 1:
+ self.master = args[0]
+ else:
+ return False
+
+ [upstream, settings] = findUpstreamBranchPoint()
+ depotPath = settings['depot-paths'][0]
+ if len(self.origin) == 0:
+ self.origin = upstream
+
+ if self.verbose:
+ print "Origin branch is " + self.origin
+
+ if len(depotPath) == 0:
+ print "Internal error: cannot locate perforce depot path from existing branches"
+ sys.exit(128)
+
+ self.clientPath = p4Where(depotPath)
+
+ if len(self.clientPath) == 0:
+ print "Error: Cannot locate perforce checkout of %s in client view" % depotPath
+ sys.exit(128)
+
+ print "Perforce checkout for depot path %s located at %s" % (depotPath, self.clientPath)
+ self.oldWorkingDirectory = os.getcwd()
+
+ if self.directSubmit:
+ self.diffStatus = read_pipe_lines("git diff -r --name-status HEAD")
+ if len(self.diffStatus) == 0:
+ print "No changes in working directory to submit."
+ return True
+ patch = read_pipe("git diff -p --binary --diff-filter=ACMRTUXB HEAD")
+ self.diffFile = self.gitdir + "/p4-git-diff"
+ f = open(self.diffFile, "wb")
+ f.write(patch)
+ f.close();
+
+ os.chdir(self.clientPath)
+ response = raw_input("Do you want to sync %s with p4 sync? [y]es/[n]o " % self.clientPath)
+ if response == "y" or response == "yes":
+ system("p4 sync ...")
+
+ if self.reset:
+ self.firstTime = True
+
+ if len(self.substFile) > 0:
+ for line in open(self.substFile, "r").readlines():
+ tokens = line.strip().split("=")
+ self.logSubstitutions[tokens[0]] = tokens[1]
+
+ self.check()
+ self.configFile = self.gitdir + "/p4-git-sync.cfg"
+ self.config = shelve.open(self.configFile, writeback=True)
+
+ if self.firstTime:
+ self.start()
+
+ commits = self.config.get("commits", [])
+
+ while len(commits) > 0:
+ self.firstTime = False
+ commit = commits[0]
+ commits = commits[1:]
+ self.config["commits"] = commits
+ self.applyCommit(commit)
+ if not self.interactive:
+ break
+
+ self.config.close()
+
+ if self.directSubmit:
+ os.remove(self.diffFile)
+
+ if len(commits) == 0:
+ if self.firstTime:
+ print "No changes found to apply between %s and current HEAD" % self.origin
+ else:
+ print "All changes applied!"
+ os.chdir(self.oldWorkingDirectory)
+ response = raw_input("Do you want to sync from Perforce now using git-p4 rebase? [y]es/[n]o ")
+ if response == "y" or response == "yes":
+ rebase = P4Rebase()
+ rebase.run([])
+ os.remove(self.configFile)
+
+ return True
+
+class P4Sync(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = [
+ optparse.make_option("--branch", dest="branch"),
+ optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
+ optparse.make_option("--changesfile", dest="changesFile"),
+ optparse.make_option("--silent", dest="silent", action="store_true"),
+ optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
+ optparse.make_option("--verbose", dest="verbose", action="store_true"),
+ optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
+ help="Import into refs/heads/ , not refs/remotes"),
+ optparse.make_option("--max-changes", dest="maxChanges"),
+ optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
+ help="Keep entire BRANCH/DIR/SUBDIR prefix during import")
+ ]
+ self.description = """Imports from Perforce into a git repository.\n
+ example:
+ //depot/my/project/ -- to import the current head
+ //depot/my/project/@all -- to import everything
+ //depot/my/project/@1,6 -- to import only from revision 1 to 6
+
+ (a ... is not needed in the path p4 specification, it's added implicitly)"""
+
+ self.usage += " //depot/path[@revRange]"
+ self.silent = False
+ self.createdBranches = Set()
+ self.committedChanges = Set()
+ self.branch = ""
+ self.detectBranches = False
+ self.detectLabels = False
+ self.changesFile = ""
+ self.syncWithOrigin = True
+ self.verbose = False
+ self.importIntoRemotes = True
+ self.maxChanges = ""
+ self.isWindows = (platform.system() == "Windows")
+ self.keepRepoPath = False
+ self.depotPaths = None
+ self.p4BranchesInGit = []
+
+ if gitConfig("git-p4.syncFromOrigin") == "false":
+ self.syncWithOrigin = False
+
+ def extractFilesFromCommit(self, commit):
+ files = []
+ fnum = 0
+ while commit.has_key("depotFile%s" % fnum):
+ path = commit["depotFile%s" % fnum]
+
+ found = [p for p in self.depotPaths
+ if path.startswith (p)]
+ if not found:
+ fnum = fnum + 1
+ continue
+
+ file = {}
+ file["path"] = path
+ file["rev"] = commit["rev%s" % fnum]
+ file["action"] = commit["action%s" % fnum]
+ file["type"] = commit["type%s" % fnum]
+ files.append(file)
+ fnum = fnum + 1
+ return files
+
+ def stripRepoPath(self, path, prefixes):
+ if self.keepRepoPath:
+ prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
+
+ for p in prefixes:
+ if path.startswith(p):
+ path = path[len(p):]
+
+ return path
+
+ def splitFilesIntoBranches(self, commit):
+ branches = {}
+ fnum = 0
+ while commit.has_key("depotFile%s" % fnum):
+ path = commit["depotFile%s" % fnum]
+ found = [p for p in self.depotPaths
+ if path.startswith (p)]
+ if not found:
+ fnum = fnum + 1
+ continue
+
+ file = {}
+ file["path"] = path
+ file["rev"] = commit["rev%s" % fnum]
+ file["action"] = commit["action%s" % fnum]
+ file["type"] = commit["type%s" % fnum]
+ fnum = fnum + 1
+
+ relPath = self.stripRepoPath(path, self.depotPaths)
+
+ for branch in self.knownBranches.keys():
+
+ # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
+ if relPath.startswith(branch + "/"):
+ if branch not in branches:
+ branches[branch] = []
+ branches[branch].append(file)
+ break
+
+ return branches
+
+ ## Should move this out, doesn't use SELF.
+ def readP4Files(self, files):
+ files = [f for f in files
+ if f['action'] != 'delete']
+
+ if not files:
+ return
+
+ filedata = p4CmdList('-x - print',
+ stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
+ for f in files]),
+ stdin_mode='w+')
+ if "p4ExitCode" in filedata[0]:
+ die("Problems executing p4. Error: [%d]."
+ % (filedata[0]['p4ExitCode']));
+
+ j = 0;
+ contents = {}
+ while j < len(filedata):
+ stat = filedata[j]
+ j += 1
+ text = ''
+ while j < len(filedata) and filedata[j]['code'] in ('text',
+ 'binary'):
+ text += filedata[j]['data']
+ j += 1
+
+
+ if not stat.has_key('depotFile'):
+ sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
+ continue
+
+ contents[stat['depotFile']] = text
+
+ for f in files:
+ assert not f.has_key('data')
+ f['data'] = contents[f['path']]
+
+ def commit(self, details, files, branch, branchPrefixes, parent = ""):
+ epoch = details["time"]
+ author = details["user"]
+
+ if self.verbose:
+ print "commit into %s" % branch
+
+ # start with reading files; if that fails, we should not
+ # create a commit.
+ new_files = []
+ for f in files:
+ if [p for p in branchPrefixes if f['path'].startswith(p)]:
+ new_files.append (f)
+ else:
+ sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
+ files = new_files
+ self.readP4Files(files)
+
+
+
+
+ self.gitStream.write("commit %s\n" % branch)
+# gitStream.write("mark :%s\n" % details["change"])
+ self.committedChanges.add(int(details["change"]))
+ committer = ""
+ if author not in self.users:
+ self.getUserMapFromPerforceServer()
+ if author in self.users:
+ committer = "%s %s %s" % (self.users[author], epoch, self.tz)
+ else:
+ committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
+
+ self.gitStream.write("committer %s\n" % committer)
+
+ self.gitStream.write("data <<EOT\n")
+ self.gitStream.write(details["desc"])
+ self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
+ % (','.join (branchPrefixes), details["change"]))
+ if len(details['options']) > 0:
+ self.gitStream.write(": options = %s" % details['options'])
+ self.gitStream.write("]\nEOT\n\n")
+
+ if len(parent) > 0:
+ if self.verbose:
+ print "parent %s" % parent
+ self.gitStream.write("from %s\n" % parent)
+
+ for file in files:
+ if file["type"] == "apple":
+ print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
+ continue
+
+ relPath = self.stripRepoPath(file['path'], branchPrefixes)
+ if file["action"] == "delete":
+ self.gitStream.write("D %s\n" % relPath)
+ else:
+ mode = 644
+ if file["type"].startswith("x"):
+ mode = 755
+
+ data = file['data']
+
+ if self.isWindows and file["type"].endswith("text"):
+ data = data.replace("\r\n", "\n")
+
+ self.gitStream.write("M %d inline %s\n" % (mode, relPath))
+ self.gitStream.write("data %s\n" % len(data))
+ self.gitStream.write(data)
+ self.gitStream.write("\n")
+
+ self.gitStream.write("\n")
+
+ change = int(details["change"])
+
+ if self.labels.has_key(change):
+ label = self.labels[change]
+ labelDetails = label[0]
+ labelRevisions = label[1]
+ if self.verbose:
+ print "Change %s is labelled %s" % (change, labelDetails)
+
+ files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
+ for p in branchPrefixes]))
+
+ if len(files) == len(labelRevisions):
+
+ cleanedFiles = {}
+ for info in files:
+ if info["action"] == "delete":
+ continue
+ cleanedFiles[info["depotFile"]] = info["rev"]
+
+ if cleanedFiles == labelRevisions:
+ self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
+ self.gitStream.write("from %s\n" % branch)
+
+ owner = labelDetails["Owner"]
+ tagger = ""
+ if author in self.users:
+ tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
+ else:
+ tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
+ self.gitStream.write("tagger %s\n" % tagger)
+ self.gitStream.write("data <<EOT\n")
+ self.gitStream.write(labelDetails["Description"])
+ self.gitStream.write("EOT\n\n")
+
+ else:
+ if not self.silent:
+ print ("Tag %s does not match with change %s: files do not match."
+ % (labelDetails["label"], change))
+
+ else:
+ if not self.silent:
+ print ("Tag %s does not match with change %s: file count is different."
+ % (labelDetails["label"], change))
+
+ def getUserCacheFilename(self):
+ home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
+ return home + "/.gitp4-usercache.txt"
+
+ def getUserMapFromPerforceServer(self):
+ if self.userMapFromPerforceServer:
+ return
+ self.users = {}
+
+ for output in p4CmdList("users"):
+ if not output.has_key("User"):
+ continue
+ self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
+
+
+ s = ''
+ for (key, val) in self.users.items():
+ s += "%s\t%s\n" % (key, val)
+
+ open(self.getUserCacheFilename(), "wb").write(s)
+ self.userMapFromPerforceServer = True
+
+ def loadUserMapFromCache(self):
+ self.users = {}
+ self.userMapFromPerforceServer = False
+ try:
+ cache = open(self.getUserCacheFilename(), "rb")
+ lines = cache.readlines()
+ cache.close()
+ for line in lines:
+ entry = line.strip().split("\t")
+ self.users[entry[0]] = entry[1]
+ except IOError:
+ self.getUserMapFromPerforceServer()
+
+ def getLabels(self):
+ self.labels = {}
+
+ l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
+ if len(l) > 0 and not self.silent:
+ print "Finding files belonging to labels in %s" % `self.depotPath`
+
+ for output in l:
+ label = output["label"]
+ revisions = {}
+ newestChange = 0
+ if self.verbose:
+ print "Querying files for label %s" % label
+ for file in p4CmdList("files "
+ + ' '.join (["%s...@%s" % (p, label)
+ for p in self.depotPaths])):
+ revisions[file["depotFile"]] = file["rev"]
+ change = int(file["change"])
+ if change > newestChange:
+ newestChange = change
+
+ self.labels[newestChange] = [output, revisions]
+
+ if self.verbose:
+ print "Label changes: %s" % self.labels.keys()
+
+ def guessProjectName(self):
+ for p in self.depotPaths:
+ if p.endswith("/"):
+ p = p[:-1]
+ p = p[p.strip().rfind("/") + 1:]
+ if not p.endswith("/"):
+ p += "/"
+ return p
+
+ def getBranchMapping(self):
+ lostAndFoundBranches = set()
+
+ for info in p4CmdList("branches"):
+ details = p4Cmd("branch -o %s" % info["branch"])
+ viewIdx = 0
+ while details.has_key("View%s" % viewIdx):
+ paths = details["View%s" % viewIdx].split(" ")
+ viewIdx = viewIdx + 1
+ # require standard //depot/foo/... //depot/bar/... mapping
+ if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
+ continue
+ source = paths[0]
+ destination = paths[1]
+ ## HACK
+ if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
+ source = source[len(self.depotPaths[0]):-4]
+ destination = destination[len(self.depotPaths[0]):-4]
+
+ if destination in self.knownBranches:
+ if not self.silent:
+ print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
+ print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
+ continue
+
+ self.knownBranches[destination] = source
+
+ lostAndFoundBranches.discard(destination)
+
+ if source not in self.knownBranches:
+ lostAndFoundBranches.add(source)
+
+
+ for branch in lostAndFoundBranches:
+ self.knownBranches[branch] = branch
+
+ def listExistingP4GitBranches(self):
+ # branches holds mapping from name to commit
+ branches = p4BranchesInGit(self.importIntoRemotes)
+ self.p4BranchesInGit = branches.keys()
+ for branch in branches.keys():
+ self.initialParents[self.refPrefix + branch] = branches[branch]
+
+ def createOrUpdateBranchesFromOrigin(self):
+ if not self.silent:
+ print ("Creating/updating branch(es) in %s based on origin branch(es)"
+ % self.refPrefix)
+
+ originPrefix = "origin/p4/"
+
+ for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
+ line = line.strip()
+ if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
+ continue
+
+ headName = line[len(originPrefix):]
+ remoteHead = self.refPrefix + headName
+ originHead = line
+
+ original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
+ if (not original.has_key('depot-paths')
+ or not original.has_key('change')):
+ continue
+
+ update = False
+ if not gitBranchExists(remoteHead):
+ if self.verbose:
+ print "creating %s" % remoteHead
+ update = True
+ else:
+ settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
+ if settings.has_key('change') > 0:
+ if settings['depot-paths'] == original['depot-paths']:
+ originP4Change = int(original['change'])
+ p4Change = int(settings['change'])
+ if originP4Change > p4Change:
+ print ("%s (%s) is newer than %s (%s). "
+ "Updating p4 branch from origin."
+ % (originHead, originP4Change,
+ remoteHead, p4Change))
+ update = True
+ else:
+ print ("Ignoring: %s was imported from %s while "
+ "%s was imported from %s"
+ % (originHead, ','.join(original['depot-paths']),
+ remoteHead, ','.join(settings['depot-paths'])))
+
+ if update:
+ system("git update-ref %s %s" % (remoteHead, originHead))
+
+ def updateOptionDict(self, d):
+ option_keys = {}
+ if self.keepRepoPath:
+ option_keys['keepRepoPath'] = 1
+
+ d["options"] = ' '.join(sorted(option_keys.keys()))
+
+ def readOptions(self, d):
+ self.keepRepoPath = (d.has_key('options')
+ and ('keepRepoPath' in d['options']))
+
+ def run(self, args):
+ self.depotPaths = []
+ self.changeRange = ""
+ self.initialParent = ""
+ self.previousDepotPaths = []
+
+ # map from branch depot path to parent branch
+ self.knownBranches = {}
+ self.initialParents = {}
+ self.hasOrigin = gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
+ if not self.syncWithOrigin:
+ self.hasOrigin = False
+
+ if self.importIntoRemotes:
+ self.refPrefix = "refs/remotes/p4/"
+ else:
+ self.refPrefix = "refs/heads/p4/"
+
+ if self.syncWithOrigin and self.hasOrigin:
+ if not self.silent:
+ print "Syncing with origin first by calling git fetch origin"
+ system("git fetch origin")
+
+ if len(self.branch) == 0:
+ self.branch = self.refPrefix + "master"
+ if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
+ system("git update-ref %s refs/heads/p4" % self.branch)
+ system("git branch -D p4");
+ # create it /after/ importing, when master exists
+ if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes:
+ system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
+
+ # TODO: should always look at previous commits,
+ # merge with previous imports, if possible.
+ if args == []:
+ if self.hasOrigin:
+ self.createOrUpdateBranchesFromOrigin()
+ self.listExistingP4GitBranches()
+
+ if len(self.p4BranchesInGit) > 1:
+ if not self.silent:
+ print "Importing from/into multiple branches"
+ self.detectBranches = True
+
+ if self.verbose:
+ print "branches: %s" % self.p4BranchesInGit
+
+ p4Change = 0
+ for branch in self.p4BranchesInGit:
+ logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
+
+ settings = extractSettingsGitLog(logMsg)
+
+ self.readOptions(settings)
+ if (settings.has_key('depot-paths')
+ and settings.has_key ('change')):
+ change = int(settings['change']) + 1
+ p4Change = max(p4Change, change)
+
+ depotPaths = sorted(settings['depot-paths'])
+ if self.previousDepotPaths == []:
+ self.previousDepotPaths = depotPaths
+ else:
+ paths = []
+ for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
+ for i in range(0, min(len(cur), len(prev))):
+ if cur[i] <> prev[i]:
+ i = i - 1
+ break
+
+ paths.append (cur[:i + 1])
+
+ self.previousDepotPaths = paths
+
+ if p4Change > 0:
+ self.depotPaths = sorted(self.previousDepotPaths)
+ self.changeRange = "@%s,#head" % p4Change
+ if not self.detectBranches:
+ self.initialParent = parseRevision(self.branch)
+ if not self.silent and not self.detectBranches:
+ print "Performing incremental import into %s git branch" % self.branch
+
+ if not self.branch.startswith("refs/"):
+ self.branch = "refs/heads/" + self.branch
+
+ if len(args) == 0 and self.depotPaths:
+ if not self.silent:
+ print "Depot paths: %s" % ' '.join(self.depotPaths)
+ else:
+ if self.depotPaths and self.depotPaths != args:
+ print ("previous import used depot path %s and now %s was specified. "
+ "This doesn't work!" % (' '.join (self.depotPaths),
+ ' '.join (args)))
+ sys.exit(1)
+
+ self.depotPaths = sorted(args)
+
+ self.revision = ""
+ self.users = {}
+
+ newPaths = []
+ for p in self.depotPaths:
+ if p.find("@") != -1:
+ atIdx = p.index("@")
+ self.changeRange = p[atIdx:]
+ if self.changeRange == "@all":
+ self.changeRange = ""
+ elif ',' not in self.changeRange:
+ self.revision = self.changeRange
+ self.changeRange = ""
+ p = p[:atIdx]
+ elif p.find("#") != -1:
+ hashIdx = p.index("#")
+ self.revision = p[hashIdx:]
+ p = p[:hashIdx]
+ elif self.previousDepotPaths == []:
+ self.revision = "#head"
+
+ p = re.sub ("\.\.\.$", "", p)
+ if not p.endswith("/"):
+ p += "/"
+
+ newPaths.append(p)
+
+ self.depotPaths = newPaths
+
+
+ self.loadUserMapFromCache()
+ self.labels = {}
+ if self.detectLabels:
+ self.getLabels();
+
+ if self.detectBranches:
+ ## FIXME - what's a P4 projectName ?
+ self.projectName = self.guessProjectName()
+
+ if not self.hasOrigin:
+ self.getBranchMapping();
+ if self.verbose:
+ print "p4-git branches: %s" % self.p4BranchesInGit
+ print "initial parents: %s" % self.initialParents
+ for b in self.p4BranchesInGit:
+ if b != "master":
+
+ ## FIXME
+ b = b[len(self.projectName):]
+ self.createdBranches.add(b)
+
+ self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
+
+ importProcess = subprocess.Popen(["git", "fast-import"],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE);
+ self.gitOutput = importProcess.stdout
+ self.gitStream = importProcess.stdin
+ self.gitError = importProcess.stderr
+
+ if self.revision:
+ print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), self.revision, self.branch)
+
+ details = { "user" : "git perforce import user", "time" : int(time.time()) }
+ details["desc"] = ("Initial import of %s from the state at revision %s"
+ % (' '.join(self.depotPaths), self.revision))
+ details["change"] = self.revision
+ newestRevision = 0
+
+ fileCnt = 0
+ for info in p4CmdList("files "
+ + ' '.join(["%s...%s"
+ % (p, self.revision)
+ for p in self.depotPaths])):
+
+ if info['code'] == 'error':
+ sys.stderr.write("p4 returned an error: %s\n"
+ % info['data'])
+ sys.exit(1)
+
+
+ change = int(info["change"])
+ if change > newestRevision:
+ newestRevision = change
+
+ if info["action"] == "delete":
+ # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
+ #fileCnt = fileCnt + 1
+ continue
+
+ for prop in ["depotFile", "rev", "action", "type" ]:
+ details["%s%s" % (prop, fileCnt)] = info[prop]
+
+ fileCnt = fileCnt + 1
+
+ details["change"] = newestRevision
+ self.updateOptionDict(details)
+ try:
+ self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
+ except IOError:
+ print "IO error with git fast-import. Is your git version recent enough?"
+ print self.gitError.read()
+
+ else:
+ changes = []
+
+ if len(self.changesFile) > 0:
+ output = open(self.changesFile).readlines()
+ changeSet = Set()
+ for line in output:
+ changeSet.add(int(line))
+
+ for change in changeSet:
+ changes.append(change)
+
+ changes.sort()
+ else:
+ if self.verbose:
+ print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
+ self.changeRange)
+ assert self.depotPaths
+ output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, self.changeRange)
+ for p in self.depotPaths]))
+
+ for line in output:
+ changeNum = line.split(" ")[1]
+ changes.append(changeNum)
+
+ changes.sort()
+
+ if len(self.maxChanges) > 0:
+ changes = changes[:min(int(self.maxChanges), len(changes))]
+
+ if len(changes) == 0:
+ if not self.silent:
+ print "No changes to import!"
+ return True
+
+ if not self.silent and not self.detectBranches:
+ print "Import destination: %s" % self.branch
+
+ self.updatedBranches = set()
+
+ cnt = 1
+ for change in changes:
+ description = p4Cmd("describe %s" % change)
+ self.updateOptionDict(description)
+
+ if not self.silent:
+ sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
+ sys.stdout.flush()
+ cnt = cnt + 1
+
+ try:
+ if self.detectBranches:
+ branches = self.splitFilesIntoBranches(description)
+ for branch in branches.keys():
+ ## HACK --hwn
+ branchPrefix = self.depotPaths[0] + branch + "/"
+
+ parent = ""
+
+ filesForCommit = branches[branch]
+
+ if self.verbose:
+ print "branch is %s" % branch
+
+ self.updatedBranches.add(branch)
+
+ if branch not in self.createdBranches:
+ self.createdBranches.add(branch)
+ parent = self.knownBranches[branch]
+ if parent == branch:
+ parent = ""
+ elif self.verbose:
+ print "parent determined through known branches: %s" % parent
+
+ # main branch? use master
+ if branch == "main":
+ branch = "master"
+ else:
+
+ ## FIXME
+ branch = self.projectName + branch
+
+ if parent == "main":
+ parent = "master"
+ elif len(parent) > 0:
+ ## FIXME
+ parent = self.projectName + parent
+
+ branch = self.refPrefix + branch
+ if len(parent) > 0:
+ parent = self.refPrefix + parent
+
+ if self.verbose:
+ print "looking for initial parent for %s; current parent is %s" % (branch, parent)
+
+ if len(parent) == 0 and branch in self.initialParents:
+ parent = self.initialParents[branch]
+ del self.initialParents[branch]
+
+ self.commit(description, filesForCommit, branch, [branchPrefix], parent)
+ else:
+ files = self.extractFilesFromCommit(description)
+ self.commit(description, files, self.branch, self.depotPaths,
+ self.initialParent)
+ self.initialParent = ""
+ except IOError:
+ print self.gitError.read()
+ sys.exit(1)
+
+ if not self.silent:
+ print ""
+ if len(self.updatedBranches) > 0:
+ sys.stdout.write("Updated branches: ")
+ for b in self.updatedBranches:
+ sys.stdout.write("%s " % b)
+ sys.stdout.write("\n")
+
+
+ self.gitStream.close()
+ if importProcess.wait() != 0:
+ die("fast-import failed: %s" % self.gitError.read())
+ self.gitOutput.close()
+ self.gitError.close()
+
+ return True
+
+class P4Rebase(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = [ ]
+ self.description = ("Fetches the latest revision from perforce and "
+ + "rebases the current work (branch) against it")
+ self.verbose = False
+
+ def run(self, args):
+ sync = P4Sync()
+ sync.run([])
+
+ [upstream, settings] = findUpstreamBranchPoint()
+ if len(upstream) == 0:
+ die("Cannot find upstream branchpoint for rebase")
+
+ # the branchpoint may be p4/foo~3, so strip off the parent
+ upstream = re.sub("~[0-9]+$", "", upstream)
+
+ print "Rebasing the current branch onto %s" % upstream
+ oldHead = read_pipe("git rev-parse HEAD").strip()
+ system("git rebase %s" % upstream)
+ system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
+ return True
+
+class P4Clone(P4Sync):
+ def __init__(self):
+ P4Sync.__init__(self)
+ self.description = "Creates a new git repository and imports from Perforce into it"
+ self.usage = "usage: %prog [options] //depot/path[@revRange]"
+ self.options.append(
+ optparse.make_option("--destination", dest="cloneDestination",
+ action='store', default=None,
+ help="where to leave result of the clone"))
+ self.cloneDestination = None
+ self.needsGit = False
+
+ def defaultDestination(self, args):
+ ## TODO: use common prefix of args?
+ depotPath = args[0]
+ depotDir = re.sub("(@[^@]*)$", "", depotPath)
+ depotDir = re.sub("(#[^#]*)$", "", depotDir)
+ depotDir = re.sub(r"\.\.\.$,", "", depotDir)
+ depotDir = re.sub(r"/$", "", depotDir)
+ return os.path.split(depotDir)[1]
+
+ def run(self, args):
+ if len(args) < 1:
+ return False
+
+ if self.keepRepoPath and not self.cloneDestination:
+ sys.stderr.write("Must specify destination for --keep-path\n")
+ sys.exit(1)
+
+ depotPaths = args
+
+ if not self.cloneDestination and len(depotPaths) > 1:
+ self.cloneDestination = depotPaths[-1]
+ depotPaths = depotPaths[:-1]
+
+ for p in depotPaths:
+ if not p.startswith("//"):
+ return False
+
+ if not self.cloneDestination:
+ self.cloneDestination = self.defaultDestination(args)
+
+ print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
+ if not os.path.exists(self.cloneDestination):
+ os.makedirs(self.cloneDestination)
+ os.chdir(self.cloneDestination)
+ system("git init")
+ self.gitdir = os.getcwd() + "/.git"
+ if not P4Sync.run(self, depotPaths):
+ return False
+ if self.branch != "master":
+ if gitBranchExists("refs/remotes/p4/master"):
+ system("git branch master refs/remotes/p4/master")
+ system("git checkout -f")
+ else:
+ print "Could not detect main branch. No checkout/master branch created."
+
+ return True
+
+class P4Branches(Command):
+ def __init__(self):
+ Command.__init__(self)
+ self.options = [ ]
+ self.description = ("Shows the git branches that hold imports and their "
+ + "corresponding perforce depot paths")
+ self.verbose = False
+
+ def run(self, args):
+ cmdline = "git rev-parse --symbolic "
+ cmdline += " --remotes"
+
+ for line in read_pipe_lines(cmdline):
+ line = line.strip()
+
+ if not line.startswith('p4/') or line == "p4/HEAD":
+ continue
+ branch = line
+
+ log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
+ settings = extractSettingsGitLog(log)
+
+ print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
+ return True
+
+class HelpFormatter(optparse.IndentedHelpFormatter):
+ def __init__(self):
+ optparse.IndentedHelpFormatter.__init__(self)
+
+ def format_description(self, description):
+ if description:
+ return description + "\n"
+ else:
+ return ""
+
+def printUsage(commands):
+ print "usage: %s <command> [options]" % sys.argv[0]
+ print ""
+ print "valid commands: %s" % ", ".join(commands)
+ print ""
+ print "Try %s <command> --help for command specific help." % sys.argv[0]
+ print ""
+
+commands = {
+ "debug" : P4Debug,
+ "submit" : P4Submit,
+ "sync" : P4Sync,
+ "rebase" : P4Rebase,
+ "clone" : P4Clone,
+ "rollback" : P4RollBack,
+ "branches" : P4Branches
+}
+
+
+def main():
+ if len(sys.argv[1:]) == 0:
+ printUsage(commands.keys())
+ sys.exit(2)
+
+ cmd = ""
+ cmdName = sys.argv[1]
+ try:
+ klass = commands[cmdName]
+ cmd = klass()
+ except KeyError:
+ print "unknown command %s" % cmdName
+ print ""
+ printUsage(commands.keys())
+ sys.exit(2)
+
+ options = cmd.options
+ cmd.gitdir = os.environ.get("GIT_DIR", None)
+
+ args = sys.argv[2:]
+
+ if len(options) > 0:
+ options.append(optparse.make_option("--git-dir", dest="gitdir"))
+
+ parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
+ options,
+ description = cmd.description,
+ formatter = HelpFormatter())
+
+ (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
+ global verbose
+ verbose = cmd.verbose
+ if cmd.needsGit:
+ if cmd.gitdir == None:
+ cmd.gitdir = os.path.abspath(".git")
+ if not isValidGitDir(cmd.gitdir):
+ cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
+ if os.path.exists(cmd.gitdir):
+ cdup = read_pipe("git rev-parse --show-cdup").strip()
+ if len(cdup) > 0:
+ os.chdir(cdup);
+
+ if not isValidGitDir(cmd.gitdir):
+ if isValidGitDir(cmd.gitdir + "/.git"):
+ cmd.gitdir += "/.git"
+ else:
+ die("fatal: cannot locate git repository at %s" % cmd.gitdir)
+
+ os.environ["GIT_DIR"] = cmd.gitdir
+
+ if not cmd.run(args):
+ parser.print_help()
+
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+@python "%~d0%~p0git-p4" %*
--- /dev/null
+git-p4 - Perforce <-> Git converter using git-fast-import
+
+Usage
+=====
+
+git-p4 supports two main modes: Importing from Perforce to a Git repository is
+done using "git-p4 sync" or "git-p4 rebase". Submitting changes from Git back
+to Perforce is done using "git-p4 submit".
+
+Importing
+=========
+
+You can simply start with
+
+ git-p4 clone //depot/path/project
+
+or
+
+ git-p4 clone //depot/path/project myproject
+
+This will create an empty git repository in a subdirectory called "project" (or
+"myproject" with the second command), import the head revision from the
+specified perforce path into a git "p4" branch (remotes/p4 actually), create a
+master branch off it and check it out. If you want the entire history (not just
+the head revision) then you can simply append a "@all" to the depot path:
+
+ git-p4 clone //depot/project/main@all myproject
+
+
+
+If you want more control you can also use the git-p4 sync command directly:
+
+ mkdir repo-git
+ cd repo-git
+ git init
+ git-p4 sync //path/in/your/perforce/depot
+
+This will import the current head revision of the specified depot path into a
+"remotes/p4/master" branch of your git repository. You can use the
+--branch=mybranch option to use a different branch.
+
+If you want to import the entire history of a given depot path just use
+
+ git-p4 sync //path/in/depot@all
+
+To achieve optimal compression you may want to run 'git repack -a -d -f' after
+a big import. This may take a while.
+
+Support for Perforce integrations is still work in progress. Don't bother
+trying it unless you want to hack on it :)
+
+Incremental Imports
+===================
+
+After an initial import you can easily synchronize your git repository with
+newer changes from the Perforce depot by just calling
+
+ git-p4 sync
+
+in your git repository. By default the "remotes/p4/master" branch is updated.
+
+It is recommended to run 'git repack -a -d -f' from time to time when using
+incremental imports to optimally combine the individual git packs that each
+incremental import creates through the use of git-fast-import.
+
+
+A useful setup may be that you have a periodically updated git repository
+somewhere that contains a complete import of a Perforce project. That git
+repository can be used to clone the working repository from and one would
+import from Perforce directly after cloning using git-p4. If the connection to
+the Perforce server is slow and the working repository hasn't been synced for a
+while it may be desirable to fetch changes from the origin git repository using
+the efficient git protocol. git-p4 supports this setup by calling "git fetch origin"
+by default if there is an origin branch. You can disable this using
+
+ git config git-p4.syncFromOrigin false
+
+Updating
+========
+
+A common working pattern is to fetch the latest changes from the Perforce depot
+and merge them with local uncommitted changes. The recommended way is to use
+git's rebase mechanism to preserve linear history. git-p4 provides a convenient
+
+ git-p4 rebase
+
+command that calls git-p4 sync followed by git rebase to rebase the current
+working branch.
+
+Submitting
+==========
+
+git-p4 has support for submitting changes from a git repository back to the
+Perforce depot. This requires a Perforce checkout separate to your git
+repository. To submit all changes that are in the current git branch but not in
+the "p4" branch (or "origin" if "p4" doesn't exist) simply call
+
+ git-p4 submit
+
+in your git repository. If you want to submit changes in a specific branch that
+is not your current git branch you can also pass that as an argument:
+
+ git-p4 submit mytopicbranch
+
+You can override the reference branch with the --origin=mysourcebranch option.
+
+If a submit fails you may have to "p4 resolve" and submit manually. You can
+continue importing the remaining changes with
+
+ git-p4 submit --continue
+
+After submitting you should sync your perforce import branch ("p4" or "origin")
+from Perforce using git-p4's sync command.
+
+If you have changes in your working directory that you haven't committed into
+git yet but that you want to commit to Perforce directly ("quick fixes") then
+you do not have to go through the intermediate step of creating a git commit
+first but you can just call
+
+ git-p4 submit --direct
+
+
+Example
+=======
+
+# Clone a repository
+ git-p4 clone //depot/path/project
+# Enter the newly cloned directory
+ cd project
+# Do some work...
+ vi foo.h
+# ... and commit locally to gi
+ git commit foo.h
+# In the meantime somebody submitted changes to the Perforce depot. Rebase your latest
+# changes against the latest changes in Perforce:
+ git-p4 rebase
+# Submit your locally committed changes back to Perforce
+ git-p4 submit
+# ... and synchronize with Perforce
+ git-p4 rebase
+
+
+Implementation Details...
+=========================
+
+* Changesets from Perforce are imported using git fast-import.
+* The import does not require anything from the Perforce client view as it just uses
+ "p4 print //depot/path/file#revision" to get the actual file contents.
+* Every imported changeset has a special [git-p4...] line at the
+ end of the log message that gives information about the corresponding
+ Perforce change number and is also used by git-p4 itself to find out
+ where to continue importing when doing incremental imports.
+ Basically when syncing it extracts the perforce change number of the
+ latest commit in the "p4" branch and uses "p4 changes //depot/path/...@changenum,#head"
+ to find out which changes need to be imported.
+* git-p4 submit uses "git rev-list" to pick the commits between the "p4" branch
+ and the current branch.
+ The commits themselves are applied using git diff/format-patch ... | git apply
+
self.set_colour(ctx, colour, 0.0, 0.5)
ctx.show_text(name)
-class Commit:
+class Commit(object):
""" This represent a commit object obtained after parsing the git-rev-list
output """
+ __slots__ = ['children_sha1', 'message', 'author', 'date', 'committer',
+ 'commit_date', 'commit_sha1', 'parent_sha1']
+
children_sha1 = {}
def __init__(self, commit_lines):
fp.close()
return diff
-class AnnotateWindow:
+class AnnotateWindow(object):
"""Annotate window.
This object represents and manages a single window containing the
annotate information of the file
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_border_width(0)
self.window.set_title("Git repository browser annotation window")
+ self.prev_read = ""
# Use two thirds of the screen by default
screen = self.window.get_screen()
def data_ready(self, source, condition):
while (1):
try :
- buffer = source.read(8192)
+ # A simple readline doesn't work
+ # a readline bug ??
+ buffer = source.read(100)
+
except:
# resource temporary not available
return True
source.close()
return False
+ if (self.prev_read != ""):
+ buffer = self.prev_read + buffer
+ self.prev_read = ""
+
+ if (buffer[len(buffer) -1] != '\n'):
+ try:
+ newline_index = buffer.rindex("\n")
+ except ValueError:
+ newline_index = 0
+
+ self.prev_read = buffer[newline_index:(len(buffer))]
+ buffer = buffer[0:newline_index]
+
for buff in buffer.split("\n"):
annotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$')
m = annotate_line.match(buff)
self.add_file_data(filename, commit_sha1, line_num)
- fp = os.popen("git blame --incremental -- " + filename + " " + commit_sha1)
+ fp = os.popen("git blame --incremental -C -C -- " + filename + " " + commit_sha1)
flags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL)
fcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
self.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready)
-class DiffWindow:
+class DiffWindow(object):
"""Diff window.
This object represents and manages a single window containing the
differences between two revisions on a branch.
fp.close()
dialog.destroy()
-class GitView:
+class GitView(object):
""" This is the main class
"""
version = "0.9"
view = GitView( without_diff != 1)
view.run(sys.argv[without_diff:])
-
-
hooks/post-receive
- --
+ --
$projectdesc
EOF
}
--- /dev/null
+Please see contrib/fast-import/git-p4 for a better Perforce importer.
--- /dev/null
+#!/usr/bin/python
+#
+# This tool is copyright (c) 2006, Sean Estabrooks.
+# It is released under the Gnu Public License, version 2.
+#
+# Import Perforce branches into Git repositories.
+# Checking out the files is done by calling the standard p4
+# client which you must have properly configured yourself
+#
+
+import marshal
+import os
+import sys
+import time
+import getopt
+
+from signal import signal, \
+ SIGPIPE, SIGINT, SIG_DFL, \
+ default_int_handler
+
+signal(SIGPIPE, SIG_DFL)
+s = signal(SIGINT, SIG_DFL)
+if s != default_int_handler:
+ signal(SIGINT, s)
+
+def die(msg, *args):
+ for a in args:
+ msg = "%s %s" % (msg, a)
+ print "git-p4import fatal error:", msg
+ sys.exit(1)
+
+def usage():
+ print "USAGE: git-p4import [-q|-v] [--authors=<file>] [-t <timezone>] [//p4repo/path <branch>]"
+ sys.exit(1)
+
+verbosity = 1
+logfile = "/dev/null"
+ignore_warnings = False
+stitch = 0
+tagall = True
+
+def report(level, msg, *args):
+ global verbosity
+ global logfile
+ for a in args:
+ msg = "%s %s" % (msg, a)
+ fd = open(logfile, "a")
+ fd.writelines(msg)
+ fd.close()
+ if level <= verbosity:
+ print msg
+
+class p4_command:
+ def __init__(self, _repopath):
+ try:
+ global logfile
+ self.userlist = {}
+ if _repopath[-1] == '/':
+ self.repopath = _repopath[:-1]
+ else:
+ self.repopath = _repopath
+ if self.repopath[-4:] != "/...":
+ self.repopath= "%s/..." % self.repopath
+ f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
+ a = f.readlines()
+ if f.close():
+ raise
+ except:
+ die("Could not find the \"p4\" command")
+
+ def p4(self, cmd, *args):
+ global logfile
+ cmd = "%s %s" % (cmd, ' '.join(args))
+ report(2, "P4:", cmd)
+ f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
+ list = []
+ while 1:
+ try:
+ list.append(marshal.load(f))
+ except EOFError:
+ break
+ self.ret = f.close()
+ return list
+
+ def sync(self, id, force=False, trick=False, test=False):
+ if force:
+ ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
+ elif trick:
+ ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
+ elif test:
+ ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
+ else:
+ ret = self.p4("sync %s@%s"%(self.repopath, id))[0]
+ if ret['code'] == "error":
+ data = ret['data'].upper()
+ if data.find('VIEW') > 0:
+ die("Perforce reports %s is not in client view"% self.repopath)
+ elif data.find('UP-TO-DATE') < 0:
+ die("Could not sync files from perforce", self.repopath)
+
+ def changes(self, since=0):
+ try:
+ list = []
+ for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
+ list.append(rec['change'])
+ list.reverse()
+ return list
+ except:
+ return []
+
+ def authors(self, filename):
+ f=open(filename)
+ for l in f.readlines():
+ self.userlist[l[:l.find('=')].rstrip()] = \
+ (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
+ f.close()
+ for f,e in self.userlist.items():
+ report(2, f, ":", e[0], " <", e[1], ">")
+
+ def _get_user(self, id):
+ if not self.userlist.has_key(id):
+ try:
+ user = self.p4("users", id)[0]
+ self.userlist[id] = (user['FullName'], user['Email'])
+ except:
+ self.userlist[id] = (id, "")
+ return self.userlist[id]
+
+ def _format_date(self, ticks):
+ symbol='+'
+ name = time.tzname[0]
+ offset = time.timezone
+ if ticks[8]:
+ name = time.tzname[1]
+ offset = time.altzone
+ if offset < 0:
+ offset *= -1
+ symbol = '-'
+ localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
+ tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
+ return "%s %s" % (tickso, localo)
+
+ def where(self):
+ try:
+ return self.p4("where %s" % self.repopath)[-1]['path']
+ except:
+ return ""
+
+ def describe(self, num):
+ desc = self.p4("describe -s", num)[0]
+ self.msg = desc['desc']
+ self.author, self.email = self._get_user(desc['user'])
+ self.date = self._format_date(time.localtime(long(desc['time'])))
+ return self
+
+class git_command:
+ def __init__(self):
+ try:
+ self.version = self.git("--version")[0][12:].rstrip()
+ except:
+ die("Could not find the \"git\" command")
+ try:
+ self.gitdir = self.get_single("rev-parse --git-dir")
+ report(2, "gdir:", self.gitdir)
+ except:
+ die("Not a git repository... did you forget to \"git init\" ?")
+ try:
+ self.cdup = self.get_single("rev-parse --show-cdup")
+ if self.cdup != "":
+ os.chdir(self.cdup)
+ self.topdir = os.getcwd()
+ report(2, "topdir:", self.topdir)
+ except:
+ die("Could not find top git directory")
+
+ def git(self, cmd):
+ global logfile
+ report(2, "GIT:", cmd)
+ f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
+ r=f.readlines()
+ self.ret = f.close()
+ return r
+
+ def get_single(self, cmd):
+ return self.git(cmd)[0].rstrip()
+
+ def current_branch(self):
+ try:
+ testit = self.git("rev-parse --verify HEAD")[0]
+ return self.git("symbolic-ref HEAD")[0][11:].rstrip()
+ except:
+ return None
+
+ def get_config(self, variable):
+ try:
+ return self.git("config --get %s" % variable)[0].rstrip()
+ except:
+ return None
+
+ def set_config(self, variable, value):
+ try:
+ self.git("config %s %s"%(variable, value) )
+ except:
+ die("Could not set %s to " % variable, value)
+
+ def make_tag(self, name, head):
+ self.git("tag -f %s %s"%(name,head))
+
+ def top_change(self, branch):
+ try:
+ a=self.get_single("name-rev --tags refs/heads/%s" % branch)
+ loc = a.find(' tags/') + 6
+ if a[loc:loc+3] != "p4/":
+ raise
+ return int(a[loc+3:][:-2])
+ except:
+ return 0
+
+ def update_index(self):
+ self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
+
+ def checkout(self, branch):
+ self.git("checkout %s" % branch)
+
+ def repoint_head(self, branch):
+ self.git("symbolic-ref HEAD refs/heads/%s" % branch)
+
+ def remove_files(self):
+ self.git("ls-files | xargs rm")
+
+ def clean_directories(self):
+ self.git("clean -d")
+
+ def fresh_branch(self, branch):
+ report(1, "Creating new branch", branch)
+ self.git("ls-files | xargs rm")
+ os.remove(".git/index")
+ self.repoint_head(branch)
+ self.git("clean -d")
+
+ def basedir(self):
+ return self.topdir
+
+ def commit(self, author, email, date, msg, id):
+ self.update_index()
+ fd=open(".msg", "w")
+ fd.writelines(msg)
+ fd.close()
+ try:
+ current = self.get_single("rev-parse --verify HEAD")
+ head = "-p HEAD"
+ except:
+ current = ""
+ head = ""
+ tree = self.get_single("write-tree")
+ for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
+ os.environ['GIT_AUTHOR_%s'%r] = l
+ os.environ['GIT_COMMITTER_%s'%r] = l
+ commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
+ os.remove(".msg")
+ self.make_tag("p4/%s"%id, commit)
+ self.git("update-ref HEAD %s %s" % (commit, current) )
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
+ ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
+except getopt.GetoptError:
+ usage()
+
+for o, a in opts:
+ if o == "-q":
+ verbosity = 0
+ if o == "-v":
+ verbosity += 1
+ if o in ("--log"):
+ logfile = a
+ if o in ("--notags"):
+ tagall = False
+ if o in ("-h", "--help"):
+ usage()
+ if o in ("--ignore"):
+ ignore_warnings = True
+
+git = git_command()
+branch=git.current_branch()
+
+for o, a in opts:
+ if o in ("-t", "--timezone"):
+ git.set_config("perforce.timezone", a)
+ if o in ("--stitch"):
+ git.set_config("perforce.%s.path" % branch, a)
+ stitch = 1
+
+if len(args) == 2:
+ branch = args[1]
+ git.checkout(branch)
+ if branch == git.current_branch():
+ die("Branch %s already exists!" % branch)
+ report(1, "Setting perforce to ", args[0])
+ git.set_config("perforce.%s.path" % branch, args[0])
+elif len(args) != 0:
+ die("You must specify the perforce //depot/path and git branch")
+
+p4path = git.get_config("perforce.%s.path" % branch)
+if p4path == None:
+ die("Do not know Perforce //depot/path for git branch", branch)
+
+p4 = p4_command(p4path)
+
+for o, a in opts:
+ if o in ("-a", "--authors"):
+ p4.authors(a)
+
+localdir = git.basedir()
+if p4.where()[:len(localdir)] != localdir:
+ report(1, "**WARNING** Appears p4 client is misconfigured")
+ report(1, " for sync from %s to %s" % (p4.repopath, localdir))
+ if ignore_warnings != True:
+ die("Reconfigure or use \"--ignore\" on command line")
+
+if stitch == 0:
+ top = git.top_change(branch)
+else:
+ top = 0
+changes = p4.changes(top)
+count = len(changes)
+if count == 0:
+ report(1, "Already up to date...")
+ sys.exit(0)
+
+ptz = git.get_config("perforce.timezone")
+if ptz:
+ report(1, "Setting timezone to", ptz)
+ os.environ['TZ'] = ptz
+ time.tzset()
+
+if stitch == 1:
+ git.remove_files()
+ git.clean_directories()
+ p4.sync(changes[0], force=True)
+elif top == 0 and branch != git.current_branch():
+ p4.sync(changes[0], test=True)
+ report(1, "Creating new initial commit");
+ git.fresh_branch(branch)
+ p4.sync(changes[0], force=True)
+else:
+ p4.sync(changes[0], trick=True)
+
+report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
+for id in changes:
+ report(1, "Importing changeset", id)
+ change = p4.describe(id)
+ p4.sync(id)
+ if tagall :
+ git.commit(change.author, change.email, change.date, change.msg, id)
+ else:
+ git.commit(change.author, change.email, change.date, change.msg, "import")
+ if stitch == 1:
+ git.clean_directories()
+ stitch = 0
--- /dev/null
+git-p4import(1)
+===============
+
+NAME
+----
+git-p4import - Import a Perforce repository into git
+
+
+SYNOPSIS
+--------
+[verse]
+`git-p4import` [-q|-v] [--notags] [--authors <file>] [-t <timezone>]
+ <//p4repo/path> <branch>
+`git-p4import` --stitch <//p4repo/path>
+`git-p4import`
+
+
+DESCRIPTION
+-----------
+Import a Perforce repository into an existing git repository. When
+a <//p4repo/path> and <branch> are specified a new branch with the
+given name will be created and the initial import will begin.
+
+Once the initial import is complete you can do an incremental import
+of new commits from the Perforce repository. You do this by checking
+out the appropriate git branch and then running `git-p4import` without
+any options.
+
+The standard p4 client is used to communicate with the Perforce
+repository; it must be configured correctly in order for `git-p4import`
+to operate (see below).
+
+
+OPTIONS
+-------
+-q::
+ Do not display any progress information.
+
+-v::
+ Give extra progress information.
+
+\--authors::
+ Specify an authors file containing a mapping of Perforce user
+ ids to full names and email addresses (see Notes below).
+
+\--notags::
+ Do not create a tag for each imported commit.
+
+\--stitch::
+ Import the contents of the given perforce branch into the
+ currently checked out git branch.
+
+\--log::
+ Store debugging information in the specified file.
+
+-t::
+ Specify that the remote repository is in the specified timezone.
+ Timezone must be in the format "US/Pacific" or "Europe/London"
+ etc. You only need to specify this once, it will be saved in
+ the git config file for the repository.
+
+<//p4repo/path>::
+ The Perforce path that will be imported into the specified branch.
+
+<branch>::
+ The new branch that will be created to hold the Perforce imports.
+
+
+P4 Client
+---------
+You must make the `p4` client command available in your $PATH and
+configure it to communicate with the target Perforce repository.
+Typically this means you must set the "$P4PORT" and "$P4CLIENT"
+environment variables.
+
+You must also configure a `p4` client "view" which maps the Perforce
+branch into the top level of your git repository, for example:
+
+------------
+Client: myhost
+
+Root: /home/sean/import
+
+Options: noallwrite clobber nocompress unlocked modtime rmdir
+
+View:
+ //public/jam/... //myhost/jam/...
+------------
+
+With the above `p4` client setup, you could import the "jam"
+perforce branch into a branch named "jammy", like so:
+
+------------
+$ mkdir -p /home/sean/import/jam
+$ cd /home/sean/import/jam
+$ git init
+$ git p4import //public/jam jammy
+------------
+
+
+Multiple Branches
+-----------------
+Note that by creating multiple "views" you can use `git-p4import`
+to import additional branches into the same git repository.
+However, the `p4` client has a limitation in that it silently
+ignores all but the last "view" that maps into the same local
+directory. So the following will *not* work:
+
+------------
+View:
+ //public/jam/... //myhost/jam/...
+ //public/other/... //myhost/jam/...
+ //public/guest/... //myhost/jam/...
+------------
+
+If you want more than one Perforce branch to be imported into the
+same directory you must employ a workaround. A simple option is
+to adjust your `p4` client before each import to only include a
+single view.
+
+Another option is to create multiple symlinks locally which all
+point to the same directory in your git repository and then use
+one per "view" instead of listing the actual directory.
+
+
+Tags
+----
+A git tag of the form p4/xx is created for every change imported from
+the Perforce repository where xx is the Perforce changeset number.
+Therefore after the import you can use git to access any commit by its
+Perforce number, e.g. git show p4/327.
+
+The tag associated with the HEAD commit is also how `git-p4import`
+determines if there are new changes to incrementally import from the
+Perforce repository.
+
+If you import from a repository with many thousands of changes
+you will have an equal number of p4/xxxx git tags. Git tags can
+be expensive in terms of disk space and repository operations.
+If you don't need to perform further incremental imports, you
+may delete the tags.
+
+
+Notes
+-----
+You can interrupt the import (e.g. ctrl-c) at any time and restart it
+without worry.
+
+Author information is automatically determined by querying the
+Perforce "users" table using the id associated with each change.
+However, if you want to manually supply these mappings you can do
+so with the "--authors" option. It accepts a file containing a list
+of mappings with each line containing one mapping in the format:
+
+------------
+ perforce_id = Full Name <email@address.com>
+------------
+
+
+Author
+------
+Written by Sean Estabrooks <seanlkml@sympatico.ca>
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
--- /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 -->
mv "$GIT_DIR"/remotes "$GIT_DIR"/remotes.old
fi ;;
*)
- echo "git-config $key "$value" $regex"
- git-config $key "$value" $regex || error=1 ;;
+ echo "git config $key "$value" $regex"
+ git config $key "$value" $regex || error=1 ;;
esac
done
fi
-
-
--- /dev/null
+#!/bin/sh
+
+# This script displays the distribution of longest common hash prefixes.
+# This can be used to determine the minimum prefix length to use
+# for object names to be unique.
+
+git rev-list --objects --all | sort | perl -lne '
+ substr($_, 40) = "";
+ # uncomment next line for a distribution of bits instead of hex chars
+ # $_ = unpack("B*",pack("H*",$_));
+ if (defined $p) {
+ ($p ^ $_) =~ /^(\0*)/;
+ $common = length $1;
+ if (defined $pcommon) {
+ $count[$pcommon > $common ? $pcommon : $common]++;
+ } else {
+ $count[$common]++; # first item
+ }
+ }
+ $p = $_;
+ $pcommon = $common;
+ END {
+ $count[$common]++; # last item
+ print "$_: $count[$_]" for 0..$#count;
+ }
+'
--- /dev/null
+#!/usr/bin/perl -w
+my %mailmap = ();
+open I, "<", ".mailmap";
+while (<I>) {
+ chomp;
+ next if /^#/;
+ if (my ($author, $mail) = /^(.*?)\s+<(.+)>$/) {
+ $mailmap{$mail} = $author;
+ }
+}
+close I;
+
+my %mail2author = ();
+open I, "git log --pretty='format:%ae %an' |";
+while (<I>) {
+ chomp;
+ my ($mail, $author) = split(/\t/, $_);
+ next if exists $mailmap{$mail};
+ $mail2author{$mail} ||= {};
+ $mail2author{$mail}{$author} ||= 0;
+ $mail2author{$mail}{$author}++;
+}
+close I;
+
+while (my ($mail, $authorcount) = each %mail2author) {
+ # %$authorcount is ($author => $count);
+ # sort and show the names from the most frequent ones.
+ my @names = (map { $_->[0] }
+ sort { $b->[1] <=> $a->[1] }
+ map { [$_, $authorcount->{$_}] }
+ keys %$authorcount);
+ if (1 < @names) {
+ for (@names) {
+ print "$_ <$mail>\n";
+ }
+ }
+}
+
--- /dev/null
+#!/usr/bin/perl
+#
+# This tool will print vaguely pretty information about a pack. It
+# expects the output of "git-verify-pack -v" as input on stdin.
+#
+# $ git-verify-pack -v | packinfo.pl
+#
+# This prints some full-pack statistics; currently "all sizes", "all
+# path sizes", "tree sizes", "tree path sizes", and "depths".
+#
+# * "all sizes" stats are across every object size in the file;
+# full sizes for base objects, and delta size for deltas.
+# * "all path sizes" stats are across all object's "path sizes".
+# A path size is the sum of the size of the delta chain, including the
+# base object. In other words, it's how many bytes need be read to
+# reassemble the file from deltas.
+# * "tree sizes" are object sizes grouped into delta trees.
+# * "tree path sizes" are path sizes grouped into delta trees.
+# * "depths" should be obvious.
+#
+# When run as:
+#
+# $ git-verify-pack -v | packinfo.pl -tree
+#
+# the trees of objects are output along with the stats. This looks
+# like:
+#
+# 0 commit 031321c6... 803 803
+#
+# 0 blob 03156f21... 1767 1767
+# 1 blob f52a9d7f... 10 1777
+# 2 blob a8cc5739... 51 1828
+# 3 blob 660e90b1... 15 1843
+# 4 blob 0cb8e3bb... 33 1876
+# 2 blob e48607f0... 311 2088
+# size: count 6 total 2187 min 10 max 1767 mean 364.50 median 51 std_dev 635.85
+# path size: count 6 total 11179 min 1767 max 2088 mean 1863.17 median 1843 std_dev 107.26
+#
+# The first number after the sha1 is the object size, the second
+# number is the path size. The statistics are across all objects in
+# the previous delta tree. Obviously they are omitted for trees of
+# one object.
+#
+# When run as:
+#
+# $ git-verify-pack -v | packinfo.pl -tree -filenames
+#
+# it adds filenames to the tree. Getting this information is slow:
+#
+# 0 blob 03156f21... 1767 1767 Documentation/git-lost-found.txt @ tags/v1.2.0~142
+# 1 blob f52a9d7f... 10 1777 Documentation/git-lost-found.txt @ tags/v1.5.0-rc1~74
+# 2 blob a8cc5739... 51 1828 Documentation/git-lost+found.txt @ tags/v0.99.9h^0
+# 3 blob 660e90b1... 15 1843 Documentation/git-lost+found.txt @ master~3222^2~2
+# 4 blob 0cb8e3bb... 33 1876 Documentation/git-lost+found.txt @ master~3222^2~3
+# 2 blob e48607f0... 311 2088 Documentation/git-lost-found.txt @ tags/v1.5.2-rc3~4
+# size: count 6 total 2187 min 10 max 1767 mean 364.50 median 51 std_dev 635.85
+# path size: count 6 total 11179 min 1767 max 2088 mean 1863.17 median 1843 std_dev 107.26
+#
+# When run as:
+#
+# $ git-verify-pack -v | packinfo.pl -dump
+#
+# it prints out "sha1 size pathsize depth" for each sha1 in lexical
+# order.
+#
+# 000079a2eaef17b7eae70e1f0f635557ea67b644 30 472 7
+# 00013cafe6980411aa6fdd940784917b5ff50f0a 44 1542 4
+# 000182eacf99cde27d5916aa415921924b82972c 499 499 0
+# ...
+#
+# This is handy for comparing two packs. Adding "-filenames" will add
+# filenames, as per "-tree -filenames" above.
+
+use strict;
+use Getopt::Long;
+
+my $filenames = 0;
+my $tree = 0;
+my $dump = 0;
+GetOptions("tree" => \$tree,
+ "filenames" => \$filenames,
+ "dump" => \$dump);
+
+my %parents;
+my %children;
+my %sizes;
+my @roots;
+my %paths;
+my %types;
+my @commits;
+my %names;
+my %depths;
+my @depths;
+
+while (<STDIN>) {
+ my ($sha1, $type, $size, $offset, $depth, $parent) = split(/\s+/, $_);
+ next unless ($sha1 =~ /^[0-9a-f]{40}$/);
+ $depths{$sha1} = $depth || 0;
+ push(@depths, $depth || 0);
+ push(@commits, $sha1) if ($type eq 'commit');
+ push(@roots, $sha1) unless $parent;
+ $parents{$sha1} = $parent;
+ $types{$sha1} = $type;
+ push(@{$children{$parent}}, $sha1);
+ $sizes{$sha1} = $size;
+}
+
+if ($filenames && ($tree || $dump)) {
+ open(NAMES, "git-name-rev --all|");
+ while (<NAMES>) {
+ if (/^(\S+)\s+(.*)$/) {
+ my ($sha1, $name) = ($1, $2);
+ $names{$sha1} = $name;
+ }
+ }
+ close NAMES;
+
+ for my $commit (@commits) {
+ my $name = $names{$commit};
+ open(TREE, "git-ls-tree -t -r $commit|");
+ print STDERR "Plumbing tree $name\n";
+ while (<TREE>) {
+ if (/^(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/) {
+ my ($mode, $type, $sha1, $path) = ($1, $2, $3, $4);
+ $paths{$sha1} = "$path @ $name";
+ }
+ }
+ close TREE;
+ }
+}
+
+sub stats {
+ my @data = sort {$a <=> $b} @_;
+ my $min = $data[0];
+ my $max = $data[$#data];
+ my $total = 0;
+ my $count = scalar @data;
+ for my $datum (@data) {
+ $total += $datum;
+ }
+ my $mean = $total / $count;
+ my $median = $data[int(@data / 2)];
+ my $diff_sum = 0;
+ for my $datum (@data) {
+ $diff_sum += ($datum - $mean)**2;
+ }
+ my $std_dev = sqrt($diff_sum / $count);
+ return ($count, $total, $min, $max, $mean, $median, $std_dev);
+}
+
+sub print_stats {
+ my $name = shift;
+ my ($count, $total, $min, $max, $mean, $median, $std_dev) = stats(@_);
+ printf("%s: count %s total %s min %s max %s mean %.2f median %s std_dev %.2f\n",
+ $name, $count, $total, $min, $max, $mean, $median, $std_dev);
+}
+
+my @sizes;
+my @path_sizes;
+my @all_sizes;
+my @all_path_sizes;
+my %path_sizes;
+
+sub dig {
+ my ($sha1, $depth, $path_size) = @_;
+ $path_size += $sizes{$sha1};
+ push(@sizes, $sizes{$sha1});
+ push(@all_sizes, $sizes{$sha1});
+ push(@path_sizes, $path_size);
+ push(@all_path_sizes, $path_size);
+ $path_sizes{$sha1} = $path_size;
+ if ($tree) {
+ printf("%3d%s %6s %s %8d %8d %s\n",
+ $depth, (" " x $depth), $types{$sha1},
+ $sha1, $sizes{$sha1}, $path_size, $paths{$sha1});
+ }
+ for my $child (@{$children{$sha1}}) {
+ dig($child, $depth + 1, $path_size);
+ }
+}
+
+my @tree_sizes;
+my @tree_path_sizes;
+
+for my $root (@roots) {
+ undef @sizes;
+ undef @path_sizes;
+ dig($root, 0, 0);
+ my ($aa, $sz_total) = stats(@sizes);
+ my ($bb, $psz_total) = stats(@path_sizes);
+ push(@tree_sizes, $sz_total);
+ push(@tree_path_sizes, $psz_total);
+ if ($tree) {
+ if (@sizes > 1) {
+ print_stats(" size", @sizes);
+ print_stats("path size", @path_sizes);
+ }
+ print "\n";
+ }
+}
+
+if ($dump) {
+ for my $sha1 (sort keys %sizes) {
+ print "$sha1 $sizes{$sha1} $path_sizes{$sha1} $depths{$sha1} $paths{$sha1}\n";
+ }
+} else {
+ print_stats(" all sizes", @all_sizes);
+ print_stats(" all path sizes", @all_path_sizes);
+ print_stats(" tree sizes", @tree_sizes);
+ print_stats("tree path sizes", @tree_path_sizes);
+ print_stats(" depths", @depths);
+}
branch=$3
# want to make sure that what is pointed to has a .git directory ...
-test -d "$orig_git/.git" || die "\"$orig_git\" is not a git repository!"
+git_dir=$(cd "$orig_git" 2>/dev/null &&
+ git rev-parse --git-dir 2>/dev/null) ||
+ die "\"$orig_git\" is not a git repository!"
+
+if test "$git_dir" = ".git"
+then
+ git_dir="$orig_git/.git"
+fi
# don't link to a workdir
-if test -L "$orig_git/.git/config"
+if test -L "$git_dir/config"
then
die "\"$orig_git\" is a working directory only, please specify" \
"a complete repository."
fi
# make sure the the links use full paths
-orig_git=$(cd "$orig_git"; pwd)
+git_dir=$(cd "$git_dir"; pwd)
# create the workdir
mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!"
mkdir -p "$(dirname "$new_workdir/.git/$x")"
;;
esac
- ln -s "$orig_git/.git/$x" "$new_workdir/.git/$x"
+ ln -s "$git_dir/$x" "$new_workdir/.git/$x"
done
# now setup the workdir
cd "$new_workdir"
# copy the HEAD from the original repository as a default branch
-cp "$orig_git/.git/HEAD" .git/HEAD
+cp "$git_dir/HEAD" .git/HEAD
# checkout the branch (either the same as HEAD from the original repository, or
# the one that was asked for)
git checkout -f $branch
fmt++;
} while (*buf && *fmt);
printf("left: %s\n", buf);
- return mktime(&tm);
+ return mktime(&tm);
}
static int convert_date_line(char *dst, void **buf, unsigned long *sp)
close(ifd);
return 0;
}
-
}
}
-int sha1close(struct sha1file *f, unsigned char *result, int update)
+int sha1close(struct sha1file *f, unsigned char *result, int final)
{
unsigned offset = f->offset;
if (offset) {
SHA1_Update(&f->ctx, f->buffer, offset);
sha1flush(f, offset);
+ f->offset = 0;
}
+ if (!final)
+ return 0; /* only want to flush (no checksum write, no close) */
SHA1_Final(f->buffer, &f->ctx);
if (result)
hashcpy(result, f->buffer);
- if (update)
- sha1flush(f, 20);
+ sha1flush(f, 20);
if (close(f->fd))
die("%s: sha1 file error on close (%s)", f->name, strerror(errno));
free(f);
return 0;
}
-struct sha1file *sha1create(const char *fmt, ...)
-{
- struct sha1file *f;
- unsigned len;
- va_list arg;
- int fd;
-
- f = xmalloc(sizeof(*f));
-
- va_start(arg, fmt);
- len = vsnprintf(f->name, sizeof(f->name), fmt, arg);
- va_end(arg);
- if (len >= PATH_MAX)
- die("you wascally wabbit, you");
- f->namelen = len;
-
- fd = open(f->name, O_CREAT | O_EXCL | O_WRONLY, 0666);
- if (fd < 0)
- die("unable to open %s (%s)", f->name, strerror(errno));
- f->fd = fd;
- f->error = 0;
- f->offset = 0;
- f->do_crc = 0;
- SHA1_Init(&f->ctx);
- return f;
-}
-
struct sha1file *sha1fd(int fd, const char *name)
{
struct sha1file *f;
return f;
}
-int sha1write_compressed(struct sha1file *f, void *in, unsigned int size)
-{
- z_stream stream;
- unsigned long maxsize;
- void *out;
-
- memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, zlib_compression_level);
- maxsize = deflateBound(&stream, size);
- out = xmalloc(maxsize);
-
- /* Compress it */
- stream.next_in = in;
- stream.avail_in = size;
-
- stream.next_out = out;
- stream.avail_out = maxsize;
-
- while (deflate(&stream, Z_FINISH) == Z_OK)
- /* nothing */;
- deflateEnd(&stream);
-
- size = stream.total_out;
- sha1write(f, out, size);
- free(out);
- return size;
-}
-
void crc32_begin(struct sha1file *f)
{
f->crc32 = crc32(0, Z_NULL, 0);
};
extern struct sha1file *sha1fd(int fd, const char *name);
-extern struct sha1file *sha1create(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
extern int sha1close(struct sha1file *, unsigned char *, int);
extern int sha1write(struct sha1file *, void *, unsigned int);
-extern int sha1write_compressed(struct sha1file *, void *, unsigned int);
extern void crc32_begin(struct sha1file *);
extern uint32_t crc32_end(struct sha1file *);
AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 112-15 */
/* Nothing in the 128.. range */
};
-
static const char daemon_usage[] =
"git-daemon [--verbose] [--syslog] [--export-all]\n"
" [--timeout=n] [--init-timeout=n] [--strict-paths]\n"
-" [--base-path=path] [--user-path | --user-path=path]\n"
+" [--base-path=path] [--base-path-relaxed]\n"
+" [--user-path | --user-path=path]\n"
" [--interpolated-path=path]\n"
" [--reuseaddr] [--detach] [--pid-file=file]\n"
" [--[enable|disable|allow-override|forbid-override]=service]\n"
/* Take all paths relative to this one if non-NULL */
static char *base_path;
static char *interpolated_path;
+static int base_path_relaxed;
/* Flag indicating client sent extra args. */
static int saw_extended_args;
{
int sl, ndot;
- /*
+ /*
* This resurrects the belts and suspenders paranoia check by HPA
* done in <435560F7.4080006@zytor.com> thread, now enter_repo()
* does not do getcwd() based path canonicalizations.
{
static char rpath[PATH_MAX];
static char interp_path[PATH_MAX];
+ int retried_path = 0;
char *path;
char *dir;
dir = rpath;
}
- path = enter_repo(dir, strict_paths);
+ do {
+ path = enter_repo(dir, strict_paths);
+ if (path)
+ break;
+
+ /*
+ * if we fail and base_path_relaxed is enabled, try without
+ * prefixing the base path
+ */
+ if (base_path && base_path_relaxed && !retried_path) {
+ dir = itable[INTERP_SLOT_DIR].value;
+ retried_path = 1;
+ continue;
+ }
+ break;
+ } while (1);
if (!path) {
logerror("'%s': unable to chdir or not a git archive", dir);
int pathlen = strlen(path);
/* The validation is done on the paths after enter_repo
- * appends optional {.git,.git/.git} and friends, but
+ * appends optional {.git,.git/.git} and friends, but
* it does not use getcwd(). So if your /pub is
* a symlink to /mnt/pub, you can whitelist /pub and
* do not have to say /mnt/pub.
}
}
-void fill_in_extra_table_entries(struct interp *itable)
+static void fill_in_extra_table_entries(struct interp *itable)
{
char *hp;
base_path = arg+12;
continue;
}
+ if (!strcmp(arg, "--base-path-relaxed")) {
+ base_path_relaxed = 1;
+ continue;
+ }
if (!prefixcmp(arg, "--interpolated-path=")) {
interpolated_path = arg+20;
continue;
if (mode == DATE_SHORT)
sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
tm->tm_mon + 1, tm->tm_mday);
+ else if (mode == DATE_ISO8601)
+ sprintf(timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
+ tm->tm_year + 1900,
+ tm->tm_mon + 1,
+ tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tz);
+ else if (mode == DATE_RFC2822)
+ sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
+ weekday_names[tm->tm_wday], tm->tm_mday,
+ month_names[tm->tm_mon], tm->tm_year + 1900,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
else
sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
weekday_names[tm->tm_wday],
return timebuf;
}
-const char *show_rfc2822_date(unsigned long time, int tz)
-{
- struct tm *tm;
- static char timebuf[200];
-
- tm = time_to_tm(time, tz);
- if (!tm)
- return NULL;
- sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
- weekday_names[tm->tm_wday], tm->tm_mday,
- month_names[tm->tm_mon], tm->tm_year + 1900,
- tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
- return timebuf;
-}
-
/*
* Check these. And note how it doesn't do the summer-time conversion.
*
}
/*
- * We've seen a digit. Time? Year? Date?
+ * We've seen a digit. Time? Year? Date?
*/
static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
{
} else if (num > 0 && num < 13) {
tm->tm_mon = num-1;
}
-
+
return n;
}
if (!match) {
/* BAD CRAP */
match = 1;
- }
+ }
date += match;
}
/* mktime uses local timezone */
- then = my_mktime(&tm);
+ then = my_mktime(&tm);
if (offset == -1)
offset = (then - mktime(&tm)) / 60;
tm->tm_hour = (hour % 12);
}
+static void date_never(struct tm *tm, int *num)
+{
+ tm->tm_mon = tm->tm_wday = tm->tm_yday
+ = tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
+ tm->tm_year = 70;
+ tm->tm_mday = 1;
+}
+
static const struct special {
const char *name;
void (*fn)(struct tm *, int *);
{ "tea", date_tea },
{ "PM", date_pm },
{ "AM", date_am },
+ { "never", date_never },
{ NULL }
};
{ "days", 24*60*60 },
{ "weeks", 7*24*60*60 },
{ NULL }
-};
+};
static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
{
*/
extern void free_delta_index(struct delta_index *index);
+/*
+ * sizeof_delta_index: returns memory usage of delta index
+ *
+ * Given pointer must be what create_delta_index() returned, or NULL.
+ */
+extern unsigned long sizeof_delta_index(struct delta_index *index);
+
/*
* create_delta: create a delta from given index for the given buffer
*
/*
* diff-delta.c: generate a delta between two buffers
*
- * Many parts of this file have been lifted from LibXDiff version 0.10.
- * http://www.xmailserver.org/xdiff-lib.html
+ * This code was greatly inspired by parts of LibXDiff from Davide Libenzi
+ * http://www.xmailserver.org/xdiff-lib.html
*
- * LibXDiff was written by Davide Libenzi <davidel@xmailserver.org>
- * Copyright (C) 2003 Davide Libenzi
+ * Rewritten for GIT by Nicolas Pitre <nico@cam.org>, (C) 2005-2007
*
- * Many mods for GIT usage by Nicolas Pitre <nico@cam.org>, (C) 2005.
- *
- * This file is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * Use of this within git automatically means that the LGPL
- * licensing gets turned into GPLv2 within this project.
+ * This code 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.
*/
#include "git-compat-util.h"
};
struct delta_index {
+ unsigned long memsize;
const void *src_buf;
unsigned long src_size;
unsigned int hash_mask;
mem = hash + hsize;
entry = mem;
+ index->memsize = memsize;
index->src_buf = buf;
index->src_size = bufsize;
index->hash_mask = hmask;
free(index);
}
+unsigned long sizeof_delta_index(struct delta_index *index)
+{
+ if (index)
+ return index->memsize;
+ else
+ return 0;
+}
+
/*
* The maximum size for any opcode sequence, including the initial header
* plus Rabin window plus biggest copy.
const void *trg_buf, unsigned long trg_size,
unsigned long *delta_size, unsigned long max_size)
{
- unsigned int i, outpos, outsize, val;
+ unsigned int i, outpos, outsize, moff, msize, val;
int inscnt;
const unsigned char *ref_data, *ref_top, *data, *top;
unsigned char *out;
}
inscnt = i;
+ moff = 0;
+ msize = 0;
while (data < top) {
- unsigned int moff = 0, msize = 0;
- struct index_entry *entry;
- val ^= U[data[-RABIN_WINDOW]];
- val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
- i = val & index->hash_mask;
- for (entry = index->hash[i]; entry; entry = entry->next) {
- const unsigned char *ref = entry->ptr;
- const unsigned char *src = data;
- unsigned int ref_size = ref_top - ref;
- if (entry->val != val)
- continue;
- if (ref_size > top - src)
- ref_size = top - src;
- if (ref_size > 0x10000)
- ref_size = 0x10000;
- if (ref_size <= msize)
- break;
- while (ref_size-- && *src++ == *ref)
- ref++;
- if (msize < ref - entry->ptr) {
- /* this is our best match so far */
- msize = ref - entry->ptr;
- moff = entry->ptr - ref_data;
+ if (msize < 4096) {
+ struct index_entry *entry;
+ val ^= U[data[-RABIN_WINDOW]];
+ val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+ i = val & index->hash_mask;
+ for (entry = index->hash[i]; entry; entry = entry->next) {
+ const unsigned char *ref = entry->ptr;
+ const unsigned char *src = data;
+ unsigned int ref_size = ref_top - ref;
+ if (entry->val != val)
+ continue;
+ if (ref_size > top - src)
+ ref_size = top - src;
+ if (ref_size <= msize)
+ break;
+ while (ref_size-- && *src++ == *ref)
+ ref++;
+ if (msize < ref - entry->ptr) {
+ /* this is our best match so far */
+ msize = ref - entry->ptr;
+ moff = entry->ptr - ref_data;
+ if (msize >= 4096) /* good enough */
+ break;
+ }
}
}
out[outpos - inscnt - 1] = inscnt;
inscnt = 0;
}
+ msize = 0;
} else {
+ unsigned int left;
unsigned char *op;
- if (msize >= RABIN_WINDOW) {
- const unsigned char *sk;
- sk = data + msize - RABIN_WINDOW;
- val = 0;
- for (i = 0; i < RABIN_WINDOW; i++)
- val = ((val << 8) | *sk++) ^ T[val >> RABIN_SHIFT];
- } else {
- const unsigned char *sk = data + 1;
- for (i = 1; i < msize; i++) {
- val ^= U[sk[-RABIN_WINDOW]];
- val = ((val << 8) | *sk++) ^ T[val >> RABIN_SHIFT];
- }
- }
-
if (inscnt) {
while (moff && ref_data[moff-1] == data[-1]) {
- if (msize == 0x10000)
- break;
/* we can match one byte back */
msize++;
moff--;
inscnt = 0;
}
- data += msize;
+ /* A copy op is currently limited to 64KB (pack v2) */
+ left = (msize < 0x10000) ? 0 : (msize - 0x10000);
+ msize -= left;
+
op = out + outpos++;
i = 0x80;
- if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
- moff >>= 8;
- if (moff & 0xff) { out[outpos++] = moff; i |= 0x02; }
- moff >>= 8;
- if (moff & 0xff) { out[outpos++] = moff; i |= 0x04; }
- moff >>= 8;
- if (moff & 0xff) { out[outpos++] = moff; i |= 0x08; }
+ if (moff & 0x000000ff)
+ out[outpos++] = moff >> 0, i |= 0x01;
+ if (moff & 0x0000ff00)
+ out[outpos++] = moff >> 8, i |= 0x02;
+ if (moff & 0x00ff0000)
+ out[outpos++] = moff >> 16, i |= 0x04;
+ if (moff & 0xff000000)
+ out[outpos++] = moff >> 24, i |= 0x08;
- if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; }
- msize >>= 8;
- if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
+ if (msize & 0x00ff)
+ out[outpos++] = msize >> 0, i |= 0x10;
+ if (msize & 0xff00)
+ out[outpos++] = msize >> 8, i |= 0x20;
*op = i;
+
+ data += msize;
+ moff += msize;
+ msize = left;
+
+ if (msize < 4096) {
+ int j;
+ val = 0;
+ for (j = -RABIN_WINDOW; j < 0; j++)
+ val = ((val << 8) | data[j])
+ ^ T[val >> RABIN_SHIFT];
+ }
}
if (outpos >= outsize - MAX_OP_SIZE) {
outsize = max_size + MAX_OP_SIZE + 1;
if (max_size && outpos > max_size)
break;
- out = xrealloc(out, outsize);
+ out = realloc(out, outsize);
if (!out) {
free(tmp);
return NULL;
while ((e = readdir(dir)))
if (strcmp(".", e->d_name) && strcmp("..", e->d_name))
- path_list_insert(xstrdup(e->d_name), list);
+ path_list_insert(e->d_name, list);
closedir(dir);
return 0;
const char *tree_name;
int match_missing = 0;
- /*
+ /*
* Backward compatibility wart - "diff-index -m" does
* not mean "do not ignore merges", but totally different.
*/
char *cmd;
} *user_diff, **user_diff_tail;
+static void read_config_if_needed(void)
+{
+ if (!user_diff_tail) {
+ user_diff_tail = &user_diff;
+ git_config(git_diff_ui_config);
+ }
+}
+
/*
* Currently there is only "diff.<drivername>.command" variable;
* because there are "diff.color.<slot>" variables, we are parsing
return 0;
}
+/*
+ * 'diff.<what>.funcname' attribute can be specified in the configuration
+ * to define a customized regexp to find the beginning of a function to
+ * be used for hunk header lines of "diff -p" style output.
+ */
+static struct funcname_pattern {
+ char *name;
+ char *pattern;
+ struct funcname_pattern *next;
+} *funcname_pattern_list;
+
+static int parse_funcname_pattern(const char *var, const char *ep, const char *value)
+{
+ const char *name;
+ int namelen;
+ struct funcname_pattern *pp;
+
+ name = var + 5; /* "diff." */
+ namelen = ep - name;
+
+ for (pp = funcname_pattern_list; pp; pp = pp->next)
+ if (!strncmp(pp->name, name, namelen) && !pp->name[namelen])
+ break;
+ if (!pp) {
+ char *namebuf;
+ pp = xcalloc(1, sizeof(*pp));
+ namebuf = xmalloc(namelen + 1);
+ memcpy(namebuf, name, namelen);
+ namebuf[namelen] = 0;
+ pp->name = namebuf;
+ pp->next = funcname_pattern_list;
+ funcname_pattern_list = pp;
+ }
+ if (pp->pattern)
+ free(pp->pattern);
+ pp->pattern = xstrdup(value);
+ return 0;
+}
+
/*
* These are to give UI layer defaults.
* The core-level commands such as git-diff-files should
if (!prefixcmp(var, "diff.")) {
const char *ep = strrchr(var, '.');
- if (ep != var + 4 && !strcmp(ep, ".command"))
- return parse_lldiff_command(var, ep, value);
+ if (ep != var + 4) {
+ if (!strcmp(ep, ".command"))
+ return parse_lldiff_command(var, ep, value);
+ if (!strcmp(ep, ".funcname"))
+ return parse_funcname_pattern(var, ep, value);
+ }
}
if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
int slot = parse_diff_color_slot(var, 11);
mmfile_t minus, plus;
int i;
+ memset(&xecfg, 0, sizeof(xecfg));
minus.size = diff_words->minus.text.size;
minus.ptr = xmalloc(minus.size);
memcpy(minus.ptr, diff_words->minus.text.ptr, minus.size);
xpp.flags = XDF_NEED_MINIMAL;
xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc;
- xecfg.flags = 0;
ecb.outf = xdiff_outf;
ecb.priv = diff_words;
diff_words->xm.consume = fn_out_diff_words_aux;
{
static struct git_attr *attr_diff;
- if (!attr_diff)
+ if (!attr_diff) {
attr_diff = git_attr("diff", 4);
- check->attr = attr_diff;
+ }
+ check[0].attr = attr_diff;
}
-static int file_is_binary(struct diff_filespec *one)
+static void diff_filespec_check_attr(struct diff_filespec *one)
{
struct git_attr_check attr_diff_check;
+ int check_from_data = 0;
+
+ if (one->checked_attr)
+ return;
setup_diff_attr_check(&attr_diff_check);
+ one->is_binary = 0;
+ one->funcname_pattern_ident = NULL;
+
if (!git_checkattr(one->path, 1, &attr_diff_check)) {
- const char *value = attr_diff_check.value;
+ const char *value;
+
+ /* binaryness */
+ value = attr_diff_check.value;
if (ATTR_TRUE(value))
- return 0;
+ ;
else if (ATTR_FALSE(value))
- return 1;
+ one->is_binary = 1;
+ else
+ check_from_data = 1;
+
+ /* funcname pattern ident */
+ if (ATTR_TRUE(value) || ATTR_FALSE(value) || ATTR_UNSET(value))
+ ;
+ else
+ one->funcname_pattern_ident = value;
}
- if (!one->data) {
- if (!DIFF_FILE_VALID(one))
- return 0;
- diff_populate_filespec(one, 0);
+ if (check_from_data) {
+ if (!one->data && DIFF_FILE_VALID(one))
+ diff_populate_filespec(one, 0);
+
+ if (one->data)
+ one->is_binary = buffer_is_binary(one->data, one->size);
}
- return buffer_is_binary(one->data, one->size);
+}
+
+int diff_filespec_is_binary(struct diff_filespec *one)
+{
+ diff_filespec_check_attr(one);
+ return one->is_binary;
+}
+
+static const char *funcname_pattern(const char *ident)
+{
+ struct funcname_pattern *pp;
+
+ read_config_if_needed();
+ for (pp = funcname_pattern_list; pp; pp = pp->next)
+ if (!strcmp(ident, pp->name))
+ return pp->pattern;
+ return NULL;
+}
+
+static struct builtin_funcname_pattern {
+ const char *name;
+ const char *pattern;
+} builtin_funcname_pattern[] = {
+ { "java", "!^[ ]*\\(catch\\|do\\|for\\|if\\|instanceof\\|"
+ "new\\|return\\|switch\\|throw\\|while\\)\n"
+ "^[ ]*\\(\\([ ]*"
+ "[A-Za-z_][A-Za-z_0-9]*\\)\\{2,\\}"
+ "[ ]*([^;]*$\\)" },
+ { "tex", "^\\(\\\\\\(sub\\)*section{.*\\)$" },
+};
+
+static const char *diff_funcname_pattern(struct diff_filespec *one)
+{
+ const char *ident, *pattern;
+ int i;
+
+ diff_filespec_check_attr(one);
+ ident = one->funcname_pattern_ident;
+
+ if (!ident)
+ /*
+ * If the config file has "funcname.default" defined, that
+ * regexp is used; otherwise NULL is returned and xemit uses
+ * the built-in default.
+ */
+ return funcname_pattern("default");
+
+ /* Look up custom "funcname.$ident" regexp from config. */
+ pattern = funcname_pattern(ident);
+ if (pattern)
+ return pattern;
+
+ /*
+ * And define built-in fallback patterns here. Note that
+ * these can be overriden by the user's config settings.
+ */
+ for (i = 0; i < ARRAY_SIZE(builtin_funcname_pattern); i++)
+ if (!strcmp(ident, builtin_funcname_pattern[i].name))
+ return builtin_funcname_pattern[i].pattern;
+
+ return NULL;
}
static void builtin_diff(const char *name_a,
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
die("unable to read files to diff");
- if (!o->text && (file_is_binary(one) || file_is_binary(two))) {
+ if (!o->text &&
+ (diff_filespec_is_binary(one) || diff_filespec_is_binary(two))) {
/* Quite common confusing case */
if (mf1.size == mf2.size &&
!memcmp(mf1.ptr, mf2.ptr, mf1.size))
xdemitconf_t xecfg;
xdemitcb_t ecb;
struct emit_callback ecbdata;
+ const char *funcname_pattern;
+
+ funcname_pattern = diff_funcname_pattern(one);
+ if (!funcname_pattern)
+ funcname_pattern = diff_funcname_pattern(two);
+ memset(&xecfg, 0, sizeof(xecfg));
memset(&ecbdata, 0, sizeof(ecbdata));
ecbdata.label_path = lbl;
ecbdata.color_diff = o->color_diff;
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
xecfg.ctxlen = o->context;
xecfg.flags = XDL_EMIT_FUNCNAMES;
+ if (funcname_pattern)
+ xdiff_set_find_func(&xecfg, funcname_pattern);
if (!diffopts)
;
else if (!prefixcmp(diffopts, "--unified="))
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
die("unable to read files to diff");
- if (file_is_binary(one) || file_is_binary(two)) {
+ if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
data->is_binary = 1;
data->added = mf2.size;
data->deleted = mf1.size;
xdemitconf_t xecfg;
xdemitcb_t ecb;
+ memset(&xecfg, 0, sizeof(xecfg));
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
- xecfg.ctxlen = 0;
- xecfg.flags = 0;
ecb.outf = xdiff_outf;
ecb.priv = diffstat;
xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
die("unable to read files to diff");
- if (file_is_binary(two))
+ if (diff_filespec_is_binary(two))
goto free_and_return;
else {
/* Crazy xdl interfaces.. */
xdemitconf_t xecfg;
xdemitcb_t ecb;
+ memset(&xecfg, 0, sizeof(xecfg));
xpp.flags = XDF_NEED_MINIMAL;
- xecfg.ctxlen = 0;
- xecfg.flags = 0;
ecb.outf = xdiff_outf;
ecb.priv = &data;
xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
if (size_only && 0 < s->size)
return 0;
- if (S_ISDIRLNK(s->mode))
+ if (S_ISGITLINK(s->mode))
return diff_populate_gitlink(s, size_only);
if (!s->sha1_valid ||
fd = git_mkstemp(temp->tmp_path, PATH_MAX, ".diff_XXXXXX");
if (fd < 0)
- die("unable to create temp-file");
+ die("unable to create temp-file: %s", strerror(errno));
if (write_in_full(fd, blob, size) != size)
die("unable to write temp-file");
close(fd);
!ATTR_UNSET(value)) {
struct ll_diff_driver *drv;
- if (!user_diff_tail) {
- user_diff_tail = &user_diff;
- git_config(git_diff_ui_config);
- }
+ read_config_if_needed();
for (drv = user_diff; drv; drv = drv->next)
if (!strcmp(drv->name, value))
return drv->cmd;
hashclr(one->sha1);
}
+static int similarity_index(struct diff_filepair *p)
+{
+ return p->score * 100 / MAX_SCORE;
+}
+
static void run_diff(struct diff_filepair *p, struct diff_options *o)
{
const char *pgm = external_diff();
"similarity index %d%%\n"
"copy from %s\n"
"copy to %s\n",
- (int)(0.5 + p->score * 100.0/MAX_SCORE),
- name_munged, other_munged);
+ similarity_index(p), name_munged, other_munged);
break;
case DIFF_STATUS_RENAMED:
len += snprintf(msg + len, sizeof(msg) - len,
"similarity index %d%%\n"
"rename from %s\n"
"rename to %s\n",
- (int)(0.5 + p->score * 100.0/MAX_SCORE),
- name_munged, other_munged);
+ similarity_index(p), name_munged, other_munged);
break;
case DIFF_STATUS_MODIFIED:
if (p->score) {
len += snprintf(msg + len, sizeof(msg) - len,
"dissimilarity index %d%%\n",
- (int)(0.5 + p->score *
- 100.0/MAX_SCORE));
+ similarity_index(p));
complete_rewrite = 1;
break;
}
if (o->binary) {
mmfile_t mf;
- if ((!fill_mmfile(&mf, one) && file_is_binary(one)) ||
- (!fill_mmfile(&mf, two) && file_is_binary(two)))
+ if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
+ (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
abbrev = 40;
}
len += snprintf(msg + len, sizeof(msg) - len,
return 1;
}
+static int diff_scoreopt_parse(const char *opt);
+
int diff_opt_parse(struct diff_options *options, const char **av, int ac)
{
const char *arg = av[0];
options->detect_rename = DIFF_DETECT_RENAME;
}
else if (!prefixcmp(arg, "-C")) {
+ if (options->detect_rename == DIFF_DETECT_COPY)
+ options->find_copies_harder = 1;
if ((options->rename_score =
diff_scoreopt_parse(arg)) == -1)
return -1;
}
else if (!strcmp(arg, "--find-copies-harder"))
options->find_copies_harder = 1;
+ else if (!strcmp(arg, "--follow"))
+ options->follow_renames = 1;
else if (!strcmp(arg, "--abbrev"))
options->abbrev = DEFAULT_ABBREV;
else if (!prefixcmp(arg, "--abbrev=")) {
options->exit_with_status = 1;
else if (!strcmp(arg, "--quiet"))
options->quiet = 1;
+ else if (!strcmp(arg, "--ext-diff"))
+ options->allow_external = 1;
+ else if (!strcmp(arg, "--no-ext-diff"))
+ options->allow_external = 0;
else
return 0;
return 1;
return (int)((num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale));
}
-int diff_scoreopt_parse(const char *opt)
+static int diff_scoreopt_parse(const char *opt)
{
int opt1, opt2, cmd;
}
if (p->score)
- sprintf(status, "%c%03d", p->status,
- (int)(0.5 + p->score * 100.0/MAX_SCORE));
+ sprintf(status, "%c%03d", p->status, similarity_index(p));
else {
status[0] = p->status;
status[1] = 0;
{
char *names = pprint_rename(p->one->path, p->two->path);
- printf(" %s %s (%d%%)\n", renamecopy, names,
- (int)(0.5 + p->score * 100.0/MAX_SCORE));
+ printf(" %s %s (%d%%)\n", renamecopy, names, similarity_index(p));
free(names);
show_mode_change(p, 0);
}
if (p->score) {
char *name = quote_one(p->two->path);
printf(" rewrite %s (%d%%)\n", name,
- (int)(0.5 + p->score * 100.0/MAX_SCORE));
+ similarity_index(p));
free(name);
show_mode_change(p, 0);
} else show_mode_change(p, 1);
struct diff_filepair *p = q->queue[i];
int len1, len2;
+ memset(&xecfg, 0, sizeof(xecfg));
if (p->status == 0)
return error("internal diff status error");
if (p->status == DIFF_STATUS_UNKNOWN)
return error("unable to read files to diff");
/* Maybe hash p->two? into the patch id? */
- if (file_is_binary(p->two))
+ if (diff_filespec_is_binary(p->two))
continue;
len1 = remove_space(p->one->path, strlen(p->one->path));
{
if (options->quiet)
return;
+
if (options->break_opt != -1)
diffcore_break(options->break_opt);
if (options->detect_rename)
* entries to the diff-core. They will be prefixed
* with something like '=' or '*' (I haven't decided
* which but should not make any difference).
- * Feeding the same new and old to diff_change()
+ * Feeding the same new and old to diff_change()
* also has the same effect.
* Before the final output happens, they are pruned after
* merged into rename/copy pairs as appropriate.
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *base, const char *path)
+ const char *base, const char *path)
{
char concatpath[PATH_MAX];
struct diff_filespec *one, *two;
full_index:1,
silent_on_remove:1,
find_copies_harder:1,
+ follow_renames:1,
color_diff:1,
color_diff_words:1,
has_changes:1,
unsigned mode,
const unsigned char *sha1);
-extern int diff_scoreopt_parse(const char *opt);
-
#define DIFF_SETUP_REVERSE 1
#define DIFF_SETUP_USE_CACHE 2
#define DIFF_SETUP_USE_SIZE_CACHE 4
if (base_size < MINIMUM_BREAK_SIZE)
return 0; /* we do not break too small filepair */
- if (diffcore_count_changes(src->data, src->size,
- dst->data, dst->size,
+ if (diffcore_count_changes(src, dst,
NULL, NULL,
0,
&src_copied, &literal_added))
/*
* Idea here is very simple.
*
- * We have total of (sz-N+1) N-byte overlapping sequences in buf whose
- * size is sz. If the same N-byte sequence appears in both source and
- * destination, we say the byte that starts that sequence is shared
- * between them (i.e. copied from source to destination).
+ * Almost all data we are interested in are text, but sometimes we have
+ * to deal with binary data. So we cut them into chunks delimited by
+ * LF byte, or 64-byte sequence, whichever comes first, and hash them.
*
- * For each possible N-byte sequence, if the source buffer has more
- * instances of it than the destination buffer, that means the
- * difference are the number of bytes not copied from source to
- * destination. If the counts are the same, everything was copied
- * from source to destination. If the destination has more,
- * everything was copied, and destination added more.
+ * For those chunks, if the source buffer has more instances of it
+ * than the destination buffer, that means the difference are the
+ * number of bytes not copied from source to destination. If the
+ * counts are the same, everything was copied from source to
+ * destination. If the destination has more, everything was copied,
+ * and destination added more.
*
* We are doing an approximation so we do not really have to waste
* memory by actually storing the sequence. We just hash them into
* somewhere around 2^16 hashbuckets and count the occurrences.
- *
- * The length of the sequence is arbitrarily set to 8 for now.
*/
/* Wild guess at the initial hash size */
}
}
-static struct spanhash_top *hash_chars(unsigned char *buf, unsigned int sz)
+static struct spanhash_top *hash_chars(struct diff_filespec *one)
{
int i, n;
unsigned int accum1, accum2, hashval;
struct spanhash_top *hash;
+ unsigned char *buf = one->data;
+ unsigned int sz = one->size;
+ int is_text = !diff_filespec_is_binary(one);
i = INITIAL_HASH_SIZE;
hash = xmalloc(sizeof(*hash) + sizeof(struct spanhash) * (1<<i));
unsigned int c = *buf++;
unsigned int old_1 = accum1;
sz--;
+
+ /* Ignore CR in CRLF sequence if text */
+ if (is_text && c == '\r' && sz && *buf == '\n')
+ continue;
+
accum1 = (accum1 << 7) ^ (accum2 >> 25);
accum2 = (accum2 << 7) ^ (old_1 >> 25);
accum1 += c;
return hash;
}
-int diffcore_count_changes(void *src, unsigned long src_size,
- void *dst, unsigned long dst_size,
+int diffcore_count_changes(struct diff_filespec *src,
+ struct diff_filespec *dst,
void **src_count_p,
void **dst_count_p,
unsigned long delta_limit,
if (src_count_p)
src_count = *src_count_p;
if (!src_count) {
- src_count = hash_chars(src, src_size);
+ src_count = hash_chars(src);
if (src_count_p)
*src_count_p = src_count;
}
if (dst_count_p)
dst_count = *dst_count_p;
if (!dst_count) {
- dst_count = hash_chars(dst, dst_size);
+ dst_count = hash_chars(dst);
if (dst_count_p)
*dst_count_p = dst_count;
}
for (i = 0; i < q->nr; i++)
diff_free_filepair(q->queue[i]);
}
- else
+ else
/* Showing only the filepairs that has the needle */
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
return 0;
}
+static int basename_same(struct diff_filespec *src, struct diff_filespec *dst)
+{
+ int src_len = strlen(src->path), dst_len = strlen(dst->path);
+ while (src_len && dst_len) {
+ char c1 = src->path[--src_len];
+ char c2 = dst->path[--dst_len];
+ if (c1 != c2)
+ return 0;
+ if (c1 == '/')
+ return 1;
+ }
+ return (!src_len || src->path[src_len - 1] == '/') &&
+ (!dst_len || dst->path[dst_len - 1] == '/');
+}
+
struct diff_score {
int src; /* index in rename_src */
int dst; /* index in rename_dst */
int score;
+ int name_score;
};
static int estimate_similarity(struct diff_filespec *src,
delta_limit = (unsigned long)
(base_size * (MAX_SCORE-minimum_score) / MAX_SCORE);
- if (diffcore_count_changes(src->data, src->size,
- dst->data, dst->size,
+ if (diffcore_count_changes(src, dst,
&src->cnt_data, &dst->cnt_data,
delta_limit,
&src_copied, &literal_added))
static int score_compare(const void *a_, const void *b_)
{
const struct diff_score *a = a_, *b = b_;
+
+ if (a->score == b->score)
+ return b->name_score - a->name_score;
+
return b->score - a->score;
}
if (rename_dst[i].pair)
continue; /* dealt with an earlier round */
for (j = 0; j < rename_src_nr; j++) {
+ int k;
struct diff_filespec *one = rename_src[j].one;
if (!is_exact_match(one, two, contents_too))
continue;
+
+ /* see if there is a basename match, too */
+ for (k = j; k < rename_src_nr; k++) {
+ one = rename_src[k].one;
+ if (basename_same(one, two) &&
+ is_exact_match(one, two,
+ contents_too)) {
+ j = k;
+ break;
+ }
+ }
+
record_rename_pair(i, j, (int)MAX_SCORE);
rename_count++;
break; /* we are done with this entry */
m->dst = i;
m->score = estimate_similarity(one, two,
minimum_score);
+ m->name_score = basename_same(one, two);
diff_free_filespec_data(one);
}
/* We do not need the text anymore */
char *path;
void *data;
void *cnt_data;
+ const char *funcname_pattern_ident;
unsigned long size;
int xfrm_flags; /* for use by the xfrm */
unsigned short mode; /* file mode */
#define DIFF_FILE_VALID(spec) (((spec)->mode) != 0)
unsigned should_free : 1; /* data should be free()'ed */
unsigned should_munmap : 1; /* data should be munmap()'ed */
+ unsigned checked_attr : 1;
+ unsigned is_binary : 1; /* data should be considered "binary" */
};
extern struct diff_filespec *alloc_filespec(const char *);
extern int diff_populate_filespec(struct diff_filespec *, int);
extern void diff_free_filespec_data(struct diff_filespec *);
+extern int diff_filespec_is_binary(struct diff_filespec *);
struct diff_filepair {
struct diff_filespec *one;
#define diff_debug_queue(a,b) do {} while(0)
#endif
-extern int diffcore_count_changes(void *src, unsigned long src_size,
- void *dst, unsigned long dst_size,
+extern int diffcore_count_changes(struct diff_filespec *src,
+ struct diff_filespec *dst,
void **src_count_p,
void **dst_count_p,
unsigned long delta_limit,
return 0;
}
-struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
-{
+static struct dir_entry *dir_entry_new(const char *pathname, int len) {
struct dir_entry *ent;
- if (cache_name_pos(pathname, len) >= 0)
- return NULL;
-
- if (dir->nr == dir->alloc) {
- int alloc = alloc_nr(dir->alloc);
- dir->alloc = alloc;
- dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
- }
ent = xmalloc(sizeof(*ent) + len + 1);
- ent->ignored = ent->ignored_dir = 0;
ent->len = len;
memcpy(ent->name, pathname, len);
ent->name[len] = 0;
- dir->entries[dir->nr++] = ent;
return ent;
}
+struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
+{
+ if (cache_name_pos(pathname, len) >= 0)
+ return NULL;
+
+ ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
+ return dir->entries[dir->nr++] = dir_entry_new(pathname, len);
+}
+
+struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
+{
+ if (cache_name_pos(pathname, len) >= 0)
+ return NULL;
+
+ ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc);
+ return dir->ignored[dir->ignored_nr++] = dir_entry_new(pathname, len);
+}
+
enum exist_status {
index_nonexistent = 0,
index_directory,
break;
if (endchar == '/')
return index_directory;
- if (!endchar && S_ISDIRLNK(ntohl(ce->ce_mode)))
+ if (!endchar && S_ISGITLINK(ntohl(ce->ce_mode)))
return index_gitdir;
}
return index_nonexistent;
* also true and the directory is empty, in which case
* we just ignore it entirely.
* (b) if it looks like a git directory, and we don't have
- * 'no_dirlinks' set we treat it as a gitlink, and show it
+ * 'no_gitlinks' set we treat it as a gitlink, and show it
* as a directory.
* (c) otherwise, we recurse into it.
*/
case index_nonexistent:
if (dir->show_other_directories)
break;
- if (!dir->no_dirlinks) {
+ if (!dir->no_gitlinks) {
unsigned char sha1[20];
if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
return show_directory;
return 0;
}
+static int in_pathspec(const char *path, int len, const struct path_simplify *simplify)
+{
+ if (simplify) {
+ for (; simplify->path; simplify++) {
+ if (len == simplify->len
+ && !memcmp(path, simplify->path, len))
+ return 1;
+ }
+ }
+ return 0;
+}
+
/*
* Read a directory tree. We currently ignore anything but
* directories, regular files and symlinks. That's because git
continue;
exclude = excluded(dir, fullname);
+ if (exclude && dir->collect_ignored
+ && in_pathspec(fullname, baselen + len, simplify))
+ dir_add_ignored(dir, fullname, baselen + len);
if (exclude != dir->show_ignored) {
if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
continue;
read_directory_recursive(dir, path, base, baselen, 0, simplify);
free_simplify(simplify);
qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
+ qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
return dir->nr;
}
struct stat sb;
return stat(f, &sb) == 0;
}
+
+/*
+ * get_relative_cwd() gets the prefix of the current working directory
+ * relative to 'dir'. If we are not inside 'dir', it returns NULL.
+ *
+ * As a convenience, it also returns NULL if 'dir' is already NULL. The
+ * reason for this behaviour is that it is natural for functions returning
+ * directory names to return NULL to say "this directory does not exist"
+ * or "this directory is invalid". These cases are usually handled the
+ * same as if the cwd is not inside 'dir' at all, so get_relative_cwd()
+ * returns NULL for both of them.
+ *
+ * Most notably, get_relative_cwd(buffer, size, get_git_work_tree())
+ * unifies the handling of "outside work tree" with "no work tree at all".
+ */
+char *get_relative_cwd(char *buffer, int size, const char *dir)
+{
+ char *cwd = buffer;
+
+ if (!dir)
+ return NULL;
+ if (!getcwd(buffer, size))
+ die("can't find the current directory: %s", strerror(errno));
+
+ if (!is_absolute_path(dir))
+ dir = make_absolute_path(dir);
+
+ while (*dir && *dir == *cwd) {
+ dir++;
+ cwd++;
+ }
+ if (*dir)
+ return NULL;
+ if (*cwd == '/')
+ return cwd + 1;
+ return cwd;
+}
+
+int is_inside_dir(const char *dir)
+{
+ char buffer[PATH_MAX];
+ return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
+}
struct dir_entry {
- unsigned int ignored : 1;
- unsigned int ignored_dir : 1;
- unsigned int len : 30;
+ unsigned int len;
char name[FLEX_ARRAY]; /* more */
};
struct dir_struct {
int nr, alloc;
+ int ignored_nr, ignored_alloc;
unsigned int show_ignored:1,
show_other_directories:1,
hide_empty_directories:1,
- no_dirlinks:1;
+ no_gitlinks:1,
+ collect_ignored:1;
struct dir_entry **entries;
+ struct dir_entry **ignored;
/* Exclude info */
const char *exclude_per_dir;
extern int file_exists(const char *);
extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len);
+extern char *get_relative_cwd(char *buffer, int size, const char *dir);
+extern int is_inside_dir(const char *dir);
+
#endif
struct dirent *de;
char pathbuf[PATH_MAX];
char *name;
-
+
if (!dir)
die("cannot opendir %s (%s)", path, strerror(errno));
strcpy(pathbuf, path);
"symlink %s (%s)", path, strerror(errno));
}
break;
- case S_IFDIRLNK:
+ case S_IFGITLINK:
if (to_tempfile)
return error("git-checkout-index: cannot create temporary subproject %s", path);
if (mkdir(path, 0777) < 0)
unlink(path);
if (S_ISDIR(st.st_mode)) {
/* If it is a gitlink, leave it alone! */
- if (S_ISDIRLNK(ntohl(ce->ce_mode)))
+ if (S_ISGITLINK(ntohl(ce->ce_mode)))
return 0;
if (!state->force)
return error("%s is a directory", path);
char git_default_email[MAX_GITNAME];
char git_default_name[MAX_GITNAME];
-int use_legacy_headers = 1;
int trust_executable_bit = 1;
+int quote_path_fully = 1;
int has_symlinks = 1;
int assume_unchanged;
int prefer_symlink_refs;
const char *git_log_output_encoding;
int shared_repository = PERM_UMASK;
const char *apply_default_whitespace;
-int zlib_compression_level = Z_DEFAULT_COMPRESSION;
+int zlib_compression_level = Z_BEST_SPEED;
+int core_compression_level;
+int core_compression_seen;
size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
size_t delta_base_cache_limit = 16 * 1024 * 1024;
+char *pager_program;
int pager_in_use;
int pager_use_color = 1;
int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */
+/* This is set by setup_git_dir_gently() and/or git_default_config() */
+char *git_work_tree_cfg;
+static const char *work_tree;
+
static const char *git_dir;
static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
int is_bare_repository(void)
{
- const char *dir, *s;
- if (0 <= is_bare_repository_cfg)
- return is_bare_repository_cfg;
-
- dir = get_git_dir();
- if (!strcmp(dir, DEFAULT_GIT_DIR_ENVIRONMENT))
- return 0;
- s = strrchr(dir, '/');
- return !s || strcmp(s + 1, DEFAULT_GIT_DIR_ENVIRONMENT);
+ /* if core.bare is not 'false', let's see if there is a work tree */
+ return is_bare_repository_cfg && !get_git_work_tree();
}
const char *get_git_dir(void)
return git_dir;
}
+const char *get_git_work_tree(void)
+{
+ static int initialized = 0;
+ if (!initialized) {
+ work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+ /* core.bare = true overrides implicit and config work tree */
+ if (!work_tree && is_bare_repository_cfg < 1) {
+ work_tree = git_work_tree_cfg;
+ /* make_absolute_path also normalizes the path */
+ if (work_tree && !is_absolute_path(work_tree))
+ work_tree = xstrdup(make_absolute_path(git_path(work_tree)));
+ } else if (work_tree)
+ work_tree = xstrdup(make_absolute_path(work_tree));
+ initialized = 1;
+ if (work_tree)
+ is_bare_repository_cfg = 0;
+ }
+ return work_tree;
+}
+
char *get_object_directory(void)
{
if (!git_object_dir)
return git_graft_file;
}
-
+int set_git_dir(const char *path)
+{
+ if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
+ return error("Could not set GIT_DIR to '%s'", path);
+ setup_git_env();
+ return 0;
+}
lf;
commit_msg ::= data;
- file_change ::= file_clr | file_del | file_obm | file_inm;
+ file_change ::= file_clr
+ | file_del
+ | file_rnm
+ | file_cpy
+ | file_obm
+ | file_inm;
file_clr ::= 'deleteall' lf;
file_del ::= 'D' sp path_str lf;
+ file_rnm ::= 'R' sp path_str sp path_str lf;
+ file_cpy ::= 'C' sp path_str sp path_str lf;
file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
data;
avail_tree_entry = e;
}
+static struct tree_content *dup_tree_content(struct tree_content *s)
+{
+ struct tree_content *d;
+ struct tree_entry *a, *b;
+ unsigned int i;
+
+ if (!s)
+ return NULL;
+ d = new_tree_content(s->entry_count);
+ for (i = 0; i < s->entry_count; i++) {
+ a = s->entries[i];
+ b = new_tree_entry();
+ memcpy(b, a, sizeof(*a));
+ if (a->tree && is_null_sha1(b->versions[1].sha1))
+ b->tree = dup_tree_content(a->tree);
+ else
+ b->tree = NULL;
+ d->entries[i] = b;
+ }
+ d->entry_count = s->entry_count;
+ d->delta_depth = s->delta_depth;
+
+ return d;
+}
+
static void start_packfile(void)
{
static char tmpfile[PATH_MAX];
struct tree_entry *root,
const char *p,
const unsigned char *sha1,
- const uint16_t mode)
+ const uint16_t mode,
+ struct tree_content *subtree)
{
struct tree_content *t = root->tree;
const char *slash1;
n = strlen(p);
if (!n)
die("Empty path component found in input");
+ if (!slash1 && !S_ISDIR(mode) && subtree)
+ die("Non-directories cannot have subtrees");
for (i = 0; i < t->entry_count; i++) {
e = t->entries[i];
if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
if (!slash1) {
- if (e->versions[1].mode == mode
+ if (!S_ISDIR(mode)
+ && e->versions[1].mode == mode
&& !hashcmp(e->versions[1].sha1, sha1))
return 0;
e->versions[1].mode = mode;
hashcpy(e->versions[1].sha1, sha1);
- if (e->tree) {
+ if (e->tree)
release_tree_content_recursive(e->tree);
- e->tree = NULL;
- }
+ e->tree = subtree;
hashclr(root->versions[1].sha1);
return 1;
}
}
if (!e->tree)
load_tree(e);
- if (tree_content_set(e, slash1 + 1, sha1, mode)) {
+ if (tree_content_set(e, slash1 + 1, sha1, mode, subtree)) {
hashclr(root->versions[1].sha1);
return 1;
}
if (slash1) {
e->tree = new_tree_content(8);
e->versions[1].mode = S_IFDIR;
- tree_content_set(e, slash1 + 1, sha1, mode);
+ tree_content_set(e, slash1 + 1, sha1, mode, subtree);
} else {
- e->tree = NULL;
+ e->tree = subtree;
e->versions[1].mode = mode;
hashcpy(e->versions[1].sha1, sha1);
}
return 1;
}
-static int tree_content_remove(struct tree_entry *root, const char *p)
+static int tree_content_remove(
+ struct tree_entry *root,
+ const char *p,
+ struct tree_entry *backup_leaf)
{
struct tree_content *t = root->tree;
const char *slash1;
goto del_entry;
if (!e->tree)
load_tree(e);
- if (tree_content_remove(e, slash1 + 1)) {
+ if (tree_content_remove(e, slash1 + 1, backup_leaf)) {
for (n = 0; n < e->tree->entry_count; n++) {
if (e->tree->entries[n]->versions[1].mode) {
hashclr(root->versions[1].sha1);
return 1;
}
}
+ backup_leaf = NULL;
goto del_entry;
}
return 0;
return 0;
del_entry:
- if (e->tree) {
+ if (backup_leaf)
+ memcpy(backup_leaf, e, sizeof(*backup_leaf));
+ else if (e->tree)
release_tree_content_recursive(e->tree);
- e->tree = NULL;
- }
+ e->tree = NULL;
e->versions[1].mode = 0;
hashclr(e->versions[1].sha1);
hashclr(root->versions[1].sha1);
return 1;
}
+static int tree_content_get(
+ struct tree_entry *root,
+ const char *p,
+ struct tree_entry *leaf)
+{
+ struct tree_content *t = root->tree;
+ const char *slash1;
+ unsigned int i, n;
+ struct tree_entry *e;
+
+ slash1 = strchr(p, '/');
+ if (slash1)
+ n = slash1 - p;
+ else
+ n = strlen(p);
+
+ for (i = 0; i < t->entry_count; i++) {
+ e = t->entries[i];
+ if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+ if (!slash1) {
+ memcpy(leaf, e, sizeof(*leaf));
+ if (e->tree && is_null_sha1(e->versions[1].sha1))
+ leaf->tree = dup_tree_content(e->tree);
+ else
+ leaf->tree = NULL;
+ return 1;
+ }
+ if (!S_ISDIR(e->versions[1].mode))
+ return 0;
+ if (!e->tree)
+ load_tree(e);
+ return tree_content_get(e, slash1 + 1, leaf);
+ }
+ }
+ return 0;
+}
+
static int update_branch(struct branch *b)
{
static const char *msg = "fast-import";
if (read_ref(b->name, old_sha1))
hashclr(old_sha1);
- lock = lock_any_ref_for_update(b->name, old_sha1);
+ lock = lock_any_ref_for_update(b->name, old_sha1, 0);
if (!lock)
return error("Unable to lock %s", b->name);
if (!force_update && !is_null_sha1(old_sha1)) {
typename(type), command_buf.buf);
}
- tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode);
+ tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode, NULL);
free(p_uq);
}
die("Garbage after path in: %s", command_buf.buf);
p = p_uq;
}
- tree_content_remove(&b->branch_tree, p);
+ tree_content_remove(&b->branch_tree, p, NULL);
free(p_uq);
}
+static void file_change_cr(struct branch *b, int rename)
+{
+ const char *s, *d;
+ char *s_uq, *d_uq;
+ const char *endp;
+ struct tree_entry leaf;
+
+ s = command_buf.buf + 2;
+ s_uq = unquote_c_style(s, &endp);
+ if (s_uq) {
+ if (*endp != ' ')
+ die("Missing space after source: %s", command_buf.buf);
+ }
+ else {
+ endp = strchr(s, ' ');
+ if (!endp)
+ die("Missing space after source: %s", command_buf.buf);
+ s_uq = xmalloc(endp - s + 1);
+ memcpy(s_uq, s, endp - s);
+ s_uq[endp - s] = 0;
+ }
+ s = s_uq;
+
+ endp++;
+ if (!*endp)
+ die("Missing dest: %s", command_buf.buf);
+
+ d = endp;
+ d_uq = unquote_c_style(d, &endp);
+ if (d_uq) {
+ if (*endp)
+ die("Garbage after dest in: %s", command_buf.buf);
+ d = d_uq;
+ }
+
+ memset(&leaf, 0, sizeof(leaf));
+ if (rename)
+ tree_content_remove(&b->branch_tree, s, &leaf);
+ else
+ tree_content_get(&b->branch_tree, s, &leaf);
+ if (!leaf.versions[1].mode)
+ die("Path %s not in branch", s);
+ tree_content_set(&b->branch_tree, d,
+ leaf.versions[1].sha1,
+ leaf.versions[1].mode,
+ leaf.tree);
+
+ free(s_uq);
+ free(d_uq);
+}
+
static void file_change_deleteall(struct branch *b)
{
release_tree_content_recursive(b->branch_tree.tree);
file_change_m(b);
else if (!prefixcmp(command_buf.buf, "D "))
file_change_d(b);
+ else if (!prefixcmp(command_buf.buf, "R "))
+ file_change_cr(b, 1);
+ else if (!prefixcmp(command_buf.buf, "C "))
+ file_change_cr(b, 0);
else if (!strcmp("deleteall", command_buf.buf))
file_change_deleteall(b);
else
commit->object.flags |= POPPED;
if (!(commit->object.flags & COMMON))
non_common_revs--;
-
+
parents = commit->parents;
if (commit->object.flags & COMMON) {
}
if (!dest)
usage(fetch_pack_usage);
- pid = git_connect(fd, dest, uploadpack);
+ pid = git_connect(fd, dest, uploadpack, verbose ? CONNECT_VERBOSE : 0);
if (pid < 0)
return 1;
if (heads && nr_heads)
int get_recover = 0;
static unsigned char current_commit_sha1[20];
-void pull_say(const char *fmt, const char *hex)
+void pull_say(const char *fmt, const char *hex)
{
if (get_verbosely)
fprintf(stderr, fmt, hex);
struct object *obj = NULL;
/* submodule commits are not stored in the superproject */
- if (S_ISDIRLNK(entry.mode))
+ if (S_ISGITLINK(entry.mode))
continue;
if (S_ISDIR(entry.mode)) {
struct tree *tree = lookup_tree(entry.sha1);
return 0;
prefetch(obj->sha1);
}
-
+
object_list_insert(obj, process_queue_end);
process_queue_end = &(*process_queue_end)->next;
return 0;
--- /dev/null
+#!/bin/sh
+while [ "$1" ]
+do
+ old="$1"
+ new=$(echo "$1" | sed 's/git-/git /')
+ echo "Converting '$old' to '$new'"
+ git ls-files '*.sh' | while read file
+ do
+ sed "s/\\<$old\\>/$new/g" < $file > $file.new
+ chmod --reference=$file $file.new
+ mv $file.new $file
+ done
+ shift
+done
+git update-index --refresh >& /dev/null
+exit 0
char help[80];
};
-struct cmdname_help common_cmds[] = {"
+static struct cmdname_help common_cmds[] = {"
sort <<\EOF |
add
use strict;
sub run_cmd_pipe {
- my $fh = undef;
- open($fh, '-|', @_) or die;
- return <$fh>;
+ if ($^O eq 'MSWin32') {
+ my @invalid = grep {m/[":*]/} @_;
+ die "$^O does not support: @invalid\n" if @invalid;
+ my @args = map { m/ /o ? "\"$_\"": $_ } @_;
+ return qx{@args};
+ } else {
+ my $fh = undef;
+ open($fh, '-|', @_) or die;
+ return <$fh>;
+ }
}
my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
sub refresh {
my $fh;
- open $fh, '-|', qw(git update-index --refresh)
+ open $fh, 'git update-index --refresh |'
or die;
while (<$fh>) {
;# ignore 'needs update'
my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
map { $_->{VALUE} } @update);
my $fh;
- open $fh, '|-', qw(git update-index --index-info)
+ open $fh, '| git update-index --index-info'
or die;
for (@lines) {
print $fh $_;
if (@result) {
my $fh;
- open $fh, '|-', qw(git apply --cached);
+ open $fh, '| git apply --cached';
for (@{$head->{TEXT}}, @result) {
print $fh $_;
}
mkdir "$dotest/patch-merge-tmp-dir"
# First see if the patch records the index info that we can use.
- git-apply -z --index-info "$dotest/patch" \
+ git apply -z --index-info "$dotest/patch" \
>"$dotest/patch-merge-index-info" &&
GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
- git-update-index -z --index-info <"$dotest/patch-merge-index-info" &&
+ git update-index -z --index-info <"$dotest/patch-merge-index-info" &&
GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
- git-write-tree >"$dotest/patch-merge-base+" ||
+ git write-tree >"$dotest/patch-merge-base+" ||
cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
echo Using index info to reconstruct a base tree...
if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
- git-apply $binary --cached <"$dotest/patch"
+ git apply $binary --cached <"$dotest/patch"
then
mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
fi
test -f "$dotest/patch-merge-index" &&
- his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git-write-tree) &&
+ his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git write-tree) &&
orig_tree=$(cat "$dotest/patch-merge-base") &&
rm -fr "$dotest"/patch-merge-* || exit 1
eval GITHEAD_$his_tree='"$SUBJECT"'
export GITHEAD_$his_tree
git-merge-recursive $orig_tree -- HEAD $his_tree || {
- if test -d "$GIT_DIR/rr-cache"
- then
- git-rerere
- fi
+ git rerere
echo Failed to merge in the changes.
exit 1
}
# Start afresh.
mkdir -p "$dotest" || exit
- git-mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" || {
+ git mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" || {
rm -fr "$dotest"
exit 1
}
case "$resolved" in
'')
- files=$(git-diff-index --cached --name-only HEAD) || exit
+ files=$(git diff-index --cached --name-only HEAD) || exit
if [ "$files" ]; then
echo "Dirty index: cannot apply patches (dirty: $files)" >&2
exit 1
this=`cat "$dotest/next"`
if test "$skip" = t
then
- if test -d "$GIT_DIR/rr-cache"
- then
- git-rerere clear
- fi
+ git rerere clear
this=`expr "$this" + 1`
resume=
fi
# by the user, or the user can tell us to do so by --resolved flag.
case "$resume" in
'')
- git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
+ git mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
<"$dotest/$msgnum" >"$dotest/info" ||
stop_here $this
+
+ # skip pine's internal folder data
+ grep '^Author: Mail System Internal Data$' \
+ <"$dotest"/info >/dev/null &&
+ go_next && continue
+
test -s $dotest/patch || {
echo "Patch is empty. Was it split wrong?"
stop_here $this
}
- git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
+ git stripspace < "$dotest/msg" > "$dotest/msg-clean"
;;
esac
case "$resolved$interactive" in
tt)
# This is used only for interactive view option.
- git-diff-index -p --cached HEAD >"$dotest/patch"
+ git diff-index -p --cached HEAD >"$dotest/patch"
;;
esac
esac
[yY]*) action=yes ;;
[aA]*) action=yes interactive= ;;
[nN]*) action=skip ;;
- [eE]*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit"
+ [eE]*) git_editor "$dotest/final-commit"
action=again ;;
[vV]*) action=again
LESS=-S ${PAGER:-less} "$dotest/patch" ;;
case "$resolved" in
'')
- git-apply $git_apply_opt $binary --index "$dotest/patch"
+ git apply $git_apply_opt $binary --index "$dotest/patch"
apply_status=$?
;;
t)
# trust what the user has in the index file and the
# working tree.
resolved=
- git-diff-index --quiet --cached HEAD && {
+ git diff-index --quiet --cached HEAD && {
echo "No changes - did you forget to use 'git add'?"
stop_here_user_resolve $this
}
- unmerged=$(git-ls-files -u)
+ unmerged=$(git ls-files -u)
if test -n "$unmerged"
then
echo "You still have unmerged paths in your index"
stop_here_user_resolve $this
fi
apply_status=0
- if test -d "$GIT_DIR/rr-cache"
- then
- git rerere
- fi
+ git rerere
;;
esac
then
# Applying the patch to an earlier tree and merging the
# result may have produced the same tree as ours.
- git-diff-index --quiet --cached HEAD && {
+ git diff-index --quiet --cached HEAD && {
echo No changes -- Patch already applied.
go_next
continue
"$GIT_DIR"/hooks/pre-applypatch || stop_here $this
fi
- tree=$(git-write-tree) &&
+ tree=$(git write-tree) &&
echo Wrote tree $tree &&
- parent=$(git-rev-parse --verify HEAD) &&
- commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
+ parent=$(git rev-parse --verify HEAD) &&
+ commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") &&
echo Committed: $commit &&
- git-update-ref -m "$GIT_REFLOG_ACTION: $SUBJECT" HEAD $commit $parent ||
+ git update-ref -m "$GIT_REFLOG_ACTION: $SUBJECT" HEAD $commit $parent ||
stop_here $this
if test -x "$GIT_DIR"/hooks/post-applypatch
+++ /dev/null
-#!/bin/sh
-##
-## "dotest" is my stupid name for my patch-application script, which
-## I never got around to renaming after I tested it. We're now on the
-## second generation of scripts, still called "dotest".
-##
-## Update: Ryan Anderson finally shamed me into naming this "applymbox".
-##
-## You give it a mbox-format collection of emails, and it will try to
-## apply them to the kernel using "applypatch"
-##
-## The patch application may fail in the middle. In which case:
-## (1) look at .dotest/patch and fix it up to apply
-## (2) re-run applymbox with -c .dotest/msg-number for the current one.
-## Pay a special attention to the commit log message if you do this and
-## use a Signoff_file, because applypatch wants to append the sign-off
-## message to msg-clean every time it is run.
-##
-## git-am is supposed to be the newer and better tool for this job.
-
-USAGE='[-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]'
-. git-sh-setup
-
-git var GIT_COMMITTER_IDENT >/dev/null || exit
-
-keep_subject= query_apply= continue= utf8=-u resume=t
-while case "$#" in 0) break ;; esac
-do
- case "$1" in
- -u) utf8=-u ;;
- -n) utf8=-n ;;
- -k) keep_subject=-k ;;
- -q) query_apply=t ;;
- -c) continue="$2"; resume=f; shift ;;
- -m) fall_back_3way=t ;;
- -*) usage ;;
- *) break ;;
- esac
- shift
-done
-
-case "$continue" in
-'')
- rm -rf .dotest
- mkdir .dotest
- num_msgs=$(git-mailsplit "$1" .dotest) || exit 1
- echo "$num_msgs patch(es) to process."
- shift
-esac
-
-files=$(git-diff-index --cached --name-only HEAD) || exit
-if [ "$files" ]; then
- echo "Dirty index: cannot apply patches (dirty: $files)" >&2
- exit 1
-fi
-
-case "$query_apply" in
-t) touch .dotest/.query_apply
-esac
-case "$fall_back_3way" in
-t) : >.dotest/.3way
-esac
-case "$keep_subject" in
--k) : >.dotest/.keep_subject
-esac
-
-signoff="$1"
-set x .dotest/0*
-shift
-while case "$#" in 0) break;; esac
-do
- i="$1"
- case "$resume,$continue" in
- f,$i) resume=t;;
- f,*) shift
- continue;;
- *)
- git-mailinfo $keep_subject $utf8 \
- .dotest/msg .dotest/patch <$i >.dotest/info || exit 1
- test -s .dotest/patch || {
- echo "Patch is empty. Was it split wrong?"
- exit 1
- }
- git-stripspace < .dotest/msg > .dotest/msg-clean
- ;;
- esac
- while :; # for fixing up and retry
- do
- git-applypatch .dotest/msg-clean .dotest/patch .dotest/info "$signoff"
- case "$?" in
- 0)
- # Remove the cleanly applied one to reduce clutter.
- rm -f .dotest/$i
- ;;
- 2)
- # 2 is a special exit code from applypatch to indicate that
- # the patch wasn't applied, but continue anyway
- ;;
- *)
- ret=$?
- if test -f .dotest/.query_apply
- then
- echo >&2 "* Patch failed."
- echo >&2 "* You could fix it up in your editor and"
- echo >&2 " retry. If you want to do so, say yes here"
- echo >&2 " AFTER fixing .dotest/patch up."
- echo >&2 -n "Retry [y/N]? "
- read yesno
- case "$yesno" in
- [Yy]*)
- continue ;;
- esac
- fi
- exit $ret
- esac
- break
- done
- shift
-done
-# return to pristine
-rm -fr .dotest
+++ /dev/null
-#!/bin/sh
-##
-## applypatch takes four file arguments, and uses those to
-## apply the unpacked patch (surprise surprise) that they
-## represent to the current tree.
-##
-## The arguments are:
-## $1 - file with commit message
-## $2 - file with the actual patch
-## $3 - "info" file with Author, email and subject
-## $4 - optional file containing signoff to add
-##
-
-USAGE='<msg> <patch> <info> [<signoff>]'
-. git-sh-setup
-
-case "$#" in 3|4) ;; *) usage ;; esac
-
-final=.dotest/final-commit
-##
-## If this file exists, we ask before applying
-##
-query_apply=.dotest/.query_apply
-
-## We do not munge the first line of the commit message too much
-## if this file exists.
-keep_subject=.dotest/.keep_subject
-
-## We do not attempt the 3-way merge fallback unless this file exists.
-fall_back_3way=.dotest/.3way
-
-MSGFILE=$1
-PATCHFILE=$2
-INFO=$3
-SIGNOFF=$4
-EDIT=${VISUAL:-${EDITOR:-vi}}
-
-export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$INFO")"
-export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$INFO")"
-export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$INFO")"
-export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$INFO")"
-
-if test '' != "$SIGNOFF"
-then
- if test -f "$SIGNOFF"
- then
- SIGNOFF=`cat "$SIGNOFF"` || exit
- elif case "$SIGNOFF" in yes | true | me | please) : ;; *) false ;; esac
- then
- SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
- s/>.*/>/
- s/^/Signed-off-by: /'
- `
- else
- SIGNOFF=
- fi
- if test '' != "$SIGNOFF"
- then
- LAST_SIGNED_OFF_BY=`
- sed -ne '/^Signed-off-by: /p' "$MSGFILE" |
- tail -n 1
- `
- test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
- test '' = "$LAST_SIGNED_OFF_BY" && echo
- echo "$SIGNOFF"
- } >>"$MSGFILE"
- fi
-fi
-
-patch_header=
-test -f "$keep_subject" || patch_header='[PATCH] '
-
-{
- echo "$patch_header$SUBJECT"
- if test -s "$MSGFILE"
- then
- echo
- cat "$MSGFILE"
- fi
-} >"$final"
-
-interactive=yes
-test -f "$query_apply" || interactive=no
-
-while [ "$interactive" = yes ]; do
- echo "Commit Body is:"
- echo "--------------------------"
- cat "$final"
- echo "--------------------------"
- printf "Apply? [y]es/[n]o/[e]dit/[a]ccept all "
- read reply
- case "$reply" in
- y|Y) interactive=no;;
- n|N) exit 2;; # special value to tell dotest to keep going
- e|E) "$EDIT" "$final";;
- a|A) rm -f "$query_apply"
- interactive=no ;;
- esac
-done
-
-if test -x "$GIT_DIR"/hooks/applypatch-msg
-then
- "$GIT_DIR"/hooks/applypatch-msg "$final" || exit
-fi
-
-echo
-echo Applying "'$SUBJECT'"
-echo
-
-git-apply --index "$PATCHFILE" || {
-
- # git-apply exits with status 1 when the patch does not apply,
- # but it die()s with other failures, most notably upon corrupt
- # patch. In the latter case, there is no point to try applying
- # it to another tree and do 3-way merge.
- test $? = 1 || exit 1
-
- test -f "$fall_back_3way" || exit 1
-
- # Here if we know which revision the patch applies to,
- # we create a temporary working tree and index, apply the
- # patch, and attempt 3-way merge with the resulting tree.
-
- O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
- rm -fr .patch-merge-*
-
- if git-apply -z --index-info "$PATCHFILE" \
- >.patch-merge-index-info 2>/dev/null &&
- GIT_INDEX_FILE=.patch-merge-tmp-index \
- git-update-index -z --index-info <.patch-merge-index-info &&
- GIT_INDEX_FILE=.patch-merge-tmp-index \
- git-write-tree >.patch-merge-tmp-base &&
- (
- mkdir .patch-merge-tmp-dir &&
- cd .patch-merge-tmp-dir &&
- GIT_INDEX_FILE="../.patch-merge-tmp-index" \
- GIT_OBJECT_DIRECTORY="$O_OBJECT" \
- git-apply $binary --index
- ) <"$PATCHFILE"
- then
- echo Using index info to reconstruct a base tree...
- mv .patch-merge-tmp-base .patch-merge-base
- mv .patch-merge-tmp-index .patch-merge-index
- else
- (
- N=10
-
- # Otherwise, try nearby trees that can be used to apply the
- # patch.
- git-rev-list --max-count=$N HEAD
-
- # or hoping the patch is against known tags...
- git-ls-remote --tags .
- ) |
- while read base junk
- do
- # Try it if we have it as a tree.
- git-cat-file tree "$base" >/dev/null 2>&1 || continue
-
- rm -fr .patch-merge-tmp-* &&
- mkdir .patch-merge-tmp-dir || break
- (
- cd .patch-merge-tmp-dir &&
- GIT_INDEX_FILE=../.patch-merge-tmp-index &&
- GIT_OBJECT_DIRECTORY="$O_OBJECT" &&
- export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY &&
- git-read-tree "$base" &&
- git-apply --index &&
- mv ../.patch-merge-tmp-index ../.patch-merge-index &&
- echo "$base" >../.patch-merge-base
- ) <"$PATCHFILE" 2>/dev/null && break
- done
- fi
-
- test -f .patch-merge-index &&
- his_tree=$(GIT_INDEX_FILE=.patch-merge-index git-write-tree) &&
- orig_tree=$(cat .patch-merge-base) &&
- rm -fr .patch-merge-* || exit 1
-
- echo Falling back to patching base and 3-way merge using $orig_tree...
-
- # This is not so wrong. Depending on which base we picked,
- # orig_tree may be wildly different from ours, but his_tree
- # has the same set of wildly different changes in parts the
- # patch did not touch, so resolve ends up canceling them,
- # saying that we reverted all those changes.
-
- if git-merge-resolve $orig_tree -- HEAD $his_tree
- then
- echo Done.
- else
- echo Failed to merge in the changes.
- exit 1
- fi
-}
-
-if test -x "$GIT_DIR"/hooks/pre-applypatch
-then
- "$GIT_DIR"/hooks/pre-applypatch || exit
-fi
-
-tree=$(git-write-tree) || exit 1
-echo Wrote tree $tree
-parent=$(git-rev-parse --verify HEAD) &&
-commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1
-echo Committed: $commit
-git-update-ref -m "applypatch: $SUBJECT" HEAD $commit $parent || exit
-
-if test -x "$GIT_DIR"/hooks/post-applypatch
-then
- "$GIT_DIR"/hooks/post-applypatch
-fi
# This tool is copyright (c) 2005, Martin Langhoff.
# It is released under the Gnu Public License, version 2.
#
-# The basic idea is to walk the output of tla abrowse,
-# fetch the changesets and apply them.
+# The basic idea is to walk the output of tla abrowse,
+# fetch the changesets and apply them.
#
=head1 Invocation
- git-archimport [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ]
- [ -D depth] [ -t tempdir ] <archive>/<branch> [ <archive>/<branch> ]
+ git-archimport [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ]
+ [ -D depth] [ -t tempdir ] <archive>/<branch> [ <archive>/<branch> ]
Imports a project from one or more Arch repositories. It will follow branches
and repositories within the namespaces defined by the <archive/branch>
parameters supplied. If it cannot find the remote branch a merge comes from
-it will just import it as a regular commit. If it can find it, it will mark it
+it will just import it as a regular commit. If it can find it, it will mark it
as a merge whenever possible.
See man (1) git-archimport for more details.
- create tag objects instead of ref tags
- audit shell-escaping of filenames
- hide our private tags somewhere smarter
- - find a way to make "cat *patches | patch" safe even when patchfiles are missing newlines
+ - find a way to make "cat *patches | patch" safe even when patchfiles are missing newlines
- sort and apply patches by graphing ancestry relations instead of just
relying in dates supplied in the changeset itself.
tla ancestry-graph -m could be helpful here...
=head1 Devel tricks
-Add print in front of the shell commands invoked via backticks.
+Add print in front of the shell commands invoked via backticks.
=head1 Devel Notes
my $stage = shift;
while (my ($limit, $level) = each %arch_branches) {
next unless $level == $stage;
-
- open ABROWSE, "$TLA abrowse -fkD --merges $limit |"
+
+ open ABROWSE, "$TLA abrowse -fkD --merges $limit |"
or die "Problems with tla abrowse: $!";
-
+
my %ps = (); # the current one
my $lastseen = '';
-
+
while (<ABROWSE>) {
chomp;
-
+
# first record padded w 8 spaces
if (s/^\s{8}\b//) {
my ($id, $type) = split(m/\s+/, $_, 2);
push (@psets, \%last_ps);
$psets{ $last_ps{id} } = \%last_ps;
}
-
+
my $branch = extract_versionname($id);
%ps = ( id => $id, branch => $branch );
if (%last_ps && ($last_ps{branch} eq $branch)) {
$ps{parent_id} = $last_ps{id};
}
-
+
$arch_branches{$branch} = 1;
$lastseen = 'id';
$ps{type} = 't';
# read which revision we've tagged when we parse the log
$ps{tag} = $1;
- } else {
+ } else {
warn "Unknown type $type";
}
$arch_branches{$branch} = 1;
$lastseen = 'id';
- } elsif (s/^\s{10}//) {
- # 10 leading spaces or more
+ } elsif (s/^\s{10}//) {
+ # 10 leading spaces or more
# indicate commit metadata
-
+
# date
if ($lastseen eq 'id' && m/^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d)/){
$ps{date} = $1;
} elsif ($lastseen eq 'merges' && s/^\s{2}//) {
my $id = $_;
push (@{$ps{merges}}, $id);
-
+
# aggressive branch finding:
if ($opt_D) {
my $branch = extract_versionname($id);
my $repo = extract_reponame($branch);
-
+
if (archive_reachable($repo) &&
!defined $arch_branches{$branch}) {
$arch_branches{$branch} = $stage + 1;
if (@psets && $psets[$#psets]{branch} eq $ps{branch}) {
$temp{parent_id} = $psets[$#psets]{id};
}
- push (@psets, \%temp);
+ push (@psets, \%temp);
$psets{ $temp{id} } = \%temp;
- }
-
+ }
+
close ABROWSE or die "$TLA abrowse failed on $limit\n";
}
} # end foreach $root
while (my $file = readdir(DIR)) {
# skip non-interesting-files
next unless -f "$ptag_dir/$file";
-
+
# convert first '--' to '/' from old git-archimport to use
# as an archivename/c--b--v private tag
if ($file !~ m!,!) {
my $fq_cvbr = shift; # archivename/[[[[category]branch]version]revision]
return (split(/\//, $fq_cvbr))[0];
}
-
+
sub extract_versionname {
my $name = shift;
$name =~ s/--(?:patch|version(?:fix)?|base)-\d+$//;
}
# convert a fully-qualified revision or version to a unique dirname:
-# normalperson@yhbt.net-05/mpd--uclinux--1--patch-2
+# normalperson@yhbt.net-05/mpd--uclinux--1--patch-2
# becomes: normalperson@yhbt.net-05,mpd--uclinux--1
#
# the git notion of a branch is closer to
sub process_patchset_accurate {
my $ps = shift;
-
+
# switch to that branch if we're not already in that branch:
if (-e "$git_dir/refs/heads/$ps->{branch}") {
system('git-checkout','-f',$ps->{branch}) == 0 or die "$! $?\n";
my $rm = safe_pipe_capture('git-ls-files','--others','-z');
rmtree(split(/\0/,$rm)) if $rm;
}
-
+
# Apply the import/changeset/merge into the working tree
my $dir = sync_to_ps($ps);
# read the new log entry:
parselog($ps, \@commitlog);
if ($ps->{id} =~ /--base-0$/ && $ps->{id} ne $psets[0]{id}) {
- # this should work when importing continuations
+ # this should work when importing continuations
if ($ps->{tag} && (my $branchpoint = eval { ptag($ps->{tag}) })) {
-
+
# find where we are supposed to branch from
if (! -e "$git_dir/refs/heads/$ps->{branch}") {
system('git-branch',$ps->{branch},$branchpoint) == 0 or die "$! $?\n";
}
# allow multiple bases/imports here since Arch supports cherry-picks
# from unrelated trees
- }
-
+ }
+
# update the index with all the changes we got
system('git-diff-files --name-only -z | '.
'git-update-index --remove -z --stdin') == 0 or die "$! $?\n";
# does not handle permissions or any renames involving directories
sub process_patchset_fast {
my $ps = shift;
- #
+ #
# create the branch if needed
#
if ($ps->{type} eq 'i' && !$import) {
# new branch! we need to verify a few things
die "Branch on a non-tag!" unless $ps->{type} eq 't';
my $branchpoint = ptag($ps->{tag});
- die "Tagging from unknown id unsupported: $ps->{tag}"
+ die "Tagging from unknown id unsupported: $ps->{tag}"
unless $branchpoint;
-
+
# find where we are supposed to branch from
if (! -e "$git_dir/refs/heads/$ps->{branch}") {
system('git-branch',$ps->{branch},$branchpoint) == 0 or die "$! $?\n";
}
system('git-checkout',$ps->{branch}) == 0 or die "$! $?\n";
return 0;
- }
+ }
die $! if $?;
- }
+ }
#
# Apply the import/changeset/merge into the working tree
- #
+ #
if ($ps->{type} eq 'i' || $ps->{type} eq 't') {
apply_import($ps) or die $!;
$stats{import_or_tag}++;
# prepare update git's index, based on what arch knows
# about the pset, resolve parents, etc
#
-
- my @commitlog = safe_pipe_capture($TLA,'cat-archive-log',$ps->{id});
+
+ my @commitlog = safe_pipe_capture($TLA,'cat-archive-log',$ps->{id});
die "Error in cat-archive-log: $!" if $?;
-
+
parselog($ps,\@commitlog);
# imports don't give us good info
if (@$ren % 2) {
die "Odd number of entries in rename!?";
}
-
+
while (@$ren) {
my $from = shift @$ren;
- my $to = shift @$ren;
+ my $to = shift @$ren;
unless (-d dirname($to)) {
mkpath(dirname($to)); # will die on err
"Things may be a bit slow\n";
*process_patchset = *process_patchset_accurate;
}
-
+
foreach my $ps (@psets) {
# process patchsets
$ps->{branch} = git_branchname($ps->{id});
#
- # ensure we have a clean state
- #
+ # ensure we have a clean state
+ #
if (my $dirty = `git-diff-files`) {
die "Unclean tree when about to process $ps->{id} " .
" - did we fail to commit cleanly before?\n$dirty";
}
die $! if $?;
-
+
#
# skip commits already in repo
#
my $tree = `git-write-tree`;
die "cannot write tree $!" if $?;
chomp $tree;
-
+
#
# Who's your daddy?
#
close HEAD;
chomp $p;
push @par, '-p', $p;
- } else {
+ } else {
if ($ps->{type} eq 's') {
warn "Could not find the right head for the branch $ps->{branch}";
}
}
}
-
+
if ($ps->{merges}) {
push @par, find_parents($ps);
}
- #
+ #
# Commit, tag and clean state
#
$ENV{TZ} = 'GMT';
$ENV{GIT_COMMITTER_EMAIL} = $ps->{email};
$ENV{GIT_COMMITTER_DATE} = $ps->{date};
- my $pid = open2(*READER, *WRITER,'git-commit-tree',$tree,@par)
+ my $pid = open2(*READER, *WRITER,'git-commit-tree',$tree,@par)
or die $!;
print WRITER $ps->{summary},"\n\n";
print WRITER $ps->{message},"\n";
-
+
# make it easy to backtrack and figure out which Arch revision this was:
print WRITER 'git-archimport-id: ',$ps->{id},"\n";
-
+
close WRITER;
my $commitid = <READER>; # read
chomp $commitid;
}
#
# Update the branch
- #
+ #
open HEAD, ">","$git_dir/refs/heads/$ps->{branch}";
print HEAD $commitid;
close HEAD;
sub sync_to_ps {
my $ps = shift;
my $tree_dir = $tmp.'/'.tree_dirname($ps->{id});
-
+
$opt_v && print "sync_to_ps($ps->{id}) method: ";
if (-d $tree_dir) {
safe_pipe_capture($TLA,'get','--no-pristine',$ps->{id},$tree_dir);
$stats{get_new}++;
}
-
+
# added -I flag to rsync since we're going to fast! AIEEEEE!!!!
system('rsync','-aI','--delete','--exclude',$git_dir,
# '--exclude','.arch-inventory',
mkpath($tmp);
safe_pipe_capture($TLA,'get','-s','--no-pristine',$ps->{id},"$tmp/import");
- die "Cannot get import: $!" if $?;
+ die "Cannot get import: $!" if $?;
system('rsync','-aI','--delete', '--exclude',$git_dir,
'--exclude','.arch-ids','--exclude','{arch}',
"$tmp/import/", './');
die "Cannot rsync import:$!" if $?;
-
+
rmtree("$tmp/import");
die "Cannot remove tempdir: $!" if $?;
-
+
return 1;
}
# get the changeset
safe_pipe_capture($TLA,'get-changeset',$ps->{id},"$tmp/changeset");
die "Cannot get changeset: $!" if $?;
-
+
# apply patches
if (`find $tmp/changeset/patches -type f -name '*.patch'`) {
# this can be sped up considerably by doing
# (find | xargs cat) | patch
# but that can get mucked up by patches
- # with missing trailing newlines or the standard
+ # with missing trailing newlines or the standard
# 'missing newline' flag in the patch - possibly
# produced with an old/buggy diff.
# slow and safe, we invoke patch once per patchfile
# bring in new files
system('rsync','-aI','--exclude',$git_dir,
- '--exclude','.arch-ids',
+ '--exclude','.arch-ids',
'--exclude', '{arch}',
"$tmp/changeset/new-files-archive/",'./');
removed_files => 1,
removed_directories => 1,
);
-
+
chomp (@$log);
while ($_ = shift @$log) {
if (/^Continuation-of:\s*(.*)/) {
}
}
}
-
+
# drop leading empty lines from the log message
while (@$log && $log->[0] eq '') {
shift @$log;
$ps->{summary} = $log->[0] . '...';
}
$ps->{message} = join("\n",@$log);
-
+
# skip Arch control files, unescape pika-escaped files
foreach my $k (keys %want_headers) {
next unless (defined $ps->{$k});
# write/read a tag
sub tag {
my ($tag, $commit) = @_;
-
+
if ($opt_o) {
$tag =~ s|/|--|g;
} else {
$patchname =~ s/.*--//;
$tag = git_branchname ($tag) . '--' . $patchname;
}
-
+
if ($commit) {
open(C,">","$git_dir/refs/tags/$tag")
or die "Cannot create tag $tag: $!\n";
my ($tag, $commit) = @_;
# don't use subdirs for tags yet, it could screw up other porcelains
- $tag =~ s|/|,|g;
-
+ $tag =~ s|/|,|g;
+
my $tag_file = "$ptag_dir/$tag";
my $tag_branch_dir = dirname($tag_file);
mkpath($tag_branch_dir) unless (-d $tag_branch_dir);
or die "Cannot write tag $tag: $!\n";
close(C)
or die "Cannot write tag $tag: $!\n";
- $rptags{$commit} = $tag
+ $rptags{$commit} = $tag
unless $tag =~ m/--base-0$/;
} else { # read
# if the tag isn't there, return 0
# Identify what branches are merging into me
# and whether we are fully merged
# git-merge-base <headsha> <headsha> should tell
- # me what the base of the merge should be
+ # me what the base of the merge should be
#
my $ps = shift;
}
#
- # foreach branch find a merge base and walk it to the
+ # foreach branch find a merge base and walk it to the
# head where we are, collecting the merged patchsets that
# Arch has recorded. Keep that in @have
# Compare that with the commits on the other branch
# between merge-base and the tip of the branch (@need)
# and see if we have a series of consecutive patches
# starting from the merge base. The tip of the series
- # of consecutive patches merged is our new parent for
+ # of consecutive patches merged is our new parent for
# that branch.
#
foreach my $branch (keys %branches) {
next unless -e "$git_dir/refs/heads/$branch";
my $mergebase = `git-merge-base $branch $ps->{branch}`;
- if ($?) {
- # Don't die here, Arch supports one-way cherry-picking
- # between branches with no common base (or any relationship
- # at all beforehand)
- warn "Cannot find merge base for $branch and $ps->{branch}";
- next;
- }
+ if ($?) {
+ # Don't die here, Arch supports one-way cherry-picking
+ # between branches with no common base (or any relationship
+ # at all beforehand)
+ warn "Cannot find merge base for $branch and $ps->{branch}";
+ next;
+ }
chomp $mergebase;
# now walk up to the mergepoint collecting what patches we have
# merge what we have with what ancestors have
%have = (%have, %ancestorshave);
- # see what the remote branch has - these are the merges we
+ # see what the remote branch has - these are the merges we
# will want to have in a consecutive series from the mergebase
my $otherbranchtip = git_rev_parse($branch);
my @needraw = `git-rev-list --topo-order $otherbranchtip ^$mergebase`;
foreach my $needps (@needraw) { # get the psets
$needps = commitid2pset($needps);
# git-rev-list will also
- # list commits merged in via earlier
+ # list commits merged in via earlier
# merges. we are only interested in commits
# from the branch we're looking at
if ($branch eq $needps->{branch}) {
next unless ref $psets{$p}{merges};
my @merges = @{$psets{$p}{merges}};
foreach my $merge (@merges) {
- if ($parents{$merge}) {
+ if ($parents{$merge}) {
delete $parents{$merge};
}
}
sub commitid2pset {
my $commitid = shift;
chomp $commitid;
- my $name = $rptags{$commitid}
+ my $name = $rptags{$commitid}
|| die "Cannot find reverse tag mapping for $commitid";
$name =~ s|,|/|;
- my $ps = $psets{$name}
+ my $ps = $psets{$name}
|| (print Dumper(sort keys %psets)) && die "Cannot find patchset for $name";
return $ps;
}
my $archive = shift;
return 1 if $reachable{$archive};
return 0 if $unreachable{$archive};
-
+
if (system "$TLA whereis-archive $archive >/dev/null") {
if ($opt_a && (system($TLA,'register-archive',
"http://mirrors.sourcecontrol.net/$archive") == 0)) {
return 1;
}
}
-
# Verify HEAD. If we were bisecting before this, reset to the
# top-of-line master first!
#
- head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
+ head=$(GIT_DIR="$GIT_DIR" git symbolic-ref HEAD) ||
die "Bad HEAD - I need a symbolic ref"
case "$head" in
refs/heads/bisect)
break
;;
*)
- rev=$(git-rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
+ rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
test $has_double_dash -eq 1 &&
die "'$arg' does not appear to be a valid revision"
break
bisect_autostart
case "$#" in
0)
- rev=$(git-rev-parse --verify HEAD) ;;
+ rev=$(git rev-parse --verify HEAD) ;;
1)
- rev=$(git-rev-parse --verify "$1^{commit}") ;;
+ rev=$(git rev-parse --verify "$1^{commit}") ;;
*)
usage ;;
esac || exit
bisect_write_bad() {
rev="$1"
echo "$rev" >"$GIT_DIR/refs/bisect/bad"
- echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+ echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
}
bisect_good() {
bisect_autostart
case "$#" in
- 0) revs=$(git-rev-parse --verify HEAD) || exit ;;
- *) revs=$(git-rev-parse --revs-only --no-flags "$@") &&
+ 0) revs=$(git rev-parse --verify HEAD) || exit ;;
+ *) revs=$(git rev-parse --revs-only --no-flags "$@") &&
test '' != "$revs" || die "Bad rev input: $@" ;;
esac
for rev in $revs
do
- rev=$(git-rev-parse --verify "$rev^{commit}") || exit
+ rev=$(git rev-parse --verify "$rev^{commit}") || exit
bisect_write_good "$rev"
echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
bisect_write_good() {
rev="$1"
echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
- echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+ echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
}
bisect_next_check() {
bisect_autostart
bisect_next_check good
- bad=$(git-rev-parse --verify refs/bisect/bad) &&
+ bad=$(git rev-parse --verify refs/bisect/bad) &&
good=$(git for-each-ref --format='^%(objectname)' \
"refs/bisect/good-*" | tr '[\012]' ' ') &&
- eval="git-rev-list --bisect-vars $good $bad --" &&
+ eval="git rev-list --bisect-vars $good $bad --" &&
eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
eval=$(eval "$eval") &&
eval "$eval" || exit
fi
if [ "$bisect_rev" = "$bad" ]; then
echo "$bisect_rev is first bad commit"
- git-diff-tree --pretty $bisect_rev
+ git diff-tree --pretty $bisect_rev
exit 0
fi
echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect"
git checkout -q new-bisect || exit
mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
- GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
- git-show-branch "$bisect_rev"
+ GIT_DIR="$GIT_DIR" git symbolic-ref HEAD refs/heads/bisect
+ git show-branch "$bisect_rev"
}
bisect_visualize() {
else
branch=master
fi ;;
- 1) git-show-ref --verify --quiet -- "refs/heads/$1" || {
+ 1) git show-ref --verify --quiet -- "refs/heads/$1" || {
echo >&2 "$1 does not seem to be a valid branch"
exit 1
}
;;
good)
echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
- echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+ echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
;;
bad)
echo "$rev" >"$GIT_DIR/refs/bisect/bad"
- echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+ echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
;;
*)
require_work_tree
old_name=HEAD
-old=$(git-rev-parse --verify $old_name 2>/dev/null)
-oldbranch=$(git-symbolic-ref $old_name 2>/dev/null)
+old=$(git rev-parse --verify $old_name 2>/dev/null)
+oldbranch=$(git symbolic-ref $old_name 2>/dev/null)
new=
new_name=
force=
shift
[ -z "$newbranch" ] &&
die "git checkout: -b needs a branch name"
- git-show-ref --verify --quiet -- "refs/heads/$newbranch" &&
+ git show-ref --verify --quiet -- "refs/heads/$newbranch" &&
die "git checkout: branch $newbranch already exists"
- git-check-ref-format "heads/$newbranch" ||
+ git check-ref-format "heads/$newbranch" ||
die "git checkout: we do not like '$newbranch' as a branch name."
;;
"-l")
usage
;;
*)
- if rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null)
+ if rev=$(git rev-parse --verify "$arg^0" 2>/dev/null)
then
if [ -z "$rev" ]; then
echo "unknown flag $arg"
exit 1
fi
new_name="$arg"
- if git-show-ref --verify --quiet -- "refs/heads/$arg"
+ if git show-ref --verify --quiet -- "refs/heads/$arg"
then
- rev=$(git-rev-parse --verify "refs/heads/$arg^0")
+ rev=$(git rev-parse --verify "refs/heads/$arg^0")
branch="$arg"
fi
new="$rev"
- elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
+ elif rev=$(git rev-parse --verify "$arg^{tree}" 2>/dev/null)
then
# checking out selected paths from a tree-ish.
new="$rev"
# from a specific tree-ish; note that this is for
# rescuing paths and is never meant to remove what
# is not in the named tree-ish.
- git-ls-tree --full-name -r "$new" "$@" |
- git-update-index --index-info || exit $?
+ git ls-tree --full-name -r "$new" "$@" |
+ git update-index --index-info || exit $?
fi
# Make sure the request is about existing paths.
- git-ls-files --error-unmatch -- "$@" >/dev/null || exit
- git-ls-files -- "$@" |
- git-checkout-index -f -u --stdin
+ git ls-files --error-unmatch -- "$@" >/dev/null || exit
+ git ls-files -- "$@" |
+ git checkout-index -f -u --stdin
exit $?
else
# Make sure we did not fall back on $arg^{tree} codepath
# but switching branches.
if test '' != "$new"
then
- git-rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
+ git rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
die "Cannot switch branch to a non-commit."
fi
fi
if [ "$force" ]
then
- git-read-tree $v --reset -u $new
+ git read-tree $v --reset -u $new
else
- git-update-index --refresh >/dev/null
- merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
+ git update-index --refresh >/dev/null
+ merge_error=$(git read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
case "$merge" in
'')
echo >&2 "$merge_error"
esac
# Match the index to the working tree, and do a three-way.
- git diff-files --name-only | git update-index --remove --stdin &&
+ git diff-files --name-only | git update-index --remove --stdin &&
work=`git write-tree` &&
git read-tree $v --reset -u $new || exit
(exit $saved_err)
fi
-#
+#
# Switch the HEAD pointer to the new branch if we
# checked out a branch head, and remove any potential
# old MERGE_HEAD's (subsequent commits will clearly not
#
if [ "$?" -eq 0 ]; then
if [ "$newbranch" ]; then
- git-branch $track $newbranch_log "$newbranch" "$new_name" || exit
+ git branch $track $newbranch_log "$newbranch" "$new_name" || exit
branch="$newbranch"
fi
if test -n "$branch"
then
- GIT_DIR="$GIT_DIR" git-symbolic-ref -m "checkout: moving to $branch" HEAD "refs/heads/$branch"
+ old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
+ GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from $old_branch_name to $branch" HEAD "refs/heads/$branch"
if test -n "$quiet"
then
true # nothing
fi
elif test -n "$detached"
then
- # NEEDSWORK: we would want a command to detach the HEAD
- # atomically, instead of this handcrafted command sequence.
- # Perhaps:
- # git update-ref --detach HEAD $new
- # or something like that...
- #
- git-rev-parse HEAD >"$GIT_DIR/HEAD.new" &&
- mv "$GIT_DIR/HEAD.new" "$GIT_DIR/HEAD" &&
- git-update-ref -m "checkout: moving to $arg" HEAD "$detached" ||
+ git update-ref --no-deref -m "checkout: moving to $arg" HEAD "$detached" ||
die "Cannot detach HEAD"
if test -n "$detach_warn"
then
ignored=
ignoredonly=
cleandir=
-disabled="`git-config --bool clean.requireForce`"
+disabled="`git config --bool clean.requireForce`"
rmf="rm -f --"
rmrf="rm -rf --"
rm_refuse="echo Not removing"
fi
fi
-git-ls-files --others --directory $excl ${excl_info:+"$excl_info"} -- "$@" |
+git ls-files --others --directory $excl ${excl_info:+"$excl_info"} -- "$@" |
while read -r file; do
if [ -d "$file" -a ! -L "$file" ]; then
if [ -z "$cleandir" ]; then
#
# Copyright (c) 2005, Linus Torvalds
# Copyright (c) 2005, Junio C Hamano
-#
+#
# Clone a repository into a different directory that does not yet exist.
# See git-sh-setup why.
clone_tmp="$GIT_DIR/clone-tmp" &&
mkdir -p "$clone_tmp" || exit 1
if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
- "`git-config --bool http.noEPSV`" = true ]; then
+ "`git config --bool http.noEPSV`" = true ]; then
curl_extra_args="${curl_extra_args} --disable-epsv"
fi
http_fetch "$1/info/refs" "$clone_tmp/refs" ||
rm -fr "$clone_tmp"
http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
rm -f "$GIT_DIR/REMOTE_HEAD"
+ if test -f "$GIT_DIR/REMOTE_HEAD"; then
+ head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+ case "$head_sha1" in
+ 'ref: refs/'*)
+ ;;
+ *)
+ git-http-fetch $v -a "$head_sha1" "$1" ||
+ rm -f "$GIT_DIR/REMOTE_HEAD"
+ ;;
+ esac
+ fi
}
quiet=
*,--na|*,--nak|*,--nake|*,--naked|\
*,-b|*,--b|*,--ba|*,--bar|*,--bare) bare=yes ;;
*,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) use_local=yes ;;
- *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared)
+ *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared)
local_shared=yes; use_local=yes ;;
1,--template) usage ;;
*,--template)
*/*)
die "'$2' is not suitable for an origin name"
esac
- git-check-ref-format "heads/$2" ||
+ git check-ref-format "heads/$2" ||
die "'$2' is not suitable for a branch name"
test -z "$origin_override" ||
die "Do not give more than one --origin options."
# Try using "humanish" part of source repo if user didn't specify one
[ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
[ -e "$dir" ] && die "destination directory '$dir' already exists."
-mkdir -p "$dir" &&
-D=$(cd "$dir" && pwd) &&
-trap 'err=$?; cd ..; rm -rf "$D"; exit $err' 0
-case "$bare" in
-yes)
- GIT_DIR="$D" ;;
-*)
- GIT_DIR="$D/.git" ;;
-esac && export GIT_DIR && git-init ${template+"$template"} || usage
+[ yes = "$bare" ] && unset GIT_WORK_TREE
+[ -n "$GIT_WORK_TREE" ] && [ -e "$GIT_WORK_TREE" ] &&
+die "working tree '$GIT_WORK_TREE' already exists."
+D=
+W=
+cleanup() {
+ err=$?
+ test -z "$D" && rm -rf "$dir"
+ test -z "$W" && test -n "$GIT_WORK_TREE" && rm -rf "$GIT_WORK_TREE"
+ cd ..
+ test -n "$D" && rm -rf "$D"
+ test -n "$W" && rm -rf "$W"
+ exit $err
+}
+trap cleanup 0
+mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage
+test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" &&
+W=$(cd "$GIT_WORK_TREE" && pwd) && export GIT_WORK_TREE="$W"
+if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then
+ GIT_DIR="$D"
+else
+ GIT_DIR="$D/.git"
+fi &&
+export GIT_DIR &&
+git-init $quiet ${template+"$template"} || usage
if test -n "$reference"
then
*)
continue ;;
esac
- git-update-ref -m "clone: from $repo" "$destname" "$sha1" ""
+ git update-ref -m "clone: from $repo" "$destname" "$sha1" ""
done < "$GIT_DIR/CLONE_HEAD"
fi
-cd "$D" || exit
+if test -n "$W"; then
+ cd "$W" || exit
+else
+ cd "$D" || exit
+fi
if test -z "$bare" && test -f "$GIT_DIR/REMOTE_HEAD"
then
)
)
+ # Upstream URL
+ git config remote."$origin".url "$repo" &&
+
+ # Set up the mappings to track the remote branches.
+ git config remote."$origin".fetch \
+ "+refs/heads/*:$remote_top/*" '^$' &&
+
# Write out remote.$origin config, and update our "$head_points_at".
case "$head_points_at" in
?*)
# Local default branch
- git-symbolic-ref HEAD "refs/heads/$head_points_at" &&
+ git symbolic-ref HEAD "refs/heads/$head_points_at" &&
# Tracking branch for the primary branch at the remote.
- origin_track="$remote_top/$head_points_at" &&
- git-update-ref HEAD "$head_sha1" &&
-
- # Upstream URL
- git-config remote."$origin".url "$repo" &&
+ git update-ref HEAD "$head_sha1" &&
- # Set up the mappings to track the remote branches.
- git-config remote."$origin".fetch \
- "+refs/heads/*:$remote_top/*" '^$' &&
rm -f "refs/remotes/$origin/HEAD"
- git-symbolic-ref "refs/remotes/$origin/HEAD" \
+ git symbolic-ref "refs/remotes/$origin/HEAD" \
"refs/remotes/$origin/$head_points_at" &&
- git-config branch."$head_points_at".remote "$origin" &&
- git-config branch."$head_points_at".merge "refs/heads/$head_points_at"
+ git config branch."$head_points_at".remote "$origin" &&
+ git config branch."$head_points_at".merge "refs/heads/$head_points_at"
+ ;;
+ '')
+ # Source had detached HEAD pointing nowhere
+ git update-ref --no-deref HEAD "$head_sha1" &&
+ rm -f "refs/remotes/$origin/HEAD"
+ ;;
esac
case "$no_checkout" in
'')
test "z$quiet" = z -a "z$no_progress" = z && v=-v || v=
- git-read-tree -m -u $v HEAD HEAD
+ git read-tree -m -u $v HEAD HEAD
esac
fi
rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
trap - 0
-
# Copyright (c) 2005 Linus Torvalds
# Copyright (c) 2006 Junio C Hamano
-USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [[-i | -o] <path>...]'
+USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
SUBDIRECTORY_OK=Yes
. git-sh-setup
require_work_tree
-git-rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
+git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
case "$0" in
*status)
t) color= ;;
*) color=--nocolor ;;
esac
- git-runstatus ${color} \
+ git runstatus ${color} \
${verbose:+--verbose} \
${amend:+--amend} \
${untracked_files:+--untracked}
force_author=
only_include_assumed=
untracked_files=
+templatefile="`git config commit.template`"
while case "$#" in 0) break;; esac
do
case "$1" in
;;
--a|--am|--ame|--amen|--amend)
amend=t
- log_given=t$log_given
use_commit=HEAD
shift
;;
signoff=t
shift
;;
+ -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
+ case "$#" in 1) usage ;; esac
+ shift
+ templatefile="$1"
+ no_edit=
+ shift
+ ;;
-q|--q|--qu|--qui|--quie|--quiet)
quiet=t
shift
case "$log_given" in
tt*)
- die "Only one of -c/-C/-F/--amend can be used." ;;
+ die "Only one of -c/-C/-F can be used." ;;
*tm*|*mt*)
- die "Option -m cannot be combined with -c/-C/-F/--amend." ;;
+ die "Option -m cannot be combined with -c/-C/-F." ;;
esac
case "$#,$also,$only,$amend" in
die "No paths with -i does not make sense." ;;
esac
+if test ! -z "$templatefile" -a -z "$log_given"
+then
+ if test ! -f "$templatefile"
+ then
+ die "Commit template file does not exist."
+ fi
+fi
+
################################################################
# Prepare index to have a tree to be committed
cd_to_toplevel &&
GIT_INDEX_FILE="$NEXT_INDEX" &&
export GIT_INDEX_FILE &&
- git-diff-files --name-only -z |
- git-update-index --remove -z --stdin
+ git diff-files --name-only -z |
+ git update-index --remove -z --stdin
) || exit
;;
,t)
save_index &&
- git-ls-files --error-unmatch -- "$@" >/dev/null || exit
+ git ls-files --error-unmatch -- "$@" >/dev/null || exit
- git-diff-files --name-only -z -- "$@" |
+ git diff-files --name-only -z -- "$@" |
(
cd_to_toplevel &&
GIT_INDEX_FILE="$NEXT_INDEX" &&
export GIT_INDEX_FILE &&
- git-update-index --remove -z --stdin
+ git update-index --remove -z --stdin
) || exit
;;
,)
refuse_partial "Cannot do a partial commit during a merge."
fi
TMP_INDEX="$GIT_DIR/tmp-index$$"
- commit_only=`git-ls-files --error-unmatch -- "$@"` || exit
+ commit_only=`git ls-files --error-unmatch -- "$@"` || exit
# Build a temporary index and update the real index
# the same way.
if test -z "$initial_commit"
then
GIT_INDEX_FILE="$THIS_INDEX" \
- git-read-tree --index-output="$TMP_INDEX" -i -m HEAD
+ git read-tree --index-output="$TMP_INDEX" -i -m HEAD
else
rm -f "$TMP_INDEX"
fi || exit
printf '%s\n' "$commit_only" |
GIT_INDEX_FILE="$TMP_INDEX" \
- git-update-index --add --remove --stdin &&
+ git update-index --add --remove --stdin &&
save_index &&
printf '%s\n' "$commit_only" |
(
GIT_INDEX_FILE="$NEXT_INDEX"
export GIT_INDEX_FILE
- git-update-index --remove --stdin
+ git update-index --remove --stdin
) || exit
;;
esac
t)
# This will silently fail in a read-only repository, which is
# what we want.
- GIT_INDEX_FILE="$USE_INDEX" git-update-index -q --unmerged --refresh
+ GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh
run_status
exit $?
;;
'')
- GIT_INDEX_FILE="$USE_INDEX" git-update-index -q --refresh || exit
+ GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit
;;
esac
elif test -f "$GIT_DIR/SQUASH_MSG"
then
cat "$GIT_DIR/SQUASH_MSG"
-fi | git-stripspace >"$GIT_DIR"/COMMIT_EDITMSG
+elif test "$templatefile" != ""
+then
+ cat "$templatefile"
+fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG
case "$signoff" in
t)
- need_blank_before_signoff=
+ sign=$(git-var GIT_COMMITTER_IDENT | sed -e '
+ s/>.*/>/
+ s/^/Signed-off-by: /
+ ')
+ blank_before_signoff=
tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
- grep 'Signed-off-by:' >/dev/null || need_blank_before_signoff=yes
- {
- test -z "$need_blank_before_signoff" || echo
- git-var GIT_COMMITTER_IDENT | sed -e '
- s/>.*/>/
- s/^/Signed-off-by: /
- '
- } >>"$GIT_DIR"/COMMIT_EDITMSG
+ grep 'Signed-off-by:' >/dev/null || blank_before_signoff='
+'
+ tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
+ grep "$sign"$ >/dev/null ||
+ printf '%s%s\n' "$blank_before_signoff" "$sign" \
+ >>"$GIT_DIR"/COMMIT_EDITMSG
;;
esac
# Author
if test '' != "$use_commit"
then
- pick_author_script='
- /^author /{
- s/'\''/'\''\\'\'\''/g
- h
- s/^author \([^<]*\) <[^>]*> .*$/\1/
- s/'\''/'\''\'\'\''/g
- s/.*/GIT_AUTHOR_NAME='\''&'\''/p
-
- g
- s/^author [^<]* <\([^>]*\)> .*$/\1/
- s/'\''/'\''\'\'\''/g
- s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
-
- g
- s/^author [^<]* <[^>]*> \(.*\)$/\1/
- s/'\''/'\''\'\'\''/g
- s/.*/GIT_AUTHOR_DATE='\''&'\''/p
-
- q
- }
- '
- encoding=$(git config i18n.commitencoding || echo UTF-8)
- set_author_env=`git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
- LANG=C LC_ALL=C sed -ne "$pick_author_script"`
- eval "$set_author_env"
- export GIT_AUTHOR_NAME
- export GIT_AUTHOR_EMAIL
- export GIT_AUTHOR_DATE
+ eval "$(get_author_ident_from_commit "$use_commit")"
+ export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
fi
if test '' != "$force_author"
then
PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
elif test -n "$amend"; then
rloga='commit (amend)'
- PARENTS=$(git-cat-file commit HEAD |
+ PARENTS=$(git cat-file commit HEAD |
sed -n -e '/^$/q' -e 's/^parent /-p /p')
fi
- current="$(git-rev-parse --verify HEAD)"
+ current="$(git rev-parse --verify HEAD)"
else
- if [ -z "$(git-ls-files)" ]; then
+ if [ -z "$(git ls-files)" ]; then
echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
exit 1
fi
} >>"$GIT_DIR"/COMMIT_EDITMSG
else
# we need to check if there is anything to commit
- run_status >/dev/null
+ run_status >/dev/null
fi
if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
then
case "$no_edit" in
'')
- case "${VISUAL:-$EDITOR},$TERM" in
- ,dumb)
- echo >&2 "Terminal is dumb but no VISUAL nor EDITOR defined."
- echo >&2 "Please supply the commit log message using either"
- echo >&2 "-m or -F option. A boilerplate log message has"
- echo >&2 "been prepared in $GIT_DIR/COMMIT_EDITMSG"
- exit 1
- ;;
- esac
git-var GIT_AUTHOR_IDENT > /dev/null || die
git-var GIT_COMMITTER_IDENT > /dev/null || die
- ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG"
+ git_editor "$GIT_DIR/COMMIT_EDITMSG"
;;
esac
else
cat "$GIT_DIR"/COMMIT_EDITMSG
fi |
-git-stripspace >"$GIT_DIR"/COMMIT_MSG
+git stripspace >"$GIT_DIR"/COMMIT_MSG
+
+# Test whether the commit message has any content we didn't supply.
+have_commitmsg=
+grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
+ git stripspace > "$GIT_DIR"/COMMIT_BAREMSG
+
+# Is the commit message totally empty?
+if test -s "$GIT_DIR"/COMMIT_BAREMSG
+then
+ if test "$templatefile" != ""
+ then
+ # Test whether this is just the unaltered template.
+ if cnt=`sed -e '/^#/d' < "$templatefile" |
+ git stripspace |
+ diff "$GIT_DIR"/COMMIT_BAREMSG - |
+ wc -l` &&
+ test 0 -lt $cnt
+ then
+ have_commitmsg=t
+ fi
+ else
+ # No template, so the content in the commit message must
+ # have come from the user.
+ have_commitmsg=t
+ fi
+fi
+
+rm -f "$GIT_DIR"/COMMIT_BAREMSG
-if cnt=`grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
- git-stripspace |
- wc -l` &&
- test 0 -lt $cnt
+if test "$have_commitmsg" = "t"
then
if test -z "$TMP_INDEX"
then
- tree=$(GIT_INDEX_FILE="$USE_INDEX" git-write-tree)
+ tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree)
else
- tree=$(GIT_INDEX_FILE="$TMP_INDEX" git-write-tree) &&
+ tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) &&
rm -f "$TMP_INDEX"
fi &&
- commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
+ commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") &&
rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
- git-update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
+ git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
if test -f "$NEXT_INDEX"
then
cd_to_toplevel
-if test -d "$GIT_DIR/rr-cache"
-then
- git-rerere
-fi
+git rerere
if test "$ret" = 0
then
fi
if test -z "$quiet"
then
- commit=`git-diff-tree --always --shortstat --pretty="format:%h: %s"\
+ commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\
--summary --root HEAD --`
echo "Created${initial_commit:+ initial} commit $commit"
fi
}
}
+static inline int xdup(int fd)
+{
+ int ret = dup(fd);
+ if (ret < 0)
+ die("dup failed: %s", strerror(errno));
+ return ret;
+}
+
+static inline FILE *xfdopen(int fd, const char *mode)
+{
+ FILE *stream = fdopen(fd, mode);
+ if (stream == NULL)
+ die("Out of memory? fdopen failed: %s", strerror(errno));
+ return stream;
+}
+
static inline size_t xsize_t(off_t len)
{
return (size_t)len;
die "GIT_DIR is not defined or is unreadable";
}
-our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d);
+our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u);
-getopts('hPpvcfam:d:');
+getopts('uhPpvcfam:d:');
$opt_h && usage();
my %cvsstat;
if (@canstatusfiles) {
+ if ($opt_u) {
+ my @updated = safe_pipe_capture(@cvs, 'update', @canstatusfiles);
+ print @updated;
+ }
my @cvsoutput;
@cvsoutput= safe_pipe_capture(@cvs, 'status', @canstatusfiles);
my $matchcount = 0;
# ... validate new files,
foreach my $f (@afiles) {
if (defined ($cvsstat{$f}) and $cvsstat{$f} ne "Unknown") {
- $dirty = 1;
+ $dirty = 1;
warn "File $f is already known in your CVS checkout -- perhaps it has been added by another user. Or this may indicate that it exists on a different branch. If this is the case, use -f to force the merge.\n";
warn "Status was: $cvsstat{$f}\n";
}
# clean up
unlink(".cvsexportcommit.diff");
+# CVS version 1.11.x and 1.12.x sleeps the wrong way to ensure the timestamp
+# used by CVS and the one set by subsequence file modifications are different.
+# If they are not different CVS will not detect changes.
+sleep(1);
+
sub usage {
print STDERR <<END;
Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit
$SIG{'PIPE'}="IGNORE";
$ENV{'TZ'}="UTC";
-our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L, $opt_a);
+our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r);
my (%conv_author_name, %conv_author_email);
sub usage(;$) {
[-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
[-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]
[-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]
- [CVS_module]
+ [-r remote] [CVS_module]
END
exit(1);
}
}
}
-my $opts = "haivmkuo:d:p:C:z:s:M:P:A:S:L:";
+my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:";
read_repo_config($opts);
getopts($opts) or usage();
usage if $opt_h;
} else {
usage("CVSROOT needs to be set");
}
-$opt_o ||= "origin";
$opt_s ||= "-";
$opt_a ||= 0;
my $git_tree = $opt_C;
$git_tree ||= ".";
+my $remote;
+if (defined $opt_r) {
+ $remote = 'refs/remotes/' . $opt_r;
+ $opt_o ||= "master";
+} else {
+ $opt_o ||= "origin";
+ $remote = 'refs/heads';
+}
+
my $cvs_tree;
if ($#ARGV == 0) {
$cvs_tree = $ARGV[0];
} elsif (-f 'CVS/Repository') {
- open my $f, '<', 'CVS/Repository' or
+ open my $f, '<', 'CVS/Repository' or
die 'Failed to open CVS/Repository';
$cvs_tree = <$f>;
chomp $cvs_tree;
my ($self,$fn,$rev) = @_;
my $res;
- my ($fh, $name) = tempfile('gitcvs.XXXXXX',
+ my ($fh, $name) = tempfile('gitcvs.XXXXXX',
DIR => File::Spec->tmpdir(), UNLINK => 1);
$self->_file($fn,$rev) and $res = $self->_line($fh);
sub get_headref ($$) {
my $name = shift;
- my $git_dir = shift;
-
- my $f = "$git_dir/refs/heads/$name";
+ my $git_dir = shift;
+
+ my $f = "$git_dir/$remote/$name";
if (open(my $fh, $f)) {
chomp(my $r = <$fh>);
is_sha1($r) or die "Cannot get head id for $name ($r): $!";
# Get the last import timestamps
my $fmt = '($ref, $author) = (%(refname), %(author));';
- open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or
+ open(H, "git-for-each-ref --perl --format='$fmt' $remote |") or
die "Cannot run git-for-each-ref: $!\n";
while (defined(my $entry = <H>)) {
my ($ref, $author);
eval($entry) || die "cannot eval refs list: $@";
- my ($head) = ($ref =~ m|^refs/heads/(.*)|);
+ my ($head) = ($ref =~ m|^$remote/(.*)|);
$author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
$branch_date{$head} = $1;
}
$index{$branch} = tmpnam();
$ENV{GIT_INDEX_FILE} = $index{$branch};
if ($ancestor) {
- system("git-read-tree", $ancestor);
+ system("git-read-tree", "$remote/$ancestor");
} else {
- system("git-read-tree", $branch);
+ system("git-read-tree", "$remote/$branch");
}
die "read-tree failed: $?\n" if $?;
}
waitpid($pid,0);
die "Error running git-commit-tree: $?\n" if $?;
- system("git-update-ref refs/heads/$branch $cid") == 0
+ system("git-update-ref $remote/$branch $cid") == 0
or die "Cannot write branch $branch for update: $!\n";
if ($tag) {
- my ($in, $out) = ('','');
my ($xtag) = $tag;
$xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
$xtag =~ tr/_/\./ if ( $opt_u );
$xtag =~ s/[\/]/$opt_s/g;
-
- my $pid = open2($in, $out, 'git-mktag');
- print $out "object $cid\n".
- "type commit\n".
- "tag $xtag\n".
- "tagger $author_name <$author_email>\n"
- or die "Cannot create tag object $xtag: $!\n";
- close($out)
- or die "Cannot create tag object $xtag: $!\n";
-
- my $tagobj = <$in>;
- chomp $tagobj;
-
- if ( !close($in) or waitpid($pid, 0) != $pid or
- $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
- die "Cannot create tag object $xtag: $!\n";
- }
-
-
- open(C,">$git_dir/refs/tags/$xtag")
+
+ system('git-tag', $xtag, $cid) == 0
or die "Cannot create tag $xtag: $!\n";
- print C "$tagobj\n"
- or die "Cannot write tag $xtag: $!\n";
- close(C)
- or die "Cannot write tag $xtag: $!\n";
print "Created tag '$xtag' on '$branch'\n" if $opt_v;
}
print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
$ancestor = $opt_o;
}
- if (-f "$git_dir/refs/heads/$branch") {
+ if (-f "$git_dir/$remote/$branch") {
print STDERR "Branch $branch already exists!\n";
$state=11;
next;
}
- unless (open(H,"$git_dir/refs/heads/$ancestor")) {
+ unless (open(H,"$git_dir/$remote/$ancestor")) {
print STDERR "Branch $ancestor does not exist!\n";
$ignorebranch{$branch} = 1;
$state=11;
}
chomp(my $id = <H>);
close(H);
- unless (open(H,"> $git_dir/refs/heads/$branch")) {
+ unless (open(H,"> $git_dir/$remote/$branch")) {
print STDERR "Could not create branch $branch: $!\n";
$ignorebranch{$branch} = 1;
$state=11;
die "Fast-forward update failed: $?\n" if $?;
}
else {
- system(qw(git-merge cvsimport HEAD), "refs/heads/$opt_o");
+ system(qw(git-merge cvsimport HEAD), "$remote/$opt_o");
die "Could not merge $opt_o into the current branch.\n" if $?;
}
} else {
$orig_branch = "master";
print "DONE; creating $orig_branch branch\n" if $opt_v;
- system("git-update-ref", "refs/heads/master", "refs/heads/$opt_o")
+ system("git-update-ref", "refs/heads/master", "$remote/$opt_o")
unless -f "$git_dir/refs/heads/master";
+ system("git-symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
+ if ($opt_r && $opt_o ne 'HEAD');
system('git-update-ref', 'HEAD', "$orig_branch");
unless ($opt_i) {
- system('git checkout');
+ system('git checkout -f');
die "checkout failed: $?\n" if $?;
}
}
use Fcntl;
use File::Temp qw/tempdir tempfile/;
use File::Basename;
+use Getopt::Long qw(:config require_order no_ignore_case);
+
+my $VERSION = '@@GIT_VERSION@@';
my $log = GITCVS::log->new();
my $cfg;
my $state = { prependdir => '' };
$log->info("--------------- STARTING -----------------");
+my $usage =
+ "Usage: git-cvsserver [options] [pserver|server] [<directory> ...]\n".
+ " --base-path <path> : Prepend to requested CVSROOT\n".
+ " --strict-paths : Don't allow recursing into subdirectories\n".
+ " --export-all : Don't check for gitcvs.enabled in config\n".
+ " --version, -V : Print version information and exit\n".
+ " --help, -h, -H : Print usage information and exit\n".
+ "\n".
+ "<directory> ... is a list of allowed directories. If no directories\n".
+ "are given, all are allowed. This is an additional restriction, gitcvs\n".
+ "access still needs to be enabled by the gitcvs.enabled config option.\n";
+
+my @opts = ( 'help|h|H', 'version|V',
+ 'base-path=s', 'strict-paths', 'export-all' );
+GetOptions( $state, @opts )
+ or die $usage;
+
+if ($state->{version}) {
+ print "git-cvsserver version $VERSION\n";
+ exit;
+}
+if ($state->{help}) {
+ print $usage;
+ exit;
+}
+
my $TEMP_DIR = tempdir( CLEANUP => 1 );
$log->debug("Temporary directory is '$TEMP_DIR'");
+$state->{method} = 'ext';
+if (@ARGV) {
+ if ($ARGV[0] eq 'pserver') {
+ $state->{method} = 'pserver';
+ shift @ARGV;
+ } elsif ($ARGV[0] eq 'server') {
+ shift @ARGV;
+ }
+}
+
+# everything else is a directory
+$state->{allowed_roots} = [ @ARGV ];
+
+# don't export the whole system unless the users requests it
+if ($state->{'export-all'} && !@{$state->{allowed_roots}}) {
+ die "--export-all can only be used together with an explicit whitelist\n";
+}
+
# if we are called with a pserver argument,
# deal with the authentication cat before entering the
# main loop
-$state->{method} = 'ext';
-if (@ARGV && $ARGV[0] eq 'pserver') {
- $state->{method} = 'pserver';
+if ($state->{method} eq 'pserver') {
my $line = <STDIN>; chomp $line;
- unless( $line eq 'BEGIN AUTH REQUEST') {
+ unless( $line =~ /^BEGIN (AUTH|VERIFICATION) REQUEST$/) {
die "E Do not understand $line - expecting BEGIN AUTH REQUEST\n";
}
+ my $request = $1;
$line = <STDIN>; chomp $line;
req_Root('root', $line) # reuse Root
or die "E Invalid root $line \n";
}
$line = <STDIN>; chomp $line; # validate the password?
$line = <STDIN>; chomp $line;
- unless ($line eq 'END AUTH REQUEST') {
- die "E Do not understand $line -- expecting END AUTH REQUEST\n";
+ unless ($line eq "END $request REQUEST") {
+ die "E Do not understand $line -- expecting END $request REQUEST\n";
}
print "I LOVE YOU\n";
+ exit if $request eq 'VERIFICATION'; # cvs login
# and now back to our regular programme...
}
my ( $cmd, $data ) = @_;
$log->debug("req_Root : $data");
- $state->{CVSROOT} = $data;
+ unless ($data =~ m#^/#) {
+ print "error 1 Root must be an absolute pathname\n";
+ return 0;
+ }
+
+ my $cvsroot = $state->{'base-path'} || '';
+ $cvsroot =~ s#/+$##;
+ $cvsroot .= $data;
+
+ if ($state->{CVSROOT}
+ && ($state->{CVSROOT} ne $cvsroot)) {
+ print "error 1 Conflicting roots specified\n";
+ return 0;
+ }
+
+ $state->{CVSROOT} = $cvsroot;
$ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+
+ if (@{$state->{allowed_roots}}) {
+ my $allowed = 0;
+ foreach my $dir (@{$state->{allowed_roots}}) {
+ next unless $dir =~ m#^/#;
+ $dir =~ s#/+$##;
+ if ($state->{'strict-paths'}) {
+ if ($ENV{GIT_DIR} =~ m#^\Q$dir\E/?$#) {
+ $allowed = 1;
+ last;
+ }
+ } elsif ($ENV{GIT_DIR} =~ m#^\Q$dir\E(/?$|/)#) {
+ $allowed = 1;
+ last;
+ }
+ }
+
+ unless ($allowed) {
+ print "E $ENV{GIT_DIR} does not seem to be a valid GIT repository\n";
+ print "E \n";
+ print "error 1 $ENV{GIT_DIR} is not a valid repository\n";
+ return 0;
+ }
+ }
+
unless (-d $ENV{GIT_DIR} && -e $ENV{GIT_DIR}.'HEAD') {
print "E $ENV{GIT_DIR} does not seem to be a valid GIT repository\n";
- print "E \n";
- print "error 1 $ENV{GIT_DIR} is not a valid repository\n";
+ print "E \n";
+ print "error 1 $ENV{GIT_DIR} is not a valid repository\n";
return 0;
}
my $enabled = ($cfg->{gitcvs}{$state->{method}}{enabled}
|| $cfg->{gitcvs}{enabled});
- unless ($enabled && $enabled =~ /^\s*(1|true|yes)\s*$/i) {
+ unless ($state->{'export-all'} ||
+ ($enabled && $enabled =~ /^\s*(1|true|yes)\s*$/i)) {
print "E GITCVS emulation needs to be enabled on this repo\n";
print "E the repo config file needs a [gitcvs] section added, and the parameter 'enabled' set to 1\n";
print "E \n";
my ( $cmd, $data ) = @_;
my $mode = <STDIN>;
+ defined $mode
+ or (print "E end of file reading mode for $data\n"), return;
chomp $mode;
my $size = <STDIN>;
+ defined $size
+ or (print "E end of file reading size of $data\n"), return;
chomp $size;
# Grab config information
$bytesleft -= $blocksize;
}
- close $fh;
+ close $fh
+ or (print "E failed to write temporary, $filename: $!\n"), return;
# Ensure we have something sensible for the file mode
if ( $mode =~ /u=(\w+)/ )
# projects (heads in this case) to checkout.
#
if ($state->{module} eq '') {
+ my $heads_dir = $state->{CVSROOT} . '/refs/heads';
+ if (!opendir HEADS, $heads_dir) {
+ print "E [server aborted]: Failed to open directory, "
+ . "$heads_dir: $!\nerror\n";
+ return 0;
+ }
print "E cvs update: Updating .\n";
- opendir HEADS, $state->{CVSROOT} . '/refs/heads';
while (my $head = readdir(HEADS)) {
if (-f $state->{CVSROOT} . '/refs/heads/' . $head) {
print "E cvs update: New directory `$head'\n";
system("git-read-tree", $lastseenin);
unless ($? == 0)
{
- die "Error running git-read-tree $lastseenin $file_index $!";
+ print "E error running git-read-tree $lastseenin $file_index $!\n";
+ return;
}
$log->info("Created index '$file_index' with commit $lastseenin - exit status $?");
# do a checkout of the file
system('git-checkout-index', '-f', '-u', $filename);
unless ($? == 0) {
- die "Error running git-checkout-index -f -u $filename : $!";
+ print "E error running git-checkout-index -f -u $filename : $!\n";
+ return;
}
$log->info("Annotate $filename");
# git-jsannotate telling us about commits we are hiding
# from the client.
- open(ANNOTATEHINTS, ">$tmpdir/.annotate_hints") or die "Error opening > $tmpdir/.annotate_hints $!";
+ my $a_hints = "$tmpdir/.annotate_hints";
+ if (!open(ANNOTATEHINTS, '>', $a_hints)) {
+ print "E failed to open '$a_hints' for writing: $!\n";
+ return;
+ }
for (my $i=0; $i < @$revisions; $i++)
{
print ANNOTATEHINTS $revisions->[$i][2];
}
print ANNOTATEHINTS "\n";
- close ANNOTATEHINTS;
+ close ANNOTATEHINTS
+ or (print "E failed to write $a_hints: $!\n"), return;
- my $annotatecmd = 'git-annotate';
- open(ANNOTATE, "-|", $annotatecmd, '-l', '-S', "$tmpdir/.annotate_hints", $filename)
- or die "Error invoking $annotatecmd -l -S $tmpdir/.annotate_hints $filename : $!";
+ my @cmd = (qw(git-annotate -l -S), $a_hints, $filename);
+ if (!open(ANNOTATE, "-|", @cmd)) {
+ print "E error invoking ". join(' ',@cmd) .": $!\n";
+ return;
+ }
my $metadata = {};
print "E Annotations for $filename\n";
print "E ***************\n";
# the second is $state->{files} which is everything after it.
sub argsplit
{
- return unless( defined($state->{arguments}) and ref $state->{arguments} eq "ARRAY" );
-
- my $type = shift;
-
$state->{args} = [];
$state->{files} = [];
$state->{opt} = {};
+ return unless( defined($state->{arguments}) and ref $state->{arguments} eq "ARRAY" );
+
+ my $type = shift;
+
if ( defined($type) )
{
my $opt = {};
{
open NEWFILE, ">", $targetfile or die("Couldn't open '$targetfile' for writing : $!");
print NEWFILE $_ while ( <$fh> );
- close NEWFILE;
+ close NEWFILE or die("Failed to write '$targetfile': $!");
} else {
print "$size\n";
print while ( <$fh> );
}
- close $fh or die ("Couldn't close filehandle for transmitfile()");
+ close $fh or die ("Couldn't close filehandle for transmitfile(): $!");
} else {
die("Couldn't execute git-cat-file");
}
if ($parent eq $lastpicked) {
next;
}
- open my $p, 'git-merge-base '. $lastpicked . ' '
- . $parent . '|';
- my @output = (<$p>);
- close $p;
- my $base = join('', @output);
+ my $base = safe_pipe_capture('git-merge-base',
+ $lastpicked, $parent);
chomp $base;
if ($base) {
my @merged;
# print "want to log between $base $parent \n";
open(GITLOG, '-|', 'git-log', "$base..$parent")
- or die "Cannot call git-log: $!";
+ or die "Cannot call git-log: $!";
my $mergedhash;
while (<GITLOG>) {
chomp;
quiet=--quiet
;;
-v|--verbose)
- verbose=Yes
+ verbose="$verbose"Yes
;;
-k|--k|--ke|--kee|--keep)
keep='-k -k'
test -n "$verbose" && flags="$flags$LF-v"
test -n "$force$single_force" && flags="$flags$LF-f"
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
- git-fetch--tool $flags append-fetch-head "$@"
+ git fetch--tool $flags append-fetch-head "$@"
}
# updating the current HEAD with git-fetch in a bare
# repository is always fine.
if test -z "$update_head_ok" && test $(is_bare_repository) = false
then
- orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+ orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
fi
# Allow --notags from remote.$1.tagopt
case "$tags$no_tags" in
'')
- case "$(git-config --get "remote.$1.tagopt")" in
+ case "$(git config --get "remote.$1.tagopt")" in
--no-tags)
no_tags=t ;;
esac
then
taglist=`IFS=' ' &&
echo "$ls_remote_result" |
- git-show-ref --exclude-existing=refs/tags/ |
+ git show-ref --exclude-existing=refs/tags/ |
while read sha1 name
do
echo ".${name}:${name}"
fetch_all_at_once () {
- eval=$(echo "$1" | git-fetch--tool parse-reflist "-")
+ eval=$(echo "$1" | git fetch--tool parse-reflist "-")
eval "$eval"
( : subshell because we muck with IFS
IFS=" $LF"
(
if test "$remote" = . ; then
- git-show-ref $rref || echo failed "$remote"
+ git show-ref $rref || echo failed "$remote"
elif test -f "$remote" ; then
test -n "$shallow_depth" &&
die "shallow clone with bundle is not supported"
- git-bundle unbundle "$remote" $rref ||
+ git bundle unbundle "$remote" $rref ||
echo failed "$remote"
else
if test -d "$remote" &&
# connected to our repository's tips, in which
# case we do not have to do any fetch.
theirs=$(echo "$ls_remote_result" | \
- git-fetch--tool -s pick-rref "$rref" "-") &&
+ git fetch--tool -s pick-rref "$rref" "-") &&
# This will barf when $theirs reach an object that
# we do not have in our repository. Otherwise,
# we already have everything the fetch would bring in.
- git-rev-list --objects $theirs --not --all \
+ git rev-list --objects $theirs --not --all \
>/dev/null 2>/dev/null
then
echo "$ls_remote_result" | \
- git-fetch--tool pick-rref "$rref" "-"
+ git fetch--tool pick-rref "$rref" "-"
else
+ flags=
+ case $verbose in
+ YesYes*)
+ flags="-v"
+ ;;
+ esac
git-fetch-pack --thin $exec $keep $shallow_depth \
- $quiet $no_progress "$remote" $rref ||
+ $quiet $no_progress $flags "$remote" $rref ||
echo failed "$remote"
fi
fi
test -n "$verbose" && flags="$flags -v"
test -n "$force" && flags="$flags -f"
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
- git-fetch--tool $flags native-store \
+ git fetch--tool $flags native-store \
"$remote" "$remote_nick" "$refs"
)
) || exit
curl_extra_args="-k"
fi
if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
- "`git-config --bool http.noEPSV`" = true ]; then
+ "`git config --bool http.noEPSV`" = true ]; then
noepsv_opt="--disable-epsv"
fi
# Find $remote_name from ls-remote output.
head=$(echo "$ls_remote_result" | \
- git-fetch--tool -s pick-rref "$remote_name" "-")
+ git fetch--tool -s pick-rref "$remote_name" "-")
expr "z$head" : "z$_x40\$" >/dev/null ||
die "No such ref $remote_name at $remote"
echo >&2 "Fetching $remote_name from $remote using $proto"
die "shallow clone with rsync not supported"
TMP_HEAD="$GIT_DIR/TMP_HEAD"
rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
- head=$(git-rev-parse --verify TMP_HEAD)
+ head=$(git rev-parse --verify TMP_HEAD)
rm -f "$TMP_HEAD"
case "$quiet" in '') v=-v ;; *) v= ;; esac
test "$rsync_slurped_objects" || {
# using local tracking branch.
taglist=$(IFS=' ' &&
echo "$ls_remote_result" |
- git-show-ref --exclude-existing=refs/tags/ |
+ git show-ref --exclude-existing=refs/tags/ |
while read sha1 name
do
- git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
+ git cat-file -t "$sha1" >/dev/null 2>&1 || continue
echo >&2 "Auto-following $name"
echo ".${name}:${name}"
done)
'')
;;
?*)
- curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+ curr_head=$(git rev-parse --verify HEAD 2>/dev/null)
if test "$curr_head" != "$orig_head"
then
- git-update-ref \
+ git update-ref \
-m "$GIT_REFLOG_ACTION: Undoing incorrectly fetched HEAD." \
HEAD "$orig_head"
die "Cannot fetch into the current branch."
--- /dev/null
+#!/bin/sh
+#
+# Rewrite revision history
+# Copyright (c) Petr Baudis, 2006
+# Minimal changes to "port" it to core-git (c) Johannes Schindelin, 2007
+#
+# Lets you rewrite the revision history of the current branch, creating
+# a new branch. You can specify a number of filters to modify the commits,
+# files and trees.
+
+USAGE="git-filter-branch [-d TEMPDIR] [FILTERS] DESTBRANCH [REV-RANGE]"
+. git-sh-setup
+
+warn () {
+ echo "$*" >&2
+}
+
+map()
+{
+ # if it was not rewritten, take the original
+ if test -r "$workdir/../map/$1"
+ then
+ cat "$workdir/../map/$1"
+ else
+ echo "$1"
+ fi
+}
+
+# override die(): this version puts in an extra line break, so that
+# the progress is still visible
+
+die()
+{
+ echo >&2
+ echo "$*" >&2
+ exit 1
+}
+
+# When piped a commit, output a script to set the ident of either
+# "author" or "committer
+
+set_ident () {
+ lid="$(echo "$1" | tr "A-Z" "a-z")"
+ uid="$(echo "$1" | tr "a-z" "A-Z")"
+ pick_id_script='
+ /^'$lid' /{
+ s/'\''/'\''\\'\'\''/g
+ h
+ s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/export GIT_'$uid'_NAME='\''&'\''/p
+
+ g
+ s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/export GIT_'$uid'_EMAIL='\''&'\''/p
+
+ g
+ s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/export GIT_'$uid'_DATE='\''&'\''/p
+
+ q
+ }
+ '
+
+ LANG=C LC_ALL=C sed -ne "$pick_id_script"
+ # Ensure non-empty id name.
+ echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\""
+}
+
+tempdir=.git-rewrite
+filter_env=
+filter_tree=
+filter_index=
+filter_parent=
+filter_msg=cat
+filter_commit='git commit-tree "$@"'
+filter_tag_name=
+filter_subdir=
+orig_namespace=refs/original/
+force=
+while case "$#" in 0) usage;; esac
+do
+ case "$1" in
+ --)
+ shift
+ break
+ ;;
+ --force|-f)
+ shift
+ force=t
+ continue
+ ;;
+ -*)
+ ;;
+ *)
+ break;
+ esac
+
+ # all switches take one argument
+ ARG="$1"
+ case "$#" in 1) usage ;; esac
+ shift
+ OPTARG="$1"
+ shift
+
+ case "$ARG" in
+ -d)
+ tempdir="$OPTARG"
+ ;;
+ --env-filter)
+ filter_env="$OPTARG"
+ ;;
+ --tree-filter)
+ filter_tree="$OPTARG"
+ ;;
+ --index-filter)
+ filter_index="$OPTARG"
+ ;;
+ --parent-filter)
+ filter_parent="$OPTARG"
+ ;;
+ --msg-filter)
+ filter_msg="$OPTARG"
+ ;;
+ --commit-filter)
+ filter_commit="$OPTARG"
+ ;;
+ --tag-name-filter)
+ filter_tag_name="$OPTARG"
+ ;;
+ --subdirectory-filter)
+ filter_subdir="$OPTARG"
+ ;;
+ --original)
+ orig_namespace="$OPTARG"
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+
+case "$force" in
+t)
+ rm -rf "$tempdir"
+;;
+'')
+ test -d "$tempdir" &&
+ die "$tempdir already exists, please remove it"
+esac
+mkdir -p "$tempdir/t" &&
+tempdir="$(cd "$tempdir"; pwd)" &&
+cd "$tempdir/t" &&
+workdir="$(pwd)" ||
+die ""
+
+# Make sure refs/original is empty
+git for-each-ref > "$tempdir"/backup-refs
+while read sha1 type name
+do
+ case "$force,$name" in
+ ,$orig_namespace*)
+ die "Namespace $orig_namespace not empty"
+ ;;
+ t,$orig_namespace*)
+ git update-ref -d "$name" $sha1
+ ;;
+ esac
+done < "$tempdir"/backup-refs
+
+case "$GIT_DIR" in
+/*)
+ ;;
+*)
+ GIT_DIR="$(pwd)/../../$GIT_DIR"
+ ;;
+esac
+export GIT_DIR GIT_WORK_TREE=.
+
+# These refs should be updated if their heads were rewritten
+
+git rev-parse --revs-only --symbolic "$@" |
+while read ref
+do
+ # normalize ref
+ case "$ref" in
+ HEAD)
+ ref="$(git symbolic-ref "$ref")"
+ ;;
+ refs/*)
+ ;;
+ *)
+ ref="$(git for-each-ref --format='%(refname)' |
+ grep /"$ref")"
+ esac
+
+ git check-ref-format "$ref" && echo "$ref"
+done > "$tempdir"/heads
+
+test -s "$tempdir"/heads ||
+ die "Which ref do you want to rewrite?"
+
+export GIT_INDEX_FILE="$(pwd)/../index"
+git read-tree || die "Could not seed the index"
+
+ret=0
+
+# map old->new commit ids for rewriting parents
+mkdir ../map || die "Could not create map/ directory"
+
+case "$filter_subdir" in
+"")
+ git rev-list --reverse --topo-order --default HEAD \
+ --parents "$@"
+ ;;
+*)
+ git rev-list --reverse --topo-order --default HEAD \
+ --parents --full-history "$@" -- "$filter_subdir"
+esac > ../revs || die "Could not get the commits"
+commits=$(wc -l <../revs | tr -d " ")
+
+test $commits -eq 0 && die "Found nothing to rewrite"
+
+# Rewrite the commits
+
+i=0
+while read commit parents; do
+ i=$(($i+1))
+ printf "\rRewrite $commit ($i/$commits)"
+
+ case "$filter_subdir" in
+ "")
+ git read-tree -i -m $commit
+ ;;
+ *)
+ git read-tree -i -m $commit:"$filter_subdir"
+ esac || die "Could not initialize the index"
+
+ export GIT_COMMIT=$commit
+ git cat-file commit "$commit" >../commit ||
+ die "Cannot read commit $commit"
+
+ eval "$(set_ident AUTHOR <../commit)" ||
+ die "setting author failed for commit $commit"
+ eval "$(set_ident COMMITTER <../commit)" ||
+ die "setting committer failed for commit $commit"
+ eval "$filter_env" < /dev/null ||
+ die "env filter failed: $filter_env"
+
+ if [ "$filter_tree" ]; then
+ git checkout-index -f -u -a ||
+ die "Could not checkout the index"
+ # files that $commit removed are now still in the working tree;
+ # remove them, else they would be added again
+ git ls-files -z --others | xargs -0 rm -f
+ eval "$filter_tree" < /dev/null ||
+ die "tree filter failed: $filter_tree"
+
+ git diff-index -r $commit | cut -f 2- | tr '\n' '\0' | \
+ xargs -0 git update-index --add --replace --remove
+ git ls-files -z --others | \
+ xargs -0 git update-index --add --replace --remove
+ fi
+
+ eval "$filter_index" < /dev/null ||
+ die "index filter failed: $filter_index"
+
+ parentstr=
+ for parent in $parents; do
+ for reparent in $(map "$parent"); do
+ parentstr="$parentstr -p $reparent"
+ done
+ done
+ if [ "$filter_parent" ]; then
+ parentstr="$(echo "$parentstr" | eval "$filter_parent")" ||
+ die "parent filter failed: $filter_parent"
+ fi
+
+ sed -e '1,/^$/d' <../commit | \
+ eval "$filter_msg" > ../message ||
+ die "msg filter failed: $filter_msg"
+ sh -c "$filter_commit" "git commit-tree" \
+ $(git write-tree) $parentstr < ../message > ../map/$commit
+done <../revs
+
+# In case of a subdirectory filter, it is possible that a specified head
+# is not in the set of rewritten commits, because it was pruned by the
+# revision walker. Fix it by mapping these heads to the next rewritten
+# ancestor(s), i.e. the boundaries in the set of rewritten commits.
+
+# NEEDSWORK: we should sort the unmapped refs topologically first
+while read ref
+do
+ sha1=$(git rev-parse "$ref"^0)
+ test -f "$workdir"/../map/$sha1 && continue
+ # Assign the boundarie(s) in the set of rewritten commits
+ # as the replacement commit(s).
+ # (This would look a bit nicer if --not --stdin worked.)
+ for p in $( (cd "$workdir"/../map; ls | sed "s/^/^/") |
+ git rev-list $ref --boundary --stdin |
+ sed -n "s/^-//p")
+ do
+ map $p >> "$workdir"/../map/$sha1
+ done
+done < "$tempdir"/heads
+
+# Finally update the refs
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+count=0
+echo
+while read ref
+do
+ # avoid rewriting a ref twice
+ test -f "$orig_namespace$ref" && continue
+
+ sha1=$(git rev-parse "$ref"^0)
+ rewritten=$(map $sha1)
+
+ test $sha1 = "$rewritten" &&
+ warn "WARNING: Ref '$ref' is unchanged" &&
+ continue
+
+ case "$rewritten" in
+ '')
+ echo "Ref '$ref' was deleted"
+ git update-ref -m "filter-branch: delete" -d "$ref" $sha1 ||
+ die "Could not delete $ref"
+ ;;
+ $_x40)
+ echo "Ref '$ref' was rewritten"
+ git update-ref -m "filter-branch: rewrite" \
+ "$ref" $rewritten $sha1 ||
+ die "Could not rewrite $ref"
+ ;;
+ *)
+ # NEEDSWORK: possibly add -Werror, making this an error
+ warn "WARNING: '$ref' was rewritten into multiple commits:"
+ warn "$rewritten"
+ warn "WARNING: Ref '$ref' points to the first one now."
+ rewritten=$(echo "$rewritten" | head -n 1)
+ git update-ref -m "filter-branch: rewrite to first" \
+ "$ref" $rewritten $sha1 ||
+ die "Could not rewrite $ref"
+ ;;
+ esac
+ git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1
+ count=$(($count+1))
+done < "$tempdir"/heads
+
+# TODO: This should possibly go, with the semantics that all positive given
+# refs are updated, and their original heads stored in refs/original/
+# Filter tags
+
+if [ "$filter_tag_name" ]; then
+ git for-each-ref --format='%(objectname) %(objecttype) %(refname)' refs/tags |
+ while read sha1 type ref; do
+ ref="${ref#refs/tags/}"
+ # XXX: Rewrite tagged trees as well?
+ if [ "$type" != "commit" -a "$type" != "tag" ]; then
+ continue;
+ fi
+
+ if [ "$type" = "tag" ]; then
+ # Dereference to a commit
+ sha1t="$sha1"
+ sha1="$(git rev-parse "$sha1"^{commit} 2>/dev/null)" || continue
+ fi
+
+ [ -f "../map/$sha1" ] || continue
+ new_sha1="$(cat "../map/$sha1")"
+ export GIT_COMMIT="$sha1"
+ new_ref="$(echo "$ref" | eval "$filter_tag_name")" ||
+ die "tag name filter failed: $filter_tag_name"
+
+ echo "$ref -> $new_ref ($sha1 -> $new_sha1)"
+
+ if [ "$type" = "tag" ]; then
+ # Warn that we are not rewriting the tag object itself.
+ warn "unreferencing tag object $sha1t"
+ fi
+
+ git update-ref "refs/tags/$new_ref" "$new_sha1" ||
+ die "Could not write tag $new_ref"
+ done
+fi
+
+cd ../..
+rm -rf "$tempdir"
+echo
+test $count -gt 0 && echo "These refs were rewritten:"
+git show-ref | grep ^"$orig_namespace"
+
+exit $ret
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=0.7.GITGUI
+DEF_VER=0.8.GITGUI
LF='
'
INSTALL = install
endif
+INSTALL_D0 = $(INSTALL) -d -m755 # space is required here
+INSTALL_D1 =
+INSTALL_R0 = $(INSTALL) -m644 # space is required here
+INSTALL_R1 =
+INSTALL_X0 = $(INSTALL) -m755 # space is required here
+INSTALL_X1 =
+INSTALL_L0 = rm -f # space is required here
+INSTALL_L1 = && ln # space is required here
+INSTALL_L2 =
+INSTALL_L3 =
+
ifndef V
- QUIET_GEN = @echo ' ' GEN $@;
- QUIET_BUILT_IN = @echo ' ' BUILTIN $@;
- QUIET_INDEX = @echo ' ' INDEX $(dir $@);
+ QUIET = @
+ QUIET_GEN = $(QUIET)echo ' ' GEN $@ &&
+ QUIET_BUILT_IN = $(QUIET)echo ' ' BUILTIN $@ &&
+ QUIET_INDEX = $(QUIET)echo ' ' INDEX $(dir $@) &&
QUIET_2DEVNULL = 2>/dev/null
+
+ INSTALL_D0 = dir=
+ INSTALL_D1 = && echo ' ' DEST $$dir && $(INSTALL) -d -m755 "$$dir"
+ INSTALL_R0 = src=
+ INSTALL_R1 = && echo ' ' INSTALL 644 `basename $$src` && $(INSTALL) -m644 $$src
+ INSTALL_X0 = src=
+ INSTALL_X1 = && echo ' ' INSTALL 755 `basename $$src` && $(INSTALL) -m755 $$src
+
+ INSTALL_L0 = dst=
+ INSTALL_L1 = && src=
+ INSTALL_L2 = && dst=
+ INSTALL_L3 = && echo ' ' 'LINK ' `basename "$$dst"` '->' `basename "$$src"` && rm -f "$$dst" && ln "$$src" "$$dst"
endif
TCL_PATH ?= tclsh
all:: $(ALL_PROGRAMS) lib/tclIndex
install: all
- $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
- $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)'
- $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;)
- $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)'
- $(INSTALL) -m644 lib/tclIndex '$(DESTDIR_SQ)$(libdir_SQ)'
- $(foreach p,$(ALL_LIBFILES), $(INSTALL) -m644 $p '$(DESTDIR_SQ)$(libdir_SQ)' ;)
+ $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1)
+ $(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+ $(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true
+ $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(INSTALL_D1)
+ $(QUIET)$(INSTALL_R0)lib/tclIndex $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)'
+ $(QUIET)$(foreach p,$(ALL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true
dist-version:
@mkdir -p $(TARDIR)
exit 1
}
+######################################################################
+##
+## enable verbose loading?
+
+if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
+ unset _verbose
+ rename auto_load real__auto_load
+ proc auto_load {name args} {
+ puts stderr "auto_load $name"
+ return [uplevel 1 real__auto_load $name $args]
+ }
+ rename source real__source
+ proc source {name} {
+ puts stderr "source $name"
+ uplevel 1 real__source $name
+ }
+}
+
######################################################################
##
## configure our library
} elseif {[string match @@* $oguirel]} {
set oguilib [file join [file dirname [file normalize $argv0]] lib]
}
+
set idx [file join $oguilib tclIndex]
-catch {
- set fd [open $idx r]
- if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
- set idx [list]
- while {[gets $fd n] >= 0} {
- if {$n ne {} && ![string match #* $n]} {
- lappend idx $n
- }
+if {[catch {set fd [open $idx r]} err]} {
+ catch {wm withdraw .}
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title "git-gui: fatal error" \
+ -message $err
+ exit 1
+}
+if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
+ set idx [list]
+ while {[gets $fd n] >= 0} {
+ if {$n ne {} && ![string match #* $n]} {
+ lappend idx $n
}
- } else {
- set idx {}
}
- close $fd
+} else {
+ set idx {}
}
+close $fd
+
if {$idx ne {}} {
set loaded [list]
foreach p $idx {
if {[lsearch -exact $loaded $p] >= 0} continue
- puts $p
source [file join $oguilib $p]
lappend loaded $p
}
} else {
set auto_path [concat [list $oguilib] $auto_path]
}
-unset -nocomplain oguilib oguirel idx fd
-
-if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
- unset _verbose
- rename auto_load real__auto_load
- proc auto_load {name args} {
- puts stderr "auto_load $name"
- return [uplevel 1 real__auto_load $name $args]
- }
- rename source real__source
- proc source {name} {
- puts stderr "source $name"
- uplevel 1 real__source $name
- }
-}
+unset -nocomplain oguirel idx fd
######################################################################
##
set _gitexec {}
set _reponame {}
set _iscygwin {}
+set _search_path {}
proc appname {} {
global _appname
if {$args eq {}} {
return $_gitdir
}
- return [eval [concat [list file join $_gitdir] $args]]
+ return [eval [list file join $_gitdir] $args]
}
proc gitexec {args} {
if {[catch {set _gitexec [git --exec-path]} err]} {
error "Git not installed?\n\n$err"
}
+ if {[is_Cygwin]} {
+ set _gitexec [exec cygpath \
+ --windows \
+ --absolute \
+ $_gitexec]
+ } else {
+ set _gitexec [file normalize $_gitexec]
+ }
}
if {$args eq {}} {
return $_gitexec
}
- return [eval [concat [list file join $_gitexec] $args]]
+ return [eval [list file join $_gitexec] $args]
}
proc reponame {} {
- global _reponame
- return $_reponame
+ return $::_reponame
}
proc is_MacOSX {} {
- global tcl_platform tk_library
if {[tk windowingsystem] eq {aqua}} {
return 1
}
}
proc is_Windows {} {
- global tcl_platform
- if {$tcl_platform(platform) eq {windows}} {
+ if {$::tcl_platform(platform) eq {windows}} {
return 1
}
return 0
}
proc is_Cygwin {} {
- global tcl_platform _iscygwin
+ global _iscygwin
if {$_iscygwin eq {}} {
- if {$tcl_platform(platform) eq {windows}} {
+ if {$::tcl_platform(platform) eq {windows}} {
if {[catch {set p [exec cygpath --windir]} err]} {
set _iscygwin 0
} else {
}
}
+proc get_config {name} {
+ global repo_config
+ if {[catch {set v $repo_config($name)}]} {
+ return {}
+ } else {
+ return $v
+ }
+}
+
proc load_config {include_global} {
global repo_config global_config default_config
array unset global_config
if {$include_global} {
catch {
- set fd_rc [open "| git config --global --list" r]
+ set fd_rc [git_read config --global --list]
while {[gets $fd_rc line] >= 0} {
if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
if {[is_many_config $name]} {
array unset repo_config
catch {
- set fd_rc [open "| git config --list" r]
+ set fd_rc [git_read config --list]
while {[gets $fd_rc line] >= 0} {
if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
if {[is_many_config $name]} {
##
## handy utils
+proc _git_cmd {name} {
+ global _git_cmd_path
+
+ if {[catch {set v $_git_cmd_path($name)}]} {
+ switch -- $name {
+ version -
+ --version -
+ --exec-path { return [list $::_git $name] }
+ }
+
+ set p [gitexec git-$name$::_search_exe]
+ if {[file exists $p]} {
+ set v [list $p]
+ } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
+ # Try to determine what sort of magic will make
+ # git-$name go and do its thing, because native
+ # Tcl on Windows doesn't know it.
+ #
+ set p [gitexec git-$name]
+ set f [open $p r]
+ set s [gets $f]
+ close $f
+
+ switch -glob -- $s {
+ #!*sh { set i sh }
+ #!*perl { set i perl }
+ #!*python { set i python }
+ default { error "git-$name is not supported: $s" }
+ }
+
+ upvar #0 _$i interp
+ if {![info exists interp]} {
+ set interp [_which $i]
+ }
+ if {$interp eq {}} {
+ error "git-$name requires $i (not in PATH)"
+ }
+ set v [list $interp $p]
+ } else {
+ # Assume it is builtin to git somehow and we
+ # aren't actually able to see a file for it.
+ #
+ set v [list $::_git $name]
+ }
+ set _git_cmd_path($name) $v
+ }
+ return $v
+}
+
+proc _which {what} {
+ global env _search_exe _search_path
+
+ if {$_search_path eq {}} {
+ if {[is_Cygwin]} {
+ set _search_path [split [exec cygpath \
+ --windows \
+ --path \
+ --absolute \
+ $env(PATH)] {;}]
+ set _search_exe .exe
+ } elseif {[is_Windows]} {
+ set _search_path [split $env(PATH) {;}]
+ set _search_exe .exe
+ } else {
+ set _search_path [split $env(PATH) :]
+ set _search_exe {}
+ }
+ }
+
+ foreach p $_search_path {
+ set p [file join $p $what$_search_exe]
+ if {[file exists $p]} {
+ return [file normalize $p]
+ }
+ }
+ return {}
+}
+
+proc _lappend_nice {cmd_var} {
+ global _nice
+ upvar $cmd_var cmd
+
+ if {![info exists _nice]} {
+ set _nice [_which nice]
+ }
+ if {$_nice ne {}} {
+ lappend cmd $_nice
+ }
+}
+
proc git {args} {
- return [eval exec git $args]
+ set opt [list exec]
+
+ while {1} {
+ switch -- [lindex $args 0] {
+ --nice {
+ _lappend_nice opt
+ }
+
+ default {
+ break
+ }
+
+ }
+
+ set args [lrange $args 1 end]
+ }
+
+ set cmdp [_git_cmd [lindex $args 0]]
+ set args [lrange $args 1 end]
+
+ return [eval $opt $cmdp $args]
+}
+
+proc _open_stdout_stderr {cmd} {
+ if {[catch {
+ set fd [open $cmd r]
+ } err]} {
+ if { [lindex $cmd end] eq {2>@1}
+ && $err eq {can not find channel named "1"}
+ } {
+ # Older versions of Tcl 8.4 don't have this 2>@1 IO
+ # redirect operator. Fallback to |& cat for those.
+ # The command was not actually started, so its safe
+ # to try to start it a second time.
+ #
+ set fd [open [concat \
+ [lrange $cmd 0 end-1] \
+ [list |& cat] \
+ ] r]
+ } else {
+ error $err
+ }
+ }
+ fconfigure $fd -eofchar {}
+ return $fd
+}
+
+proc git_read {args} {
+ set opt [list |]
+
+ while {1} {
+ switch -- [lindex $args 0] {
+ --nice {
+ _lappend_nice opt
+ }
+
+ --stderr {
+ lappend args 2>@1
+ }
+
+ default {
+ break
+ }
+
+ }
+
+ set args [lrange $args 1 end]
+ }
+
+ set cmdp [_git_cmd [lindex $args 0]]
+ set args [lrange $args 1 end]
+
+ return [_open_stdout_stderr [concat $opt $cmdp $args]]
+}
+
+proc git_write {args} {
+ set opt [list |]
+
+ while {1} {
+ switch -- [lindex $args 0] {
+ --nice {
+ _lappend_nice opt
+ }
+
+ default {
+ break
+ }
+
+ }
+
+ set args [lrange $args 1 end]
+ }
+
+ set cmdp [_git_cmd [lindex $args 0]]
+ set args [lrange $args 1 end]
+
+ return [open [concat $opt $cmdp $args] w]
+}
+
+proc sq {value} {
+ regsub -all ' $value "'\\''" value
+ return "'$value'"
+}
+
+proc load_current_branch {} {
+ global current_branch is_detached
+
+ set fd [open [gitdir HEAD] r]
+ if {[gets $fd ref] < 1} {
+ set ref {}
+ }
+ close $fd
+
+ set pfx {ref: refs/heads/}
+ set len [string length $pfx]
+ if {[string equal -length $len $pfx $ref]} {
+ # We're on a branch. It might not exist. But
+ # HEAD looks good enough to be a branch.
+ #
+ set current_branch [string range $ref $len end]
+ set is_detached 0
+ } else {
+ # Assume this is a detached head.
+ #
+ set current_branch HEAD
+ set is_detached 1
+ }
}
auto_load tk_optionMenu
######################################################################
##
-## version check
+## find git
-set req_maj 1
-set req_min 5
+set _git [_which git]
+if {$_git eq {}} {
+ catch {wm withdraw .}
+ error_popup "Cannot find git in PATH."
+ exit 1
+}
+
+######################################################################
+##
+## version check
-if {[catch {set v [git --version]} err]} {
+if {[catch {set _git_version [git --version]} err]} {
catch {wm withdraw .}
error_popup "Cannot determine Git version:
$err
-[appname] requires Git $req_maj.$req_min or later."
+[appname] requires Git 1.5.0 or later."
exit 1
}
-if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} {
- if {$act_maj < $req_maj
- || ($act_maj == $req_maj && $act_min < $req_min)} {
- catch {wm withdraw .}
- error_popup "[appname] requires Git $req_maj.$req_min or later.
+if {![regsub {^git version } $_git_version {} _git_version]} {
+ catch {wm withdraw .}
+ error_popup "Cannot parse Git version string:\n\n$_git_version"
+ exit 1
+}
+
+set _real_git_version $_git_version
+regsub -- {-dirty$} $_git_version {} _git_version
+regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
+regsub {\.rc[0-9]+$} $_git_version {} _git_version
+regsub {\.GIT$} $_git_version {} _git_version
-You are using $v."
+if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
+ catch {wm withdraw .}
+ if {[tk_messageBox \
+ -icon warning \
+ -type yesno \
+ -default no \
+ -title "[appname]: warning" \
+ -message "Git version cannot be determined.
+
+$_git claims it is version '$_real_git_version'.
+
+[appname] requires at least Git 1.5.0 or later.
+
+Assume '$_real_git_version' is version 1.5.0?
+"] eq {yes}} {
+ set _git_version 1.5.0
+ } else {
exit 1
}
-} else {
+}
+unset _real_git_version
+
+proc git-version {args} {
+ global _git_version
+
+ switch [llength $args] {
+ 0 {
+ return $_git_version
+ }
+
+ 2 {
+ set op [lindex $args 0]
+ set vr [lindex $args 1]
+ set cm [package vcompare $_git_version $vr]
+ return [expr $cm $op 0]
+ }
+
+ 4 {
+ set type [lindex $args 0]
+ set name [lindex $args 1]
+ set parm [lindex $args 2]
+ set body [lindex $args 3]
+
+ if {($type ne {proc} && $type ne {method})} {
+ error "Invalid arguments to git-version"
+ }
+ if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
+ error "Last arm of $type $name must be default"
+ }
+
+ foreach {op vr cb} [lrange $body 0 end-2] {
+ if {[git-version $op $vr]} {
+ return [uplevel [list $type $name $parm $cb]]
+ }
+ }
+
+ return [uplevel [list $type $name $parm [lindex $body end]]]
+ }
+
+ default {
+ error "git-version >= x"
+ }
+
+ }
+}
+
+if {[git-version < 1.5]} {
catch {wm withdraw .}
- error_popup "Cannot parse Git version string:\n\n$v"
+ error_popup "[appname] requires Git 1.5.0 or later.
+
+You are using [git-version]:
+
+[git --version]"
exit 1
}
-unset -nocomplain v _junk act_maj act_min req_maj req_min
+
+######################################################################
+##
+## feature option selection
+
+if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
+ unset _junk
+} else {
+ set subcommand gui
+}
+if {$subcommand eq {gui.sh}} {
+ set subcommand gui
+}
+if {$subcommand eq {gui} && [llength $argv] > 0} {
+ set subcommand [lindex $argv 0]
+ set argv [lrange $argv 1 end]
+}
+
+enable_option multicommit
+enable_option branch
+enable_option transport
+disable_option bare
+
+switch -- $subcommand {
+browser -
+blame {
+ enable_option bare
+
+ disable_option multicommit
+ disable_option branch
+ disable_option transport
+}
+citool {
+ enable_option singlecommit
+
+ disable_option multicommit
+ disable_option branch
+ disable_option transport
+}
+}
######################################################################
##
error_popup "Git directory not found:\n\n$_gitdir"
exit 1
}
-if {[lindex [file split $_gitdir] end] ne {.git}} {
- catch {wm withdraw .}
- error_popup "Cannot use funny .git directory:\n\n$_gitdir"
- exit 1
+if {![is_enabled bare]} {
+ if {[lindex [file split $_gitdir] end] ne {.git}} {
+ catch {wm withdraw .}
+ error_popup "Cannot use funny .git directory:\n\n$_gitdir"
+ exit 1
+ }
+ if {[catch {cd [file dirname $_gitdir]} err]} {
+ catch {wm withdraw .}
+ error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
+ exit 1
+ }
}
-if {[catch {cd [file dirname $_gitdir]} err]} {
- catch {wm withdraw .}
- error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
- exit 1
+set _reponame [file split [file normalize $_gitdir]]
+if {[lindex $_reponame end] eq {.git}} {
+ set _reponame [lindex $_reponame end-1]
+} else {
+ set _reponame [lindex $_reponame end]
}
-set _reponame [lindex [file split \
- [file normalize [file dirname $_gitdir]]] \
- end]
######################################################################
##
set current_diff_path {}
set current_diff_side {}
set diff_actions [list]
-set ui_status_value {Initializing...}
set HEAD {}
set PARENT {}
set commit_type {}
set empty_tree {}
set current_branch {}
+set is_detached 0
set current_diff_path {}
set selected_commit_type new
set mh [list]
- if {[catch {set current_branch [git symbolic-ref HEAD]}]} {
- set current_branch {}
- } else {
- regsub ^refs/((heads|tags|remotes)/)? \
- $current_branch \
- {} \
- current_branch
- }
-
+ load_current_branch
if {[catch {set hd [git rev-parse --verify HEAD]}]} {
set hd {}
set ct initial
proc rescan {after {honor_trustmtime 1}} {
global HEAD PARENT MERGE_HEAD commit_type
- global ui_index ui_workdir ui_status_value ui_comm
+ global ui_index ui_workdir ui_comm
global rescan_active file_states
global repo_config
array unset file_states
- if {![$ui_comm edit modified]
- || [string trim [$ui_comm get 0.0 end]] eq {}} {
+ if {!$::GITGUI_BCK_exists &&
+ (![$ui_comm edit modified]
+ || [string trim [$ui_comm get 0.0 end]] eq {})} {
if {[string match amend* $commit_type]} {
} elseif {[load_message GITGUI_MSG]} {
} elseif {[load_message MERGE_MSG]} {
$ui_comm edit modified false
}
- if {[is_enabled branch]} {
- load_all_heads
- populate_branch_menu
- }
-
if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
rescan_stage2 {} $after
} else {
set rescan_active 1
- set ui_status_value {Refreshing file status...}
- set cmd [list git update-index]
- lappend cmd -q
- lappend cmd --unmerged
- lappend cmd --ignore-missing
- lappend cmd --refresh
- set fd_rf [open "| $cmd" r]
+ ui_status {Refreshing file status...}
+ set fd_rf [git_read update-index \
+ -q \
+ --unmerged \
+ --ignore-missing \
+ --refresh \
+ ]
fconfigure $fd_rf -blocking 0 -translation binary
fileevent $fd_rf readable \
[list rescan_stage2 $fd_rf $after]
}
proc rescan_stage2 {fd after} {
- global ui_status_value
global rescan_active buf_rdi buf_rdf buf_rlo
if {$fd ne {}} {
close $fd
}
- set ls_others [list | git ls-files --others -z \
- --exclude-per-directory=.gitignore]
+ set ls_others [list --exclude-per-directory=.gitignore]
set info_exclude [gitdir info exclude]
if {[file readable $info_exclude]} {
lappend ls_others "--exclude-from=$info_exclude"
}
+ set user_exclude [get_config core.excludesfile]
+ if {$user_exclude ne {} && [file readable $user_exclude]} {
+ lappend ls_others "--exclude-from=$user_exclude"
+ }
set buf_rdi {}
set buf_rdf {}
set buf_rlo {}
set rescan_active 3
- set ui_status_value {Scanning for modified files ...}
- set fd_di [open "| git diff-index --cached -z [PARENT]" r]
- set fd_df [open "| git diff-files -z" r]
- set fd_lo [open $ls_others r]
+ ui_status {Scanning for modified files ...}
+ set fd_di [git_read diff-index --cached -z [PARENT]]
+ set fd_df [git_read diff-files -z]
+ set fd_lo [eval git_read ls-files --others -z $ls_others]
fconfigure $fd_di -blocking 0 -translation binary -encoding binary
fconfigure $fd_df -blocking 0 -translation binary -encoding binary
if {[catch {set fd [open $f r]}]} {
return 0
}
+ fconfigure $fd -eofchar {}
set content [string trim [read $fd]]
close $fd
regsub -all -line {[ \r\t]+$} $content {} content
return $r
}
+proc ui_status {msg} {
+ $::main_status show $msg
+}
+
+proc ui_ready {{test {}}} {
+ $::main_status show {Ready.} $test
+}
+
proc escape_path {path} {
regsub -all {\\} $path "\\\\" path
regsub -all "\n" $path "\\n" path
0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
} -maskdata $filemask
-set file_dir_data {
-#define file_width 18
-#define file_height 18
-static unsigned char file_bits[] = {
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
- 0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
- 0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
- 0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-}
-image create bitmap file_dir -background white -foreground blue \
- -data $file_dir_data -maskdata $file_dir_data
-unset file_dir_data
-
-set file_uplevel_data {
-#define up_width 15
-#define up_height 15
-static unsigned char up_bits[] = {
- 0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
- 0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
- 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
-}
-image create bitmap file_uplevel -background white -foreground red \
- -data $file_uplevel_data -maskdata $file_uplevel_data
-unset file_uplevel_data
-
set ui_index .vpane.files.index.list
set ui_workdir .vpane.files.workdir.list
set starting_gitk_msg {Starting gitk... please wait...}
proc do_gitk {revs} {
- global env ui_status_value starting_gitk_msg
-
# -- Always start gitk through whatever we were loaded with. This
# lets us bypass using shell process on Windows systems.
#
- set cmd [list [info nameofexecutable]]
- set exe [gitexec gitk]
- lappend cmd $exe
- if {$revs ne {}} {
- append cmd { }
- append cmd $revs
- }
-
+ set exe [file join [file dirname $::_git] gitk]
+ set cmd [list [info nameofexecutable] $exe]
if {! [file exists $exe]} {
error_popup "Unable to start gitk:\n\n$exe does not exist"
} else {
- eval exec $cmd &
- set ui_status_value $starting_gitk_msg
+ eval exec $cmd $revs &
+ ui_status $::starting_gitk_msg
after 10000 {
- if {$ui_status_value eq $starting_gitk_msg} {
- set ui_status_value {Ready.}
- }
+ ui_ready $starting_gitk_msg
}
}
}
proc do_quit {} {
global ui_comm is_quitting repo_config commit_type
+ global GITGUI_BCK_exists GITGUI_BCK_i
if {$is_quitting} return
set is_quitting 1
# -- Stash our current commit buffer.
#
set save [gitdir GITGUI_MSG]
- set msg [string trim [$ui_comm get 0.0 end]]
- regsub -all -line {[ \r\t]+$} $msg {} msg
- if {(![string match amend* $commit_type]
- || [$ui_comm edit modified])
- && $msg ne {}} {
- catch {
- set fd [open $save w]
- puts -nonewline $fd $msg
- close $fd
- }
+ if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
+ file rename -force [gitdir GITGUI_BCK] $save
+ set GITGUI_BCK_exists 0
} else {
- catch {file delete $save}
+ set msg [string trim [$ui_comm get 0.0 end]]
+ regsub -all -line {[ \r\t]+$} $msg {} msg
+ if {(![string match amend* $commit_type]
+ || [$ui_comm edit modified])
+ && $msg ne {}} {
+ catch {
+ set fd [open $save w]
+ puts -nonewline $fd $msg
+ close $fd
+ }
+ } else {
+ catch {file delete $save}
+ }
+ }
+
+ # -- Remove our editor backup, its not needed.
+ #
+ after cancel $GITGUI_BCK_i
+ if {$GITGUI_BCK_exists} {
+ catch {file delete [gitdir GITGUI_BCK]}
}
# -- Stash our current window geometry into this repository.
}
proc do_rescan {} {
- rescan {set ui_status_value {Ready.}}
+ rescan ui_ready
}
proc do_commit {} {
update_indexinfo \
"Unstaging [short_path $path] from commit" \
[list $path] \
- [concat $after {set ui_status_value {Ready.}}]
+ [concat $after [list ui_ready]]
} elseif {$w eq $ui_workdir} {
update_index \
"Adding [short_path $path]" \
[list $path] \
- [concat $after {set ui_status_value {Ready.}}]
+ [concat $after [list ui_ready]]
}
} else {
show_diff $path $w $lno
}
unset class
+if {[is_Windows] || [is_MacOSX]} {
+ option add *Menu.tearOff 0
+}
+
if {[is_MacOSX]} {
set M1B M1
set M1T Cmd
}
}
+set default_config(merge.diffstat) true
set default_config(merge.summary) false
set default_config(merge.verbosity) 2
set default_config(user.name) {}
set default_config(user.email) {}
+set default_config(gui.matchtrackingbranch) false
+set default_config(gui.pruneduringfetch) false
set default_config(gui.trustmtime) false
set default_config(gui.diffcontext) 5
set default_config(gui.newbranchtemplate) {}
load_config 0
apply_config
-######################################################################
-##
-## feature option selection
-
-if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
- unset _junk
-} else {
- set subcommand gui
-}
-if {$subcommand eq {gui.sh}} {
- set subcommand gui
-}
-if {$subcommand eq {gui} && [llength $argv] > 0} {
- set subcommand [lindex $argv 0]
- set argv [lrange $argv 1 end]
-}
-
-enable_option multicommit
-enable_option branch
-enable_option transport
-
-switch -- $subcommand {
-browser -
-blame {
- disable_option multicommit
- disable_option branch
- disable_option transport
-}
-citool {
- enable_option singlecommit
-
- disable_option multicommit
- disable_option branch
- disable_option transport
-}
-}
-
######################################################################
##
## ui construction
menu .mbar.repository
.mbar.repository add command \
- -label {Browse Current Branch} \
+ -label {Browse Current Branch's Files} \
-command {browser::new $current_branch}
-trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
+set ui_browse_current [.mbar.repository index last]
+.mbar.repository add command \
+ -label {Browse Branch Files...} \
+ -command browser_open::dialog
.mbar.repository add separator
.mbar.repository add command \
- -label {Visualize Current Branch} \
+ -label {Visualize Current Branch's History} \
-command {do_gitk $current_branch}
-trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
+set ui_visualize_current [.mbar.repository index last]
.mbar.repository add command \
- -label {Visualize All Branches} \
+ -label {Visualize All Branch History} \
-command {do_gitk --all}
.mbar.repository add separator
+proc current_branch_write {args} {
+ global current_branch
+ .mbar.repository entryconf $::ui_browse_current \
+ -label "Browse $current_branch's Files"
+ .mbar.repository entryconf $::ui_visualize_current \
+ -label "Visualize $current_branch's History"
+}
+trace add variable current_branch write current_branch_write
+
if {[is_enabled multicommit]} {
.mbar.repository add command -label {Database Statistics} \
-command do_stats
menu .mbar.branch
.mbar.branch add command -label {Create...} \
- -command do_create_branch \
+ -command branch_create::dialog \
-accelerator $M1T-N
lappend disable_on_lock [list .mbar.branch entryconf \
[.mbar.branch index last] -state]
+ .mbar.branch add command -label {Checkout...} \
+ -command branch_checkout::dialog \
+ -accelerator $M1T-O
+ lappend disable_on_lock [list .mbar.branch entryconf \
+ [.mbar.branch index last] -state]
+
+ .mbar.branch add command -label {Rename...} \
+ -command branch_rename::dialog
+ lappend disable_on_lock [list .mbar.branch entryconf \
+ [.mbar.branch index last] -state]
+
.mbar.branch add command -label {Delete...} \
- -command do_delete_branch
+ -command branch_delete::dialog
lappend disable_on_lock [list .mbar.branch entryconf \
[.mbar.branch index last] -state]
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
- .mbar.commit add command -label {Add To Commit} \
+ .mbar.commit add command -label {Stage To Commit} \
-command do_add_selection
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
- .mbar.commit add command -label {Add Existing To Commit} \
+ .mbar.commit add command -label {Stage Changed Files To Commit} \
-command do_add_all \
-accelerator $M1T-I
lappend disable_on_lock \
if {[is_enabled branch]} {
menu .mbar.merge
.mbar.merge add command -label {Local Merge...} \
- -command merge::dialog
+ -command merge::dialog \
+ -accelerator $M1T-M
lappend disable_on_lock \
[list .mbar.merge entryconf [.mbar.merge index last] -state]
.mbar.merge add command -label {Abort Merge...} \
-command merge::reset_hard
lappend disable_on_lock \
[list .mbar.merge entryconf [.mbar.merge index last] -state]
-
}
# -- Transport Menu
.mbar.push add command -label {Push...} \
-command do_push_anywhere \
-accelerator $M1T-P
+ .mbar.push add command -label {Delete...} \
+ -command remote_branch_delete::dialog
}
if {[is_MacOSX]} {
.mbar.edit add separator
.mbar.edit add command -label {Options...} \
-command do_options
-
- # -- Tools Menu
- #
- if {[is_Cygwin] && [file exists /usr/local/miga/lib/gui-miga]} {
- proc do_miga {} {
- global ui_status_value
- if {![lock_index update]} return
- set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
- set miga_fd [open "|$cmd" r]
- fconfigure $miga_fd -blocking 0
- fileevent $miga_fd readable [list miga_done $miga_fd]
- set ui_status_value {Running miga...}
- }
- proc miga_done {fd} {
- read $fd 512
- if {[eof $fd]} {
- close $fd
- unlock_index
- rescan [list set ui_status_value {Ready.}]
- }
- }
- .mbar add cascade -label Tools -menu .mbar.tools
- menu .mbar.tools
- .mbar.tools add command -label "Migrate" \
- -command do_miga
- lappend disable_on_lock \
- [list .mbar.tools entryconf [.mbar.tools index last] -state]
- }
}
# -- Help Menu
# -- Not a normal commit type invocation? Do that instead!
#
switch -- $subcommand {
-browser {
- set subcommand_args {rev?}
- switch [llength $argv] {
- 0 {
- set current_branch [git symbolic-ref HEAD]
- regsub ^refs/((heads|tags|remotes)/)? \
- $current_branch {} current_branch
- }
- 1 {
- set current_branch [lindex $argv 0]
- }
- default usage
- }
- browser::new $current_branch
- return
-}
+browser -
blame {
- set subcommand_args {rev? path?}
+ set subcommand_args {rev? path}
+ if {$argv eq {}} usage
set head {}
set path {}
set is_path 0
} elseif {$head eq {}} {
if {$head ne {}} usage
set head $a
+ set is_path 1
} else {
usage
}
}
unset is_path
+ if {$head ne {} && $path eq {}} {
+ set path $_prefix$head
+ set head {}
+ }
+
if {$head eq {}} {
- set current_branch [git symbolic-ref HEAD]
- regsub ^refs/((heads|tags|remotes)/)? \
- $current_branch {} current_branch
+ load_current_branch
} else {
+ if {[regexp {^[0-9a-f]{1,39}$} $head]} {
+ if {[catch {
+ set head [git rev-parse --verify $head]
+ } err]} {
+ puts stderr $err
+ exit 1
+ }
+ }
set current_branch $head
}
- if {$path eq {}} usage
- blame::new $head $path
+ switch -- $subcommand {
+ browser {
+ if {$head eq {}} {
+ if {$path ne {} && [file isdirectory $path]} {
+ set head $current_branch
+ } else {
+ set head $path
+ set path {}
+ }
+ }
+ browser::new $head $path
+ }
+ blame {
+ if {$head eq {} && ![file exists $path]} {
+ puts stderr "fatal: cannot stat path $path: No such file or directory"
+ exit 1
+ }
+ blame::new $head $path
+ }
+ }
return
}
citool -
lappend disable_on_lock \
{.vpane.lower.commarea.buttons.rescan conf -state}
-button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
+button .vpane.lower.commarea.buttons.incall -text {Stage Changed} \
-command do_add_all
pack .vpane.lower.commarea.buttons.incall -side top -fill x
lappend disable_on_lock \
$ctxm add separator
$ctxm add command -label {Options...} \
-command do_options
-bind_button3 $ui_diff "
- set cursorX %x
- set cursorY %y
- if {\$ui_index eq \$current_diff_side} {
- $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
+proc popup_diff_menu {ctxm x y X Y} {
+ set ::cursorX $x
+ set ::cursorY $y
+ if {$::ui_index eq $::current_diff_side} {
+ $ctxm entryconf $::ui_diff_applyhunk \
+ -state normal \
+ -label {Unstage Hunk From Commit}
+ } elseif {{_O} eq [lindex $::file_states($::current_diff_path) 0]} {
+ $ctxm entryconf $::ui_diff_applyhunk \
+ -state disabled \
+ -label {Stage Hunk For Commit}
} else {
- $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
+ $ctxm entryconf $::ui_diff_applyhunk \
+ -state normal \
+ -label {Stage Hunk For Commit}
}
- tk_popup $ctxm %X %Y
-"
-unset ui_diff_applyhunk
+ tk_popup $ctxm $X $Y
+}
+bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
# -- Status Bar
#
-label .status -textvariable ui_status_value \
- -anchor w \
- -justify left \
- -borderwidth 1 \
- -relief sunken
+set main_status [::status_bar::new .status]
pack .status -anchor w -side bottom -fill x
+$main_status show {Initializing...}
# -- Load geometry
#
bind $ui_diff <Button-1> {focus %W}
if {[is_enabled branch]} {
- bind . <$M1B-Key-n> do_create_branch
- bind . <$M1B-Key-N> do_create_branch
+ bind . <$M1B-Key-n> branch_create::dialog
+ bind . <$M1B-Key-N> branch_create::dialog
+ bind . <$M1B-Key-o> branch_checkout::dialog
+ bind . <$M1B-Key-O> branch_checkout::dialog
+ bind . <$M1B-Key-m> merge::dialog
+ bind . <$M1B-Key-M> merge::dialog
}
if {[is_enabled transport]} {
bind . <$M1B-Key-p> do_push_anywhere
#
if {[is_enabled transport]} {
load_all_remotes
- load_all_heads
- populate_branch_menu
populate_fetch_menu
populate_push_menu
}
-# -- Only suggest a gc run if we are going to stay running.
-#
-if {[is_enabled multicommit]} {
- set object_limit 2000
- if {[is_Windows]} {set object_limit 200}
- regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
- if {$objects_current >= $object_limit} {
- if {[ask_popup \
- "This repository currently has $objects_current loose objects.
+if {[winfo exists $ui_comm]} {
+ set GITGUI_BCK_exists [load_message GITGUI_BCK]
+
+ # -- If both our backup and message files exist use the
+ # newer of the two files to initialize the buffer.
+ #
+ if {$GITGUI_BCK_exists} {
+ set m [gitdir GITGUI_MSG]
+ if {[file isfile $m]} {
+ if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
+ catch {file delete [gitdir GITGUI_MSG]}
+ } else {
+ $ui_comm delete 0.0 end
+ $ui_comm edit reset
+ $ui_comm edit modified false
+ catch {file delete [gitdir GITGUI_BCK]}
+ set GITGUI_BCK_exists 0
+ }
+ }
+ unset m
+ }
-To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
+ proc backup_commit_buffer {} {
+ global ui_comm GITGUI_BCK_exists
-Compress the database now?"] eq yes} {
- do_gc
+ set m [$ui_comm edit modified]
+ if {$m || $GITGUI_BCK_exists} {
+ set msg [string trim [$ui_comm get 0.0 end]]
+ regsub -all -line {[ \r\t]+$} $msg {} msg
+
+ if {$msg eq {}} {
+ if {$GITGUI_BCK_exists} {
+ catch {file delete [gitdir GITGUI_BCK]}
+ set GITGUI_BCK_exists 0
+ }
+ } elseif {$m} {
+ catch {
+ set fd [open [gitdir GITGUI_BCK] w]
+ puts -nonewline $fd $msg
+ close $fd
+ set GITGUI_BCK_exists 1
+ }
+ }
+
+ $ui_comm edit modified false
}
+
+ set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
}
- unset object_limit _junk objects_current
+
+ backup_commit_buffer
}
lock_index begin-read
+if {![winfo ismapped .]} {
+ wm deiconify .
+}
after 1 do_rescan
+if {[is_enabled multicommit]} {
+ after 1000 hint_gc
+}
field w_asim ; # text column: annotations (simple computation)
field w_file ; # text column: actual file data
field w_cviewer ; # pane showing commit message
-field status ; # text variable bound to status bar
+field status ; # status mega-widget instance
field old_height ; # last known height of $w.file_pane
# Tk UI colors
#ececec
}
+# Switches for original location detection
+#
+variable original_options [list -C -C]
+if {[git-version >= 1.5.3]} {
+ lappend original_options -w ; # ignore indentation changes
+}
+
# Current blame data; cleared/reset on each load
#
field commit ; # input commit to blame
pack $w.file_pane.cm.sbx -side bottom -fill x
pack $w_cviewer -expand 1 -fill both
- frame $w.status \
- -borderwidth 1 \
- -relief sunken
- label $w.status.l \
- -textvariable @status \
- -anchor w \
- -justify left
- pack $w.status.l -side left
+ set status [::status_bar::new $w.status]
menu $w.ctxm -tearoff 0
$w.ctxm add command \
set req_w [winfo reqwidth $top]
set req_h [winfo reqheight $top]
+ set scr_h [expr {[winfo screenheight $top] - 100}]
if {$req_w < 600} {set req_w 600}
- if {$req_h < 400} {set req_h 400}
+ if {$req_h < $scr_h} {set req_h $scr_h}
set g "${req_w}x${req_h}"
wm geometry $top $g
update
set total_lines 0
}
- if {[winfo exists $w.status.c]} {
- $w.status.c coords bar 0 0 0 20
- } else {
- canvas $w.status.c \
- -width 100 \
- -height [expr {int([winfo reqheight $w.status.l] * 0.6)}] \
- -borderwidth 1 \
- -relief groove \
- -highlightt 0
- $w.status.c create rectangle 0 0 0 20 -tags bar -fill navy
- pack $w.status.c -side right
- }
-
if {$history eq {}} {
$w_back conf -state disabled
} else {
set amov_data [list [list]]
set asim_data [list [list]]
- set status "Loading $commit:[escape_path $path]..."
+ $status show "Reading $commit:[escape_path $path]..."
$w_path conf -text [escape_path $path]
if {$commit eq {}} {
set fd [open $path r]
+ fconfigure $fd -eofchar {}
} else {
- set cmd [list git cat-file blob "$commit:$path"]
- set fd [open "| $cmd" r]
+ set fd [git_read cat-file blob "$commit:$path"]
}
fconfigure $fd -blocking 0 -translation lf -encoding binary
fileevent $fd readable [cb _read_file $fd $jump]
} ifdeleted { catch {close $fd} }
method _exec_blame {cur_w cur_d options cur_s} {
- set cmd [list]
- if {![is_Windows] || [is_Cygwin]} {
- lappend cmd nice
- }
- lappend cmd git blame
- set cmd [concat $cmd $options]
- lappend cmd --incremental
+ lappend options --incremental
if {$commit eq {}} {
- lappend cmd --contents $path
+ lappend options --contents $path
} else {
- lappend cmd $commit
+ lappend options $commit
}
- lappend cmd -- $path
- set fd [open "| $cmd" r]
+ lappend options -- $path
+ set fd [eval git_read --nice blame $options]
fconfigure $fd -blocking 0 -translation lf -encoding binary
- fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d $cur_s]
+ fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d]
set current_fd $fd
set blame_lines 0
- _status $this $cur_s
+
+ $status start \
+ "Loading$cur_s annotations..." \
+ {lines annotated}
}
-method _read_blame {fd cur_w cur_d cur_s} {
+method _read_blame {fd cur_w cur_d} {
upvar #0 $cur_d line_data
variable group_colors
+ variable original_options
if {$fd ne $current_fd} {
catch {close $fd}
close $fd
if {$cur_w eq $w_asim} {
_exec_blame $this $w_amov @amov_data \
- [list -M -C -C] \
+ $original_options \
{ original location}
} else {
set current_fd {}
- set status {Annotation complete.}
- destroy $w.status.c
+ $status stop {Annotation complete.}
}
} else {
- _status $this $cur_s
+ $status update $blame_lines $total_lines
}
} ifdeleted { catch {close $fd} }
-method _status {cur_s} {
- set have $blame_lines
- set total $total_lines
- set pdone 0
- if {$total} {set pdone [expr {100 * $have / $total}]}
-
- set status [format \
- "Loading%s annotations... %i of %i lines annotated (%2i%%)" \
- $cur_s $have $total $pdone]
- $w.status.c coords bar 0 0 $pdone 20
-}
-
method _click {cur_w pos} {
set lno [lindex [split [$cur_w index $pos] .] 0]
_showcommit $this $cur_w $lno
if {[catch {set msg $header($cmit,message)}]} {
set msg {}
catch {
- set fd [open "| git cat-file commit $cmit" r]
+ set fd [git_read cat-file commit $cmit]
fconfigure $fd -encoding binary -translation lf
if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
set enc utf-8
set enc [string tolower [string range $line 9 end]]
}
}
- set msg [encoding convertfrom $enc [read $fd]]
- set msg [string trim $msg]
+ set msg [read $fd]
close $fd
- set author_name [encoding convertfrom $enc $author_name]
- set committer_name [encoding convertfrom $enc $committer_name]
-
- set header($cmit,author) $author_name
- set header($cmit,committer) $committer_name
+ set enc [tcl_encoding $enc]
+ if {$enc ne {}} {
+ set msg [encoding convertfrom $enc $msg]
+ set author_name [encoding convertfrom $enc $author_name]
+ set committer_name [encoding convertfrom $enc $committer_name]
+ set header($cmit,author) $author_name
+ set header($cmit,committer) $committer_name
+ set header($cmit,summary) \
+ [encoding convertfrom $enc $header($cmit,summary)]
+ }
+ set msg [string trim $msg]
}
set header($cmit,message) $msg
}
set org [lindex $amov_data $lno]
}
+ if {$dat eq {}} {
+ _hide_tooltip $this
+ return
+ }
+
set cmit [lindex $dat 0]
set tooltip_commit [list $cmit]
# Copyright (C) 2006, 2007 Shawn Pearce
proc load_all_heads {} {
- global all_heads
+ global some_heads_tracking
+ set rh refs/heads
+ set rh_len [expr {[string length $rh] + 1}]
set all_heads [list]
- set fd [open "| git for-each-ref --format=%(refname) refs/heads" r]
+ set fd [git_read for-each-ref --format=%(refname) $rh]
while {[gets $fd line] > 0} {
- if {[is_tracking_branch $line]} continue
- if {![regsub ^refs/heads/ $line {} name]} continue
- lappend all_heads $name
+ if {!$some_heads_tracking || ![is_tracking_branch $line]} {
+ lappend all_heads [string range $line $rh_len end]
+ }
}
close $fd
- set all_heads [lsort $all_heads]
+ return [lsort $all_heads]
}
proc load_all_tags {} {
set all_tags [list]
- set fd [open "| git for-each-ref --format=%(refname) refs/tags" r]
+ set fd [git_read for-each-ref \
+ --sort=-taggerdate \
+ --format=%(refname) \
+ refs/tags]
while {[gets $fd line] > 0} {
if {![regsub ^refs/tags/ $line {} name]} continue
lappend all_tags $name
}
close $fd
-
- return [lsort $all_tags]
-}
-
-proc populate_branch_menu {} {
- global all_heads disable_on_lock
-
- set m .mbar.branch
- set last [$m index last]
- for {set i 0} {$i <= $last} {incr i} {
- if {[$m type $i] eq {separator}} {
- $m delete $i last
- set new_dol [list]
- foreach a $disable_on_lock {
- if {[lindex $a 0] ne $m || [lindex $a 2] < $i} {
- lappend new_dol $a
- }
- }
- set disable_on_lock $new_dol
- break
- }
- }
-
- if {$all_heads ne {}} {
- $m add separator
- }
- foreach b $all_heads {
- $m add radiobutton \
- -label $b \
- -command [list switch_branch $b] \
- -variable current_branch \
- -value $b
- lappend disable_on_lock \
- [list $m entryconf [$m index last] -state]
- }
-}
-
-proc do_create_branch_action {w} {
- global all_heads null_sha1 repo_config
- global create_branch_checkout create_branch_revtype
- global create_branch_head create_branch_trackinghead
- global create_branch_name create_branch_revexp
- global create_branch_tag
-
- set newbranch $create_branch_name
- if {$newbranch eq {}
- || $newbranch eq $repo_config(gui.newbranchtemplate)} {
- tk_messageBox \
- -icon error \
- -type ok \
- -title [wm title $w] \
- -parent $w \
- -message "Please supply a branch name."
- focus $w.desc.name_t
- return
- }
- if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} {
- tk_messageBox \
- -icon error \
- -type ok \
- -title [wm title $w] \
- -parent $w \
- -message "Branch '$newbranch' already exists."
- focus $w.desc.name_t
- return
- }
- if {[catch {git check-ref-format "heads/$newbranch"}]} {
- tk_messageBox \
- -icon error \
- -type ok \
- -title [wm title $w] \
- -parent $w \
- -message "We do not like '$newbranch' as a branch name."
- focus $w.desc.name_t
- return
- }
-
- set rev {}
- switch -- $create_branch_revtype {
- head {set rev $create_branch_head}
- tracking {set rev $create_branch_trackinghead}
- tag {set rev $create_branch_tag}
- expression {set rev $create_branch_revexp}
- }
- if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} {
- tk_messageBox \
- -icon error \
- -type ok \
- -title [wm title $w] \
- -parent $w \
- -message "Invalid starting revision: $rev"
- return
- }
- if {[catch {
- git update-ref \
- -m "branch: Created from $rev" \
- "refs/heads/$newbranch" \
- $cmt \
- $null_sha1
- } err]} {
- tk_messageBox \
- -icon error \
- -type ok \
- -title [wm title $w] \
- -parent $w \
- -message "Failed to create '$newbranch'.\n\n$err"
- return
- }
-
- lappend all_heads $newbranch
- set all_heads [lsort $all_heads]
- populate_branch_menu
- destroy $w
- if {$create_branch_checkout} {
- switch_branch $newbranch
- }
+ return $all_tags
}
proc radio_selector {varname value args} {
upvar #0 $varname var
set var $value
}
-
-trace add variable create_branch_head write \
- [list radio_selector create_branch_revtype head]
-trace add variable create_branch_trackinghead write \
- [list radio_selector create_branch_revtype tracking]
-trace add variable create_branch_tag write \
- [list radio_selector create_branch_revtype tag]
-
-trace add variable delete_branch_head write \
- [list radio_selector delete_branch_checktype head]
-trace add variable delete_branch_trackinghead write \
- [list radio_selector delete_branch_checktype tracking]
-
-proc do_create_branch {} {
- global all_heads current_branch repo_config
- global create_branch_checkout create_branch_revtype
- global create_branch_head create_branch_trackinghead
- global create_branch_name create_branch_revexp
- global create_branch_tag
-
- set w .branch_editor
- toplevel $w
- wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
- label $w.header -text {Create New Branch} \
- -font font_uibold
- pack $w.header -side top -fill x
-
- frame $w.buttons
- button $w.buttons.create -text Create \
- -default active \
- -command [list do_create_branch_action $w]
- pack $w.buttons.create -side right
- button $w.buttons.cancel -text {Cancel} \
- -command [list destroy $w]
- pack $w.buttons.cancel -side right -padx 5
- pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
- labelframe $w.desc -text {Branch Description}
- label $w.desc.name_l -text {Name:}
- entry $w.desc.name_t \
- -borderwidth 1 \
- -relief sunken \
- -width 40 \
- -textvariable create_branch_name \
- -validate key \
- -validatecommand {
- if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0}
- return 1
- }
- grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5}
- grid columnconfigure $w.desc 1 -weight 1
- pack $w.desc -anchor nw -fill x -pady 5 -padx 5
-
- labelframe $w.from -text {Starting Revision}
- if {$all_heads ne {}} {
- radiobutton $w.from.head_r \
- -text {Local Branch:} \
- -value head \
- -variable create_branch_revtype
- eval tk_optionMenu $w.from.head_m create_branch_head $all_heads
- grid $w.from.head_r $w.from.head_m -sticky w
- }
- set all_trackings [all_tracking_branches]
- if {$all_trackings ne {}} {
- set create_branch_trackinghead [lindex $all_trackings 0]
- radiobutton $w.from.tracking_r \
- -text {Tracking Branch:} \
- -value tracking \
- -variable create_branch_revtype
- eval tk_optionMenu $w.from.tracking_m \
- create_branch_trackinghead \
- $all_trackings
- grid $w.from.tracking_r $w.from.tracking_m -sticky w
- }
- set all_tags [load_all_tags]
- if {$all_tags ne {}} {
- set create_branch_tag [lindex $all_tags 0]
- radiobutton $w.from.tag_r \
- -text {Tag:} \
- -value tag \
- -variable create_branch_revtype
- eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags
- grid $w.from.tag_r $w.from.tag_m -sticky w
- }
- radiobutton $w.from.exp_r \
- -text {Revision Expression:} \
- -value expression \
- -variable create_branch_revtype
- entry $w.from.exp_t \
- -borderwidth 1 \
- -relief sunken \
- -width 50 \
- -textvariable create_branch_revexp \
- -validate key \
- -validatecommand {
- if {%d == 1 && [regexp {\s} %S]} {return 0}
- if {%d == 1 && [string length %S] > 0} {
- set create_branch_revtype expression
- }
- return 1
- }
- grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5}
- grid columnconfigure $w.from 1 -weight 1
- pack $w.from -anchor nw -fill x -pady 5 -padx 5
-
- labelframe $w.postActions -text {Post Creation Actions}
- checkbutton $w.postActions.checkout \
- -text {Checkout after creation} \
- -variable create_branch_checkout
- pack $w.postActions.checkout -anchor nw
- pack $w.postActions -anchor nw -fill x -pady 5 -padx 5
-
- set create_branch_checkout 1
- set create_branch_head $current_branch
- set create_branch_revtype head
- set create_branch_name $repo_config(gui.newbranchtemplate)
- set create_branch_revexp {}
-
- bind $w <Visibility> "
- grab $w
- $w.desc.name_t icursor end
- focus $w.desc.name_t
- "
- bind $w <Key-Escape> "destroy $w"
- bind $w <Key-Return> "do_create_branch_action $w;break"
- wm title $w "[appname] ([reponame]): Create Branch"
- tkwait window $w
-}
-
-proc do_delete_branch_action {w} {
- global all_heads
- global delete_branch_checktype delete_branch_head delete_branch_trackinghead
-
- set check_rev {}
- switch -- $delete_branch_checktype {
- head {set check_rev $delete_branch_head}
- tracking {set check_rev $delete_branch_trackinghead}
- always {set check_rev {:none}}
- }
- if {$check_rev eq {:none}} {
- set check_cmt {}
- } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} {
- tk_messageBox \
- -icon error \
- -type ok \
- -title [wm title $w] \
- -parent $w \
- -message "Invalid check revision: $check_rev"
- return
- }
-
- set to_delete [list]
- set not_merged [list]
- foreach i [$w.list.l curselection] {
- set b [$w.list.l get $i]
- if {[catch {set o [git rev-parse --verify $b]}]} continue
- if {$check_cmt ne {}} {
- if {$b eq $check_rev} continue
- if {[catch {set m [git merge-base $o $check_cmt]}]} continue
- if {$o ne $m} {
- lappend not_merged $b
- continue
- }
- }
- lappend to_delete [list $b $o]
- }
- if {$not_merged ne {}} {
- set msg "The following branches are not completely merged into $check_rev:
-
- - [join $not_merged "\n - "]"
- tk_messageBox \
- -icon info \
- -type ok \
- -title [wm title $w] \
- -parent $w \
- -message $msg
- }
- if {$to_delete eq {}} return
- if {$delete_branch_checktype eq {always}} {
- set msg {Recovering deleted branches is difficult.
-
-Delete the selected branches?}
- if {[tk_messageBox \
- -icon warning \
- -type yesno \
- -title [wm title $w] \
- -parent $w \
- -message $msg] ne yes} {
- return
- }
- }
-
- set failed {}
- foreach i $to_delete {
- set b [lindex $i 0]
- set o [lindex $i 1]
- if {[catch {git update-ref -d "refs/heads/$b" $o} err]} {
- append failed " - $b: $err\n"
- } else {
- set x [lsearch -sorted -exact $all_heads $b]
- if {$x >= 0} {
- set all_heads [lreplace $all_heads $x $x]
- }
- }
- }
-
- if {$failed ne {}} {
- tk_messageBox \
- -icon error \
- -type ok \
- -title [wm title $w] \
- -parent $w \
- -message "Failed to delete branches:\n$failed"
- }
-
- set all_heads [lsort $all_heads]
- populate_branch_menu
- destroy $w
-}
-
-proc do_delete_branch {} {
- global all_heads tracking_branches current_branch
- global delete_branch_checktype delete_branch_head delete_branch_trackinghead
-
- set w .branch_editor
- toplevel $w
- wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
- label $w.header -text {Delete Local Branch} \
- -font font_uibold
- pack $w.header -side top -fill x
-
- frame $w.buttons
- button $w.buttons.create -text Delete \
- -command [list do_delete_branch_action $w]
- pack $w.buttons.create -side right
- button $w.buttons.cancel -text {Cancel} \
- -command [list destroy $w]
- pack $w.buttons.cancel -side right -padx 5
- pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
- labelframe $w.list -text {Local Branches}
- listbox $w.list.l \
- -height 10 \
- -width 70 \
- -selectmode extended \
- -yscrollcommand [list $w.list.sby set]
- foreach h $all_heads {
- if {$h ne $current_branch} {
- $w.list.l insert end $h
- }
- }
- scrollbar $w.list.sby -command [list $w.list.l yview]
- pack $w.list.sby -side right -fill y
- pack $w.list.l -side left -fill both -expand 1
- pack $w.list -fill both -expand 1 -pady 5 -padx 5
-
- labelframe $w.validate -text {Delete Only If}
- radiobutton $w.validate.head_r \
- -text {Merged Into Local Branch:} \
- -value head \
- -variable delete_branch_checktype
- eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads
- grid $w.validate.head_r $w.validate.head_m -sticky w
- set all_trackings [all_tracking_branches]
- if {$all_trackings ne {}} {
- set delete_branch_trackinghead [lindex $all_trackings 0]
- radiobutton $w.validate.tracking_r \
- -text {Merged Into Tracking Branch:} \
- -value tracking \
- -variable delete_branch_checktype
- eval tk_optionMenu $w.validate.tracking_m \
- delete_branch_trackinghead \
- $all_trackings
- grid $w.validate.tracking_r $w.validate.tracking_m -sticky w
- }
- radiobutton $w.validate.always_r \
- -text {Always (Do not perform merge checks)} \
- -value always \
- -variable delete_branch_checktype
- grid $w.validate.always_r -columnspan 2 -sticky w
- grid columnconfigure $w.validate 1 -weight 1
- pack $w.validate -anchor nw -fill x -pady 5 -padx 5
-
- set delete_branch_head $current_branch
- set delete_branch_checktype head
-
- bind $w <Visibility> "grab $w; focus $w"
- bind $w <Key-Escape> "destroy $w"
- wm title $w "[appname] ([reponame]): Delete Branch"
- tkwait window $w
-}
-
-proc switch_branch {new_branch} {
- global HEAD commit_type current_branch repo_config
-
- if {![lock_index switch]} return
-
- # -- Our in memory state should match the repository.
- #
- repository_state curType curHEAD curMERGE_HEAD
- if {[string match amend* $commit_type]
- && $curType eq {normal}
- && $curHEAD eq $HEAD} {
- } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
- info_popup {Last scanned state does not match repository state.
-
-Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed.
-
-The rescan will be automatically started now.
-}
- unlock_index
- rescan {set ui_status_value {Ready.}}
- return
- }
-
- # -- Don't do a pointless switch.
- #
- if {$current_branch eq $new_branch} {
- unlock_index
- return
- }
-
- if {$repo_config(gui.trustmtime) eq {true}} {
- switch_branch_stage2 {} $new_branch
- } else {
- set ui_status_value {Refreshing file status...}
- set cmd [list git update-index]
- lappend cmd -q
- lappend cmd --unmerged
- lappend cmd --ignore-missing
- lappend cmd --refresh
- set fd_rf [open "| $cmd" r]
- fconfigure $fd_rf -blocking 0 -translation binary
- fileevent $fd_rf readable \
- [list switch_branch_stage2 $fd_rf $new_branch]
- }
-}
-
-proc switch_branch_stage2 {fd_rf new_branch} {
- global ui_status_value HEAD
-
- if {$fd_rf ne {}} {
- read $fd_rf
- if {![eof $fd_rf]} return
- close $fd_rf
- }
-
- set ui_status_value "Updating working directory to '$new_branch'..."
- set cmd [list git read-tree]
- lappend cmd -m
- lappend cmd -u
- lappend cmd --exclude-per-directory=.gitignore
- lappend cmd $HEAD
- lappend cmd $new_branch
- set fd_rt [open "| $cmd" r]
- fconfigure $fd_rt -blocking 0 -translation binary
- fileevent $fd_rt readable \
- [list switch_branch_readtree_wait $fd_rt $new_branch]
-}
-
-proc switch_branch_readtree_wait {fd_rt new_branch} {
- global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
- global current_branch
- global ui_comm ui_status_value
-
- # -- We never get interesting output on stdout; only stderr.
- #
- read $fd_rt
- fconfigure $fd_rt -blocking 1
- if {![eof $fd_rt]} {
- fconfigure $fd_rt -blocking 0
- return
- }
-
- # -- The working directory wasn't in sync with the index and
- # we'd have to overwrite something to make the switch. A
- # merge is required.
- #
- if {[catch {close $fd_rt} err]} {
- regsub {^fatal: } $err {} err
- warn_popup "File level merge required.
-
-$err
-
-Staying on branch '$current_branch'."
- set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)."
- unlock_index
- return
- }
-
- # -- Update the symbolic ref. Core git doesn't even check for failure
- # here, it Just Works(tm). If it doesn't we are in some really ugly
- # state that is difficult to recover from within git-gui.
- #
- if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} {
- error_popup "Failed to set current branch.
-
-This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file.
-
-This should not have occurred. [appname] will now close and give up.
-
-$err"
- do_quit
- return
- }
-
- # -- Update our repository state. If we were previously in amend mode
- # we need to toss the current buffer and do a full rescan to update
- # our file lists. If we weren't in amend mode our file lists are
- # accurate and we can avoid the rescan.
- #
- unlock_index
- set selected_commit_type new
- if {[string match amend* $commit_type]} {
- $ui_comm delete 0.0 end
- $ui_comm edit reset
- $ui_comm edit modified false
- rescan {set ui_status_value "Checked out branch '$current_branch'."}
- } else {
- repository_state commit_type HEAD MERGE_HEAD
- set PARENT $HEAD
- set ui_status_value "Checked out branch '$current_branch'."
- }
-}
--- /dev/null
+# git-gui branch checkout support
+# Copyright (C) 2007 Shawn Pearce
+
+class branch_checkout {
+
+field w ; # widget path
+field w_rev ; # mega-widget to pick the initial revision
+
+field opt_fetch 1; # refetch tracking branch if used?
+field opt_detach 0; # force a detached head case?
+
+constructor dialog {} {
+ make_toplevel top w
+ wm title $top "[appname] ([reponame]): Checkout Branch"
+ if {$top ne {.}} {
+ wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+ }
+
+ label $w.header -text {Checkout Branch} -font font_uibold
+ pack $w.header -side top -fill x
+
+ frame $w.buttons
+ button $w.buttons.create -text Checkout \
+ -default active \
+ -command [cb _checkout]
+ pack $w.buttons.create -side right
+ button $w.buttons.cancel -text {Cancel} \
+ -command [list destroy $w]
+ pack $w.buttons.cancel -side right -padx 5
+ pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+ set w_rev [::choose_rev::new $w.rev {Revision}]
+ $w_rev bind_listbox <Double-Button-1> [cb _checkout]
+ pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
+
+ labelframe $w.options -text {Options}
+
+ checkbutton $w.options.fetch \
+ -text {Fetch Tracking Branch} \
+ -variable @opt_fetch
+ pack $w.options.fetch -anchor nw
+
+ checkbutton $w.options.detach \
+ -text {Detach From Local Branch} \
+ -variable @opt_detach
+ pack $w.options.detach -anchor nw
+
+ pack $w.options -anchor nw -fill x -pady 5 -padx 5
+
+ bind $w <Visibility> [cb _visible]
+ bind $w <Key-Escape> [list destroy $w]
+ bind $w <Key-Return> [cb _checkout]\;break
+ tkwait window $w
+}
+
+method _checkout {} {
+ set spec [$w_rev get_tracking_branch]
+ if {$spec ne {} && $opt_fetch} {
+ set new {}
+ } elseif {[catch {set new [$w_rev commit_or_die]}]} {
+ return
+ }
+
+ if {$opt_detach} {
+ set ref {}
+ } else {
+ set ref [$w_rev get_local_branch]
+ }
+
+ set co [::checkout_op::new [$w_rev get] $new $ref]
+ $co parent $w
+ $co enable_checkout 1
+ if {$spec ne {} && $opt_fetch} {
+ $co enable_fetch $spec
+ }
+
+ if {[$co run]} {
+ destroy $w
+ } else {
+ $w_rev focus_filter
+ }
+}
+
+method _visible {} {
+ grab $w
+ $w_rev focus_filter
+}
+
+}
--- /dev/null
+# git-gui branch create support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+class branch_create {
+
+field w ; # widget path
+field w_rev ; # mega-widget to pick the initial revision
+field w_name ; # new branch name widget
+
+field name {}; # name of the branch the user has chosen
+field name_type user; # type of branch name to use
+
+field opt_merge ff; # type of merge to apply to existing branch
+field opt_checkout 1; # automatically checkout the new branch?
+field opt_fetch 1; # refetch tracking branch if used?
+field reset_ok 0; # did the user agree to reset?
+
+constructor dialog {} {
+ global repo_config
+
+ make_toplevel top w
+ wm title $top "[appname] ([reponame]): Create Branch"
+ if {$top ne {.}} {
+ wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+ }
+
+ label $w.header -text {Create New Branch} -font font_uibold
+ pack $w.header -side top -fill x
+
+ frame $w.buttons
+ button $w.buttons.create -text Create \
+ -default active \
+ -command [cb _create]
+ pack $w.buttons.create -side right
+ button $w.buttons.cancel -text {Cancel} \
+ -command [list destroy $w]
+ pack $w.buttons.cancel -side right -padx 5
+ pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+ labelframe $w.desc -text {Branch Name}
+ radiobutton $w.desc.name_r \
+ -anchor w \
+ -text {Name:} \
+ -value user \
+ -variable @name_type
+ set w_name $w.desc.name_t
+ entry $w_name \
+ -borderwidth 1 \
+ -relief sunken \
+ -width 40 \
+ -textvariable @name \
+ -validate key \
+ -validatecommand [cb _validate %d %S]
+ grid $w.desc.name_r $w_name -sticky we -padx {0 5}
+
+ radiobutton $w.desc.match_r \
+ -anchor w \
+ -text {Match Tracking Branch Name} \
+ -value match \
+ -variable @name_type
+ grid $w.desc.match_r -sticky we -padx {0 5} -columnspan 2
+
+ grid columnconfigure $w.desc 1 -weight 1
+ pack $w.desc -anchor nw -fill x -pady 5 -padx 5
+
+ set w_rev [::choose_rev::new $w.rev {Starting Revision}]
+ pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
+
+ labelframe $w.options -text {Options}
+
+ frame $w.options.merge
+ label $w.options.merge.l -text {Update Existing Branch:}
+ pack $w.options.merge.l -side left
+ radiobutton $w.options.merge.no \
+ -text No \
+ -value none \
+ -variable @opt_merge
+ pack $w.options.merge.no -side left
+ radiobutton $w.options.merge.ff \
+ -text {Fast Forward Only} \
+ -value ff \
+ -variable @opt_merge
+ pack $w.options.merge.ff -side left
+ radiobutton $w.options.merge.reset \
+ -text {Reset} \
+ -value reset \
+ -variable @opt_merge
+ pack $w.options.merge.reset -side left
+ pack $w.options.merge -anchor nw
+
+ checkbutton $w.options.fetch \
+ -text {Fetch Tracking Branch} \
+ -variable @opt_fetch
+ pack $w.options.fetch -anchor nw
+
+ checkbutton $w.options.checkout \
+ -text {Checkout After Creation} \
+ -variable @opt_checkout
+ pack $w.options.checkout -anchor nw
+ pack $w.options -anchor nw -fill x -pady 5 -padx 5
+
+ trace add variable @name_type write [cb _select]
+
+ set name $repo_config(gui.newbranchtemplate)
+ if {[is_config_true gui.matchtrackingbranch]} {
+ set name_type match
+ }
+
+ bind $w <Visibility> [cb _visible]
+ bind $w <Key-Escape> [list destroy $w]
+ bind $w <Key-Return> [cb _create]\;break
+ tkwait window $w
+}
+
+method _create {} {
+ global repo_config
+ global M1B
+
+ set spec [$w_rev get_tracking_branch]
+ switch -- $name_type {
+ user {
+ set newbranch $name
+ }
+ match {
+ if {$spec eq {}} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "Please select a tracking branch."
+ return
+ }
+ if {![regsub ^refs/heads/ [lindex $spec 2] {} newbranch]} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "Tracking branch [$w get] is not a branch in the remote repository."
+ return
+ }
+ }
+ }
+
+ if {$newbranch eq {}
+ || $newbranch eq $repo_config(gui.newbranchtemplate)} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "Please supply a branch name."
+ focus $w_name
+ return
+ }
+
+ if {[catch {git check-ref-format "heads/$newbranch"}]} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "'$newbranch' is not an acceptable branch name."
+ focus $w_name
+ return
+ }
+
+ if {$spec ne {} && $opt_fetch} {
+ set new {}
+ } elseif {[catch {set new [$w_rev commit_or_die]}]} {
+ return
+ }
+
+ set co [::checkout_op::new \
+ [$w_rev get] \
+ $new \
+ refs/heads/$newbranch]
+ $co parent $w
+ $co enable_create 1
+ $co enable_merge $opt_merge
+ $co enable_checkout $opt_checkout
+ if {$spec ne {} && $opt_fetch} {
+ $co enable_fetch $spec
+ }
+
+ if {[$co run]} {
+ destroy $w
+ } else {
+ focus $w_name
+ }
+}
+
+method _validate {d S} {
+ if {$d == 1} {
+ if {[regexp {[~^:?*\[\0- ]} $S]} {
+ return 0
+ }
+ if {[string length $S] > 0} {
+ set name_type user
+ }
+ }
+ return 1
+}
+
+method _select {args} {
+ if {$name_type eq {match}} {
+ $w_rev pick_tracking_branch
+ }
+}
+
+method _visible {} {
+ grab $w
+ if {$name_type eq {user}} {
+ $w_name icursor end
+ focus $w_name
+ }
+}
+
+}
--- /dev/null
+# git-gui branch delete support
+# Copyright (C) 2007 Shawn Pearce
+
+class branch_delete {
+
+field w ; # widget path
+field w_heads ; # listbox of local head names
+field w_check ; # revision picker for merge test
+field w_delete ; # delete button
+
+constructor dialog {} {
+ global current_branch
+
+ make_toplevel top w
+ wm title $top "[appname] ([reponame]): Delete Branch"
+ if {$top ne {.}} {
+ wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+ }
+
+ label $w.header -text {Delete Local Branch} -font font_uibold
+ pack $w.header -side top -fill x
+
+ frame $w.buttons
+ set w_delete $w.buttons.delete
+ button $w_delete \
+ -text Delete \
+ -default active \
+ -state disabled \
+ -command [cb _delete]
+ pack $w_delete -side right
+ button $w.buttons.cancel \
+ -text {Cancel} \
+ -command [list destroy $w]
+ pack $w.buttons.cancel -side right -padx 5
+ pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+ labelframe $w.list -text {Local Branches}
+ set w_heads $w.list.l
+ listbox $w_heads \
+ -height 10 \
+ -width 70 \
+ -selectmode extended \
+ -exportselection false \
+ -yscrollcommand [list $w.list.sby set]
+ scrollbar $w.list.sby -command [list $w.list.l yview]
+ pack $w.list.sby -side right -fill y
+ pack $w.list.l -side left -fill both -expand 1
+ pack $w.list -fill both -expand 1 -pady 5 -padx 5
+
+ set w_check [choose_rev::new \
+ $w.check \
+ {Delete Only If Merged Into} \
+ ]
+ $w_check none {Always (Do not perform merge test.)}
+ pack $w.check -anchor nw -fill x -pady 5 -padx 5
+
+ foreach h [load_all_heads] {
+ if {$h ne $current_branch} {
+ $w_heads insert end $h
+ }
+ }
+
+ bind $w_heads <<ListboxSelect>> [cb _select]
+ bind $w <Visibility> "
+ grab $w
+ focus $w
+ "
+ bind $w <Key-Escape> [list destroy $w]
+ bind $w <Key-Return> [cb _delete]\;break
+ tkwait window $w
+}
+
+method _select {} {
+ if {[$w_heads curselection] eq {}} {
+ $w_delete configure -state disabled
+ } else {
+ $w_delete configure -state normal
+ }
+}
+
+method _delete {} {
+ if {[catch {set check_cmt [$w_check commit_or_die]}]} {
+ return
+ }
+
+ set to_delete [list]
+ set not_merged [list]
+ foreach i [$w_heads curselection] {
+ set b [$w_heads get $i]
+ if {[catch {
+ set o [git rev-parse --verify "refs/heads/$b"]
+ }]} continue
+ if {$check_cmt ne {}} {
+ if {[catch {set m [git merge-base $o $check_cmt]}]} continue
+ if {$o ne $m} {
+ lappend not_merged $b
+ continue
+ }
+ }
+ lappend to_delete [list $b $o]
+ }
+ if {$not_merged ne {}} {
+ set msg "The following branches are not completely merged into [$w_check get]:
+
+ - [join $not_merged "\n - "]"
+ tk_messageBox \
+ -icon info \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message $msg
+ }
+ if {$to_delete eq {}} return
+ if {$check_cmt eq {}} {
+ set msg {Recovering deleted branches is difficult.
+
+Delete the selected branches?}
+ if {[tk_messageBox \
+ -icon warning \
+ -type yesno \
+ -title [wm title $w] \
+ -parent $w \
+ -message $msg] ne yes} {
+ return
+ }
+ }
+
+ set failed {}
+ foreach i $to_delete {
+ set b [lindex $i 0]
+ set o [lindex $i 1]
+ if {[catch {git update-ref -d "refs/heads/$b" $o} err]} {
+ append failed " - $b: $err\n"
+ }
+ }
+
+ if {$failed ne {}} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "Failed to delete branches:\n$failed"
+ }
+
+ destroy $w
+}
+
+}
--- /dev/null
+# git-gui branch rename support
+# Copyright (C) 2007 Shawn Pearce
+
+class branch_rename {
+
+field w
+field oldname
+field newname
+
+constructor dialog {} {
+ global current_branch
+
+ make_toplevel top w
+ wm title $top "[appname] ([reponame]): Rename Branch"
+ if {$top ne {.}} {
+ wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+ }
+
+ set oldname $current_branch
+ set newname [get_config gui.newbranchtemplate]
+
+ label $w.header -text {Rename Branch} -font font_uibold
+ pack $w.header -side top -fill x
+
+ frame $w.buttons
+ button $w.buttons.rename -text Rename \
+ -default active \
+ -command [cb _rename]
+ pack $w.buttons.rename -side right
+ button $w.buttons.cancel -text {Cancel} \
+ -command [list destroy $w]
+ pack $w.buttons.cancel -side right -padx 5
+ pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+ frame $w.rename
+ label $w.rename.oldname_l -text {Branch:}
+ eval tk_optionMenu $w.rename.oldname_m @oldname [load_all_heads]
+
+ label $w.rename.newname_l -text {New Name:}
+ entry $w.rename.newname_t \
+ -borderwidth 1 \
+ -relief sunken \
+ -width 40 \
+ -textvariable @newname \
+ -validate key \
+ -validatecommand {
+ if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0}
+ return 1
+ }
+
+ grid $w.rename.oldname_l $w.rename.oldname_m -sticky w -padx {0 5}
+ grid $w.rename.newname_l $w.rename.newname_t -sticky we -padx {0 5}
+ grid columnconfigure $w.rename 1 -weight 1
+ pack $w.rename -anchor nw -fill x -pady 5 -padx 5
+
+ bind $w <Key-Return> [cb _rename]
+ bind $w <Key-Escape> [list destroy $w]
+ bind $w <Visibility> "
+ grab $w
+ $w.rename.newname_t icursor end
+ focus $w.rename.newname_t
+ "
+ tkwait window $w
+}
+
+method _rename {} {
+ global current_branch
+
+ if {$oldname eq {}} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "Please select a branch to rename."
+ focus $w.rename.oldname_m
+ return
+ }
+ if {$newname eq {}
+ || $newname eq [get_config gui.newbranchtemplate]} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "Please supply a branch name."
+ focus $w.rename.newname_t
+ return
+ }
+ if {![catch {git show-ref --verify -- "refs/heads/$newname"}]} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "Branch '$newname' already exists."
+ focus $w.rename.newname_t
+ return
+ }
+ if {[catch {git check-ref-format "heads/$newname"}]} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "We do not like '$newname' as a branch name."
+ focus $w.rename.newname_t
+ return
+ }
+
+ if {[catch {git branch -m $oldname $newname} err]} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "Failed to rename '$oldname'.\n\n$err"
+ return
+ }
+
+ if {$current_branch eq $oldname} {
+ set current_branch $newname
+ }
+
+ destroy $w
+}
+
+}
class browser {
+image create photo ::browser::img_parent -data {R0lGODlhEAAQAIUAAPwCBBxSHBxOHMTSzNzu3KzCtBRGHCSKFIzCjLzSxBQ2FAxGHDzCLCyeHBQ+FHSmfAwuFBxKLDSCNMzizISyjJzOnDSyLAw+FAQSDAQeDBxWJAwmDAQOBKzWrDymNAQaDAQODAwaDDyKTFSyXFTGTEy6TAQCBAQKDAwiFBQyHAwSFAwmHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAZ1QIBwSCwaj0hiQCBICpcDQsFgGAaIguhhi0gohIsrQEDYMhiNrRfgeAQC5fMCAolIDhD2hFI5WC4YRBkaBxsOE2l/RxsHHA4dHmkfRyAbIQ4iIyQlB5NFGCAACiakpSZEJyinTgAcKSesACorgU4mJ6uxR35BACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=}
+image create photo ::browser::img_rblob -data {R0lGODlhEAAQAIUAAPwCBFxaXNze3Ly2rJSWjPz+/Ozq7GxqbJyanPT29HRydMzOzDQyNIyKjERCROTi3Pz69PTy7Pzy7PTu5Ozm3LyqlJyWlJSSjJSOhOzi1LyulPz27PTq3PTm1OzezLyqjIyKhJSKfOzaxPz29OzizLyidIyGdIyCdOTOpLymhOzavOTStMTCtMS+rMS6pMSynMSulLyedAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAaQQIAQECgajcNkQMBkDgKEQFK4LFgLhkMBIVUKroWEYlEgMLxbBKLQUBwc52HgAQ4LBo049atWQyIPA3pEdFcQEhMUFYNVagQWFxgZGoxfYRsTHB0eH5UJCJAYICEinUoPIxIcHCQkIiIllQYEGCEhJicoKYwPmiQeKisrKLFKLCwtLi8wHyUlMYwM0tPUDH5BACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=}
+image create photo ::browser::img_xblob -data {R0lGODlhEAAQAIYAAPwCBFRWVFxaXNza3OTi3Nze3Ly2tJyanPz+/Ozq7GxubNzSxMzOzMTGxHRybDQyNLy+vHRydHx6fKSipISChIyKjGxqbERCRCwuLLy6vGRiZExKTCQiJAwKDLSytLy2rJSSlHx+fDw6PKyqrBQWFPTu5Ozm3LyulLS2tCQmJAQCBPTq3Ozi1MSynCwqLAQGBOTazOzizOzezLyqjBweHNzSvOzaxKyurHRuZNzOtLymhDw+PIyCdOzWvOTOpLyidNzKtOTStLyifMTCtMS+rLyedAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAfZgACCAAEChYeGg4oCAwQFjgYBBwGKggEECJkICQoIkwADCwwNDY2mDA4Lng8QDhESsLARExQVDhYXGBkWExIaGw8cHR4SCQQfFQ8eFgUgIQEiwiMSBMYfGB4atwEXDyQd0wQlJicPKAHoFyIpJCoeDgMrLC0YKBsX6i4kL+4OMDEyZijr5oLGNxUqUCioEcPGDAwjPNyI6MEDChQjcOSwsUDHgw07RIgI4KCkAgs8cvTw8eOBogAxQtXIASTISiEuBwUYMoRIixYnZggpUgTDywdIkWJIitRPIAAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
+image create photo ::browser::img_tree -data {R0lGODlhEAAQAIYAAPwCBAQCBExKTBwWHMzKzOzq7ERCRExGTCwqLARqnAQ+ZHR2dKyqrNTOzHx2fCQiJMTi9NTu9HzC3AxmnAQ+XPTm7Dy67DymzITC3IzG5AxypHRydKymrMzOzOzu7BweHByy9AyGtFyy1IzG3NTu/ARupFRSVByazBR6rAyGvFyuzJTK3MTm9BR+tAxWhHS61MTi7Pz+/IymvCxulBRelAx2rHS63Pz6/PTy9PTu9Nza3ISitBRupFSixNTS1CxqnDQyNMzGzOTi5MTCxMTGxGxubGxqbLy2vLSutGRiZLy6vLSytKyurDQuNFxaXKSipDw6PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAfDgACCAAECg4eIAAMEBQYHCImDBgkKCwwNBQIBBw4Bhw8QERITFJYEFQUFnoIPFhcYoRkaFBscHR4Ggh8gIRciEiMQJBkltCa6JyUoKSkXKhIrLCQYuQAPLS4TEyUhKb0qLzDVAjEFMjMuNBMoNcw21QY3ODkFOjs82RM1PfDzFRU3fOggcM7Fj2pAgggRokOHDx9DhhAZUqQaISBGhjwMEvEIkiIHEgUAkgSJkiNLmFSMJChAEydPGBSBwvJQgAc0/QQCACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=}
+image create photo ::browser::img_symlink -data {R0lGODlhEAAQAIQAAPwCBCwqLLSytLy+vERGRFRWVDQ2NKSmpAQCBKyurMTGxISChJyanHR2dIyKjGxubHRydGRmZIyOjFxeXHx6fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAVbICACwWieY1CibCCsrBkMb0zchSEcNYskCtqBBzshFkOGQFk0IRqOxqPBODRHCMhCQKteRc9FI/KQWGOIyFYgkDC+gPR4snCcfRGKOIKIgSMQE31+f4OEYCZ+IQAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
+image create photo ::browser::img_unknown -data {R0lGODlhEAAQAIUAAPwCBFxaXIyKjNTW1Nze3LS2tJyanER2RGS+VPz+/PTu5GxqbPz69BQ6BCxeLFSqRPT29HRydMzOzDQyNERmPKSypCRWHIyKhERCRDyGPKz2nESiLBxGHCyCHGxubPz6/PTy7Ozi1Ly2rKSipOzm3LyqlKSWhCRyFOzizLymhNTKtNzOvOzaxOTStPz27OzWvOTOpLSupLyedMS+rMS6pMSulLyqjLymfLyifAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAamQIAQECgajcOkYEBoDgoBQyAJOCCuiENCsWBIh9aGw9F4HCARiXciRDQoBUnlYRlcIgsMG5CxXAgMGhscBRAEBRd7AB0eBBoIgxUfICEiikSPgyMMIAokJZcBkBybJgomIaBJAZoMpyCmqkMBFCcVCrgKKAwpoSorKqchKCwtvasIFBIhLiYvLzDHsxQNMcMKLDAwMqEz3jQ1NTY3ONyrE+jp6hN+QQAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
+
field w
field browser_commit
field browser_path
field ls_buf {}; # Buffered record output from ls-tree
-constructor new {commit} {
+constructor new {commit {path {}}} {
global cursor_ptr M1B
make_toplevel top w
wm title $top "[appname] ([reponame]): File Browser"
set browser_commit $commit
- set browser_path $browser_commit:
+ set browser_path $browser_commit:$path
label $w.path \
-textvariable @browser_path \
bind $w_list <Visibility> [list focus $w_list]
set w $w_list
- _ls $this $browser_commit
+ if {$path ne {}} {
+ _ls $this $browser_commit:$path $path
+ } else {
+ _ls $this $browser_commit $path
+ }
return $this
}
$w image create end \
-align center -padx 5 -pady 1 \
-name icon0 \
- -image file_uplevel
+ -image ::browser::img_parent
$w insert end {[Up To Parent]}
lappend browser_files parent
}
lappend browser_stack [list $tree_id $name]
$w conf -state disabled
- set cmd [list git ls-tree -z $tree_id]
- set fd [open "| $cmd" r]
+ set fd [git_read ls-tree -z $tree_id]
fconfigure $fd -blocking 0 -translation binary -encoding binary
fileevent $fd readable [cb _read $fd]
}
switch -- $type {
blob {
- set image file_mod
+ scan [lindex $info 0] %o mode
+ if {$mode == 0120000} {
+ set image ::browser::img_symlink
+ } elseif {($mode & 0100) != 0} {
+ set image ::browser::img_xblob
+ } else {
+ set image ::browser::img_rblob
+ }
}
tree {
- set image file_dir
+ set image ::browser::img_tree
append path /
}
default {
- set image file_question
+ set image ::browser::img_unknown
}
}
}
}
+
+class browser_open {
+
+field w ; # widget path
+field w_rev ; # mega-widget to pick the initial revision
+
+constructor dialog {} {
+ make_toplevel top w
+ wm title $top "[appname] ([reponame]): Browse Branch Files"
+ if {$top ne {.}} {
+ wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+ }
+
+ label $w.header \
+ -text {Browse Branch Files} \
+ -font font_uibold
+ pack $w.header -side top -fill x
+
+ frame $w.buttons
+ button $w.buttons.browse -text Browse \
+ -default active \
+ -command [cb _open]
+ pack $w.buttons.browse -side right
+ button $w.buttons.cancel -text {Cancel} \
+ -command [list destroy $w]
+ pack $w.buttons.cancel -side right -padx 5
+ pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+ set w_rev [::choose_rev::new $w.rev {Revision}]
+ $w_rev bind_listbox <Double-Button-1> [cb _open]
+ pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
+
+ bind $w <Visibility> [cb _visible]
+ bind $w <Key-Escape> [list destroy $w]
+ bind $w <Key-Return> [cb _open]\;break
+ tkwait window $w
+}
+
+method _open {} {
+ if {[catch {$w_rev commit_or_die} err]} {
+ return
+ }
+ set name [$w_rev get]
+ destroy $w
+ browser::new $name
+}
+
+method _visible {} {
+ grab $w
+ $w_rev focus_filter
+}
+
+}
--- /dev/null
+# git-gui commit checkout support
+# Copyright (C) 2007 Shawn Pearce
+
+class checkout_op {
+
+field w {}; # our window (if we have one)
+field w_cons {}; # embedded console window object
+
+field new_expr ; # expression the user saw/thinks this is
+field new_hash ; # commit SHA-1 we are switching to
+field new_ref ; # ref we are updating/creating
+
+field parent_w .; # window that started us
+field merge_type none; # type of merge to apply to existing branch
+field merge_base {}; # merge base if we have another ref involved
+field fetch_spec {}; # refetch tracking branch if used?
+field checkout 1; # actually checkout the branch?
+field create 0; # create the branch if it doesn't exist?
+
+field reset_ok 0; # did the user agree to reset?
+field fetch_ok 0; # did the fetch succeed?
+
+field readtree_d {}; # buffered output from read-tree
+field update_old {}; # was the update-ref call deferred?
+field reflog_msg {}; # log message for the update-ref call
+
+constructor new {expr hash {ref {}}} {
+ set new_expr $expr
+ set new_hash $hash
+ set new_ref $ref
+
+ return $this
+}
+
+method parent {path} {
+ set parent_w [winfo toplevel $path]
+}
+
+method enable_merge {type} {
+ set merge_type $type
+}
+
+method enable_fetch {spec} {
+ set fetch_spec $spec
+}
+
+method enable_checkout {co} {
+ set checkout $co
+}
+
+method enable_create {co} {
+ set create $co
+}
+
+method run {} {
+ if {$fetch_spec ne {}} {
+ global M1B
+
+ # We were asked to refresh a single tracking branch
+ # before we get to work. We should do that before we
+ # consider any ref updating.
+ #
+ set fetch_ok 0
+ set l_trck [lindex $fetch_spec 0]
+ set remote [lindex $fetch_spec 1]
+ set r_head [lindex $fetch_spec 2]
+ regsub ^refs/heads/ $r_head {} r_name
+
+ set cmd [list git fetch $remote]
+ if {$l_trck ne {}} {
+ lappend cmd +$r_head:$l_trck
+ } else {
+ lappend cmd $r_head
+ }
+
+ _toplevel $this {Refreshing Tracking Branch}
+ set w_cons [::console::embed \
+ $w.console \
+ "Fetching $r_name from $remote"]
+ pack $w.console -fill both -expand 1
+ $w_cons exec $cmd [cb _finish_fetch]
+
+ bind $w <$M1B-Key-w> break
+ bind $w <$M1B-Key-W> break
+ bind $w <Visibility> "
+ [list grab $w]
+ [list focus $w]
+ "
+ wm protocol $w WM_DELETE_WINDOW [cb _noop]
+ tkwait window $w
+
+ if {!$fetch_ok} {
+ delete_this
+ return 0
+ }
+ }
+
+ if {$new_ref ne {}} {
+ # If we have a ref we need to update it before we can
+ # proceed with a checkout (if one was enabled).
+ #
+ if {![_update_ref $this]} {
+ delete_this
+ return 0
+ }
+ }
+
+ if {$checkout} {
+ _checkout $this
+ return 1
+ }
+
+ delete_this
+ return 1
+}
+
+method _noop {} {}
+
+method _finish_fetch {ok} {
+ if {$ok} {
+ set l_trck [lindex $fetch_spec 0]
+ if {$l_trck eq {}} {
+ set l_trck FETCH_HEAD
+ }
+ if {[catch {set new_hash [git rev-parse --verify "$l_trck^0"]} err]} {
+ set ok 0
+ $w_cons insert "fatal: Cannot resolve $l_trck"
+ $w_cons insert $err
+ }
+ }
+
+ $w_cons done $ok
+ set w_cons {}
+ wm protocol $w WM_DELETE_WINDOW {}
+
+ if {$ok} {
+ destroy $w
+ set w {}
+ } else {
+ button $w.close -text Close -command [list destroy $w]
+ pack $w.close -side bottom -anchor e -padx 10 -pady 10
+ }
+
+ set fetch_ok $ok
+}
+
+method _update_ref {} {
+ global null_sha1 current_branch
+
+ set ref $new_ref
+ set new $new_hash
+
+ set is_current 0
+ set rh refs/heads/
+ set rn [string length $rh]
+ if {[string equal -length $rn $rh $ref]} {
+ set newbranch [string range $ref $rn end]
+ if {$current_branch eq $newbranch} {
+ set is_current 1
+ }
+ } else {
+ set newbranch $ref
+ }
+
+ if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} {
+ # Assume it does not exist, and that is what the error was.
+ #
+ if {!$create} {
+ _error $this "Branch '$newbranch' does not exist."
+ return 0
+ }
+
+ set reflog_msg "branch: Created from $new_expr"
+ set cur $null_sha1
+ } elseif {$create && $merge_type eq {none}} {
+ # We were told to create it, but not do a merge.
+ # Bad. Name shouldn't have existed.
+ #
+ _error $this "Branch '$newbranch' already exists."
+ return 0
+ } elseif {!$create && $merge_type eq {none}} {
+ # We aren't creating, it exists and we don't merge.
+ # We are probably just a simple branch switch.
+ # Use whatever value we just read.
+ #
+ set new $cur
+ set new_hash $cur
+ } elseif {$new eq $cur} {
+ # No merge would be required, don't compute anything.
+ #
+ } else {
+ catch {set merge_base [git merge-base $new $cur]}
+ if {$merge_base eq $cur} {
+ # The current branch is older.
+ #
+ set reflog_msg "merge $new_expr: Fast-forward"
+ } else {
+ switch -- $merge_type {
+ ff {
+ if {$merge_base eq $new} {
+ # The current branch is actually newer.
+ #
+ set new $cur
+ set new_hash $cur
+ } else {
+ _error $this "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to $new_expr.\nA merge is required."
+ return 0
+ }
+ }
+ reset {
+ # The current branch will lose things.
+ #
+ if {[_confirm_reset $this $cur]} {
+ set reflog_msg "reset $new_expr"
+ } else {
+ return 0
+ }
+ }
+ default {
+ _error $this "Merge strategy '$merge_type' not supported."
+ return 0
+ }
+ }
+ }
+ }
+
+ if {$new ne $cur} {
+ if {$is_current} {
+ # No so fast. We should defer this in case
+ # we cannot update the working directory.
+ #
+ set update_old $cur
+ return 1
+ }
+
+ if {[catch {
+ git update-ref -m $reflog_msg $ref $new $cur
+ } err]} {
+ _error $this "Failed to update '$newbranch'.\n\n$err"
+ return 0
+ }
+ }
+
+ return 1
+}
+
+method _checkout {} {
+ if {[lock_index checkout_op]} {
+ after idle [cb _start_checkout]
+ } else {
+ _error $this "Staging area (index) is already locked."
+ delete_this
+ }
+}
+
+method _start_checkout {} {
+ global HEAD commit_type
+
+ # -- Our in memory state should match the repository.
+ #
+ repository_state curType curHEAD curMERGE_HEAD
+ if {[string match amend* $commit_type]
+ && $curType eq {normal}
+ && $curHEAD eq $HEAD} {
+ } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
+ info_popup {Last scanned state does not match repository state.
+
+Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed.
+
+The rescan will be automatically started now.
+}
+ unlock_index
+ rescan ui_ready
+ delete_this
+ return
+ }
+
+ if {$curHEAD eq $new_hash} {
+ _after_readtree $this
+ } elseif {[is_config_true gui.trustmtime]} {
+ _readtree $this
+ } else {
+ ui_status {Refreshing file status...}
+ set fd [git_read update-index \
+ -q \
+ --unmerged \
+ --ignore-missing \
+ --refresh \
+ ]
+ fconfigure $fd -blocking 0 -translation binary
+ fileevent $fd readable [cb _refresh_wait $fd]
+ }
+}
+
+method _refresh_wait {fd} {
+ read $fd
+ if {[eof $fd]} {
+ close $fd
+ _readtree $this
+ }
+}
+
+method _name {} {
+ if {$new_ref eq {}} {
+ return [string range $new_hash 0 7]
+ }
+
+ set rh refs/heads/
+ set rn [string length $rh]
+ if {[string equal -length $rn $rh $new_ref]} {
+ return [string range $new_ref $rn end]
+ } else {
+ return $new_ref
+ }
+}
+
+method _readtree {} {
+ global HEAD
+
+ set readtree_d {}
+ $::main_status start \
+ "Updating working directory to '[_name $this]'..." \
+ {files checked out}
+
+ set fd [git_read --stderr read-tree \
+ -m \
+ -u \
+ -v \
+ --exclude-per-directory=.gitignore \
+ $HEAD \
+ $new_hash \
+ ]
+ fconfigure $fd -blocking 0 -translation binary
+ fileevent $fd readable [cb _readtree_wait $fd]
+}
+
+method _readtree_wait {fd} {
+ global current_branch
+
+ set buf [read $fd]
+ $::main_status update_meter $buf
+ append readtree_d $buf
+
+ fconfigure $fd -blocking 1
+ if {![eof $fd]} {
+ fconfigure $fd -blocking 0
+ return
+ }
+
+ if {[catch {close $fd}]} {
+ set err $readtree_d
+ regsub {^fatal: } $err {} err
+ $::main_status stop "Aborted checkout of '[_name $this]' (file level merging is required)."
+ warn_popup "File level merge required.
+
+$err
+
+Staying on branch '$current_branch'."
+ unlock_index
+ delete_this
+ return
+ }
+
+ $::main_status stop
+ _after_readtree $this
+}
+
+method _after_readtree {} {
+ global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
+ global current_branch is_detached
+ global ui_comm
+
+ set name [_name $this]
+ set log "checkout: moving"
+ if {!$is_detached} {
+ append log " from $current_branch"
+ }
+
+ # -- Move/create HEAD as a symbolic ref. Core git does not
+ # even check for failure here, it Just Works(tm). If it
+ # doesn't we are in some really ugly state that is difficult
+ # to recover from within git-gui.
+ #
+ set rh refs/heads/
+ set rn [string length $rh]
+ if {[string equal -length $rn $rh $new_ref]} {
+ set new_branch [string range $new_ref $rn end]
+ if {$is_detached || $current_branch ne $new_branch} {
+ append log " to $new_branch"
+ if {[catch {
+ git symbolic-ref -m $log HEAD $new_ref
+ } err]} {
+ _fatal $this $err
+ }
+ set current_branch $new_branch
+ set is_detached 0
+ }
+ } else {
+ if {$new_hash ne $HEAD} {
+ append log " to $new_expr"
+ if {[catch {
+ _detach_HEAD $log $new_hash
+ } err]} {
+ _fatal $this $err
+ }
+ }
+ set current_branch HEAD
+ set is_detached 1
+ }
+
+ # -- We had to defer updating the branch itself until we
+ # knew the working directory would update. So now we
+ # need to finish that work. If it fails we're in big
+ # trouble.
+ #
+ if {$update_old ne {}} {
+ if {[catch {
+ git update-ref \
+ -m $reflog_msg \
+ $new_ref \
+ $new_hash \
+ $update_old
+ } err]} {
+ _fatal $this $err
+ }
+ }
+
+ if {$is_detached} {
+ info_popup "You are no longer on a local branch.
+
+If you wanted to be on a branch, create one now starting from 'This Detached Checkout'."
+ }
+
+ # -- Update our repository state. If we were previously in
+ # amend mode we need to toss the current buffer and do a
+ # full rescan to update our file lists. If we weren't in
+ # amend mode our file lists are accurate and we can avoid
+ # the rescan.
+ #
+ unlock_index
+ set selected_commit_type new
+ if {[string match amend* $commit_type]} {
+ $ui_comm delete 0.0 end
+ $ui_comm edit reset
+ $ui_comm edit modified false
+ rescan [list ui_status "Checked out '$name'."]
+ } else {
+ repository_state commit_type HEAD MERGE_HEAD
+ set PARENT $HEAD
+ ui_status "Checked out '$name'."
+ }
+ delete_this
+}
+
+git-version proc _detach_HEAD {log new} {
+ >= 1.5.3 {
+ git update-ref --no-deref -m $log HEAD $new
+ }
+ default {
+ set p [gitdir HEAD]
+ file delete $p
+ set fd [open $p w]
+ fconfigure $fd -translation lf -encoding utf-8
+ puts $fd $new
+ close $fd
+ }
+}
+
+method _confirm_reset {cur} {
+ set reset_ok 0
+ set name [_name $this]
+ set gitk [list do_gitk [list $cur ^$new_hash]]
+
+ _toplevel $this {Confirm Branch Reset}
+ pack [label $w.msg1 \
+ -anchor w \
+ -justify left \
+ -text "Resetting '$name' to $new_expr will lose the following commits:" \
+ ] -anchor w
+
+ set list $w.list.l
+ frame $w.list
+ text $list \
+ -font font_diff \
+ -width 80 \
+ -height 10 \
+ -wrap none \
+ -xscrollcommand [list $w.list.sbx set] \
+ -yscrollcommand [list $w.list.sby set]
+ scrollbar $w.list.sbx -orient h -command [list $list xview]
+ scrollbar $w.list.sby -orient v -command [list $list yview]
+ pack $w.list.sbx -fill x -side bottom
+ pack $w.list.sby -fill y -side right
+ pack $list -fill both -expand 1
+ pack $w.list -fill both -expand 1 -padx 5 -pady 5
+
+ pack [label $w.msg2 \
+ -anchor w \
+ -justify left \
+ -text {Recovering lost commits may not be easy.} \
+ ]
+ pack [label $w.msg3 \
+ -anchor w \
+ -justify left \
+ -text "Reset '$name'?" \
+ ]
+
+ frame $w.buttons
+ button $w.buttons.visualize \
+ -text Visualize \
+ -command $gitk
+ pack $w.buttons.visualize -side left
+ button $w.buttons.reset \
+ -text Reset \
+ -command "
+ set @reset_ok 1
+ destroy $w
+ "
+ pack $w.buttons.reset -side right
+ button $w.buttons.cancel \
+ -default active \
+ -text Cancel \
+ -command [list destroy $w]
+ pack $w.buttons.cancel -side right -padx 5
+ pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+ set fd [git_read rev-list --pretty=oneline $cur ^$new_hash]
+ while {[gets $fd line] > 0} {
+ set abbr [string range $line 0 7]
+ set subj [string range $line 41 end]
+ $list insert end "$abbr $subj\n"
+ }
+ close $fd
+ $list configure -state disabled
+
+ bind $w <Key-v> $gitk
+ bind $w <Visibility> "
+ grab $w
+ focus $w.buttons.cancel
+ "
+ bind $w <Key-Return> [list destroy $w]
+ bind $w <Key-Escape> [list destroy $w]
+ tkwait window $w
+ return $reset_ok
+}
+
+method _error {msg} {
+ if {[winfo ismapped $parent_w]} {
+ set p $parent_w
+ } else {
+ set p .
+ }
+
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $p] \
+ -parent $p \
+ -message $msg
+}
+
+method _toplevel {title} {
+ regsub -all {::} $this {__} w
+ set w .$w
+
+ if {[winfo ismapped $parent_w]} {
+ set p $parent_w
+ } else {
+ set p .
+ }
+
+ toplevel $w
+ wm title $w $title
+ wm geometry $w "+[winfo rootx $p]+[winfo rooty $p]"
+}
+
+method _fatal {err} {
+ error_popup "Failed to set current branch.
+
+This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file.
+
+This should not have occurred. [appname] will now close and give up.
+
+$err"
+ exit 1
+}
+
+}
--- /dev/null
+# git-gui revision chooser
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+class choose_rev {
+
+image create photo ::choose_rev::img_find -data {R0lGODlhEAAQAIYAAPwCBCQmJDw+PBQSFAQCBMza3NTm5MTW1HyChOT29Ozq7MTq7Kze5Kzm7Oz6/NTy9Iza5GzGzKzS1Nzy9Nz29Kzq9HTGzHTK1Lza3AwKDLzu9JTi7HTW5GTCzITO1Mzq7Hza5FTK1ESyvHzKzKzW3DQyNDyqtDw6PIzW5HzGzAT+/Dw+RKyurNTOzMTGxMS+tJSGdATCxHRydLSqpLymnLSijBweHERCRNze3Pz69PTy9Oze1OTSxOTGrMSqlLy+vPTu5OzSvMymjNTGvNS+tMy2pMyunMSefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAe4gACCAAECA4OIiAIEBQYHBAKJgwIICQoLDA0IkZIECQ4PCxARCwSSAxITFA8VEBYXGBmJAQYLGhUbHB0eH7KIGRIMEBAgISIjJKaIJQQLFxERIialkieUGigpKRoIBCqJKyyLBwvJAioEyoICLS4v6QQwMQQyLuqLli8zNDU2BCf1lN3AkUPHDh49fAQAAEnGD1MCCALZEaSHkIUMBQS8wWMIkSJGhBzBmFEGgRsBUqpMiSgdAD+BAAAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
+
+field w ; # our megawidget path
+field w_list ; # list of currently filtered specs
+field w_filter ; # filter entry for $w_list
+
+field c_expr {}; # current revision expression
+field filter ; # current filter string
+field revtype head; # type of revision chosen
+field cur_specs [list]; # list of specs for $revtype
+field spec_head ; # list of all head specs
+field spec_trck ; # list of all tracking branch specs
+field spec_tag ; # list of all tag specs
+field tip_data ; # array of tip commit info by refname
+field log_last ; # array of reflog date by refname
+
+field tooltip_wm {} ; # Current tooltip toplevel, if open
+field tooltip_t {} ; # Text widget in $tooltip_wm
+field tooltip_timer {} ; # Current timer event for our tooltip
+
+proc new {path {title {}}} {
+ return [_new $path 0 $title]
+}
+
+proc new_unmerged {path {title {}}} {
+ return [_new $path 1 $title]
+}
+
+constructor _new {path unmerged_only title} {
+ global current_branch is_detached
+
+ if {![info exists ::all_remotes]} {
+ load_all_remotes
+ }
+
+ set w $path
+
+ if {$title ne {}} {
+ labelframe $w -text $title
+ } else {
+ frame $w
+ }
+ bind $w <Destroy> [cb _delete %W]
+
+ if {$is_detached} {
+ radiobutton $w.detachedhead_r \
+ -anchor w \
+ -text {This Detached Checkout} \
+ -value HEAD \
+ -variable @revtype
+ grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2
+ }
+
+ radiobutton $w.expr_r \
+ -text {Revision Expression:} \
+ -value expr \
+ -variable @revtype
+ entry $w.expr_t \
+ -borderwidth 1 \
+ -relief sunken \
+ -width 50 \
+ -textvariable @c_expr \
+ -validate key \
+ -validatecommand [cb _validate %d %S]
+ grid $w.expr_r $w.expr_t -sticky we -padx {0 5}
+
+ frame $w.types
+ radiobutton $w.types.head_r \
+ -text {Local Branch} \
+ -value head \
+ -variable @revtype
+ pack $w.types.head_r -side left
+ radiobutton $w.types.trck_r \
+ -text {Tracking Branch} \
+ -value trck \
+ -variable @revtype
+ pack $w.types.trck_r -side left
+ radiobutton $w.types.tag_r \
+ -text {Tag} \
+ -value tag \
+ -variable @revtype
+ pack $w.types.tag_r -side left
+ set w_filter $w.types.filter
+ entry $w_filter \
+ -borderwidth 1 \
+ -relief sunken \
+ -width 12 \
+ -textvariable @filter \
+ -validate key \
+ -validatecommand [cb _filter %P]
+ pack $w_filter -side right
+ pack [label $w.types.filter_icon \
+ -image ::choose_rev::img_find \
+ ] -side right
+ grid $w.types -sticky we -padx {0 5} -columnspan 2
+
+ frame $w.list
+ set w_list $w.list.l
+ listbox $w_list \
+ -font font_diff \
+ -width 50 \
+ -height 10 \
+ -selectmode browse \
+ -exportselection false \
+ -xscrollcommand [cb _sb_set $w.list.sbx h] \
+ -yscrollcommand [cb _sb_set $w.list.sby v]
+ pack $w_list -fill both -expand 1
+ grid $w.list -sticky nswe -padx {20 5} -columnspan 2
+ bind $w_list <Any-Motion> [cb _show_tooltip @%x,%y]
+ bind $w_list <Any-Enter> [cb _hide_tooltip]
+ bind $w_list <Any-Leave> [cb _hide_tooltip]
+ bind $w_list <Destroy> [cb _hide_tooltip]
+
+ grid columnconfigure $w 1 -weight 1
+ if {$is_detached} {
+ grid rowconfigure $w 3 -weight 1
+ } else {
+ grid rowconfigure $w 2 -weight 1
+ }
+
+ trace add variable @revtype write [cb _select]
+ bind $w_filter <Key-Return> [list focus $w_list]\;break
+ bind $w_filter <Key-Down> [list focus $w_list]
+
+ set fmt list
+ append fmt { %(refname)}
+ append fmt { [list}
+ append fmt { %(objecttype)}
+ append fmt { %(objectname)}
+ append fmt { [concat %(taggername) %(authorname)]}
+ append fmt { [concat %(taggerdate) %(authordate)]}
+ append fmt { %(subject)}
+ append fmt {] [list}
+ append fmt { %(*objecttype)}
+ append fmt { %(*objectname)}
+ append fmt { %(*authorname)}
+ append fmt { %(*authordate)}
+ append fmt { %(*subject)}
+ append fmt {]}
+ set all_refn [list]
+ set fr_fd [git_read for-each-ref \
+ --tcl \
+ --sort=-taggerdate \
+ --format=$fmt \
+ refs/heads \
+ refs/remotes \
+ refs/tags \
+ ]
+ fconfigure $fr_fd -translation lf -encoding utf-8
+ while {[gets $fr_fd line] > 0} {
+ set line [eval $line]
+ if {[lindex $line 1 0] eq {tag}} {
+ if {[lindex $line 2 0] eq {commit}} {
+ set sha1 [lindex $line 2 1]
+ } else {
+ continue
+ }
+ } elseif {[lindex $line 1 0] eq {commit}} {
+ set sha1 [lindex $line 1 1]
+ } else {
+ continue
+ }
+ set refn [lindex $line 0]
+ set tip_data($refn) [lrange $line 1 end]
+ lappend cmt_refn($sha1) $refn
+ lappend all_refn $refn
+ }
+ close $fr_fd
+
+ if {$unmerged_only} {
+ set fr_fd [git_read rev-list --all ^$::HEAD]
+ while {[gets $fr_fd sha1] > 0} {
+ if {[catch {set rlst $cmt_refn($sha1)}]} continue
+ foreach refn $rlst {
+ set inc($refn) 1
+ }
+ }
+ close $fr_fd
+ } else {
+ foreach refn $all_refn {
+ set inc($refn) 1
+ }
+ }
+
+ set spec_head [list]
+ foreach name [load_all_heads] {
+ set refn refs/heads/$name
+ if {[info exists inc($refn)]} {
+ lappend spec_head [list $name $refn]
+ }
+ }
+
+ set spec_trck [list]
+ foreach spec [all_tracking_branches] {
+ set refn [lindex $spec 0]
+ if {[info exists inc($refn)]} {
+ regsub ^refs/(heads|remotes)/ $refn {} name
+ lappend spec_trck [concat $name $spec]
+ }
+ }
+
+ set spec_tag [list]
+ foreach name [load_all_tags] {
+ set refn refs/tags/$name
+ if {[info exists inc($refn)]} {
+ lappend spec_tag [list $name $refn]
+ }
+ }
+
+ if {$is_detached} { set revtype HEAD
+ } elseif {[llength $spec_head] > 0} { set revtype head
+ } elseif {[llength $spec_trck] > 0} { set revtype trck
+ } elseif {[llength $spec_tag ] > 0} { set revtype tag
+ } else { set revtype expr
+ }
+
+ if {$revtype eq {head} && $current_branch ne {}} {
+ set i 0
+ foreach spec $spec_head {
+ if {[lindex $spec 0] eq $current_branch} {
+ $w_list selection clear 0 end
+ $w_list selection set $i
+ break
+ }
+ incr i
+ }
+ }
+
+ return $this
+}
+
+method none {text} {
+ if {![winfo exists $w.none_r]} {
+ radiobutton $w.none_r \
+ -anchor w \
+ -value none \
+ -variable @revtype
+ grid $w.none_r -sticky we -padx {0 5} -columnspan 2
+ }
+ $w.none_r configure -text $text
+}
+
+method get {} {
+ switch -- $revtype {
+ head -
+ trck -
+ tag {
+ set i [$w_list curselection]
+ if {$i ne {}} {
+ return [lindex $cur_specs $i 0]
+ } else {
+ return {}
+ }
+ }
+
+ HEAD { return HEAD }
+ expr { return $c_expr }
+ none { return {} }
+ default { error "unknown type of revision" }
+ }
+}
+
+method pick_tracking_branch {} {
+ set revtype trck
+}
+
+method focus_filter {} {
+ if {[$w_filter cget -state] eq {normal}} {
+ focus $w_filter
+ }
+}
+
+method bind_listbox {event script} {
+ bind $w_list $event $script
+}
+
+method get_local_branch {} {
+ if {$revtype eq {head}} {
+ return [_expr $this]
+ } else {
+ return {}
+ }
+}
+
+method get_tracking_branch {} {
+ set i [$w_list curselection]
+ if {$i eq {} || $revtype ne {trck}} {
+ return {}
+ }
+ return [lrange [lindex $cur_specs $i] 1 end]
+}
+
+method get_commit {} {
+ set e [_expr $this]
+ if {$e eq {}} {
+ return {}
+ }
+ return [git rev-parse --verify "$e^0"]
+}
+
+method commit_or_die {} {
+ if {[catch {set new [get_commit $this]} err]} {
+
+ # Cleanup the not-so-friendly error from rev-parse.
+ #
+ regsub {^fatal:\s*} $err {} err
+ if {$err eq {Needed a single revision}} {
+ set err {}
+ }
+
+ set top [winfo toplevel $w]
+ set msg "Invalid revision: [get $this]\n\n$err"
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $top] \
+ -parent $top \
+ -message $msg
+ error $msg
+ }
+ return $new
+}
+
+method _expr {} {
+ switch -- $revtype {
+ head -
+ trck -
+ tag {
+ set i [$w_list curselection]
+ if {$i ne {}} {
+ return [lindex $cur_specs $i 1]
+ } else {
+ error "No revision selected."
+ }
+ }
+
+ expr {
+ if {$c_expr ne {}} {
+ return $c_expr
+ } else {
+ error "Revision expression is empty."
+ }
+ }
+ HEAD { return HEAD }
+ none { return {} }
+ default { error "unknown type of revision" }
+ }
+}
+
+method _validate {d S} {
+ if {$d == 1} {
+ if {[regexp {\s} $S]} {
+ return 0
+ }
+ if {[string length $S] > 0} {
+ set revtype expr
+ }
+ }
+ return 1
+}
+
+method _filter {P} {
+ if {[regexp {\s} $P]} {
+ return 0
+ }
+ _rebuild $this $P
+ return 1
+}
+
+method _select {args} {
+ _rebuild $this $filter
+ focus_filter $this
+}
+
+method _rebuild {pat} {
+ set ste normal
+ switch -- $revtype {
+ head { set new $spec_head }
+ trck { set new $spec_trck }
+ tag { set new $spec_tag }
+ expr -
+ HEAD -
+ none {
+ set new [list]
+ set ste disabled
+ }
+ }
+
+ if {[$w_list cget -state] eq {disabled}} {
+ $w_list configure -state normal
+ }
+ $w_list delete 0 end
+
+ if {$pat ne {}} {
+ set pat *${pat}*
+ }
+ set cur_specs [list]
+ foreach spec $new {
+ set txt [lindex $spec 0]
+ if {$pat eq {} || [string match $pat $txt]} {
+ lappend cur_specs $spec
+ $w_list insert end $txt
+ }
+ }
+ if {$cur_specs ne {}} {
+ $w_list selection clear 0 end
+ $w_list selection set 0
+ }
+
+ if {[$w_filter cget -state] ne $ste} {
+ $w_list configure -state $ste
+ $w_filter configure -state $ste
+ }
+}
+
+method _delete {current} {
+ if {$current eq $w} {
+ delete_this
+ }
+}
+
+method _sb_set {sb orient first last} {
+ set old_focus [focus -lastfor $w]
+
+ if {$first == 0 && $last == 1} {
+ if {[winfo exists $sb]} {
+ destroy $sb
+ if {$old_focus ne {}} {
+ update
+ focus $old_focus
+ }
+ }
+ return
+ }
+
+ if {![winfo exists $sb]} {
+ if {$orient eq {h}} {
+ scrollbar $sb -orient h -command [list $w_list xview]
+ pack $sb -fill x -side bottom -before $w_list
+ } else {
+ scrollbar $sb -orient v -command [list $w_list yview]
+ pack $sb -fill y -side right -before $w_list
+ }
+ if {$old_focus ne {}} {
+ update
+ focus $old_focus
+ }
+ }
+ $sb set $first $last
+}
+
+method _show_tooltip {pos} {
+ if {$tooltip_wm ne {}} {
+ _open_tooltip $this
+ } elseif {$tooltip_timer eq {}} {
+ set tooltip_timer [after 1000 [cb _open_tooltip]]
+ }
+}
+
+method _open_tooltip {} {
+ global remote_url
+
+ set tooltip_timer {}
+ set pos_x [winfo pointerx $w_list]
+ set pos_y [winfo pointery $w_list]
+ if {[winfo containing $pos_x $pos_y] ne $w_list} {
+ _hide_tooltip $this
+ return
+ }
+
+ set pos @[join [list \
+ [expr {$pos_x - [winfo rootx $w_list]}] \
+ [expr {$pos_y - [winfo rooty $w_list]}]] ,]
+ set lno [$w_list index $pos]
+ if {$lno eq {}} {
+ _hide_tooltip $this
+ return
+ }
+
+ set spec [lindex $cur_specs $lno]
+ set refn [lindex $spec 1]
+ if {$refn eq {}} {
+ _hide_tooltip $this
+ return
+ }
+
+ if {$tooltip_wm eq {}} {
+ set tooltip_wm [toplevel $w_list.tooltip -borderwidth 1]
+ wm overrideredirect $tooltip_wm 1
+ wm transient $tooltip_wm [winfo toplevel $w_list]
+ set tooltip_t $tooltip_wm.label
+ text $tooltip_t \
+ -takefocus 0 \
+ -highlightthickness 0 \
+ -relief flat \
+ -borderwidth 0 \
+ -wrap none \
+ -background lightyellow \
+ -foreground black
+ $tooltip_t tag conf section_header -font font_uibold
+ bind $tooltip_wm <Escape> [cb _hide_tooltip]
+ pack $tooltip_t
+ } else {
+ $tooltip_t conf -state normal
+ $tooltip_t delete 0.0 end
+ }
+
+ set data $tip_data($refn)
+ if {[lindex $data 0 0] eq {tag}} {
+ set tag [lindex $data 0]
+ if {[lindex $data 1 0] eq {commit}} {
+ set cmit [lindex $data 1]
+ } else {
+ set cmit {}
+ }
+ } elseif {[lindex $data 0 0] eq {commit}} {
+ set tag {}
+ set cmit [lindex $data 0]
+ }
+
+ $tooltip_t insert end [lindex $spec 0]
+ set last [_reflog_last $this [lindex $spec 1]]
+ if {$last ne {}} {
+ $tooltip_t insert end "\n"
+ $tooltip_t insert end "updated"
+ $tooltip_t insert end " $last"
+ }
+ $tooltip_t insert end "\n"
+
+ if {$tag ne {}} {
+ $tooltip_t insert end "\n"
+ $tooltip_t insert end "tag" section_header
+ $tooltip_t insert end " [lindex $tag 1]\n"
+ $tooltip_t insert end [lindex $tag 2]
+ $tooltip_t insert end " ([lindex $tag 3])\n"
+ $tooltip_t insert end [lindex $tag 4]
+ $tooltip_t insert end "\n"
+ }
+
+ if {$cmit ne {}} {
+ $tooltip_t insert end "\n"
+ $tooltip_t insert end "commit" section_header
+ $tooltip_t insert end " [lindex $cmit 1]\n"
+ $tooltip_t insert end [lindex $cmit 2]
+ $tooltip_t insert end " ([lindex $cmit 3])\n"
+ $tooltip_t insert end [lindex $cmit 4]
+ }
+
+ if {[llength $spec] > 2} {
+ $tooltip_t insert end "\n"
+ $tooltip_t insert end "remote" section_header
+ $tooltip_t insert end " [lindex $spec 2]\n"
+ $tooltip_t insert end "url"
+ $tooltip_t insert end " $remote_url([lindex $spec 2])\n"
+ $tooltip_t insert end "branch"
+ $tooltip_t insert end " [lindex $spec 3]"
+ }
+
+ $tooltip_t conf -state disabled
+ _position_tooltip $this
+}
+
+method _reflog_last {name} {
+ if {[info exists reflog_last($name)]} {
+ return reflog_last($name)
+ }
+
+ set last {}
+ if {[catch {set last [file mtime [gitdir $name]]}]
+ && ![catch {set g [open [gitdir logs $name] r]}]} {
+ fconfigure $g -translation binary
+ while {[gets $g line] >= 0} {
+ if {[regexp {> ([1-9][0-9]*) } $line line when]} {
+ set last $when
+ }
+ }
+ close $g
+ }
+
+ if {$last ne {}} {
+ set last [clock format $last -format {%a %b %e %H:%M:%S %Y}]
+ }
+ set reflog_last($name) $last
+ return $last
+}
+
+method _position_tooltip {} {
+ set max_h [lindex [split [$tooltip_t index end] .] 0]
+ set max_w 0
+ for {set i 1} {$i <= $max_h} {incr i} {
+ set c [lindex [split [$tooltip_t index "$i.0 lineend"] .] 1]
+ if {$c > $max_w} {set max_w $c}
+ }
+ $tooltip_t conf -width $max_w -height $max_h
+
+ set req_w [winfo reqwidth $tooltip_t]
+ set req_h [winfo reqheight $tooltip_t]
+ set pos_x [expr {[winfo pointerx .] + 5}]
+ set pos_y [expr {[winfo pointery .] + 10}]
+
+ set g "${req_w}x${req_h}"
+ if {$pos_x >= 0} {append g +}
+ append g $pos_x
+ if {$pos_y >= 0} {append g +}
+ append g $pos_y
+
+ wm geometry $tooltip_wm $g
+ raise $tooltip_wm
+}
+
+method _hide_tooltip {} {
+ if {$tooltip_wm ne {}} {
+ destroy $tooltip_wm
+ set tooltip_wm {}
+ }
+ if {$tooltip_timer ne {}} {
+ after cancel $tooltip_timer
+ set tooltip_timer {}
+ }
+}
+
+}
if {[namespace exists $class]} {
error "class $class already declared"
}
- namespace eval $class {
+ namespace eval $class "
variable __nextid 0
variable __sealed 0
variable __field_list {}
proc cb {name args} {
upvar this this
- set args [linsert $args 0 $name $this]
- return [uplevel [list namespace code $args]]
+ concat \[list ${class}::\$name \$this\] \$args
}
- }
+ "
namespace eval $class $body
}
set mbodyc {}
append mbodyc {set this } $class
- append mbodyc {::__o[incr } $class {::__nextid]} \;
- append mbodyc {namespace eval $this {}} \;
+ append mbodyc {::__o[incr } $class {::__nextid]::__d} \;
+ append mbodyc {create_this } $class \;
+ append mbodyc {set __this [namespace qualifiers $this]} \;
if {$__field_list ne {}} {
append mbodyc {upvar #0}
foreach n $__field_list {
set n [lindex $n 0]
- append mbodyc { ${this}::} $n { } $n
- regsub -all @$n\\M $body "\${this}::$n" body
+ append mbodyc { ${__this}::} $n { } $n
+ regsub -all @$n\\M $body "\${__this}::$n" body
}
append mbodyc \;
foreach n $__field_list {
set params [linsert $params 0 this]
set mbodyc {}
+ append mbodyc {set __this [namespace qualifiers $this]} \;
+
switch $deleted {
{} {}
ifdeleted {
- append mbodyc {if {![namespace exists $this]} }
+ append mbodyc {if {![namespace exists $__this]} }
append mbodyc \{ $del_body \; return \} \;
}
default {
if { [regexp -all -- $n\\M $body] == 1
&& [regexp -all -- \\\$$n\\M $body] == 1
&& [regexp -all -- \\\$$n\\( $body] == 0} {
- regsub -all \\\$$n\\M $body "\[set \${this}::$n\]" body
+ regsub -all \
+ \\\$$n\\M $body \
+ "\[set \${__this}::$n\]" body
} else {
- append decl { ${this}::} $n { } $n
- regsub -all @$n\\M $body "\${this}::$n" body
+ append decl { ${__this}::} $n { } $n
+ regsub -all @$n\\M $body "\${__this}::$n" body
}
}
}
namespace eval $class [list proc $name $params $mbodyc]
}
+proc create_this {class} {
+ upvar this this
+ namespace eval [namespace qualifiers $this] [list proc \
+ [namespace tail $this] \
+ [list name args] \
+ "eval \[list ${class}::\$name $this\] \$args" \
+ ]
+}
+
proc delete_this {{t {}}} {
if {$t eq {}} {
upvar this this
set t $this
}
+ set t [namespace qualifiers $t]
if {[namespace exists $t]} {namespace delete $t}
}
set msg {}
set parents [list]
if {[catch {
- set fd [open "| git cat-file commit $curHEAD" r]
+ set fd [git_read cat-file commit $curHEAD]
fconfigure $fd -encoding binary -translation lf
if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
set enc utf-8
set enc [string tolower [string range $line 9 end]]
}
}
- set msg [encoding convertfrom $enc [read $fd]]
- set msg [string trim $msg]
+ set msg [read $fd]
close $fd
+
+ set enc [tcl_encoding $enc]
+ if {$enc ne {}} {
+ set msg [encoding convertfrom $enc $msg]
+ }
+ set msg [string trim $msg]
} err]} {
error_popup "Error loading commit data for amend:\n\n$err"
return
$ui_comm insert end $msg
$ui_comm edit reset
$ui_comm edit modified false
- rescan {set ui_status_value {Ready.}}
+ rescan ui_ready
}
set GIT_COMMITTER_IDENT {}
$ui_comm delete 0.0 end
$ui_comm edit reset
$ui_comm edit modified false
- rescan {set ui_status_value {Ready.}}
+ rescan ui_ready
}
proc commit_tree {} {
global HEAD commit_type file_states ui_comm repo_config
- global ui_status_value pch_error
+ global pch_error
if {[committer_ident] eq {}} return
if {![lock_index update]} return
The rescan will be automatically started now.
}
unlock_index
- rescan {set ui_status_value {Ready.}}
+ rescan ui_ready
return
}
U? {
error_popup "Unmerged files cannot be committed.
-File [short_path $path] has merge conflicts. You must resolve them and add the file before committing.
+File [short_path $path] has merge conflicts. You must resolve them and stage the file before committing.
"
unlock_index
return
if {!$files_ready && ![string match *merge $curType]} {
info_popup {No changes to commit.
-You must add at least 1 file before you can commit.
+You must stage at least 1 file before you can commit.
}
unlock_index
return
return
}
- set ui_status_value {Calling pre-commit hook...}
+ ui_status {Calling pre-commit hook...}
set pch_error {}
set fd_ph [open "| $pchook" r]
- fconfigure $fd_ph -blocking 0 -translation binary
+ fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
fileevent $fd_ph readable \
[list commit_prehook_wait $fd_ph $curHEAD $msg]
}
proc commit_prehook_wait {fd_ph curHEAD msg} {
- global pch_error ui_status_value
+ global pch_error
append pch_error [read $fd_ph]
fconfigure $fd_ph -blocking 1
if {[eof $fd_ph]} {
if {[catch {close $fd_ph}]} {
- set ui_status_value {Commit declined by pre-commit hook.}
+ ui_status {Commit declined by pre-commit hook.}
hook_failed_popup pre-commit $pch_error
unlock_index
} else {
}
proc commit_writetree {curHEAD msg} {
- global ui_status_value
-
- set ui_status_value {Committing changes...}
- set fd_wt [open "| git write-tree" r]
+ ui_status {Committing changes...}
+ set fd_wt [git_read write-tree]
fileevent $fd_wt readable \
[list commit_committree $fd_wt $curHEAD $msg]
}
proc commit_committree {fd_wt curHEAD msg} {
global HEAD PARENT MERGE_HEAD commit_type
- global all_heads current_branch
- global ui_status_value ui_comm selected_commit_type
+ global current_branch
+ global ui_comm selected_commit_type
global file_states selected_paths rescan_active
global repo_config
gets $fd_wt tree_id
if {$tree_id eq {} || [catch {close $fd_wt} err]} {
error_popup "write-tree failed:\n\n$err"
- set ui_status_value {Commit failed.}
+ ui_status {Commit failed.}
unlock_index
return
}
# -- Verify this wasn't an empty change.
#
if {$commit_type eq {normal}} {
- set fd_ot [open "| git cat-file commit $PARENT" r]
+ set fd_ot [git_read cat-file commit $PARENT]
fconfigure $fd_ot -encoding binary -translation lf
set old_tree [gets $fd_ot]
close $fd_ot
A rescan will be automatically started now.
}
unlock_index
- rescan {set ui_status_value {No changes to commit.}}
+ rescan {ui_status {No changes to commit.}}
return
}
}
#
set msg_p [gitdir COMMIT_EDITMSG]
set msg_wt [open $msg_p w]
+ fconfigure $msg_wt -translation lf
if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
set enc utf-8
}
- fconfigure $msg_wt -encoding binary -translation binary
- puts -nonewline $msg_wt [encoding convertto $enc $msg]
+ set use_enc [tcl_encoding $enc]
+ if {$use_enc ne {}} {
+ fconfigure $msg_wt -encoding $use_enc
+ } else {
+ puts stderr "warning: Tcl does not support encoding '$enc'."
+ fconfigure $msg_wt -encoding utf-8
+ }
+ puts -nonewline $msg_wt $msg
close $msg_wt
# -- Create the commit.
lappend cmd <$msg_p
if {[catch {set cmt_id [eval git $cmd]} err]} {
error_popup "commit-tree failed:\n\n$err"
- set ui_status_value {Commit failed.}
+ ui_status {Commit failed.}
unlock_index
return
}
git update-ref -m $reflogm HEAD $cmt_id $curHEAD
} err]} {
error_popup "update-ref failed:\n\n$err"
- set ui_status_value {Commit failed.}
+ ui_status {Commit failed.}
unlock_index
return
}
# -- Let rerere do its thing.
#
- if {[file isdirectory [gitdir rr-cache]]} {
+ if {[get_config rerere.enabled] eq {}} {
+ set rerere [file isdirectory [gitdir rr-cache]]
+ } else {
+ set rerere [is_config_true rerere.enabled]
+ }
+ if {$rerere} {
catch {git rerere}
}
$ui_comm delete 0.0 end
$ui_comm edit reset
$ui_comm edit modified false
+ if {$::GITGUI_BCK_exists} {
+ catch {file delete [gitdir GITGUI_BCK]}
+ set ::GITGUI_BCK_exists 0
+ }
if {[is_enabled singlecommit]} do_quit
- # -- Make sure our current branch exists.
- #
- if {$commit_type eq {initial}} {
- lappend all_heads $current_branch
- set all_heads [lsort -unique $all_heads]
- populate_branch_menu
- }
-
# -- Update in memory status
#
set selected_commit_type new
display_all_files
unlock_index
reshow_diff
- set ui_status_value \
- "Created commit [string range $cmt_id 0 7]: $subject"
+ ui_status "Created commit [string range $cmt_id 0 7]: $subject"
}
field t_long
field w
field console_cr
+field is_toplevel 1; # are we our own window?
constructor new {short_title long_title} {
set t_short $short_title
return $this
}
+constructor embed {path title} {
+ set t_short {}
+ set t_long $title
+ set w $path
+ set is_toplevel 0
+ _init $this
+ return $this
+}
+
method _init {} {
global M1B
- make_toplevel top w -autodelete 0
- wm title $top "[appname] ([reponame]): $t_short"
+
+ if {$is_toplevel} {
+ make_toplevel top w -autodelete 0
+ wm title $top "[appname] ([reponame]): $t_short"
+ } else {
+ frame $w
+ }
+
set console_cr 1.0
frame $w.m
$w.m.t tag remove sel 0.0 end
"
- button $w.ok -text {Close} \
- -state disabled \
- -command "destroy $w"
- pack $w.ok -side bottom -anchor e -pady 10 -padx 10
+ if {$is_toplevel} {
+ button $w.ok -text {Close} \
+ -state disabled \
+ -command [list destroy $w]
+ pack $w.ok -side bottom -anchor e -pady 10 -padx 10
+ bind $w <Visibility> [list focus $w]
+ }
bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
- bind $w <Visibility> "focus $w"
}
method exec {cmd {after {}}} {
- # -- Cygwin's Tcl tosses the enviroment when we exec our child.
- # But most users need that so we have to relogin. :-(
- #
- if {[is_Cygwin]} {
- set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
+ if {[lindex $cmd 0] eq {git}} {
+ set fd_f [eval git_read --stderr [lrange $cmd 1 end]]
+ } else {
+ lappend cmd 2>@1
+ set fd_f [_open_stdout_stderr $cmd]
}
-
- # -- Tcl won't let us redirect both stdout and stderr to
- # the same pipe. So pass it through cat...
- #
- set cmd [concat | $cmd |& cat]
-
- set fd_f [open $cmd r]
fconfigure $fd_f -blocking 0 -translation binary
fileevent $fd_f readable [cb _read $fd_f $after]
}
}
}
+method insert {txt} {
+ if {![winfo exists $w.m.t]} {_init $this}
+ $w.m.t conf -state normal
+ $w.m.t insert end "$txt\n"
+ set console_cr [$w.m.t index {end -1c}]
+ $w.m.t conf -state disabled
+}
+
method done {ok} {
if {$ok} {
if {[winfo exists $w.m.s]} {
$w.m.s conf -background green -text {Success}
- $w.ok conf -state normal
- focus $w.ok
+ if {$is_toplevel} {
+ $w.ok conf -state normal
+ focus $w.ok
+ }
}
} else {
if {![winfo exists $w.m.s]} {
_init $this
}
$w.m.s conf -background red -text {Error: Command Failed}
- $w.ok conf -state normal
- focus $w.ok
+ if {$is_toplevel} {
+ $w.ok conf -state normal
+ focus $w.ok
+ }
}
delete_this
}
# Copyright (C) 2006, 2007 Shawn Pearce
proc do_stats {} {
- set fd [open "| git count-objects -v" r]
+ set fd [git_read count-objects -v]
while {[gets $fd line] > 0} {
if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
set stats($name) $value
lappend cmd --strict
console::exec $w $cmd
}
+
+proc hint_gc {} {
+ set object_limit 8
+ if {[is_Windows]} {
+ set object_limit 1
+ }
+
+ set objects_current [llength [glob \
+ -directory [gitdir objects 42] \
+ -nocomplain \
+ -tails \
+ -- \
+ *]]
+
+ if {$objects_current >= $object_limit} {
+ set objects_current [expr {$objects_current * 256}]
+ set object_limit [expr {$object_limit * 256}]
+ if {[ask_popup \
+ "This repository currently has approximately $objects_current loose objects.
+
+To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
+
+Compress the database now?"] eq yes} {
+ do_gc
+ }
+ }
+}
}
proc reshow_diff {} {
- global ui_status_value file_states file_lists
+ global file_states file_lists
global current_diff_path current_diff_side
set p $current_diff_path
clear_diff
display_file $path __
- rescan {set ui_status_value {Ready.}} 0
+ rescan ui_ready 0
}
proc show_diff {path w {lno {}}} {
global file_states file_lists
global is_3way_diff diff_active repo_config
- global ui_diff ui_status_value ui_index ui_workdir
+ global ui_diff ui_index ui_workdir
global current_diff_path current_diff_side current_diff_header
if {$diff_active || ![lock_index read]} return
set current_diff_path $path
set current_diff_side $w
set current_diff_header {}
- set ui_status_value "Loading diff of [escape_path $path]..."
+ ui_status "Loading diff of [escape_path $path]..."
# - Git won't give us the diff, there's nothing to compare to!
#
set max_sz [expr {128 * 1024}]
if {[catch {
set fd [open $path r]
+ fconfigure $fd -eofchar {}
set content [read $fd $max_sz]
close $fd
set sz [file size $path]
} err ]} {
set diff_active 0
unlock_index
- set ui_status_value "Unable to display [escape_path $path]"
+ ui_status "Unable to display [escape_path $path]"
error_popup "Error loading file:\n\n$err"
return
}
$ui_diff conf -state disabled
set diff_active 0
unlock_index
- set ui_status_value {Ready.}
+ ui_ready
return
}
- set cmd [list | git]
+ set cmd [list]
if {$w eq $ui_index} {
lappend cmd diff-index
lappend cmd --cached
lappend cmd --
lappend cmd $path
- if {[catch {set fd [open $cmd r]} err]} {
+ if {[catch {set fd [eval git_read --nice $cmd]} err]} {
set diff_active 0
unlock_index
- set ui_status_value "Unable to display [escape_path $path]"
+ ui_status "Unable to display [escape_path $path]"
error_popup "Error loading diff:\n\n$err"
return
}
}
proc read_diff {fd} {
- global ui_diff ui_status_value diff_active
+ global ui_diff diff_active
global is_3way_diff current_diff_header
$ui_diff conf -state normal
close $fd
set diff_active 0
unlock_index
- set ui_status_value {Ready.}
+ ui_ready
if {[$ui_diff index end] eq {2.0}} {
handle_empty_diff
if {$current_diff_path eq {} || $current_diff_header eq {}} return
if {![lock_index apply_hunk]} return
- set apply_cmd {git apply --cached --whitespace=nowarn}
+ set apply_cmd {apply --cached --whitespace=nowarn}
set mi [lindex $file_states($current_diff_path) 0]
if {$current_diff_side eq $ui_index} {
set mode unstage
}
if {[catch {
- set p [open "| $apply_cmd" w]
+ set p [eval git_write $apply_cmd]
fconfigure $p -translation binary -encoding binary
puts -nonewline $p $current_diff_header
puts -nonewline $p [$ui_diff get $s_lno $e_lno]
--- /dev/null
+# git-gui encoding support
+# Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+# (Copied from gitk, commit fd8ccbec4f0161)
+
+# This list of encoding names and aliases is distilled from
+# http://www.iana.org/assignments/character-sets.
+# Not all of them are supported by Tcl.
+set encoding_aliases {
+ { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
+ ISO646-US US-ASCII us IBM367 cp367 csASCII }
+ { ISO-10646-UTF-1 csISO10646UTF1 }
+ { ISO_646.basic:1983 ref csISO646basic1983 }
+ { INVARIANT csINVARIANT }
+ { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
+ { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
+ { NATS-SEFI iso-ir-8-1 csNATSSEFI }
+ { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
+ { NATS-DANO iso-ir-9-1 csNATSDANO }
+ { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
+ { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
+ { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
+ { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
+ { ISO-2022-KR csISO2022KR }
+ { EUC-KR csEUCKR }
+ { ISO-2022-JP csISO2022JP }
+ { ISO-2022-JP-2 csISO2022JP2 }
+ { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
+ csISO13JISC6220jp }
+ { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
+ { IT iso-ir-15 ISO646-IT csISO15Italian }
+ { PT iso-ir-16 ISO646-PT csISO16Portuguese }
+ { ES iso-ir-17 ISO646-ES csISO17Spanish }
+ { greek7-old iso-ir-18 csISO18Greek7Old }
+ { latin-greek iso-ir-19 csISO19LatinGreek }
+ { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
+ { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
+ { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
+ { ISO_5427 iso-ir-37 csISO5427Cyrillic }
+ { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
+ { BS_viewdata iso-ir-47 csISO47BSViewdata }
+ { INIS iso-ir-49 csISO49INIS }
+ { INIS-8 iso-ir-50 csISO50INIS8 }
+ { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
+ { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
+ { ISO_5428:1980 iso-ir-55 csISO5428Greek }
+ { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
+ { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
+ { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
+ csISO60Norwegian1 }
+ { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
+ { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
+ { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
+ { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
+ { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
+ { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
+ { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
+ { greek7 iso-ir-88 csISO88Greek7 }
+ { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
+ { iso-ir-90 csISO90 }
+ { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
+ { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
+ csISO92JISC62991984b }
+ { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
+ { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
+ { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
+ csISO95JIS62291984handadd }
+ { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
+ { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
+ { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
+ { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
+ CP819 csISOLatin1 }
+ { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
+ { T.61-7bit iso-ir-102 csISO102T617bit }
+ { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
+ { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
+ { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
+ { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
+ { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
+ { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
+ { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
+ { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
+ arabic csISOLatinArabic }
+ { ISO_8859-6-E csISO88596E ISO-8859-6-E }
+ { ISO_8859-6-I csISO88596I ISO-8859-6-I }
+ { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
+ greek greek8 csISOLatinGreek }
+ { T.101-G2 iso-ir-128 csISO128T101G2 }
+ { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
+ csISOLatinHebrew }
+ { ISO_8859-8-E csISO88598E ISO-8859-8-E }
+ { ISO_8859-8-I csISO88598I ISO-8859-8-I }
+ { CSN_369103 iso-ir-139 csISO139CSN369103 }
+ { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
+ { ISO_6937-2-add iso-ir-142 csISOTextComm }
+ { IEC_P27-1 iso-ir-143 csISO143IECP271 }
+ { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
+ csISOLatinCyrillic }
+ { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
+ { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
+ { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
+ { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
+ { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
+ { ISO_6937-2-25 iso-ir-152 csISO6937Add }
+ { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
+ { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
+ { ISO_10367-box iso-ir-155 csISO10367Box }
+ { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
+ { latin-lap lap iso-ir-158 csISO158Lap }
+ { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
+ { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
+ { us-dk csUSDK }
+ { dk-us csDKUS }
+ { JIS_X0201 X0201 csHalfWidthKatakana }
+ { KSC5636 ISO646-KR csKSC5636 }
+ { ISO-10646-UCS-2 csUnicode }
+ { ISO-10646-UCS-4 csUCS4 }
+ { DEC-MCS dec csDECMCS }
+ { hp-roman8 roman8 r8 csHPRoman8 }
+ { macintosh mac csMacintosh }
+ { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
+ csIBM037 }
+ { IBM038 EBCDIC-INT cp038 csIBM038 }
+ { IBM273 CP273 csIBM273 }
+ { IBM274 EBCDIC-BE CP274 csIBM274 }
+ { IBM275 EBCDIC-BR cp275 csIBM275 }
+ { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
+ { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
+ { IBM280 CP280 ebcdic-cp-it csIBM280 }
+ { IBM281 EBCDIC-JP-E cp281 csIBM281 }
+ { IBM284 CP284 ebcdic-cp-es csIBM284 }
+ { IBM285 CP285 ebcdic-cp-gb csIBM285 }
+ { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
+ { IBM297 cp297 ebcdic-cp-fr csIBM297 }
+ { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
+ { IBM423 cp423 ebcdic-cp-gr csIBM423 }
+ { IBM424 cp424 ebcdic-cp-he csIBM424 }
+ { IBM437 cp437 437 csPC8CodePage437 }
+ { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
+ { IBM775 cp775 csPC775Baltic }
+ { IBM850 cp850 850 csPC850Multilingual }
+ { IBM851 cp851 851 csIBM851 }
+ { IBM852 cp852 852 csPCp852 }
+ { IBM855 cp855 855 csIBM855 }
+ { IBM857 cp857 857 csIBM857 }
+ { IBM860 cp860 860 csIBM860 }
+ { IBM861 cp861 861 cp-is csIBM861 }
+ { IBM862 cp862 862 csPC862LatinHebrew }
+ { IBM863 cp863 863 csIBM863 }
+ { IBM864 cp864 csIBM864 }
+ { IBM865 cp865 865 csIBM865 }
+ { IBM866 cp866 866 csIBM866 }
+ { IBM868 CP868 cp-ar csIBM868 }
+ { IBM869 cp869 869 cp-gr csIBM869 }
+ { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
+ { IBM871 CP871 ebcdic-cp-is csIBM871 }
+ { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
+ { IBM891 cp891 csIBM891 }
+ { IBM903 cp903 csIBM903 }
+ { IBM904 cp904 904 csIBBM904 }
+ { IBM905 CP905 ebcdic-cp-tr csIBM905 }
+ { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
+ { IBM1026 CP1026 csIBM1026 }
+ { EBCDIC-AT-DE csIBMEBCDICATDE }
+ { EBCDIC-AT-DE-A csEBCDICATDEA }
+ { EBCDIC-CA-FR csEBCDICCAFR }
+ { EBCDIC-DK-NO csEBCDICDKNO }
+ { EBCDIC-DK-NO-A csEBCDICDKNOA }
+ { EBCDIC-FI-SE csEBCDICFISE }
+ { EBCDIC-FI-SE-A csEBCDICFISEA }
+ { EBCDIC-FR csEBCDICFR }
+ { EBCDIC-IT csEBCDICIT }
+ { EBCDIC-PT csEBCDICPT }
+ { EBCDIC-ES csEBCDICES }
+ { EBCDIC-ES-A csEBCDICESA }
+ { EBCDIC-ES-S csEBCDICESS }
+ { EBCDIC-UK csEBCDICUK }
+ { EBCDIC-US csEBCDICUS }
+ { UNKNOWN-8BIT csUnknown8BiT }
+ { MNEMONIC csMnemonic }
+ { MNEM csMnem }
+ { VISCII csVISCII }
+ { VIQR csVIQR }
+ { KOI8-R csKOI8R }
+ { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
+ { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
+ { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
+ { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
+ { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
+ { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
+ { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
+ { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
+ { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
+ { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
+ { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
+ { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
+ { IBM1047 IBM-1047 }
+ { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
+ { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
+ { UNICODE-1-1 csUnicode11 }
+ { CESU-8 csCESU-8 }
+ { BOCU-1 csBOCU-1 }
+ { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
+ { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
+ l8 }
+ { ISO-8859-15 ISO_8859-15 Latin-9 }
+ { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
+ { GBK CP936 MS936 windows-936 }
+ { JIS_Encoding csJISEncoding }
+ { Shift_JIS MS_Kanji csShiftJIS }
+ { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
+ EUC-JP }
+ { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
+ { ISO-10646-UCS-Basic csUnicodeASCII }
+ { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
+ { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
+ { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
+ { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
+ { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
+ { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
+ { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
+ { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
+ { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
+ { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
+ { Adobe-Standard-Encoding csAdobeStandardEncoding }
+ { Ventura-US csVenturaUS }
+ { Ventura-International csVenturaInternational }
+ { PC8-Danish-Norwegian csPC8DanishNorwegian }
+ { PC8-Turkish csPC8Turkish }
+ { IBM-Symbols csIBMSymbols }
+ { IBM-Thai csIBMThai }
+ { HP-Legal csHPLegal }
+ { HP-Pi-font csHPPiFont }
+ { HP-Math8 csHPMath8 }
+ { Adobe-Symbol-Encoding csHPPSMath }
+ { HP-DeskTop csHPDesktop }
+ { Ventura-Math csVenturaMath }
+ { Microsoft-Publishing csMicrosoftPublishing }
+ { Windows-31J csWindows31J }
+ { GB2312 csGB2312 }
+ { Big5 csBig5 }
+}
+
+proc tcl_encoding {enc} {
+ global encoding_aliases
+ set names [encoding names]
+ set lcnames [string tolower $names]
+ set enc [string tolower $enc]
+ set i [lsearch -exact $lcnames $enc]
+ if {$i < 0} {
+ # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
+ if {[regsub {^iso[-_]} $enc iso encx]} {
+ set i [lsearch -exact $lcnames $encx]
+ }
+ }
+ if {$i < 0} {
+ foreach l $encoding_aliases {
+ set ll [string tolower $l]
+ if {[lsearch -exact $ll $enc] < 0} continue
+ # look through the aliases for one that tcl knows about
+ foreach e $ll {
+ set i [lsearch -exact $lcnames $e]
+ if {$i < 0} {
+ if {[regsub {^iso[-_]} $e iso ex]} {
+ set i [lsearch -exact $lcnames $ex]
+ }
+ }
+ if {$i >= 0} break
+ }
+ break
+ }
+ }
+ if {$i >= 0} {
+ return [lindex $names $i]
+ }
+ return {}
+}
if {[reponame] ne {}} {
append title " ([reponame])"
}
- return [tk_messageBox \
- -parent . \
+ set cmd [list tk_messageBox \
-icon question \
-type yesno \
-title $title \
-message $msg]
+ if {[winfo ismapped .]} {
+ lappend cmd -parent .
+ }
+ eval $cmd
}
proc hook_failed_popup {hook msg} {
# Copyright (C) 2006, 2007 Shawn Pearce
proc update_indexinfo {msg pathList after} {
- global update_index_cp ui_status_value
+ global update_index_cp
if {![lock_index update]} return
set batch [expr {int($totalCnt * .01) + 1}]
if {$batch > 25} {set batch 25}
- set ui_status_value [format \
+ ui_status [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
0.0]
- set fd [open "| git update-index -z --index-info" w]
+ set fd [git_write update-index -z --index-info]
fconfigure $fd \
-blocking 0 \
-buffering full \
}
proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
- global update_index_cp ui_status_value
+ global update_index_cp
global file_states current_diff_path
if {$update_index_cp >= $totalCnt} {
display_file $path $new
}
- set ui_status_value [format \
+ ui_status [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
}
proc update_index {msg pathList after} {
- global update_index_cp ui_status_value
+ global update_index_cp
if {![lock_index update]} return
set batch [expr {int($totalCnt * .01) + 1}]
if {$batch > 25} {set batch 25}
- set ui_status_value [format \
+ ui_status [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
0.0]
- set fd [open "| git update-index --add --remove -z --stdin" w]
+ set fd [git_write update-index --add --remove -z --stdin]
fconfigure $fd \
-blocking 0 \
-buffering full \
}
proc write_update_index {fd pathList totalCnt batch msg after} {
- global update_index_cp ui_status_value
+ global update_index_cp
global file_states current_diff_path
if {$update_index_cp >= $totalCnt} {
display_file $path $new
}
- set ui_status_value [format \
+ ui_status [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
}
proc checkout_index {msg pathList after} {
- global update_index_cp ui_status_value
+ global update_index_cp
if {![lock_index update]} return
set batch [expr {int($totalCnt * .01) + 1}]
if {$batch > 25} {set batch 25}
- set ui_status_value [format \
+ ui_status [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
0.0]
- set cmd [list git checkout-index]
- lappend cmd --index
- lappend cmd --quiet
- lappend cmd --force
- lappend cmd -z
- lappend cmd --stdin
- set fd [open "| $cmd " w]
+ set fd [git_write checkout-index \
+ --index \
+ --quiet \
+ --force \
+ -z \
+ --stdin \
+ ]
fconfigure $fd \
-blocking 0 \
-buffering full \
}
proc write_checkout_index {fd pathList totalCnt batch msg after} {
- global update_index_cp ui_status_value
+ global update_index_cp
global file_states current_diff_path
if {$update_index_cp >= $totalCnt} {
}
}
- set ui_status_value [format \
+ ui_status [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
update_indexinfo \
$txt \
$pathList \
- [concat $after {set ui_status_value {Ready.}}]
+ [concat $after [list ui_ready]]
}
}
update_index \
$txt \
$pathList \
- [concat $after {set ui_status_value {Ready to commit.}}]
+ [concat $after {ui_status {Ready to commit.}}]
}
}
"[appname] ([reponame])" \
"Revert changes in $s?
-Any unadded changes will be permanently lost by the revert." \
+Any unstaged changes will be permanently lost by the revert." \
question \
1 \
{Do Nothing} \
checkout_index \
$txt \
$pathList \
- [concat $after {set ui_status_value {Ready.}}]
+ [concat $after [list ui_ready]]
} else {
unlock_index
}
# git-gui branch merge support
# Copyright (C) 2006, 2007 Shawn Pearce
-namespace eval merge {
+class merge {
+
+field w ; # top level window
+field w_rev ; # mega-widget to pick the revision to merge
-proc _can_merge {} {
+method _can_merge {} {
global HEAD commit_type file_states
if {[string match amend* $commit_type]} {
The rescan will be automatically started now.
}
unlock_index
- rescan {set ui_status_value {Ready.}}
+ rescan ui_ready
return 0
}
File [short_path $path] has merge conflicts.
-You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge.
+You must resolve them, stage the file, and commit to complete the current merge. Only then can you begin another merge.
"
unlock_index
return 0
return 1
}
-proc _refs {w list} {
- set r {}
- foreach i [$w.source.l curselection] {
- lappend r [lindex [lindex $list $i] 0]
+method _rev {} {
+ if {[catch {$w_rev commit_or_die}]} {
+ return {}
}
- return $r
+ return [$w_rev get]
}
-proc _visualize {w list} {
- set revs [_refs $w $list]
- if {$revs eq {}} return
- lappend revs --not HEAD
- do_gitk $revs
+method _visualize {} {
+ set rev [_rev $this]
+ if {$rev ne {}} {
+ do_gitk [list $rev --not HEAD]
+ }
}
-proc _start {w list} {
- global HEAD ui_status_value current_branch
+method _start {} {
+ global HEAD current_branch remote_url
- set cmd [list git merge]
- set names [_refs $w $list]
- set revcnt [llength $names]
- append cmd { } $names
-
- if {$revcnt == 0} {
+ set name [_rev $this]
+ if {$name eq {}} {
return
- } elseif {$revcnt == 1} {
- set unit branch
- } elseif {$revcnt <= 15} {
- set unit branches
-
- if {[tk_dialog \
- $w.confirm_octopus \
- [wm title $w] \
- "Use octopus merge strategy?
-
-You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts.
-" \
- question \
- 0 \
- {Cancel} \
- {Use octopus} \
- ] != 1} return
- } else {
- tk_messageBox \
- -icon error \
- -type ok \
- -title [wm title $w] \
- -parent $w \
- -message "Too many branches selected.
+ }
-You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge.
+ set spec [$w_rev get_tracking_branch]
+ set cmit [$w_rev get_commit]
-Please select fewer branches. To merge more than 15 branches, merge the branches in batches.
-"
- return
+ set fh [open [gitdir FETCH_HEAD] w]
+ fconfigure $fh -translation lf
+ if {$spec eq {}} {
+ set remote .
+ set branch $name
+ set stitle $branch
+ } else {
+ set remote $remote_url([lindex $spec 1])
+ if {[regexp {^[^:@]*@[^:]*:/} $remote]} {
+ regsub {^[^:@]*@} $remote {} remote
+ }
+ set branch [lindex $spec 2]
+ set stitle "$branch of $remote"
}
-
- set msg "Merging $current_branch, [join $names {, }]"
- set ui_status_value "$msg..."
- set cons [console::new "Merge" $msg]
- console::exec $cons $cmd \
- [namespace code [list _finish $revcnt $cons]]
+ regsub ^refs/heads/ $branch {} branch
+ puts $fh "$cmit\t\tbranch '$branch' of $remote"
+ close $fh
+
+ set cmd [list git]
+ lappend cmd merge
+ lappend cmd --strategy=recursive
+ lappend cmd [git fmt-merge-msg <[gitdir FETCH_HEAD]]
+ lappend cmd HEAD
+ lappend cmd $cmit
+
+ set msg "Merging $current_branch and $stitle"
+ ui_status "$msg..."
+ set cons [console::new "Merge" "merge $stitle"]
+ console::exec $cons $cmd [cb _finish $cons]
wm protocol $w WM_DELETE_WINDOW {}
destroy $w
}
-proc _finish {revcnt w ok} {
- console::done $w $ok
+method _finish {cons ok} {
+ console::done $cons $ok
if {$ok} {
set msg {Merge completed successfully.}
} else {
- if {$revcnt != 1} {
- info_popup "Octopus merge failed.
-
-Your merge of $revcnt branches has failed.
-
-There are file-level conflicts between the branches which must be resolved manually.
-
-The working directory will now be reset.
-
-You can attempt this merge again by merging only one branch at a time." $w
-
- set fd [open "| git read-tree --reset -u HEAD" r]
- fconfigure $fd -blocking 0 -translation binary
- fileevent $fd readable \
- [namespace code [list _reset_wait $fd]]
- set ui_status_value {Aborting... please wait...}
- return
- }
-
set msg {Merge failed. Conflict resolution is required.}
}
unlock_index
- rescan [list set ui_status_value $msg]
+ rescan [list ui_status $msg]
+ delete_this
}
-proc dialog {} {
+constructor dialog {} {
global current_branch
global M1B
- if {![_can_merge]} return
-
- set fmt {list %(objectname) %(*objectname) %(refname) %(subject)}
- set cmd [list git for-each-ref --tcl --format=$fmt]
- lappend cmd refs/heads
- lappend cmd refs/remotes
- lappend cmd refs/tags
- set fr_fd [open "| $cmd" r]
- fconfigure $fr_fd -translation binary
- while {[gets $fr_fd line] > 0} {
- set line [eval $line]
- set ref [lindex $line 2]
- regsub ^refs/(heads|remotes|tags)/ $ref {} ref
- set subj($ref) [lindex $line 3]
- lappend sha1([lindex $line 0]) $ref
- if {[lindex $line 1] ne {}} {
- lappend sha1([lindex $line 1]) $ref
- }
- }
- close $fr_fd
-
- set to_show {}
- set fr_fd [open "| git rev-list --all --not HEAD"]
- while {[gets $fr_fd line] > 0} {
- if {[catch {set ref $sha1($line)}]} continue
- foreach n $ref {
- lappend to_show [list $n $line]
- }
+ if {![_can_merge $this]} {
+ delete_this
+ return
}
- close $fr_fd
- set to_show [lsort -unique $to_show]
- set w .merge_setup
- toplevel $w
- wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+ make_toplevel top w
+ wm title $top "[appname] ([reponame]): Merge"
+ if {$top ne {.}} {
+ wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+ }
- set _visualize [namespace code [list _visualize $w $to_show]]
- set _start [namespace code [list _start $w $to_show]]
+ set _start [cb _start]
label $w.header \
-text "Merge Into $current_branch" \
pack $w.header -side top -fill x
frame $w.buttons
- button $w.buttons.visualize -text Visualize -command $_visualize
+ button $w.buttons.visualize \
+ -text Visualize \
+ -command [cb _visualize]
pack $w.buttons.visualize -side left
- button $w.buttons.create -text Merge -command $_start
- pack $w.buttons.create -side right
+ button $w.buttons.merge \
+ -text Merge \
+ -command $_start
+ pack $w.buttons.merge -side right
button $w.buttons.cancel \
-text {Cancel} \
- -command "unlock_index;destroy $w"
+ -command [cb _cancel]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
- labelframe $w.source -text {Source Branches}
- listbox $w.source.l \
- -height 10 \
- -width 70 \
- -font font_diff \
- -selectmode extended \
- -yscrollcommand [list $w.source.sby set]
- scrollbar $w.source.sby -command [list $w.source.l yview]
- pack $w.source.sby -side right -fill y
- pack $w.source.l -side left -fill both -expand 1
- pack $w.source -fill both -expand 1 -pady 5 -padx 5
-
- foreach ref $to_show {
- set n [lindex $ref 0]
- if {[string length $n] > 20} {
- set n "[string range $n 0 16]..."
- }
- $w.source.l insert end [format {%s %-20s %s} \
- [string range [lindex $ref 1] 0 5] \
- $n \
- $subj([lindex $ref 0])]
- }
-
- bind $w.source.l <Key-K> [list event generate %W <Shift-Key-Up>]
- bind $w.source.l <Key-J> [list event generate %W <Shift-Key-Down>]
- bind $w.source.l <Key-k> [list event generate %W <Key-Up>]
- bind $w.source.l <Key-j> [list event generate %W <Key-Down>]
- bind $w.source.l <Key-h> [list event generate %W <Key-Left>]
- bind $w.source.l <Key-l> [list event generate %W <Key-Right>]
- bind $w.source.l <Key-v> $_visualize
+ set w_rev [::choose_rev::new_unmerged $w.rev {Revision To Merge}]
+ pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
bind $w <$M1B-Key-Return> $_start
- bind $w <Visibility> "grab $w; focus $w.source.l"
- bind $w <Key-Escape> "unlock_index;destroy $w"
- wm protocol $w WM_DELETE_WINDOW "unlock_index;destroy $w"
- wm title $w "[appname] ([reponame]): Merge"
+ bind $w <Key-Return> $_start
+ bind $w <Key-Escape> [cb _cancel]
+ wm protocol $w WM_DELETE_WINDOW [cb _cancel]
+
+ bind $w.buttons.merge <Visibility> [cb _visible]
tkwait window $w
}
+method _visible {} {
+ grab $w
+ if {[is_config_true gui.matchtrackingbranch]} {
+ $w_rev pick_tracking_branch
+ }
+ $w_rev focus_filter
+}
+
+method _cancel {} {
+ wm protocol $w WM_DELETE_WINDOW {}
+ unlock_index
+ destroy $w
+ delete_this
+}
+
+}
+
+namespace eval merge {
+
proc reset_hard {} {
global HEAD commit_type file_states
if {![lock_index abort]} return
if {[string match *merge* $commit_type]} {
- set op merge
+ set op_question "Abort merge?
+
+Aborting the current merge will cause *ALL* uncommitted changes to be lost.
+
+Continue with aborting the current merge?"
} else {
- set op commit
- }
+ set op_question "Reset changes?
- if {[ask_popup "Abort $op?
+Resetting the changes will cause *ALL* uncommitted changes to be lost.
-Aborting the current $op will cause *ALL* uncommitted changes to be lost.
+Continue with resetting the current changes?"
+ }
-Continue with aborting the current $op?"] eq {yes}} {
- set fd [open "| git read-tree --reset -u HEAD" r]
+ if {[ask_popup $op_question] eq {yes}} {
+ set fd [git_read --stderr read-tree --reset -u -v HEAD]
fconfigure $fd -blocking 0 -translation binary
fileevent $fd readable [namespace code [list _reset_wait $fd]]
- set ui_status_value {Aborting... please wait...}
+ $::main_status start {Aborting} {files reset}
} else {
unlock_index
}
proc _reset_wait {fd} {
global ui_comm
- read $fd
+ $::main_status update_meter [read $fd]
+
+ fconfigure $fd -blocking 1
if {[eof $fd]} {
- close $fd
+ set fail [catch {close $fd} err]
+ $::main_status stop
unlock_index
$ui_comm delete 0.0 end
catch {file delete [gitdir MERGE_MSG]}
catch {file delete [gitdir GITGUI_MSG]}
- rescan {set ui_status_value {Abort completed. Ready.}}
+ if {$fail} {
+ warn_popup "Abort failed.\n\n$err"
+ }
+ rescan {ui_status {Abort completed. Ready.}}
+ } else {
+ fconfigure $fd -blocking 0
}
}
}
proc do_about {} {
- global appvers copyright
+ global appvers copyright oguilib
global tcl_patchLevel tk_patchLevel
set w .about_dialog
append v ", Tk version $tk_patchLevel"
}
+ set d {}
+ append d "git wrapper: $::_git\n"
+ append d "git exec dir: [gitexec]\n"
+ append d "git-gui lib: $oguilib"
+
label $w.vers \
-text $v \
-padx 5 -pady 5 \
-relief solid
pack $w.vers -side top -fill x -padx 5 -pady 5
+ label $w.dirs \
+ -text $d \
+ -padx 5 -pady 5 \
+ -justify left \
+ -anchor w \
+ -borderwidth 1 \
+ -relief solid
+ pack $w.dirs -side top -fill x -padx 5 -pady 5
+
menu $w.ctxm -tearoff 0
$w.ctxm add command \
-label {Copy} \
{b merge.summary {Summarize Merge Commits}}
{i-1..5 merge.verbosity {Merge Verbosity}}
+ {b merge.diffstat {Show Diffstat After Merge}}
{b gui.trustmtime {Trust File Modification Timestamps}}
+ {b gui.pruneduringfetch {Prune Tracking Branches During Fetch}}
+ {b gui.matchtrackingbranch {Match Tracking Branches}}
{i-0..99 gui.diffcontext {Number of Diff Context Lines}}
{t gui.newbranchtemplate {New Branch Name Template}}
} {
# git-gui remote management
# Copyright (C) 2006, 2007 Shawn Pearce
+set some_heads_tracking 0; # assume not
+
proc is_tracking_branch {name} {
global tracking_branches
-
- if {![catch {set info $tracking_branches($name)}]} {
- return 1
- }
- foreach t [array names tracking_branches] {
- if {[string match {*/\*} $t] && [string match $t $name]} {
+ foreach spec $tracking_branches {
+ set t [lindex $spec 0]
+ if {$t eq $name || [string match $t $name]} {
return 1
}
}
proc all_tracking_branches {} {
global tracking_branches
- set all_trackings {}
- set cmd {}
- foreach name [array names tracking_branches] {
- if {[regsub {/\*$} $name {} name]} {
- lappend cmd $name
+ set all [list]
+ set pat [list]
+ set cmd [list]
+
+ foreach spec $tracking_branches {
+ set dst [lindex $spec 0]
+ if {[string range $dst end-1 end] eq {/*}} {
+ lappend pat $spec
+ lappend cmd [string range $dst 0 end-2]
} else {
- regsub ^refs/(heads|remotes)/ $name {} name
- lappend all_trackings $name
+ lappend all $spec
}
}
- if {$cmd ne {}} {
- set fd [open "| git for-each-ref --format=%(refname) $cmd" r]
- while {[gets $fd name] > 0} {
- regsub ^refs/(heads|remotes)/ $name {} name
- lappend all_trackings $name
+ if {$pat ne {}} {
+ set fd [eval git_read for-each-ref --format=%(refname) $cmd]
+ while {[gets $fd n] > 0} {
+ foreach spec $pat {
+ set dst [string range [lindex $spec 0] 0 end-2]
+ set len [string length $dst]
+ if {[string equal -length $len $dst $n]} {
+ set src [string range [lindex $spec 2] 0 end-2]
+ set spec [list \
+ $n \
+ [lindex $spec 1] \
+ $src[string range $n $len end] \
+ ]
+ lappend all $spec
+ }
+ }
}
close $fd
}
- return [lsort -unique $all_trackings]
+ return [lsort -index 0 -unique $all]
}
proc load_all_remotes {} {
global repo_config
- global all_remotes tracking_branches
+ global all_remotes tracking_branches some_heads_tracking
+ global remote_url
+ set some_heads_tracking 0
set all_remotes [list]
- array unset tracking_branches
+ set trck [list]
+ set rh_str refs/heads/
+ set rh_len [string length $rh_str]
set rm_dir [gitdir remotes]
if {[file isdirectory $rm_dir]} {
set all_remotes [glob \
catch {
set fd [open [file join $rm_dir $name] r]
while {[gets $fd line] >= 0} {
+ if {[regexp {^URL:[ ]*(.+)$} $line line url]} {
+ set remote_url($name) $url
+ continue
+ }
if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \
$line line src dst]} continue
- if {![regexp ^refs/ $dst]} {
- set dst "refs/heads/$dst"
+ if {[string index $src 0] eq {+}} {
+ set src [string range $src 1 end]
+ }
+ if {![string equal -length 5 refs/ $src]} {
+ set src $rh_str$src
}
- set tracking_branches($dst) [list $name $src]
+ if {![string equal -length 5 refs/ $dst]} {
+ set dst $rh_str$dst
+ }
+ if {[string equal -length $rh_len $rh_str $dst]} {
+ set some_heads_tracking 1
+ }
+ lappend trck [list $dst $name $src]
}
close $fd
}
foreach line [array names repo_config remote.*.url] {
if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue
lappend all_remotes $name
+ set remote_url($name) $repo_config(remote.$name.url)
if {[catch {set fl $repo_config(remote.$name.fetch)}]} {
set fl {}
}
foreach line $fl {
if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue
- if {![regexp ^refs/ $dst]} {
- set dst "refs/heads/$dst"
+ if {[string index $src 0] eq {+}} {
+ set src [string range $src 1 end]
+ }
+ if {![string equal -length 5 refs/ $src]} {
+ set src $rh_str$src
+ }
+ if {![string equal -length 5 refs/ $dst]} {
+ set dst $rh_str$dst
}
- set tracking_branches($dst) [list $name $src]
+ if {[string equal -length $rh_len $rh_str $dst]} {
+ set some_heads_tracking 1
+ }
+ lappend trck [list $dst $name $src]
}
}
+ set tracking_branches [lsort -index 0 -unique $trck]
set all_remotes [lsort -unique $all_remotes]
}
global all_remotes repo_config
set m .mbar.fetch
+ set prune_list [list]
foreach r $all_remotes {
set enable 0
if {![catch {set a $repo_config(remote.$r.url)}]} {
}
if {$enable} {
+ lappend prune_list $r
$m add command \
-label "Fetch from $r..." \
-command [list fetch_from $r]
}
}
+
+ if {$prune_list ne {}} {
+ $m add separator
+ }
+ foreach r $prune_list {
+ $m add command \
+ -label "Prune from $r..." \
+ -command [list prune_from $r]
+ }
}
proc populate_push_menu {} {
--- /dev/null
+# git-gui remote branch deleting support
+# Copyright (C) 2007 Shawn Pearce
+
+class remote_branch_delete {
+
+field w
+field head_m
+
+field urltype {url}
+field remote {}
+field url {}
+
+field checktype {head}
+field check_head {}
+
+field status {}
+field idle_id {}
+field full_list {}
+field head_list {}
+field active_ls {}
+field head_cache
+field full_cache
+field cached
+
+constructor dialog {} {
+ global all_remotes M1B
+
+ make_toplevel top w
+ wm title $top "[appname] ([reponame]): Delete Remote Branch"
+ if {$top ne {.}} {
+ wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+ }
+
+ label $w.header -text {Delete Remote Branch} -font font_uibold
+ pack $w.header -side top -fill x
+
+ frame $w.buttons
+ button $w.buttons.delete -text Delete \
+ -default active \
+ -command [cb _delete]
+ pack $w.buttons.delete -side right
+ button $w.buttons.cancel -text {Cancel} \
+ -command [list destroy $w]
+ pack $w.buttons.cancel -side right -padx 5
+ pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+ labelframe $w.dest -text {From Repository}
+ if {$all_remotes ne {}} {
+ radiobutton $w.dest.remote_r \
+ -text {Remote:} \
+ -value remote \
+ -variable @urltype
+ eval tk_optionMenu $w.dest.remote_m @remote $all_remotes
+ grid $w.dest.remote_r $w.dest.remote_m -sticky w
+ if {[lsearch -sorted -exact $all_remotes origin] != -1} {
+ set remote origin
+ } else {
+ set remote [lindex $all_remotes 0]
+ }
+ set urltype remote
+ trace add variable @remote write [cb _write_remote]
+ } else {
+ set urltype url
+ }
+ radiobutton $w.dest.url_r \
+ -text {Arbitrary URL:} \
+ -value url \
+ -variable @urltype
+ entry $w.dest.url_t \
+ -borderwidth 1 \
+ -relief sunken \
+ -width 50 \
+ -textvariable @url \
+ -validate key \
+ -validatecommand {
+ if {%d == 1 && [regexp {\s} %S]} {return 0}
+ return 1
+ }
+ trace add variable @url write [cb _write_url]
+ grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5}
+ grid columnconfigure $w.dest 1 -weight 1
+ pack $w.dest -anchor nw -fill x -pady 5 -padx 5
+
+ labelframe $w.heads -text {Branches}
+ listbox $w.heads.l \
+ -height 10 \
+ -width 70 \
+ -listvariable @head_list \
+ -selectmode extended \
+ -yscrollcommand [list $w.heads.sby set]
+ scrollbar $w.heads.sby -command [list $w.heads.l yview]
+
+ frame $w.heads.footer
+ label $w.heads.footer.status \
+ -textvariable @status \
+ -anchor w \
+ -justify left
+ button $w.heads.footer.rescan \
+ -text {Rescan} \
+ -command [cb _rescan]
+ pack $w.heads.footer.status -side left -fill x
+ pack $w.heads.footer.rescan -side right
+
+ pack $w.heads.footer -side bottom -fill x
+ pack $w.heads.sby -side right -fill y
+ pack $w.heads.l -side left -fill both -expand 1
+ pack $w.heads -fill both -expand 1 -pady 5 -padx 5
+
+ labelframe $w.validate -text {Delete Only If}
+ radiobutton $w.validate.head_r \
+ -text {Merged Into:} \
+ -value head \
+ -variable @checktype
+ set head_m [tk_optionMenu $w.validate.head_m @check_head {}]
+ trace add variable @head_list write [cb _write_head_list]
+ trace add variable @check_head write [cb _write_check_head]
+ grid $w.validate.head_r $w.validate.head_m -sticky w
+ radiobutton $w.validate.always_r \
+ -text {Always (Do not perform merge checks)} \
+ -value always \
+ -variable @checktype
+ grid $w.validate.always_r -columnspan 2 -sticky w
+ grid columnconfigure $w.validate 1 -weight 1
+ pack $w.validate -anchor nw -fill x -pady 5 -padx 5
+
+ trace add variable @urltype write [cb _write_urltype]
+ _rescan $this
+
+ bind $w <Key-F5> [cb _rescan]
+ bind $w <$M1B-Key-r> [cb _rescan]
+ bind $w <$M1B-Key-R> [cb _rescan]
+ bind $w <Key-Return> [cb _delete]
+ bind $w <Key-Escape> [list destroy $w]
+ return $w
+}
+
+method _delete {} {
+ switch $urltype {
+ remote {set uri $remote}
+ url {set uri $url}
+ }
+
+ set cache $urltype:$uri
+ set crev {}
+ if {$checktype eq {head}} {
+ if {$check_head eq {}} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "A branch is required for 'Merged Into'."
+ return
+ }
+ set crev $full_cache("$cache\nrefs/heads/$check_head")
+ }
+
+ set not_merged [list]
+ set need_fetch 0
+ set have_selection 0
+ set push_cmd [list git push]
+ lappend push_cmd -v
+ lappend push_cmd $uri
+
+ foreach i [$w.heads.l curselection] {
+ set ref [lindex $full_list $i]
+ if {$crev ne {}} {
+ set obj $full_cache("$cache\n$ref")
+ if {[catch {set m [git merge-base $obj $crev]}]} {
+ set need_fetch 1
+ set m {}
+ }
+ if {$obj ne $m} {
+ lappend not_merged [lindex $head_list $i]
+ continue
+ }
+ }
+
+ lappend push_cmd :$ref
+ set have_selection 1
+ }
+
+ if {$not_merged ne {}} {
+ set msg "The following branches are not completely merged into $check_head:
+
+ - [join $not_merged "\n - "]"
+
+ if {$need_fetch} {
+ append msg "
+
+One or more of the merge tests failed because you have not fetched the necessary commits. Try fetching from $uri first."
+ }
+
+ tk_messageBox \
+ -icon info \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message $msg
+ if {!$have_selection} return
+ }
+
+ if {!$have_selection} {
+ tk_messageBox \
+ -icon error \
+ -type ok \
+ -title [wm title $w] \
+ -parent $w \
+ -message "Please select one or more branches to delete."
+ return
+ }
+
+ if {[tk_messageBox \
+ -icon warning \
+ -type yesno \
+ -title [wm title $w] \
+ -parent $w \
+ -message {Recovering deleted branches is difficult.
+
+Delete the selected branches?}] ne yes} {
+ return
+ }
+
+ destroy $w
+
+ set cons [console::new \
+ "push $uri" \
+ "Deleting branches from $uri"]
+ console::exec $cons $push_cmd
+}
+
+method _rescan {{force 1}} {
+ switch $urltype {
+ remote {set uri $remote}
+ url {set uri $url}
+ }
+
+ if {$force} {
+ unset -nocomplain cached($urltype:$uri)
+ }
+
+ if {$idle_id ne {}} {
+ after cancel $idle_id
+ set idle_id {}
+ }
+
+ _load $this $urltype:$uri $uri
+}
+
+method _write_remote {args} { set urltype remote }
+method _write_url {args} { set urltype url }
+method _write_check_head {args} { set checktype head }
+
+method _write_head_list {args} {
+ $head_m delete 0 end
+ foreach abr $head_list {
+ $head_m insert end radiobutton \
+ -label $abr \
+ -value $abr \
+ -variable @check_head
+ }
+ if {[lsearch -exact -sorted $head_list $check_head] < 0} {
+ set check_head {}
+ }
+}
+
+method _write_urltype {args} {
+ if {$urltype eq {url}} {
+ if {$idle_id ne {}} {
+ after cancel $idle_id
+ }
+ _load $this none: {}
+ set idle_id [after 1000 [cb _rescan 0]]
+ } else {
+ _rescan $this 0
+ }
+}
+
+method _load {cache uri} {
+ if {$active_ls ne {}} {
+ catch {close $active_ls}
+ }
+
+ if {$uri eq {}} {
+ $w.heads.l conf -state disabled
+ set head_list [list]
+ set full_list [list]
+ set status {No repository selected.}
+ return
+ }
+
+ if {[catch {set x $cached($cache)}]} {
+ set status "Scanning $uri..."
+ $w.heads.l conf -state disabled
+ set head_list [list]
+ set full_list [list]
+ set head_cache($cache) [list]
+ set full_cache($cache) [list]
+ set active_ls [git_read ls-remote $uri]
+ fconfigure $active_ls \
+ -blocking 0 \
+ -translation lf \
+ -encoding utf-8
+ fileevent $active_ls readable [cb _read $cache $active_ls]
+ } else {
+ set status {}
+ set full_list $full_cache($cache)
+ set head_list $head_cache($cache)
+ $w.heads.l conf -state normal
+ }
+}
+
+method _read {cache fd} {
+ if {$fd ne $active_ls} {
+ catch {close $fd}
+ return
+ }
+
+ while {[gets $fd line] >= 0} {
+ if {[string match {*^{}} $line]} continue
+ if {[regexp {^([0-9a-f]{40}) (.*)$} $line _junk obj ref]} {
+ if {[regsub ^refs/heads/ $ref {} abr]} {
+ lappend head_list $abr
+ lappend head_cache($cache) $abr
+ lappend full_list $ref
+ lappend full_cache($cache) $ref
+ set full_cache("$cache\n$ref") $obj
+ }
+ }
+ }
+
+ if {[eof $fd]} {
+ if {[catch {close $fd} err]} {
+ set status $err
+ set head_list [list]
+ set full_list [list]
+ } else {
+ set status {}
+ set cached($cache) 1
+ $w.heads.l conf -state normal
+ }
+ }
+} ifdeleted {
+ catch {close $fd}
+}
+
+}
set fn ${fn}.bat
}
if {[catch {
+ set ge [file normalize [file dirname $::_git]]
set fd [open $fn w]
puts $fd "@ECHO Entering [reponame]"
puts $fd "@ECHO Starting git-gui... please wait..."
- puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%"
+ puts $fd "@SET PATH=$ge;%PATH%"
puts $fd "@SET GIT_DIR=[file normalize [gitdir]]"
puts -nonewline $fd "@\"[info nameofexecutable]\""
puts $fd " \"[file normalize $argv0]\""
set sh [exec cygpath \
--windows \
--absolute \
- /bin/sh]
+ /bin/sh.exe]
set me [exec cygpath \
--unix \
--absolute \
--unix \
--absolute \
[gitdir]]
- set gw [exec cygpath \
- --windows \
- --absolute \
- [file dirname [gitdir]]]
- regsub -all ' $me "'\\''" me
- regsub -all ' $gd "'\\''" gd
- puts $fd "@ECHO Entering $gw"
+ puts $fd "@ECHO Entering [reponame]"
puts $fd "@ECHO Starting git-gui... please wait..."
puts -nonewline $fd "@\"$sh\" --login -c \""
- puts -nonewline $fd "GIT_DIR='$gd'"
- puts -nonewline $fd " '$me'"
- puts $fd "&\""
+ puts -nonewline $fd "GIT_DIR=[sq $gd]"
+ puts -nonewline $fd " [sq $me]"
+ puts $fd " &\""
close $fd
} err]} {
error_popup "Cannot write script:\n\n$err"
-initialdir [file join $env(HOME) Desktop] \
-initialfile "Git [reponame].app"]
if {$fn != {}} {
+ if {[file extension $fn] ne {.app}} {
+ set fn ${fn}.app
+ }
if {[catch {
set Contents [file join $fn Contents]
set MacOS [file join $Contents MacOS]
close $fd
set fd [open $exe w]
- set gd [file normalize [gitdir]]
- set ep [file normalize [gitexec]]
- regsub -all ' $gd "'\\''" gd
- regsub -all ' $ep "'\\''" ep
puts $fd "#!/bin/sh"
- foreach name [array names env] {
- if {[string match GIT_* $name]} {
- regsub -all ' $env($name) "'\\''" v
- puts $fd "export $name='$v'"
+ foreach name [lsort [array names env]] {
+ set value $env($name)
+ switch -- $name {
+ GIT_DIR { set value [file normalize [gitdir]] }
+ }
+
+ switch -glob -- $name {
+ SSH_* -
+ GIT_* {
+ puts $fd "if test \"z\$$name\" = z; then"
+ puts $fd " export $name=[sq $value]"
+ puts $fd "fi &&"
+ }
}
}
- puts $fd "export PATH='$ep':\$PATH"
- puts $fd "export GIT_DIR='$gd'"
- puts $fd "exec [file normalize $argv0]"
+ puts $fd "export PATH=[sq [file dirname $::_git]]:\$PATH &&"
+ puts $fd "cd [sq [file normalize [pwd]]] &&"
+ puts $fd "exec \\"
+ puts $fd " [sq [info nameofexecutable]] \\"
+ puts $fd " [sq [file normalize $argv0]]"
close $fd
file attributes $exe -permissions u+x,g+x,o+x
--- /dev/null
+# git-gui status bar mega-widget
+# Copyright (C) 2007 Shawn Pearce
+
+class status_bar {
+
+field w ; # our own window path
+field w_l ; # text widget we draw messages into
+field w_c ; # canvas we draw a progress bar into
+field status {}; # single line of text we show
+field prefix {}; # text we format into status
+field units {}; # unit of progress
+field meter {}; # current core git progress meter (if active)
+
+constructor new {path} {
+ set w $path
+ set w_l $w.l
+ set w_c $w.c
+
+ frame $w \
+ -borderwidth 1 \
+ -relief sunken
+ label $w_l \
+ -textvariable @status \
+ -anchor w \
+ -justify left
+ pack $w_l -side left
+
+ bind $w <Destroy> [cb _delete %W]
+ return $this
+}
+
+method start {msg uds} {
+ if {[winfo exists $w_c]} {
+ $w_c coords bar 0 0 0 20
+ } else {
+ canvas $w_c \
+ -width 100 \
+ -height [expr {int([winfo reqheight $w_l] * 0.6)}] \
+ -borderwidth 1 \
+ -relief groove \
+ -highlightt 0
+ $w_c create rectangle 0 0 0 20 -tags bar -fill navy
+ pack $w_c -side right
+ }
+
+ set status $msg
+ set prefix $msg
+ set units $uds
+ set meter {}
+}
+
+method update {have total} {
+ set pdone 0
+ if {$total > 0} {
+ set pdone [expr {100 * $have / $total}]
+ }
+
+ set status [format "%s ... %i of %i %s (%2i%%)" \
+ $prefix $have $total $units $pdone]
+ $w_c coords bar 0 0 $pdone 20
+}
+
+method update_meter {buf} {
+ append meter $buf
+ set r [string last "\r" $meter]
+ if {$r == -1} {
+ return
+ }
+
+ set prior [string range $meter 0 $r]
+ set meter [string range $meter [expr {$r + 1}] end]
+ if {[regexp "\\((\\d+)/(\\d+)\\)\\s+done\r\$" $prior _j a b]} {
+ update $this $a $b
+ }
+}
+
+method stop {{msg {}}} {
+ destroy $w_c
+ if {$msg ne {}} {
+ set status $msg
+ }
+}
+
+method show {msg {test {}}} {
+ if {$test eq {} || $status eq $test} {
+ set status $msg
+ }
+}
+
+method _delete {current} {
+ if {$current eq $w} {
+ delete_this
+ }
+}
+
+}
set w [console::new \
"fetch $remote" \
"Fetching new changes from $remote"]
- set cmd [list git fetch]
- lappend cmd $remote
- console::exec $w $cmd
+ set cmds [list]
+ lappend cmds [list exec git fetch $remote]
+ if {[is_config_true gui.pruneduringfetch]} {
+ lappend cmds [list exec git remote prune $remote]
+ }
+ console::chain $w $cmds
+}
+
+proc prune_from {remote} {
+ set w [console::new \
+ "remote prune $remote" \
+ "Pruning tracking branches deleted from $remote"]
+ console::exec $w [list git remote prune $remote]
}
proc push_to {remote} {
[list radio_selector push_urltype remote]
proc do_push_anywhere {} {
- global all_heads all_remotes current_branch
+ global all_remotes current_branch
global push_urltype push_remote push_url push_thin push_tags
set w .push_setup
-width 70 \
-selectmode extended \
-yscrollcommand [list $w.source.sby set]
- foreach h $all_heads {
+ foreach h [load_all_heads] {
$w.source.l insert end $h
if {$h eq $current_branch} {
$w.source.l select set end
do
case "$dangling" in
dangling)
- if git-rev-parse --verify "$sha1^0" >/dev/null 2>/dev/null
+ if git rev-parse --verify "$sha1^0" >/dev/null 2>/dev/null
then
dir="$laf/commit"
- git-show-branch "$sha1"
+ git show-branch "$sha1"
else
dir="$laf/other"
fi
curl_extra_args="-k"
fi
if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
- "`git-config --bool http.noEPSV`" = true ]; then
+ "`git config --bool http.noEPSV`" = true ]; then
curl_extra_args="${curl_extra_args} --disable-epsv"
fi
curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
(cd $tmpdir && find refs -type f) |
while read path
do
- cat "$tmpdir/$path" | tr -d '\012'
+ tr -d '\012' <"$tmpdir/$path"
echo " $path"
done &&
rm -fr $tmpdir
# MRT is the current "merge result tree"
MRC=$head MSG= PARENT="-p $head"
-MRT=$(git-write-tree)
+MRT=$(git write-tree)
CNT=1 ;# counting our head
NON_FF_MERGE=0
OCTOPUS_FAILURE=0
exit 2
esac
- common=$(git-merge-base --all $MRC $SHA1) ||
+ common=$(git merge-base --all $MRC $SHA1) ||
die "Unable to find common commit with $SHA1"
case "$LF$common$LF" in
# We still need to count this as part of the parent set.
echo "Fast forwarding to: $SHA1"
- git-read-tree -u -m $head $SHA1 || exit
- MRC=$SHA1 MRT=$(git-write-tree)
+ git read-tree -u -m $head $SHA1 || exit
+ MRC=$SHA1 MRT=$(git write-tree)
continue
fi
NON_FF_MERGE=1
echo "Trying simple merge with $SHA1"
- git-read-tree -u -m --aggressive $common $MRT $SHA1 || exit 2
- next=$(git-write-tree 2>/dev/null)
+ git read-tree -u -m --aggressive $common $MRT $SHA1 || exit 2
+ next=$(git write-tree 2>/dev/null)
if test $? -ne 0
then
echo "Simple merge did not work, trying automatic merge."
git-merge-index -o git-merge-one-file -a ||
OCTOPUS_FAILURE=1
- next=$(git-write-tree 2>/dev/null)
+ next=$(git write-tree 2>/dev/null)
fi
# We have merged the other branch successfully. Ideally
# $7 - file in branch2 mode (or empty)
#
# Handle some trivial cases.. The _really_ trivial cases have
-# been handled already by git-read-tree, but that one doesn't
+# been handled already by git read-tree, but that one doesn't
# do any merges that might change the tree layout.
case "${1:-.}${2:-.}${3:-.}" in
# read-tree checked that index matches HEAD already,
# so we know we do not have this path tracked.
# there may be an unrelated working tree file here,
- # which we should just leave unmolested.
- exit 0
+ # which we should just leave unmolested. Make sure
+ # we do not have it in the index, though.
+ exec git update-index --remove -- "$4"
fi
if test -f "$4"; then
rm -f -- "$4" &&
rmdir -p "$(expr "z$4" : 'z\(.*\)/')" 2>/dev/null || :
fi &&
- exec git-update-index --remove -- "$4"
+ exec git update-index --remove -- "$4"
;;
#
#
".$2.")
# the other side did not add and we added so there is nothing
- # to be done.
+ # to be done, except making the path merged.
+ exec git update-index --add --cacheinfo "$6" "$2" "$4"
;;
"..$3")
echo "Adding $4"
echo "ERROR: untracked $4 is overwritten by the merge."
exit 1
}
- git-update-index --add --cacheinfo "$6$7" "$2$3" "$4" &&
- exec git-checkout-index -u -f -- "$4"
+ git update-index --add --cacheinfo "$7" "$3" "$4" &&
+ exec git checkout-index -u -f -- "$4"
;;
#
exit 1
fi
echo "Adding $4"
- git-update-index --add --cacheinfo "$6" "$2" "$4" &&
- exec git-checkout-index -u -f -- "$4"
+ git update-index --add --cacheinfo "$6" "$2" "$4" &&
+ exec git checkout-index -u -f -- "$4"
;;
#
case "$1" in
'')
echo "Added $4 in both, but differently."
- # This extracts OUR file in $orig, and uses git-apply to
+ # This extracts OUR file in $orig, and uses git apply to
# remove lines that are unique to ours.
orig=`git-unpack-file $2`
sz0=`wc -c <"$orig"`
- diff -u -La/$orig -Lb/$orig $orig $src2 | git-apply --no-add
+ diff -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add
sz1=`wc -c <"$orig"`
# If we do not have enough common material, it is not
# Be careful for funny filename such as "-L" in "$4", which
# would confuse "merge" greatly.
src1=`git-unpack-file $2`
- git-merge-file "$src1" "$orig" "$src2"
+ git merge-file "$src1" "$orig" "$src2"
ret=$?
# Create the working tree file, using "our tree" version from the
# index, and then store the result of the merge.
- git-checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4"
+ git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4"
rm -f -- "$orig" "$src1" "$src2"
if [ "$6" != "$7" ]; then
echo "ERROR: Merge conflict in $4"
exit 1
fi
- exec git-update-index -- "$4"
+ exec git update-index -- "$4"
;;
*)
# because the current index is what we will be committing as the
# merge result.
-git-diff-index --quiet --cached HEAD || exit 2
+git diff-index --quiet --cached HEAD || exit 2
exit 0
exit 2
fi
-git-update-index --refresh 2>/dev/null
-git-read-tree -u -m --aggressive $bases $head $remotes || exit 2
+git update-index --refresh 2>/dev/null
+git read-tree -u -m --aggressive $bases $head $remotes || exit 2
echo "Trying simple merge."
-if result_tree=$(git-write-tree 2>/dev/null)
+if result_tree=$(git write-tree 2>/dev/null)
then
exit 0
else
for c in $bases
do
rm -f $G
- GIT_INDEX_FILE=$G git-read-tree -m $c $head $remotes \
+ GIT_INDEX_FILE=$G git read-tree -m $c $head $remotes \
2>/dev/null || continue
# Count the paths that are unmerged.
- cnt=`GIT_INDEX_FILE=$G git-ls-files --unmerged | wc -l`
+ cnt=`GIT_INDEX_FILE=$G git ls-files --unmerged | wc -l`
if test $best_cnt -le 0 -o $cnt -le $best_cnt
then
best=$c
;;
esac
-git-update-index --refresh 2>/dev/null
-git-read-tree -u -m $common $head $remotes || exit 2
+git update-index --refresh 2>/dev/null
+git read-tree -u -m $common $head $remotes || exit 2
echo "Trying simple merge."
-if result_tree=$(git-write-tree 2>/dev/null)
+if result_tree=$(git write-tree 2>/dev/null)
then
exit 0
else
# Copyright (c) 2005 Junio C Hamano
#
-USAGE='[-n] [--no-commit] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+'
+USAGE='[-n] [--summary] [--no-commit] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+'
SUBDIRECTORY_OK=Yes
. git-sh-setup
savestate() {
# Stash away any local modifications.
- git-diff-index -z --name-only $head |
+ git diff-index -z --name-only $head |
cpio -0 -o >"$GIT_DIR/MERGE_SAVE"
}
then
git reset --hard $head >/dev/null
cpio -iuv <"$GIT_DIR/MERGE_SAVE"
- git-update-index --refresh >/dev/null
+ git update-index --refresh >/dev/null
fi
}
squash_message () {
echo Squashed commit of the following:
echo
- git-log --no-merges ^"$head" $remote
+ git log --no-merges ^"$head" $remote
}
finish () {
echo "No merge message -- not updating HEAD"
;;
*)
- git-update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
+ git update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
;;
esac
;;
'')
;;
?*)
- case "$no_summary" in
- '')
- git-diff-tree --stat --summary -M "$head" "$1"
- ;;
- esac
+ if test "$show_diffstat" = t
+ then
+ # We want color (if set), but no pager
+ GIT_PAGER='' git diff --stat --summary -M "$head" "$1"
+ fi
;;
esac
}
merge_name () {
remote="$1"
- rh=$(git-rev-parse --verify "$remote^0" 2>/dev/null) || return
- bh=$(git-show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
+ rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
+ bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
if test "$rh" = "$bh"
then
echo "$rh branch '$remote' of ."
elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
- git-show-ref -q --verify "refs/heads/$truname" 2>/dev/null
+ git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
then
echo "$rh branch '$truname' (early part) of ."
elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
case "$1" in
-n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
--no-summa|--no-summar|--no-summary)
- no_summary=t ;;
+ show_diffstat=false ;;
+ --summary)
+ show_diffstat=t ;;
--sq|--squ|--squa|--squas|--squash)
squash=t no_commit=t ;;
--no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
shift
done
+if test -z "$show_diffstat"; then
+ test "$(git config --bool merge.diffstat)" = false && show_diffstat=false
+ test -z "$show_diffstat" && show_diffstat=t
+fi
+
# This could be traditional "merge <msg> HEAD <commit>..." and the
# way we can tell it is to see if the second token is HEAD, but some
# people might have misused the interface and used a committish that
# have "-m" so it is an additional safety measure to check for it.
if test -z "$have_message" &&
- second_token=$(git-rev-parse --verify "$2^0" 2>/dev/null) &&
- head_commit=$(git-rev-parse --verify "HEAD" 2>/dev/null) &&
+ second_token=$(git rev-parse --verify "$2^0" 2>/dev/null) &&
+ head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null) &&
test "$second_token" = "$head_commit"
then
merge_msg="$1"
shift
head_arg="$1"
shift
-elif ! git-rev-parse --verify HEAD >/dev/null 2>&1
+elif ! git rev-parse --verify HEAD >/dev/null 2>&1
then
# If the merged head is a valid one there is no reason to
# forbid "git merge" into a branch yet to be born. We do
rh=$(git rev-parse --verify "$1^0") ||
die "$1 - not something we can merge"
- git-update-ref -m "initial pull" HEAD "$rh" "" &&
- git-read-tree --reset -u HEAD
+ git update-ref -m "initial pull" HEAD "$rh" "" &&
+ git read-tree --reset -u HEAD
exit
else
merge_name=$(for remote
do
merge_name "$remote"
- done | git-fmt-merge-msg
+ done | git fmt-merge-msg
)
merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
fi
-head=$(git-rev-parse --verify "$head_arg"^0) || usage
+head=$(git rev-parse --verify "$head_arg"^0) || usage
# All the rest are remote heads
test "$#" = 0 && usage ;# we need at least one remote head.
remoteheads=
for remote
do
- remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) ||
+ remotehead=$(git rev-parse --verify "$remote"^0 2>/dev/null) ||
die "$remote - not something we can merge"
remoteheads="${remoteheads}$remotehead "
eval GITHEAD_$remotehead='"$remote"'
'')
case "$#" in
1)
- var="`git-config --get pull.twohead`"
+ var="`git config --get pull.twohead`"
if test -n "$var"
then
use_strategies="$var"
use_strategies="$default_twohead_strategies"
fi ;;
*)
- var="`git-config --get pull.octopus`"
+ var="`git config --get pull.octopus`"
if test -n "$var"
then
use_strategies="$var"
case "$#" in
1)
- common=$(git-merge-base --all $head "$@")
+ common=$(git merge-base --all $head "$@")
;;
*)
- common=$(git-show-branch --merge-base $head "$@")
+ common=$(git show-branch --merge-base $head "$@")
;;
esac
echo "$head" >"$GIT_DIR/ORIG_HEAD"
;;
?,1,"$head",*)
# Again the most common case of merging one remote.
- echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)"
- git-update-index --refresh 2>/dev/null
+ echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)"
+ git update-index --refresh 2>/dev/null
msg="Fast forward"
if test -n "$have_message"
then
msg="$msg (no commit created; -m option ignored)"
fi
- new_head=$(git-rev-parse --verify "$1^0") &&
- git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
+ new_head=$(git rev-parse --verify "$1^0") &&
+ git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
finish "$new_head" "$msg" || exit
dropsave
exit 0
?,1,*,)
# We are not doing octopus, not fast forward, and have only
# one common.
- git-update-index --refresh 2>/dev/null
+ git update-index --refresh 2>/dev/null
case " $use_strategies " in
*' recursive '*|*' recur '*)
: run merge later
# See if it is really trivial.
git var GIT_COMMITTER_IDENT >/dev/null || exit
echo "Trying really trivial in-index merge..."
- if git-read-tree --trivial -m -u -v $common $head "$1" &&
- result_tree=$(git-write-tree)
+ if git read-tree --trivial -m -u -v $common $head "$1" &&
+ result_tree=$(git write-tree)
then
echo "Wonderful."
result_commit=$(
printf '%s\n' "$merge_msg" |
- git-commit-tree $result_tree -p HEAD -p "$1"
+ git commit-tree $result_tree -p HEAD -p "$1"
) || exit
finish "$result_commit" "In-index merge"
dropsave
up_to_date=t
for remote
do
- common_one=$(git-merge-base --all $head $remote)
+ common_one=$(git merge-base --all $head $remote)
if test "$common_one" != "$remote"
then
up_to_date=f
if test "$exit" -eq 1
then
cnt=`{
- git-diff-files --name-only
- git-ls-files --unmerged
+ git diff-files --name-only
+ git ls-files --unmerged
} | wc -l`
if test $best_cnt -le 0 -o $cnt -le $best_cnt
then
}
# Automerge succeeded.
- result_tree=$(git-write-tree) && break
+ result_tree=$(git write-tree) && break
done
# If we have a resulting tree, that means the strategy module
# auto resolved the merge cleanly.
if test '' != "$result_tree"
then
- parents=$(git-show-branch --independent "$head" "$@" | sed -e 's/^/-p /')
- result_commit=$(printf '%s\n' "$merge_msg" | git-commit-tree $result_tree $parents) || exit
+ parents=$(git show-branch --independent "$head" "$@" | sed -e 's/^/-p /')
+ result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
finish "$result_commit" "Merge made by $wt_strategy."
dropsave
exit 0
sed -e 's/^[^ ]* / /' |
uniq
} >>"$GIT_DIR/MERGE_MSG"
- if test -d "$GIT_DIR/rr-cache"
- then
- git-rerere
- fi
+ git rerere
die "Automatic merge failed; fix conflicts and then commit the result."
fi
# Copyright (c) 2006 Theodore Y. Ts'o
#
# This file is licensed under the GPL v2, or a later version
-# at the discretion of Junio C Hammano.
+# at the discretion of Junio C Hamano.
#
USAGE='[--tool=tool] [file to merge] ...'
read ans
case "$ans" in
[lL]*)
- git-checkout-index -f --stage=2 -- "$path"
- git-add -- "$path"
+ git checkout-index -f --stage=2 -- "$path"
+ git add -- "$path"
cleanup_temp_files --save-backup
return
;;
[rR]*)
- git-checkout-index -f --stage=3 -- "$path"
- git-add -- "$path"
+ git checkout-index -f --stage=3 -- "$path"
+ git add -- "$path"
cleanup_temp_files --save-backup
return
;;
read ans
case "$ans" in
[mMcC]*)
- git-add -- "$path"
+ git add -- "$path"
cleanup_temp_files --save-backup
return
;;
[dD]*)
- git-rm -- "$path" > /dev/null
+ git rm -- "$path" > /dev/null
cleanup_temp_files
return
;;
merge_file () {
path="$1"
- f=`git-ls-files -u -- "$path"`
+ f=`git ls-files -u -- "$path"`
if test -z "$f" ; then
if test ! -f "$path" ; then
echo "$path: file not found"
check_unchanged
save_backup
;;
+ gvimdiff)
+ touch "$BACKUP"
+ gvimdiff -f -- "$LOCAL" "$path" "$REMOTE"
+ check_unchanged
+ save_backup
+ ;;
xxdiff)
touch "$BACKUP"
if base_present ; then
done
if test -z "$merge_tool"; then
- merge_tool=`git-config merge.tool`
+ merge_tool=`git config merge.tool`
case "$merge_tool" in
- kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | "")
+ kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | "")
;; # happy
*)
echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
fi
if test -z "$merge_tool" ; then
- if type kdiff3 >/dev/null 2>&1 && test -n "$DISPLAY"; then
- merge_tool="kdiff3";
- elif type tkdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then
- merge_tool=tkdiff
- elif type xxdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then
- merge_tool=xxdiff
- elif type meld >/dev/null 2>&1 && test -n "$DISPLAY"; then
- merge_tool=meld
- elif type opendiff >/dev/null 2>&1; then
- merge_tool=opendiff
- elif type emacs >/dev/null 2>&1; then
- merge_tool=emerge
- elif type vimdiff >/dev/null 2>&1; then
- merge_tool=vimdiff
- else
+ if test -n "$DISPLAY"; then
+ merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
+ if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
+ merge_tool_candidates="meld $merge_tool_candidates"
+ fi
+ if test "$KDE_FULL_SESSION" = "true"; then
+ merge_tool_candidates="kdiff3 $merge_tool_candidates"
+ fi
+ fi
+ if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
+ merge_tool_candidates="$merge_tool_candidates emerge"
+ fi
+ if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
+ merge_tool_candidates="$merge_tool_candidates vimdiff"
+ fi
+ merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
+ echo "merge tool candidates: $merge_tool_candidates"
+ for i in $merge_tool_candidates; do
+ if test $i = emerge ; then
+ cmd=emacs
+ else
+ cmd=$i
+ fi
+ if type $cmd > /dev/null 2>&1; then
+ merge_tool=$i
+ break
+ fi
+ done
+ if test -z "$merge_tool" ; then
echo "No available merge resolution programs available."
exit 1
fi
fi
case "$merge_tool" in
- kdiff3|tkdiff|meld|xxdiff|vimdiff|opendiff)
+ kdiff3|tkdiff|meld|xxdiff|vimdiff|gvimdiff|opendiff)
if ! type "$merge_tool" > /dev/null 2>&1; then
echo "The merge tool $merge_tool is not available"
exit 1
+++ /dev/null
-#!/usr/bin/python
-#
-# This tool is copyright (c) 2006, Sean Estabrooks.
-# It is released under the Gnu Public License, version 2.
-#
-# Import Perforce branches into Git repositories.
-# Checking out the files is done by calling the standard p4
-# client which you must have properly configured yourself
-#
-
-import marshal
-import os
-import sys
-import time
-import getopt
-
-from signal import signal, \
- SIGPIPE, SIGINT, SIG_DFL, \
- default_int_handler
-
-signal(SIGPIPE, SIG_DFL)
-s = signal(SIGINT, SIG_DFL)
-if s != default_int_handler:
- signal(SIGINT, s)
-
-def die(msg, *args):
- for a in args:
- msg = "%s %s" % (msg, a)
- print "git-p4import fatal error:", msg
- sys.exit(1)
-
-def usage():
- print "USAGE: git-p4import [-q|-v] [--authors=<file>] [-t <timezone>] [//p4repo/path <branch>]"
- sys.exit(1)
-
-verbosity = 1
-logfile = "/dev/null"
-ignore_warnings = False
-stitch = 0
-tagall = True
-
-def report(level, msg, *args):
- global verbosity
- global logfile
- for a in args:
- msg = "%s %s" % (msg, a)
- fd = open(logfile, "a")
- fd.writelines(msg)
- fd.close()
- if level <= verbosity:
- print msg
-
-class p4_command:
- def __init__(self, _repopath):
- try:
- global logfile
- self.userlist = {}
- if _repopath[-1] == '/':
- self.repopath = _repopath[:-1]
- else:
- self.repopath = _repopath
- if self.repopath[-4:] != "/...":
- self.repopath= "%s/..." % self.repopath
- f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
- a = f.readlines()
- if f.close():
- raise
- except:
- die("Could not find the \"p4\" command")
-
- def p4(self, cmd, *args):
- global logfile
- cmd = "%s %s" % (cmd, ' '.join(args))
- report(2, "P4:", cmd)
- f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
- list = []
- while 1:
- try:
- list.append(marshal.load(f))
- except EOFError:
- break
- self.ret = f.close()
- return list
-
- def sync(self, id, force=False, trick=False, test=False):
- if force:
- ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
- elif trick:
- ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
- elif test:
- ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
- else:
- ret = self.p4("sync %s@%s"%(self.repopath, id))[0]
- if ret['code'] == "error":
- data = ret['data'].upper()
- if data.find('VIEW') > 0:
- die("Perforce reports %s is not in client view"% self.repopath)
- elif data.find('UP-TO-DATE') < 0:
- die("Could not sync files from perforce", self.repopath)
-
- def changes(self, since=0):
- try:
- list = []
- for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
- list.append(rec['change'])
- list.reverse()
- return list
- except:
- return []
-
- def authors(self, filename):
- f=open(filename)
- for l in f.readlines():
- self.userlist[l[:l.find('=')].rstrip()] = \
- (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
- f.close()
- for f,e in self.userlist.items():
- report(2, f, ":", e[0], " <", e[1], ">")
-
- def _get_user(self, id):
- if not self.userlist.has_key(id):
- try:
- user = self.p4("users", id)[0]
- self.userlist[id] = (user['FullName'], user['Email'])
- except:
- self.userlist[id] = (id, "")
- return self.userlist[id]
-
- def _format_date(self, ticks):
- symbol='+'
- name = time.tzname[0]
- offset = time.timezone
- if ticks[8]:
- name = time.tzname[1]
- offset = time.altzone
- if offset < 0:
- offset *= -1
- symbol = '-'
- localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
- tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
- return "%s %s" % (tickso, localo)
-
- def where(self):
- try:
- return self.p4("where %s" % self.repopath)[-1]['path']
- except:
- return ""
-
- def describe(self, num):
- desc = self.p4("describe -s", num)[0]
- self.msg = desc['desc']
- self.author, self.email = self._get_user(desc['user'])
- self.date = self._format_date(time.localtime(long(desc['time'])))
- return self
-
-class git_command:
- def __init__(self):
- try:
- self.version = self.git("--version")[0][12:].rstrip()
- except:
- die("Could not find the \"git\" command")
- try:
- self.gitdir = self.get_single("rev-parse --git-dir")
- report(2, "gdir:", self.gitdir)
- except:
- die("Not a git repository... did you forget to \"git init\" ?")
- try:
- self.cdup = self.get_single("rev-parse --show-cdup")
- if self.cdup != "":
- os.chdir(self.cdup)
- self.topdir = os.getcwd()
- report(2, "topdir:", self.topdir)
- except:
- die("Could not find top git directory")
-
- def git(self, cmd):
- global logfile
- report(2, "GIT:", cmd)
- f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
- r=f.readlines()
- self.ret = f.close()
- return r
-
- def get_single(self, cmd):
- return self.git(cmd)[0].rstrip()
-
- def current_branch(self):
- try:
- testit = self.git("rev-parse --verify HEAD")[0]
- return self.git("symbolic-ref HEAD")[0][11:].rstrip()
- except:
- return None
-
- def get_config(self, variable):
- try:
- return self.git("config --get %s" % variable)[0].rstrip()
- except:
- return None
-
- def set_config(self, variable, value):
- try:
- self.git("config %s %s"%(variable, value) )
- except:
- die("Could not set %s to " % variable, value)
-
- def make_tag(self, name, head):
- self.git("tag -f %s %s"%(name,head))
-
- def top_change(self, branch):
- try:
- a=self.get_single("name-rev --tags refs/heads/%s" % branch)
- loc = a.find(' tags/') + 6
- if a[loc:loc+3] != "p4/":
- raise
- return int(a[loc+3:][:-2])
- except:
- return 0
-
- def update_index(self):
- self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
-
- def checkout(self, branch):
- self.git("checkout %s" % branch)
-
- def repoint_head(self, branch):
- self.git("symbolic-ref HEAD refs/heads/%s" % branch)
-
- def remove_files(self):
- self.git("ls-files | xargs rm")
-
- def clean_directories(self):
- self.git("clean -d")
-
- def fresh_branch(self, branch):
- report(1, "Creating new branch", branch)
- self.git("ls-files | xargs rm")
- os.remove(".git/index")
- self.repoint_head(branch)
- self.git("clean -d")
-
- def basedir(self):
- return self.topdir
-
- def commit(self, author, email, date, msg, id):
- self.update_index()
- fd=open(".msg", "w")
- fd.writelines(msg)
- fd.close()
- try:
- current = self.get_single("rev-parse --verify HEAD")
- head = "-p HEAD"
- except:
- current = ""
- head = ""
- tree = self.get_single("write-tree")
- for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
- os.environ['GIT_AUTHOR_%s'%r] = l
- os.environ['GIT_COMMITTER_%s'%r] = l
- commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
- os.remove(".msg")
- self.make_tag("p4/%s"%id, commit)
- self.git("update-ref HEAD %s %s" % (commit, current) )
-
-try:
- opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
- ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
-except getopt.GetoptError:
- usage()
-
-for o, a in opts:
- if o == "-q":
- verbosity = 0
- if o == "-v":
- verbosity += 1
- if o in ("--log"):
- logfile = a
- if o in ("--notags"):
- tagall = False
- if o in ("-h", "--help"):
- usage()
- if o in ("--ignore"):
- ignore_warnings = True
-
-git = git_command()
-branch=git.current_branch()
-
-for o, a in opts:
- if o in ("-t", "--timezone"):
- git.set_config("perforce.timezone", a)
- if o in ("--stitch"):
- git.set_config("perforce.%s.path" % branch, a)
- stitch = 1
-
-if len(args) == 2:
- branch = args[1]
- git.checkout(branch)
- if branch == git.current_branch():
- die("Branch %s already exists!" % branch)
- report(1, "Setting perforce to ", args[0])
- git.set_config("perforce.%s.path" % branch, args[0])
-elif len(args) != 0:
- die("You must specify the perforce //depot/path and git branch")
-
-p4path = git.get_config("perforce.%s.path" % branch)
-if p4path == None:
- die("Do not know Perforce //depot/path for git branch", branch)
-
-p4 = p4_command(p4path)
-
-for o, a in opts:
- if o in ("-a", "--authors"):
- p4.authors(a)
-
-localdir = git.basedir()
-if p4.where()[:len(localdir)] != localdir:
- report(1, "**WARNING** Appears p4 client is misconfigured")
- report(1, " for sync from %s to %s" % (p4.repopath, localdir))
- if ignore_warnings != True:
- die("Reconfigure or use \"--ignore\" on command line")
-
-if stitch == 0:
- top = git.top_change(branch)
-else:
- top = 0
-changes = p4.changes(top)
-count = len(changes)
-if count == 0:
- report(1, "Already up to date...")
- sys.exit(0)
-
-ptz = git.get_config("perforce.timezone")
-if ptz:
- report(1, "Setting timezone to", ptz)
- os.environ['TZ'] = ptz
- time.tzset()
-
-if stitch == 1:
- git.remove_files()
- git.clean_directories()
- p4.sync(changes[0], force=True)
-elif top == 0 and branch != git.current_branch():
- p4.sync(changes[0], test=True)
- report(1, "Creating new initial commit");
- git.fresh_branch(branch)
- p4.sync(changes[0], force=True)
-else:
- p4.sync(changes[0], trick=True)
-
-report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
-for id in changes:
- report(1, "Importing changeset", id)
- change = p4.describe(id)
- p4.sync(id)
- if tagall :
- git.commit(change.author, change.email, change.date, change.msg, id)
- else:
- git.commit(change.author, change.email, change.date, change.msg, "import")
- if stitch == 1:
- git.clean_directories()
- stitch = 0
-
# git-ls-remote could be called from outside a git managed repository;
# this would fail in that case and would issue an error message.
-GIT_DIR=$(git-rev-parse --git-dir 2>/dev/null) || :;
+GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || :;
get_data_source () {
case "$1" in
echo self
;;
*)
- if test "$(git-config --get "remote.$1.url")"
+ if test "$(git config --get "remote.$1.url")"
then
echo config
elif test -f "$GIT_DIR/remotes/$1"
echo "$1"
;;
config)
- git-config --get "remote.$1.url"
+ git config --get "remote.$1.url"
;;
remotes)
sed -ne '/^URL: */{
}
get_default_remote () {
- curr_branch=$(git-symbolic-ref -q HEAD | sed -e 's|^refs/heads/||')
- origin=$(git-config --get "branch.$curr_branch.remote")
+ curr_branch=$(git symbolic-ref -q HEAD | sed -e 's|^refs/heads/||')
+ origin=$(git config --get "branch.$curr_branch.remote")
echo ${origin:-origin}
}
'' | branches | self)
;; # no default push mapping, just send matching refs.
config)
- git-config --get-all "remote.$1.push" ;;
+ git config --get-all "remote.$1.push" ;;
remotes)
sed -ne '/^Push: */{
s///p
shift
if test "$remote" = "$(get_default_remote)"
then
- curr_branch=$(git-symbolic-ref -q HEAD | \
+ curr_branch=$(git symbolic-ref -q HEAD | \
sed -e 's|^refs/heads/||')
- merge_branches=$(git-config \
+ merge_branches=$(git config \
--get-all "branch.${curr_branch}.merge")
fi
if test -z "$merge_branches" && test $is_explicit != explicit
if local_ref_name=$(expr "z$local" : 'zrefs/\(.*\)')
then
- git-check-ref-format "$local_ref_name" ||
+ git check-ref-format "$local_ref_name" ||
die "* refusing to create funny ref '$local_ref_name' locally"
fi
echo "${dot_prefix}${force}${remote}:${local}"
echo "HEAD:" ;;
self)
canon_refs_list_for_fetch -d "$1" \
- $(git-for-each-ref --format='%(refname):')
+ $(git for-each-ref --format='%(refname):')
;;
config)
canon_refs_list_for_fetch -d "$1" \
- $(git-config --get-all "remote.$1.fetch") ;;
+ $(git config --get-all "remote.$1.fetch") ;;
branches)
remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1")
case "$remote_branch" in '') remote_branch=master ;; esac
data_source=$(get_data_source "$1")
case "$data_source" in
config)
- uplp=$(git-config --get "remote.$1.uploadpack")
+ uplp=$(git config --get "remote.$1.uploadpack")
echo ${uplp:-git-upload-pack}
;;
*)
-n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
--no-summa|--no-summar|--no-summary)
no_summary=-n ;;
+ --summary)
+ no_summary=$1
+ ;;
--no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
no_commit=--no-commit ;;
--sq|--squ|--squa|--squas|--squash)
shift
done
-orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
git-fetch --update-head-ok "$@" || exit 1
-curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+curr_head=$(git rev-parse --verify HEAD 2>/dev/null)
if test "$curr_head" != "$orig_head"
then
# The fetch involved updating the current branch.
echo >&2 "Warning: fetch updated the current branch head."
echo >&2 "Warning: fast forwarding your working tree from"
echo >&2 "Warning: commit $orig_head."
- git-update-index --refresh 2>/dev/null
- git-read-tree -u -m "$orig_head" "$curr_head" ||
+ git update-index --refresh 2>/dev/null
+ git read-tree -u -m "$orig_head" "$curr_head" ||
die 'Cannot fast-forward your working tree.
After making sure that you saved anything precious from
$ git diff '$orig_head'
case "$merge_head" in
'')
- curr_branch=$(git-symbolic-ref -q HEAD)
+ curr_branch=$(git symbolic-ref -q HEAD)
case $? in
0) ;;
1) echo >&2 "You are not currently on a branch; you must explicitly"
if test -z "$orig_head"
then
- git-update-ref -m "initial pull" HEAD $merge_head "" &&
- git-read-tree --reset -u HEAD || exit 1
+ git update-ref -m "initial pull" HEAD $merge_head "" &&
+ git read-tree --reset -u HEAD || exit 1
exit
fi
-merge_name=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
+merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
exec git-merge $no_summary $no_commit $squash $strategy_args \
"$merge_name" HEAD $merge_head
# Find the intial commit
-commit=$(git-rev-parse HEAD)
+commit=$(git rev-parse HEAD)
mkdir $tmp_dir || exit 2
-for patch_name in $(cat "$QUILT_PATCHES/series" | grep -v '^#'); do
+for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do
echo $patch_name
- (cat $QUILT_PATCHES/$patch_name | git-mailinfo "$tmp_msg" "$tmp_patch" > "$tmp_info") || exit 3
- test -s .dotest/patch || {
+ git mailinfo "$tmp_msg" "$tmp_patch" \
+ <"$QUILT_PATCHES/$patch_name" >"$tmp_info" || exit 3
+ test -s "$tmp_patch" || {
echo "Patch is empty. Was it split wrong?"
exit 1
}
fi
if [ -z "$dry_run" ] ; then
- git-apply --index -C1 "$tmp_patch" &&
- tree=$(git-write-tree) &&
- commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git-commit-tree $tree -p $commit) &&
- git-update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
+ git apply --index -C1 "$tmp_patch" &&
+ tree=$(git write-tree) &&
+ commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) &&
+ git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
fi
done
rm -rf $tmp_dir || exit 5
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2006 Johannes E. Schindelin
+
+# SHORT DESCRIPTION
+#
+# This script makes it easy to fix up commits in the middle of a series,
+# and rearrange commits.
+#
+# The original idea comes from Eric W. Biederman, in
+# http://article.gmane.org/gmane.comp.version-control.git/22407
+
+USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose]
+ [--onto <branch>] <upstream> [<branch>])'
+
+. git-sh-setup
+require_work_tree
+
+DOTEST="$GIT_DIR/.dotest-merge"
+TODO="$DOTEST"/git-rebase-todo
+DONE="$DOTEST"/done
+MSG="$DOTEST"/message
+SQUASH_MSG="$DOTEST"/message-squash
+REWRITTEN="$DOTEST"/rewritten
+PRESERVE_MERGES=
+STRATEGY=
+VERBOSE=
+test -d "$REWRITTEN" && PRESERVE_MERGES=t
+test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
+test -f "$DOTEST"/verbose && VERBOSE=t
+
+warn () {
+ echo "$*" >&2
+}
+
+output () {
+ case "$VERBOSE" in
+ '')
+ "$@" > "$DOTEST"/output 2>&1
+ status=$?
+ test $status != 0 &&
+ cat "$DOTEST"/output
+ return $status
+ ;;
+ *)
+ "$@"
+ esac
+}
+
+require_clean_work_tree () {
+ # test if working tree is dirty
+ git rev-parse --verify HEAD > /dev/null &&
+ git update-index --refresh &&
+ git diff-files --quiet &&
+ git diff-index --cached --quiet HEAD ||
+ die "Working tree is dirty"
+}
+
+ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
+
+comment_for_reflog () {
+ case "$ORIG_REFLOG_ACTION" in
+ ''|rebase*)
+ GIT_REFLOG_ACTION="rebase -i ($1)"
+ export GIT_REFLOG_ACTION
+ esac
+}
+
+mark_action_done () {
+ sed -e 1q < "$TODO" >> "$DONE"
+ sed -e 1d < "$TODO" >> "$TODO".new
+ mv -f "$TODO".new "$TODO"
+ count=$(($(wc -l < "$DONE")))
+ total=$(($count+$(wc -l < "$TODO")))
+ printf "Rebasing (%d/%d)\r" $count $total
+ test -z "$VERBOSE" || echo
+}
+
+make_patch () {
+ parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null)
+ git diff "$parent_sha1".."$1" > "$DOTEST"/patch
+}
+
+die_with_patch () {
+ test -f "$DOTEST"/message ||
+ git cat-file commit $sha1 | sed "1,/^$/d" > "$DOTEST"/message
+ test -f "$DOTEST"/author-script ||
+ get_author_ident_from_commit $sha1 > "$DOTEST"/author-script
+ make_patch "$1"
+ die "$2"
+}
+
+die_abort () {
+ rm -rf "$DOTEST"
+ die "$1"
+}
+
+pick_one () {
+ case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+ output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+ test -d "$REWRITTEN" &&
+ pick_one_preserving_merges "$@" && return
+ parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)
+ current_sha1=$(git rev-parse --verify HEAD)
+ if test $current_sha1 = $parent_sha1; then
+ output git reset --hard $sha1
+ test "a$1" = a-n && output git reset --soft $current_sha1
+ sha1=$(git rev-parse --short $sha1)
+ output warn Fast forward to $sha1
+ else
+ output git cherry-pick $STRATEGY "$@"
+ fi
+}
+
+pick_one_preserving_merges () {
+ case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+ sha1=$(git rev-parse $sha1)
+
+ if test -f "$DOTEST"/current-commit
+ then
+ current_commit=$(cat "$DOTEST"/current-commit) &&
+ git rev-parse HEAD > "$REWRITTEN"/$current_commit &&
+ rm "$DOTEST"/current-commit ||
+ die "Cannot write current commit's replacement sha1"
+ fi
+
+ # rewrite parents; if none were rewritten, we can fast-forward.
+ fast_forward=t
+ preserve=t
+ new_parents=
+ for p in $(git rev-list --parents -1 $sha1 | cut -d\ -f2-)
+ do
+ if test -f "$REWRITTEN"/$p
+ then
+ preserve=f
+ new_p=$(cat "$REWRITTEN"/$p)
+ test $p != $new_p && fast_forward=f
+ case "$new_parents" in
+ *$new_p*)
+ ;; # do nothing; that parent is already there
+ *)
+ new_parents="$new_parents $new_p"
+ esac
+ fi
+ done
+ case $fast_forward in
+ t)
+ output warn "Fast forward to $sha1"
+ test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1
+ ;;
+ f)
+ test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
+
+ first_parent=$(expr "$new_parents" : " \([^ ]*\)")
+ # detach HEAD to current parent
+ output git checkout $first_parent 2> /dev/null ||
+ die "Cannot move HEAD to $first_parent"
+
+ echo $sha1 > "$DOTEST"/current-commit
+ case "$new_parents" in
+ \ *\ *)
+ # redo merge
+ author_script=$(get_author_ident_from_commit $sha1)
+ eval "$author_script"
+ msg="$(git cat-file commit $sha1 | \
+ sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")"
+ # NEEDSWORK: give rerere a chance
+ if ! output git merge $STRATEGY -m "$msg" $new_parents
+ then
+ echo "$msg" > "$GIT_DIR"/MERGE_MSG
+ die Error redoing merge $sha1
+ fi
+ ;;
+ *)
+ output git cherry-pick $STRATEGY "$@" ||
+ die_with_patch $sha1 "Could not pick $sha1"
+ esac
+ esac
+}
+
+nth_string () {
+ case "$1" in
+ *1[0-9]|*[04-9]) echo "$1"th;;
+ *1) echo "$1"st;;
+ *2) echo "$1"nd;;
+ *3) echo "$1"rd;;
+ esac
+}
+
+make_squash_message () {
+ if test -f "$SQUASH_MSG"; then
+ COUNT=$(($(sed -n "s/^# This is [^0-9]*\([0-9]\+\).*/\1/p" \
+ < "$SQUASH_MSG" | tail -n 1)+1))
+ echo "# This is a combination of $COUNT commits."
+ sed -n "2,\$p" < "$SQUASH_MSG"
+ else
+ COUNT=2
+ echo "# This is a combination of two commits."
+ echo "# The first commit's message is:"
+ echo
+ git cat-file commit HEAD | sed -e '1,/^$/d'
+ echo
+ fi
+ echo "# This is the $(nth_string $COUNT) commit message:"
+ echo
+ git cat-file commit $1 | sed -e '1,/^$/d'
+}
+
+peek_next_command () {
+ sed -n "1s/ .*$//p" < "$TODO"
+}
+
+do_next () {
+ test -f "$DOTEST"/message && rm "$DOTEST"/message
+ test -f "$DOTEST"/author-script && rm "$DOTEST"/author-script
+ read command sha1 rest < "$TODO"
+ case "$command" in
+ \#|'')
+ mark_action_done
+ ;;
+ pick)
+ comment_for_reflog pick
+
+ mark_action_done
+ pick_one $sha1 ||
+ die_with_patch $sha1 "Could not apply $sha1... $rest"
+ ;;
+ edit)
+ comment_for_reflog edit
+
+ mark_action_done
+ pick_one $sha1 ||
+ die_with_patch $sha1 "Could not apply $sha1... $rest"
+ make_patch $sha1
+ warn
+ warn "You can amend the commit now, with"
+ warn
+ warn " git commit --amend"
+ warn
+ exit 0
+ ;;
+ squash)
+ comment_for_reflog squash
+
+ test -z "$(grep -ve '^$' -e '^#' < $DONE)" &&
+ die "Cannot 'squash' without a previous commit"
+
+ mark_action_done
+ make_squash_message $sha1 > "$MSG"
+ case "$(peek_next_command)" in
+ squash)
+ EDIT_COMMIT=
+ USE_OUTPUT=output
+ cp "$MSG" "$SQUASH_MSG"
+ ;;
+ *)
+ EDIT_COMMIT=-e
+ USE_OUTPUT=
+ test -f "$SQUASH_MSG" && rm "$SQUASH_MSG"
+ esac
+
+ failed=f
+ output git reset --soft HEAD^
+ pick_one -n $sha1 || failed=t
+ author_script=$(get_author_ident_from_commit $sha1)
+ echo "$author_script" > "$DOTEST"/author-script
+ case $failed in
+ f)
+ # This is like --amend, but with a different message
+ eval "$author_script"
+ export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+ $USE_OUTPUT git commit -F "$MSG" $EDIT_COMMIT
+ ;;
+ t)
+ cp "$MSG" "$GIT_DIR"/MERGE_MSG
+ warn
+ warn "Could not apply $sha1... $rest"
+ die_with_patch $sha1 ""
+ esac
+ ;;
+ *)
+ warn "Unknown command: $command $sha1 $rest"
+ die_with_patch $sha1 "Please fix this in the file $TODO."
+ esac
+ test -s "$TODO" && return
+
+ comment_for_reflog finish &&
+ HEADNAME=$(cat "$DOTEST"/head-name) &&
+ OLDHEAD=$(cat "$DOTEST"/head) &&
+ SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
+ if test -d "$REWRITTEN"
+ then
+ test -f "$DOTEST"/current-commit &&
+ current_commit=$(cat "$DOTEST"/current-commit) &&
+ git rev-parse HEAD > "$REWRITTEN"/$current_commit
+ NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
+ else
+ NEWHEAD=$(git rev-parse HEAD)
+ fi &&
+ message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
+ git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
+ git symbolic-ref HEAD $HEADNAME && {
+ test ! -f "$DOTEST"/verbose ||
+ git diff --stat $(cat "$DOTEST"/head)..HEAD
+ } &&
+ rm -rf "$DOTEST" &&
+ warn "Successfully rebased and updated $HEADNAME."
+
+ exit
+}
+
+do_rest () {
+ while :
+ do
+ do_next
+ done
+}
+
+while case $# in 0) break ;; esac
+do
+ case "$1" in
+ --continue)
+ comment_for_reflog continue
+
+ test -d "$DOTEST" || die "No interactive rebase running"
+
+ # commit if necessary
+ git rev-parse --verify HEAD > /dev/null &&
+ git update-index --refresh &&
+ git diff-files --quiet &&
+ ! git diff-index --cached --quiet HEAD &&
+ . "$DOTEST"/author-script &&
+ export GIT_AUTHOR_NAME GIT_AUTHOR_NAME GIT_AUTHOR_DATE &&
+ git commit -F "$DOTEST"/message -e
+
+ require_clean_work_tree
+ do_rest
+ ;;
+ --abort)
+ comment_for_reflog abort
+
+ test -d "$DOTEST" || die "No interactive rebase running"
+
+ HEADNAME=$(cat "$DOTEST"/head-name)
+ HEAD=$(cat "$DOTEST"/head)
+ git symbolic-ref HEAD $HEADNAME &&
+ output git reset --hard $HEAD &&
+ rm -rf "$DOTEST"
+ exit
+ ;;
+ --skip)
+ comment_for_reflog skip
+
+ test -d "$DOTEST" || die "No interactive rebase running"
+
+ output git reset --hard && do_rest
+ ;;
+ -s|--strategy)
+ shift
+ case "$#,$1" in
+ *,*=*)
+ STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
+ 1,*)
+ usage ;;
+ *)
+ STRATEGY="-s $2"
+ shift ;;
+ esac
+ ;;
+ --merge)
+ # we use merge anyway
+ ;;
+ -C*)
+ die "Interactive rebase uses merge, so $1 does not make sense"
+ ;;
+ -v|--verbose)
+ VERBOSE=t
+ ;;
+ -p|--preserve-merges)
+ PRESERVE_MERGES=t
+ ;;
+ -i|--interactive)
+ # yeah, we know
+ ;;
+ ''|-h)
+ usage
+ ;;
+ *)
+ test -d "$DOTEST" &&
+ die "Interactive rebase already started"
+
+ git var GIT_COMMITTER_IDENT >/dev/null ||
+ die "You need to set your committer info first"
+
+ comment_for_reflog start
+
+ ONTO=
+ case "$1" in
+ --onto)
+ ONTO=$(git rev-parse --verify "$2") ||
+ die "Does not point to a valid commit: $2"
+ shift; shift
+ ;;
+ esac
+
+ require_clean_work_tree
+
+ mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
+ if test ! -z "$2"
+ then
+ output git show-ref --verify --quiet "refs/heads/$2" ||
+ die "Invalid branchname: $2"
+ output git checkout "$2" ||
+ die "Could not checkout $2"
+ fi
+
+ HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
+ UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
+
+ test -z "$ONTO" && ONTO=$UPSTREAM
+
+ : > "$DOTEST"/interactive || die "Could not mark as interactive"
+ git symbolic-ref HEAD > "$DOTEST"/head-name ||
+ die "Could not get HEAD"
+
+ echo $HEAD > "$DOTEST"/head
+ echo $UPSTREAM > "$DOTEST"/upstream
+ echo $ONTO > "$DOTEST"/onto
+ test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
+ test t = "$VERBOSE" && : > "$DOTEST"/verbose
+ if test t = "$PRESERVE_MERGES"
+ then
+ # $REWRITTEN contains files for each commit that is
+ # reachable by at least one merge base of $HEAD and
+ # $UPSTREAM. They are not necessarily rewritten, but
+ # their children might be.
+ # This ensures that commits on merged, but otherwise
+ # unrelated side branches are left alone. (Think "X"
+ # in the man page's example.)
+ mkdir "$REWRITTEN" &&
+ for c in $(git merge-base --all $HEAD $UPSTREAM)
+ do
+ echo $ONTO > "$REWRITTEN"/$c ||
+ die "Could not init rewritten commits"
+ done
+ MERGES_OPTION=
+ else
+ MERGES_OPTION=--no-merges
+ fi
+
+ SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
+ SHORTHEAD=$(git rev-parse --short $HEAD)
+ SHORTONTO=$(git rev-parse --short $ONTO)
+ cat > "$TODO" << EOF
+# Rebasing $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
+#
+# Commands:
+# pick = use commit
+# edit = use commit, but stop for amending
+# squash = use commit, but meld into previous commit
+#
+# If you remove a line here THAT COMMIT WILL BE LOST.
+#
+EOF
+ git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
+ --abbrev=7 --reverse --left-right --cherry-pick \
+ $UPSTREAM...$HEAD | \
+ sed -n "s/^>/pick /p" >> "$TODO"
+
+ test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
+ die_abort "Nothing to do"
+
+ cp "$TODO" "$TODO".backup
+ git_editor "$TODO" ||
+ die "Could not execute editor"
+
+ test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
+ die_abort "Nothing to do"
+
+ output git checkout $ONTO && do_rest
+ esac
+ shift
+done
# Copyright (c) 2005 Junio C Hamano.
#
-USAGE='[-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
same name. When the --onto option is provided the new branch starts
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
test -n "$prev_head" || die "prev_head must be defined"
test -d "$dotest" || die "$dotest directory does not exist"
- unmerged=$(git-ls-files -u)
+ unmerged=$(git ls-files -u)
if test -n "$unmerged"
then
echo "You still have unmerged paths in your index"
- echo "did you forget update-index?"
+ echo "did you forget to use git add?"
die "$RESOLVEMSG"
fi
- if ! git-diff-index --quiet HEAD
+ if ! git diff-index --quiet HEAD
then
if ! git-commit -C "`cat $dotest/current`"
then
else
printf "Already applied: %0${prec}d" $msgnum
fi
- echo ' '`git-rev-list --pretty=oneline -1 HEAD | \
+ echo ' '`git rev-list --pretty=oneline -1 HEAD | \
sed 's/^[a-f0-9]\+ //'`
- prev_head=`git-rev-parse HEAD^0`
+ prev_head=`git rev-parse HEAD^0`
# save the resulting commit so we can read-tree on it later
echo "$prev_head" > "$dotest/prev_head"
call_merge () {
cmt="$(cat $dotest/cmt.$1)"
echo "$cmt" > "$dotest/current"
- hd=$(git-rev-parse --verify HEAD)
- cmt_name=$(git-symbolic-ref HEAD)
+ hd=$(git rev-parse --verify HEAD)
+ cmt_name=$(git symbolic-ref HEAD)
msgnum=$(cat $dotest/msgnum)
end=$(cat $dotest/end)
eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
return
;;
1)
- test -d "$GIT_DIR/rr-cache" && git-rerere
+ git rerere
die "$RESOLVEMSG"
;;
2)
echo "All done."
}
+is_interactive () {
+ test -f "$dotest"/interactive ||
+ while case $#,"$1" in 0,|*,-i|*,--interactive) break ;; esac
+ do
+ shift
+ done && test -n "$1"
+}
+
+is_interactive "$@" && exec git-rebase--interactive "$@"
+
while case "$#" in 0) break ;; esac
do
case "$1" in
--continue)
- git-diff-files --quiet || {
+ git diff-files --quiet || {
echo "You must edit all merge conflicts and then"
- echo "mark them as resolved using git update-index"
+ echo "mark them as resolved using git add"
exit 1
}
if test -d "$dotest"
--skip)
if test -d "$dotest"
then
- if test -d "$GIT_DIR/rr-cache"
- then
- git-rerere clear
- fi
+ git rerere clear
prev_head="`cat $dotest/prev_head`"
end="`cat $dotest/end`"
msgnum="`cat $dotest/msgnum`"
exit
;;
--abort)
- if test -d "$GIT_DIR/rr-cache"
- then
- git-rerere clear
- fi
+ git rerere clear
if test -d "$dotest"
then
rm -r "$dotest"
fi
# The tree must be really really clean.
-git-update-index --refresh || exit
-diff=$(git-diff-index --cached --name-status -r HEAD)
+git update-index --refresh || exit
+diff=$(git diff-index --cached --name-status -r HEAD)
case "$diff" in
?*) echo "cannot rebase: your index is not up-to-date"
echo "$diff"
# Make sure the branch to rebase onto is valid.
onto_name=${newbase-"$upstream_name"}
-onto=$(git-rev-parse --verify "${onto_name}^0") || exit
+onto=$(git rev-parse --verify "${onto_name}^0") || exit
# If a hook exists, give it a chance to interrupt
if test -x "$GIT_DIR/hooks/pre-rebase"
fi
;;
esac
-branch=$(git-rev-parse --verify "${branch_name}^0") || exit
+branch=$(git rev-parse --verify "${branch_name}^0") || exit
# Now we are rebasing commits $upstream..$branch on top of $onto
-# Check if we are already based on $onto, but this should be
-# done only when upstream and onto are the same.
-mb=$(git-merge-base "$onto" "$branch")
-if test "$upstream" = "$onto" && test "$mb" = "$onto"
+# Check if we are already based on $onto with linear history,
+# but this should be done only when upstream and onto are the same.
+mb=$(git merge-base "$onto" "$branch")
+if test "$upstream" = "$onto" && test "$mb" = "$onto" &&
+ # linear history?
+ ! git rev-list --parents "$onto".."$branch" | grep " .* " > /dev/null
then
echo >&2 "Current branch $branch_name is up to date."
exit 0
if test -n "$verbose"
then
echo "Changes from $mb to $onto:"
- git-diff-tree --stat --summary "$mb" "$onto"
+ # We want color (if set), but no pager
+ GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
fi
# Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
if test -z "$do_merge"
then
- git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
+ git format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
git am $git_am_opt --binary -3 -k --resolvemsg="$RESOLVEMSG"
exit $?
fi
mkdir -p "$dotest"
echo "$onto" > "$dotest/onto"
echo "$onto_name" > "$dotest/onto_name"
-prev_head=`git-rev-parse HEAD^0`
+prev_head=`git rev-parse HEAD^0`
echo "$prev_head" > "$dotest/prev_head"
msgnum=0
-for cmt in `git-rev-list --reverse --no-merges "$upstream"..ORIG_HEAD`
+for cmt in `git rev-list --reverse --no-merges "$upstream"..ORIG_HEAD`
do
msgnum=$(($msgnum + 1))
echo "$cmt" > "$dotest/cmt.$msgnum"
if ($info->{'PUSH'}) {
my @pushed = map {
s|^refs/heads/||;
+ s|^\+refs/heads/|+|;
s|:refs/heads/|:|;
$_;
} @{$info->{'PUSH'}};
exit(1);
}
+local $VERBOSE = 0;
+@ARGV = grep {
+ if ($_ eq '-v' or $_ eq '--verbose') {
+ $VERBOSE=1;
+ 0
+ } else {
+ 1
+ }
+} @ARGV;
+
if (!@ARGV) {
for (sort keys %$remote) {
- print "$_\n";
+ print "$_";
+ print "\t$remote->{$_}->{URL}" if $VERBOSE;
+ print "\n";
}
}
elsif ($ARGV[0] eq 'show') {
# Copyright (c) 2005 Linus Torvalds
#
-USAGE='[-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]'
+USAGE='[-a] [-d] [-f] [-l] [-n] [-q] [--max-pack-size=N] [--window=N] [--window-memory=N] [--depth=N]'
SUBDIRECTORY_OK='Yes'
. git-sh-setup
no_update_info= all_into_one= remove_redundant=
-local= quiet= no_reuse_delta= extra=
+local= quiet= no_reuse= extra=
while case "$#" in 0) break ;; esac
do
case "$1" in
-a) all_into_one=t ;;
-d) remove_redundant=t ;;
-q) quiet=-q ;;
- -f) no_reuse_delta=--no-reuse-delta ;;
+ -f) no_reuse=--no-reuse-object ;;
-l) local=--local ;;
+ --max-pack-size=*) extra="$extra $1" ;;
--window=*) extra="$extra $1" ;;
+ --window-memory=*) extra="$extra $1" ;;
--depth=*) extra="$extra $1" ;;
*) usage ;;
esac
esac
PACKDIR="$GIT_OBJECT_DIRECTORY/pack"
-PACKTMP="$GIT_DIR/.tmp-$$-pack"
+PACKTMP="$GIT_OBJECT_DIRECTORY/.tmp-$$-pack"
rm -f "$PACKTMP"-*
trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15
;;
esac
-args="$args $local $quiet $no_reuse_delta$extra"
-name=$(git-pack-objects --non-empty --all --reflog $args </dev/null "$PACKTMP") ||
+args="$args $local $quiet $no_reuse$extra"
+names=$(git pack-objects --non-empty --all --reflog $args </dev/null "$PACKTMP") ||
exit 1
-if [ -z "$name" ]; then
- echo Nothing new to pack.
-else
+if [ -z "$names" ]; then
+ if test -z "$quiet"; then
+ echo Nothing new to pack.
+ fi
+fi
+for name in $names ; do
+ fullbases="$fullbases pack-$name"
chmod a-w "$PACKTMP-$name.pack"
chmod a-w "$PACKTMP-$name.idx"
if test "$quiet" != '-q'; then
exit 1
}
rm -f "$PACKDIR/old-pack-$name.pack" "$PACKDIR/old-pack-$name.idx"
-fi
+done
if test "$remove_redundant" = t
then
( cd "$PACKDIR" &&
for e in $existing
do
- case "$e" in
- pack-$name) ;;
+ case " $fullbases " in
+ *" $e "*) ;;
*) rm -f "$e.pack" "$e.idx" "$e.keep" ;;
esac
done
)
fi
- git-prune-packed $quiet
+ git prune-packed $quiet
fi
case "$no_update_info" in
[ "$base" ] || usage
[ "$url" ] || usage
-baserev=`git-rev-parse --verify "$base"^0` &&
-headrev=`git-rev-parse --verify "$head"^0` || exit
+baserev=`git rev-parse --verify "$base"^0` &&
+headrev=`git rev-parse --verify "$head"^0` || exit
merge_base=`git merge-base $baserev $headrev` ||
die "fatal: No commits in common between $base and $head"
usage
;;
*)
- rev=$(git-rev-parse --verify "$1") || exit
+ rev=$(git rev-parse --verify "$1") || exit
shift
break
;;
done
: ${rev=HEAD}
-rev=$(git-rev-parse --verify $rev^0) || exit
+rev=$(git rev-parse --verify $rev^0) || exit
# Skip -- in "git reset HEAD -- foo" and "git reset -- foo".
case "$1" in --) shift ;; esac
test "$reset_type" = "--mixed" ||
die "Cannot do partial $reset_type reset."
- git-diff-index --cached $rev -- "$@" |
+ git diff-index --cached $rev -- "$@" |
sed -e 's/^:\([0-7][0-7]*\) [0-7][0-7]* \([0-9a-f][0-9a-f]*\) [0-9a-f][0-9a-f]* [A-Z] \(.*\)$/\1 \2 \3/' |
git update-index --add --remove --index-info || exit
git update-index --refresh
if test "$reset_type" = "--soft"
then
if test -f "$GIT_DIR/MERGE_HEAD" ||
- test "" != "$(git-ls-files --unmerged)"
+ test "" != "$(git ls-files --unmerged)"
then
die "Cannot do a soft reset in the middle of a merge."
fi
else
- git-read-tree -v --reset $update "$rev" || exit
+ git read-tree -v --reset $update "$rev" || exit
fi
# Any resets update HEAD to the head being switched to.
-if orig=$(git-rev-parse --verify HEAD 2>/dev/null)
+if orig=$(git rev-parse --verify HEAD 2>/dev/null)
then
echo "$orig" >"$GIT_DIR/ORIG_HEAD"
else
rm -f "$GIT_DIR/ORIG_HEAD"
fi
-git-update-ref -m "$GIT_REFLOG_ACTION" HEAD "$rev"
+git update-ref -m "$GIT_REFLOG_ACTION" HEAD "$rev"
update_ref_status=$?
case "$reset_type" in
;; # Nothing else to do
--mixed )
# Report what has not been updated.
- git-update-index --refresh
+ git update-index --refresh
;;
esac
--bcc Specify a list of email addresses that should be Bcc:
on all the emails.
- --compose Use \$EDITOR to edit an introductory message for the
- patch series.
+ --compose Use \$GIT_EDITOR, core.editor, \$EDITOR, or \$VISUAL to edit
+ an introductory message for the patch series.
--subject Specify the initial "Subject:" line.
Only necessary if --compose is also set. If --compose
email sent, rather than to the first email sent.
Defaults to on.
- --no-signed-off-cc Suppress the automatic addition of email addresses
- that appear in Signed-off-by: or Cc: lines to the cc:
- list. Note: Using this option is not recommended.
+ --signed-off-cc Automatically add email addresses that appear in
+ Signed-off-by: or Cc: lines to the cc: list. Defaults to on.
--smtp-server If set, specifies the outgoing SMTP server to use.
Defaults to localhost.
--suppress-from Suppress sending emails to yourself if your address
- appears in a From: line.
+ appears in a From: line. Defaults to off.
+
+ --thread Specify that the "In-Reply-To:" header should be set on all
+ emails. Defaults to on.
--quiet Make git-send-email less verbose. One line per email
should be all that is output.
my (@to,@cc,@initial_cc,@bcclist,@xh,
$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
-# Behavior modification variables
-my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc,
- $dry_run) = (1, 0, 0, 0, 0);
my $smtp_server;
my $envelope_sender;
$term = new FakeTerm "$@: going non-interactive";
}
-my $def_chain = $repo->config_bool('sendemail.chainreplyto');
-if (defined $def_chain and not $def_chain) {
- $chain_reply_to = 0;
+# Behavior modification variables
+my ($quiet, $dry_run) = (0, 0);
+
+# Variables with corresponding config settings
+my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc);
+
+my %config_settings = (
+ "thread" => [\$thread, 1],
+ "chainreplyto" => [\$chain_reply_to, 1],
+ "suppressfrom" => [\$suppress_from, 0],
+ "signedoffcc" => [\$signed_off_cc, 1],
+);
+
+foreach my $setting (keys %config_settings) {
+ my $config = $repo->config_bool("sendemail.$setting");
+ ${$config_settings{$setting}->[0]} = (defined $config) ? $config : $config_settings{$setting}->[1];
}
@bcclist = $repo->config('sendemail.bcc');
"smtp-server=s" => \$smtp_server,
"compose" => \$compose,
"quiet" => \$quiet,
- "suppress-from" => \$suppress_from,
- "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
+ "suppress-from!" => \$suppress_from,
+ "signed-off-cc|signed-off-by-cc!" => \$signed_off_cc,
"dry-run" => \$dry_run,
"envelope-sender=s" => \$envelope_sender,
+ "thread!" => \$thread,
);
unless ($rc) {
$aliases{$1} = [ split(/\s+/, $2) ];
}}},
pine => sub { my $fh = shift; while (<$fh>) {
- if (/^(\S+)\s+(.*)$/) {
+ if (/^(\S+)\t.*\t(.*)$/) {
$aliases{$1} = [ split(/\s*,\s*/, $2) ];
}}},
gnus => sub { my $fh = shift; while (<$fh>) {
}
}
+($from) = expand_aliases($from) if defined $from;
+
my $prompting = 0;
if (!defined $from) {
$from = $author || $committer;
$prompting++;
}
-if (!defined $initial_reply_to && $prompting) {
+if ($thread && !defined $initial_reply_to && $prompting) {
do {
$_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ",
$initial_reply_to);
EOT
close(C);
- my $editor = $ENV{EDITOR};
- $editor = 'vi' unless defined $editor;
+ my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
system($editor, $compose_filename);
open(C2,">",$compose_filename . ".final")
return $address if ($address =~ /^($local_part_regexp)$/);
if ($have_email_valid) {
+ $address =~ s/^\s*<(.*)>\s*$/$1/;
return scalar Email::Valid->address($address);
} else {
# less robust/correct than the monster regexp in Email::Valid,
# 1 second since the last time we were called.
# We'll setup a template for the message id, using the "from" address:
-my $message_id_from = extract_valid_address($from);
-my $message_id_template = "<%s-git-send-email-$message_id_from>";
sub make_message_id
{
my $date = time;
my $pseudo_rand = int (rand(4200));
+ my $du_part;
+ for ($from, $committer, $author) {
+ $du_part = extract_valid_address($_);
+ last if ($du_part ne '');
+ }
+ if ($du_part eq '') {
+ use Sys::Hostname qw();
+ $du_part = 'user@' . Sys::Hostname::hostname();
+ }
+ my $message_id_template = "<%s-git-send-email-$du_part>";
$message_id = sprintf $message_id_template, "$date$pseudo_rand";
#print "new message id = $message_id\n"; # Was useful for debugging
}
$ccline = "\nCc: $cc";
}
$from = sanitize_address_rfc822($from);
+ make_message_id();
+
my $header = "From: $from
To: $to${ccline}
Subject: $subject
Message-Id: $message_id
X-Mailer: git-send-email $gitversion
";
- if ($reply_to) {
+ if ($thread && $reply_to) {
$header .= "In-Reply-To: $reply_to\n";
$header .= "References: $references\n";
$reply_to = $initial_reply_to;
$references = $initial_reply_to || '';
-make_message_id();
$subject = $initial_subject;
foreach my $t (@files) {
}
} else {
$message .= $_;
- if (/^(Signed-off-by|Cc): (.*)$/i && !$no_signed_off_cc) {
+ if (/^(Signed-off-by|Cc): (.*)$/i && $signed_off_cc) {
my $c = $2;
chomp $c;
push @cc, $c;
$references = "$message_id";
}
}
- make_message_id();
}
if ($compose) {
fi
}
-is_bare_repository () {
- git-config --bool --get core.bare ||
- case "$GIT_DIR" in
- .git | */.git) echo false ;;
- *) echo true ;;
+git_editor() {
+ : "${GIT_EDITOR:=$(git config core.editor)}"
+ : "${GIT_EDITOR:=${VISUAL:-${EDITOR}}}"
+ case "$GIT_EDITOR,$TERM" in
+ ,dumb)
+ echo >&2 "No editor specified in GIT_EDITOR, core.editor, VISUAL,"
+ echo >&2 "or EDITOR. Tried to fall back to vi but terminal is dumb."
+ echo >&2 "Please set one of these variables to an appropriate"
+ echo >&2 "editor or run $0 with options that will not cause an"
+ echo >&2 "editor to be invoked (e.g., -m or -F for git-commit)."
+ exit 1
+ ;;
esac
+ eval "${GIT_EDITOR:=vi}" '"$@"'
+}
+
+is_bare_repository () {
+ git rev-parse --is-bare-repository
}
cd_to_toplevel () {
- cdup=$(git-rev-parse --show-cdup)
+ cdup=$(git rev-parse --show-cdup)
if test ! -z "$cdup"
then
cd "$cdup" || {
}
require_work_tree () {
- test $(is_bare_repository) = false &&
- test $(git-rev-parse --is-inside-git-dir) = false ||
+ test $(git rev-parse --is-inside-work-tree) = true ||
die "fatal: $0 cannot be used without a working tree."
}
+get_author_ident_from_commit () {
+ pick_author_script='
+ /^author /{
+ s/'\''/'\''\\'\'\''/g
+ h
+ s/^author \([^<]*\) <[^>]*> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+ g
+ s/^author [^<]* <\([^>]*\)> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+ g
+ s/^author [^<]* <[^>]*> \(.*\)$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+ q
+ }
+ '
+ encoding=$(git config i18n.commitencoding || echo UTF-8)
+ git show -s --pretty=raw --encoding="$encoding" "$1" |
+ LANG=C LC_ALL=C sed -ne "$pick_author_script"
+}
+
if [ -z "$LONG_USAGE" ]
then
LONG_USAGE="Usage: $0 $USAGE"
if [ -z "$SUBDIRECTORY_OK" ]
then
: ${GIT_DIR=.git}
- GIT_DIR=$(GIT_DIR="$GIT_DIR" git-rev-parse --git-dir) || {
+ GIT_DIR=$(GIT_DIR="$GIT_DIR" git rev-parse --git-dir) || {
exit=$?
echo >&2 "You need to run this command from the toplevel of the working tree."
exit $exit
}
else
- GIT_DIR=$(git-rev-parse --git-dir) || exit
+ GIT_DIR=$(git rev-parse --git-dir) || exit
fi
: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
--- /dev/null
+#!/bin/sh
+# Copyright (c) 2007, Nanako Shiraishi
+
+USAGE='[ | list | show | apply | clear]'
+
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+require_work_tree
+cd_to_toplevel
+
+TMP="$GIT_DIR/.git-stash.$$"
+trap 'rm -f "$TMP-*"' 0
+
+ref_stash=refs/stash
+
+no_changes () {
+ git diff-index --quiet --cached HEAD &&
+ git diff-files --quiet
+}
+
+clear_stash () {
+ if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
+ then
+ git update-ref -d refs/stash $current
+ fi
+}
+
+save_stash () {
+ stash_msg="$1"
+
+ if no_changes
+ then
+ echo >&2 'No local changes to save'
+ exit 0
+ fi
+ test -f "$GIT_DIR/logs/$ref_stash" ||
+ clear_stash || die "Cannot initialize stash"
+
+ # Make sure the reflog for stash is kept.
+ : >>"$GIT_DIR/logs/$ref_stash"
+
+ # state of the base commit
+ if b_commit=$(git rev-parse --verify HEAD)
+ then
+ head=$(git log --abbrev-commit --pretty=oneline -n 1 HEAD)
+ else
+ die "You do not have the initial commit yet"
+ fi
+
+ if branch=$(git symbolic-ref -q HEAD)
+ then
+ branch=${branch#refs/heads/}
+ else
+ branch='(no branch)'
+ fi
+ msg=$(printf '%s: %s' "$branch" "$head")
+
+ # state of the index
+ i_tree=$(git write-tree) &&
+ i_commit=$(printf 'index on %s' "$msg" |
+ git commit-tree $i_tree -p $b_commit) ||
+ die "Cannot save the current index state"
+
+ # state of the working tree
+ w_tree=$( (
+ rm -f "$TMP-index" &&
+ cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
+ GIT_INDEX_FILE="$TMP-index" &&
+ export GIT_INDEX_FILE &&
+ git read-tree -m $i_tree &&
+ git add -u &&
+ git write-tree &&
+ rm -f "$TMP-index"
+ ) ) ||
+ die "Cannot save the current worktree state"
+
+ # create the stash
+ if test -z "$stash_msg"
+ then
+ stash_msg=$(printf 'WIP on %s' "$msg")
+ else
+ stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
+ fi
+ w_commit=$(printf '%s\n' "$stash_msg" |
+ git commit-tree $w_tree -p $b_commit -p $i_commit) ||
+ die "Cannot record working tree state"
+
+ git update-ref -m "$stash_msg" $ref_stash $w_commit ||
+ die "Cannot save the current status"
+ printf >&2 'Saved "%s"\n' "$stash_msg"
+}
+
+have_stash () {
+ git rev-parse --verify $ref_stash >/dev/null 2>&1
+}
+
+list_stash () {
+ have_stash || return 0
+ git log --pretty=oneline -g "$@" $ref_stash |
+ sed -n -e 's/^[.0-9a-f]* refs\///p'
+}
+
+show_stash () {
+ flags=$(git rev-parse --no-revs --flags "$@")
+ if test -z "$flags"
+ then
+ flags=--stat
+ fi
+ s=$(git rev-parse --revs-only --no-flags --default $ref_stash "$@")
+
+ w_commit=$(git rev-parse --verify "$s") &&
+ b_commit=$(git rev-parse --verify "$s^") &&
+ git diff $flags $b_commit $w_commit
+}
+
+apply_stash () {
+ git diff-files --quiet ||
+ die 'Cannot restore on top of a dirty state'
+
+ unstash_index=
+ case "$1" in
+ --index)
+ unstash_index=t
+ shift
+ esac
+
+ # current index state
+ c_tree=$(git write-tree) ||
+ die 'Cannot apply a stash in the middle of a merge'
+
+ # stash records the work tree, and is a merge between the
+ # base commit (first parent) and the index tree (second parent).
+ s=$(git rev-parse --revs-only --no-flags --default $ref_stash "$@") &&
+ w_tree=$(git rev-parse --verify "$s:") &&
+ b_tree=$(git rev-parse --verify "$s^1:") &&
+ i_tree=$(git rev-parse --verify "$s^2:") ||
+ die "$*: no valid stashed state found"
+
+ unstashed_index_tree=
+ if test -n "$unstash_index" && test "$b_tree" != "$i_tree"
+ then
+ git diff --binary $s^2^..$s^2 | git apply --cached
+ test $? -ne 0 &&
+ die 'Conflicts in index. Try without --index.'
+ unstashed_index_tree=$(git-write-tree) ||
+ die 'Could not save index tree'
+ git reset
+ fi
+
+ eval "
+ GITHEAD_$w_tree='Stashed changes' &&
+ GITHEAD_$c_tree='Updated upstream' &&
+ GITHEAD_$b_tree='Version stash was based on' &&
+ export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
+ "
+
+ if git-merge-recursive $b_tree -- $c_tree $w_tree
+ then
+ # No conflict
+ if test -n "$unstashed_index_tree"
+ then
+ git read-tree "$unstashed_index_tree"
+ else
+ a="$TMP-added" &&
+ git diff --cached --name-only --diff-filter=A $c_tree >"$a" &&
+ git read-tree --reset $c_tree &&
+ git update-index --add --stdin <"$a" ||
+ die "Cannot unstage modified files"
+ rm -f "$a"
+ fi
+ git status || :
+ else
+ # Merge conflict; keep the exit status from merge-recursive
+ status=$?
+ if test -n "$unstash_index"
+ then
+ echo >&2 'Index was not unstashed.'
+ fi
+ exit $status
+ fi
+}
+
+# Main command set
+case "$1" in
+list)
+ shift
+ if test $# = 0
+ then
+ set x -n 10
+ shift
+ fi
+ list_stash "$@"
+ ;;
+show)
+ shift
+ show_stash "$@"
+ ;;
+apply)
+ shift
+ apply_stash "$@"
+ ;;
+clear)
+ clear_stash
+ ;;
+help | usage)
+ usage
+ ;;
+*)
+ if test $# -gt 0 && test "$1" = save
+ then
+ shift
+ fi
+ save_stash "$*" && git-reset --hard
+ ;;
+esac
--- /dev/null
+#!/bin/sh
+#
+# git-submodules.sh: add, init, update or list git submodules
+#
+# Copyright (c) 2007 Lars Hjemli
+
+USAGE='[--quiet] [--cached] [add <repo> [-b branch]|status|init|update] [--] [<path>...]'
+. git-sh-setup
+require_work_tree
+
+add=
+branch=
+init=
+update=
+status=
+quiet=
+cached=
+
+#
+# print stuff on stdout unless -q was specified
+#
+say()
+{
+ if test -z "$quiet"
+ then
+ echo "$@"
+ fi
+}
+
+# NEEDSWORK: identical function exists in get_repo_base in clone.sh
+get_repo_base() {
+ (
+ cd "`/bin/pwd`" &&
+ cd "$1" || cd "$1.git" &&
+ {
+ cd .git
+ pwd
+ }
+ ) 2>/dev/null
+}
+
+#
+# Map submodule path to submodule name
+#
+# $1 = path
+#
+module_name()
+{
+ # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
+ re=$(printf '%s' "$1" | sed -e 's/\([^a-zA-Z0-9_]\)/\\\1/g')
+ name=$( GIT_CONFIG=.gitmodules \
+ git config --get-regexp '^submodule\..*\.path$' |
+ sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
+ test -z "$name" &&
+ die "No submodule mapping found in .gitmodules for path '$path'"
+ echo "$name"
+}
+
+#
+# Clone a submodule
+#
+# Prior to calling, modules_update checks that a possibly existing
+# path is not a git repository.
+# Likewise, module_add checks that path does not exist at all,
+# since it is the location of a new submodule.
+#
+module_clone()
+{
+ path=$1
+ url=$2
+
+ # If there already is a directory at the submodule path,
+ # expect it to be empty (since that is the default checkout
+ # action) and try to remove it.
+ # Note: if $path is a symlink to a directory the test will
+ # succeed but the rmdir will fail. We might want to fix this.
+ if test -d "$path"
+ then
+ rmdir "$path" 2>/dev/null ||
+ die "Directory '$path' exist, but is neither empty nor a git repository"
+ fi
+
+ test -e "$path" &&
+ die "A file already exist at path '$path'"
+
+ git-clone -n "$url" "$path" ||
+ die "Clone of '$url' into submodule path '$path' failed"
+}
+
+#
+# Add a new submodule to the working tree, .gitmodules and the index
+#
+# $@ = repo [path]
+#
+# optional branch is stored in global branch variable
+#
+module_add()
+{
+ repo=$1
+ path=$2
+
+ if test -z "$repo"; then
+ usage
+ fi
+
+ # Turn the source into an absolute path if
+ # it is local
+ if base=$(get_repo_base "$repo"); then
+ repo="$base"
+ fi
+
+ # Guess path from repo if not specified or strip trailing slashes
+ if test -z "$path"; then
+ path=$(echo "$repo" | sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
+ else
+ path=$(echo "$path" | sed -e 's|/*$||')
+ fi
+
+ test -e "$path" &&
+ die "'$path' already exists"
+
+ git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
+ die "'$path' already exists in the index"
+
+ module_clone "$path" "$repo" || exit
+ (unset GIT_DIR && cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
+ die "Unable to checkout submodule '$path'"
+ git add "$path" ||
+ die "Failed to add submodule '$path'"
+
+ GIT_CONFIG=.gitmodules git config submodule."$path".path "$path" &&
+ GIT_CONFIG=.gitmodules git config submodule."$path".url "$repo" &&
+ git add .gitmodules ||
+ die "Failed to register submodule '$path'"
+}
+
+#
+# Register submodules in .git/config
+#
+# $@ = requested paths (default to all)
+#
+modules_init()
+{
+ git ls-files --stage -- "$@" | grep -e '^160000 ' |
+ while read mode sha1 stage path
+ do
+ # Skip already registered paths
+ name=$(module_name "$path") || exit
+ url=$(git config submodule."$name".url)
+ test -z "$url" || continue
+
+ url=$(GIT_CONFIG=.gitmodules git config submodule."$name".url)
+ test -z "$url" &&
+ die "No url found for submodule path '$path' in .gitmodules"
+
+ git config submodule."$name".url "$url" ||
+ die "Failed to register url for submodule path '$path'"
+
+ say "Submodule '$name' ($url) registered for path '$path'"
+ done
+}
+
+#
+# Update each submodule path to correct revision, using clone and checkout as needed
+#
+# $@ = requested paths (default to all)
+#
+modules_update()
+{
+ git ls-files --stage -- "$@" | grep -e '^160000 ' |
+ while read mode sha1 stage path
+ do
+ name=$(module_name "$path") || exit
+ url=$(git config submodule."$name".url)
+ if test -z "$url"
+ then
+ # Only mention uninitialized submodules when its
+ # path have been specified
+ test "$#" != "0" &&
+ say "Submodule path '$path' not initialized"
+ continue
+ fi
+
+ if ! test -d "$path"/.git
+ then
+ module_clone "$path" "$url" || exit
+ subsha1=
+ else
+ subsha1=$(unset GIT_DIR && cd "$path" &&
+ git rev-parse --verify HEAD) ||
+ die "Unable to find current revision in submodule path '$path'"
+ fi
+
+ if test "$subsha1" != "$sha1"
+ then
+ (unset GIT_DIR && cd "$path" && git-fetch &&
+ git-checkout -q "$sha1") ||
+ die "Unable to checkout '$sha1' in submodule path '$path'"
+
+ say "Submodule path '$path': checked out '$sha1'"
+ fi
+ done
+}
+
+set_name_rev () {
+ revname=$( (
+ unset GIT_DIR &&
+ cd "$1" && {
+ git describe "$2" 2>/dev/null ||
+ git describe --tags "$2" 2>/dev/null ||
+ git describe --contains --tags "$2"
+ }
+ ) )
+ test -z "$revname" || revname=" ($revname)"
+}
+
+#
+# List all submodules, prefixed with:
+# - submodule not initialized
+# + different revision checked out
+#
+# If --cached was specified the revision in the index will be printed
+# instead of the currently checked out revision.
+#
+# $@ = requested paths (default to all)
+#
+modules_list()
+{
+ git ls-files --stage -- "$@" | grep -e '^160000 ' |
+ while read mode sha1 stage path
+ do
+ name=$(module_name "$path") || exit
+ url=$(git config submodule."$name".url)
+ if test -z "url" || ! test -d "$path"/.git
+ then
+ say "-$sha1 $path"
+ continue;
+ fi
+ set_name_rev "$path" "$sha1"
+ if git diff-files --quiet -- "$path"
+ then
+ say " $sha1 $path$revname"
+ else
+ if test -z "$cached"
+ then
+ sha1=$(unset GIT_DIR && cd "$path" && git rev-parse --verify HEAD)
+ set_name_rev "$path" "$sha1"
+ fi
+ say "+$sha1 $path$revname"
+ fi
+ done
+}
+
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ add)
+ add=1
+ ;;
+ init)
+ init=1
+ ;;
+ update)
+ update=1
+ ;;
+ status)
+ status=1
+ ;;
+ -q|--quiet)
+ quiet=1
+ ;;
+ -b|--branch)
+ case "$2" in
+ '')
+ usage
+ ;;
+ esac
+ branch="$2"; shift
+ ;;
+ --cached)
+ cached=1
+ ;;
+ --)
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+case "$add,$branch" in
+1,*)
+ ;;
+,)
+ ;;
+,*)
+ usage
+ ;;
+esac
+
+case "$add,$init,$update,$status,$cached" in
+1,,,,)
+ module_add "$@"
+ ;;
+,1,,,)
+ modules_init "$@"
+ ;;
+,,1,,)
+ modules_update "$@"
+ ;;
+,,,1,*)
+ modules_list "$@"
+ ;;
+*)
+ usage
+ ;;
+esac
use Git;
BEGIN {
- my $s;
+ # import functions from Git into our packages, en masse
+ no strict 'refs';
foreach (qw/command command_oneline command_noisy command_output_pipe
command_input_pipe command_close_pipe/) {
- $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ".
- "*Git::SVN::Migration::$_ = ".
- "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; ";
+ for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher
+ Git::SVN::Migration Git::SVN::Log Git::SVN),
+ __PACKAGE__) {
+ *{"${package}::$_"} = \&{"Git::$_"};
+ }
}
- eval $s;
}
my ($SVN);
die "Unable to determine upstream SVN information from ",
"$head history\n";
}
- my $c = $refs[-1];
my $last_rev;
- foreach my $d (@refs) {
- if (!verify_ref("$d~1")) {
- fatal "Commit $d\n",
- "has no parent commit, and therefore ",
- "nothing to diff against.\n",
- "You should be working from a repository ",
- "originally created by git-svn\n";
- }
+ my ($linear_refs, $parents) = linearize_history($gs, \@refs);
+ foreach my $d (@$linear_refs) {
unless (defined $last_rev) {
(undef, $last_rev, undef) = cmt_metadata("$d~1");
unless (defined $last_rev) {
svn_path => '');
if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
print "No changes\n$d~1 == $d\n";
+ } elsif ($parents->{$d} && @{$parents->{$d}}) {
+ $gs->{inject_parents_dcommit}->{$last_rev} =
+ $parents->{$d};
}
}
}
my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index";
return if -f $index;
- chomp(my $bare = `git config --bool --get core.bare`);
- return if $bare eq 'true';
+ return if command_oneline(qw/rev-parse --is-inside-work-tree/) eq 'false';
return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true';
command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
print STDERR "Checked out HEAD:\n ",
my $log = $cmd eq 'log';
while (<$authors>) {
chomp;
- next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
+ next unless /^(.+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
my ($user, $name, $email) = ($1, $2, $3);
if ($log) {
$Git::SVN::Log::rusers{"$name <$email>"} = $user;
sub extract_metadata {
my $id = shift or return (undef, undef, undef);
- my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
+ my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+)
\s([a-f\d\-]+)$/x);
if (!defined $rev || !$uuid || !$url) {
# some of the original repositories I made had
# identifiers like this:
- ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
+ ($rev, $uuid) = ($id =~/^\s*git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
}
return ($url, $rev, $uuid);
}
sub working_head_info {
my ($head, $refs) = @_;
- my ($fh, $ctx) = command_output_pipe('rev-list', $head);
- while (my $hash = <$fh>) {
- chomp($hash);
- my ($url, $rev, $uuid) = cmt_metadata($hash);
+ my ($fh, $ctx) = command_output_pipe('log', $head);
+ my $hash;
+ my %max;
+ while (<$fh>) {
+ if ( m{^commit ($::sha1)$} ) {
+ unshift @$refs, $hash if $hash and $refs;
+ $hash = $1;
+ next;
+ }
+ next unless s{^\s*(git-svn-id:)}{$1};
+ my ($url, $rev, $uuid) = extract_metadata($_);
if (defined $url && defined $rev) {
+ next if $max{$url} and $max{$url} < $rev;
if (my $gs = Git::SVN->find_by_url($url)) {
my $c = $gs->rev_db_get($rev);
if ($c && $c eq $hash) {
close $fh; # break the pipe
return ($url, $rev, $uuid, $gs);
+ } else {
+ $max{$url} ||= $gs->rev_db_max;
}
}
}
- unshift @$refs, $hash if $refs;
}
command_close_pipe($fh, $ctx);
(undef, undef, undef, undef);
}
+sub read_commit_parents {
+ my ($parents, $c) = @_;
+ my ($fh, $ctx) = command_output_pipe(qw/cat-file commit/, $c);
+ while (<$fh>) {
+ chomp;
+ last if '';
+ /^parent ($sha1)/ or next;
+ push @{$parents->{$c}}, $1;
+ }
+ close $fh; # break the pipe
+}
+
+sub linearize_history {
+ my ($gs, $refs) = @_;
+ my %parents;
+ foreach my $c (@$refs) {
+ read_commit_parents(\%parents, $c);
+ }
+
+ my @linear_refs;
+ my %skip = ();
+ my $last_svn_commit = $gs->last_commit;
+ foreach my $c (reverse @$refs) {
+ next if $c eq $last_svn_commit;
+ last if $skip{$c};
+
+ unshift @linear_refs, $c;
+ $skip{$c} = 1;
+
+ # we only want the first parent to diff against for linear
+ # history, we save the rest to inject when we finalize the
+ # svn commit
+ my $fp_a = verify_ref("$c~1");
+ my $fp_b = shift @{$parents{$c}} if $parents{$c};
+ if (!$fp_a || !$fp_b) {
+ die "Commit $c\n",
+ "has no parent commit, and therefore ",
+ "nothing to diff against.\n",
+ "You should be working from a repository ",
+ "originally created by git-svn\n";
+ }
+ if ($fp_a ne $fp_b) {
+ die "$c~1 = $fp_a, however parsing commit $c ",
+ "revealed that:\n$c~1 = $fp_b\nBUG!\n";
+ }
+
+ foreach my $p (@{$parents{$c}}) {
+ $skip{$p} = 1;
+ }
+ }
+ (\@linear_refs, \%parents);
+}
+
package Git::SVN;
use strict;
use warnings;
# some options are read globally, but can be overridden locally
# per [svn-remote "..."] section. Command-line options will *NOT*
# override options set in an [svn-remote "..."] section
- my $e;
- foreach (qw/follow_parent no_metadata use_svm_props
- use_svnsync_props/) {
- my $key = $_;
+ no strict 'refs';
+ for my $option (qw/follow_parent no_metadata use_svm_props
+ use_svnsync_props/) {
+ my $key = $option;
$key =~ tr/_//d;
- $e .= "sub $_ {
- my (\$self) = \@_;
- return \$self->{-$_} if exists \$self->{-$_};
- my \$k = \"svn-remote.\$self->{repo_id}\.$key\";
- eval { command_oneline(qw/config --get/, \$k) };
- if (\$@) {
- \$self->{-$_} = \$Git::SVN::_$_;
+ my $prop = "-$option";
+ *$option = sub {
+ my ($self) = @_;
+ return $self->{$prop} if exists $self->{$prop};
+ my $k = "svn-remote.$self->{repo_id}.$key";
+ eval { command_oneline(qw/config --get/, $k) };
+ if ($@) {
+ $self->{$prop} = ${"Git::SVN::_$option"};
} else {
- my \$v = command_oneline(qw/config --bool/,\$k);
- \$self->{-$_} = \$v eq 'false' ? 0 : 1;
+ my $v = command_oneline(qw/config --bool/,$k);
+ $self->{$prop} = $v eq 'false' ? 0 : 1;
}
- return \$self->{-$_} }\n";
+ return $self->{$prop};
+ }
}
- $e .= "1;\n";
- eval $e or die $@;
}
my %LOCKFILES;
foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) {
next unless m#^refs/remotes/$ref->{regex}$#;
my $p = $1;
- my $pathname = $path->full_path($p);
- my $refname = $ref->full_path($p);
+ my $pathname = desanitize_refname($path->full_path($p));
+ my $refname = desanitize_refname($ref->full_path($p));
if (my $existing = $fetch->{$pathname}) {
if ($existing ne $refname) {
die "Refspec conflict:\n",
my $r = {};
foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) {
- $r->{$1}->{fetch}->{$2} = $3;
+ my ($remote, $local_ref, $remote_ref) = ($1, $2, $3);
+ $local_ref =~ s{^/}{};
+ $r->{$remote}->{fetch}->{$local_ref} = $remote_ref;
} elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
$r->{$1}->{url} = $2;
} elsif (m!^(.+)\.(branches|tags)=
unless ($no_write) {
command_noisy('config',
"svn-remote.$self->{repo_id}.url", $url);
+ $self->{path} =~ s{^/}{};
command_noisy('config', '--add',
"svn-remote.$self->{repo_id}.fetch",
"$self->{path}:".$self->refname);
$self;
}
-sub refname { "refs/remotes/$_[0]->{ref_id}" }
+sub refname {
+ my ($refname) = "refs/remotes/$_[0]->{ref_id}" ;
+
+ # It cannot end with a slash /, we'll throw up on this because
+ # SVN can't have directories with a slash in their name, either:
+ if ($refname =~ m{/$}) {
+ die "ref: '$refname' ends with a trailing slash, this is ",
+ "not permitted by git nor Subversion\n";
+ }
+
+ # It cannot have ASCII control character space, tilde ~, caret ^,
+ # colon :, question-mark ?, asterisk *, space, or open bracket [
+ # anywhere.
+ #
+ # Additionally, % must be escaped because it is used for escaping
+ # and we want our escaped refname to be reversible
+ $refname =~ s{([ \%~\^:\?\*\[\t])}{uc sprintf('%%%02x',ord($1))}eg;
+
+ # no slash-separated component can begin with a dot .
+ # /.* becomes /%2E*
+ $refname =~ s{/\.}{/%2E}g;
+
+ # It cannot have two consecutive dots .. anywhere
+ # .. becomes %2E%2E
+ $refname =~ s{\.\.}{%2E%2E}g;
+
+ return $refname;
+}
+
+sub desanitize_refname {
+ my ($refname) = @_;
+ $refname =~ s{%(?:([0-9A-F]{2}))}{chr hex($1)}eg;
+ return $refname;
+}
sub svm_uuid {
my ($self) = @_;
my (@args) = @_;
my $old_def_config = "$ENV{GIT_DIR}/svn/config";
my $config = "$ENV{GIT_DIR}/svn/.metadata";
- if (-e $old_def_config && ! -e $config) {
+ if (! -f $config && -f $old_def_config) {
rename $old_def_config, $config or
die "Failed rename $old_def_config => $config: $!\n";
}
if (my $cur = ::verify_ref($self->refname.'^0')) {
push @tmp, $cur;
}
+ if (my $ipd = $self->{inject_parents_dcommit}) {
+ if (my $commit = delete $ipd->{$log_entry->{revision}}) {
+ push @tmp, @$commit;
+ }
+ }
push @tmp, $_ foreach (@{$log_entry->{parents}}, @tmp);
while (my $p = shift @tmp) {
next if $seen{$p};
return;
}
print "Rebuilding $db_path ...\n";
- my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname);
+ my ($log, $ctx) = command_output_pipe("log", $self->refname);
my $latest;
my $full_url = $self->full_url;
remove_username($full_url);
my $svn_uuid;
- while (<$rev_list>) {
- chomp;
- my $c = $_;
- die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o;
- my ($url, $rev, $uuid) = ::cmt_metadata($c);
+ my $c;
+ while (<$log>) {
+ if ( m{^commit ($::sha1)$} ) {
+ $c = $1;
+ next;
+ }
+ next unless s{^\s*(git-svn-id:)}{$1};
+ my ($url, $rev, $uuid) = ::extract_metadata($_);
remove_username($url);
# ignore merges (from set-tree)
$self->rev_db_set($rev, $c);
print "r$rev = $c\n";
}
- command_close_pipe($rev_list, $ctx);
+ command_close_pipe($log, $ctx);
print "Done rebuilding $db_path\n";
}
sub url_path {
my ($self, $path) = @_;
+ if ($self->{url} =~ m#^https?://#) {
+ $path =~ s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg;
+ }
$self->{url} . '/' . $self->repo_path($path);
}
BEGIN {
# enforce temporary pool usage for some simple functions
- my $e;
- foreach (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) {
- $e .= "sub $_ {
- my \$self = shift;
- my \$pool = SVN::Pool->new;
- my \@ret = \$self->SUPER::$_(\@_,\$pool);
- \$pool->clear;
- wantarray ? \@ret : \$ret[0]; }\n";
+ no strict 'refs';
+ for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) {
+ my $SUPER = "SUPER::$f";
+ *$f = sub {
+ my $self = shift;
+ my $pool = SVN::Pool->new;
+ my @ret = $self->$SUPER(@_,$pool);
+ $pool->clear;
+ wantarray ? @ret : $ret[0];
+ };
}
-
- eval "$e; 1;" or die $@;
}
sub new {
SVN::Client::get_ssl_server_trust_file_provider(),
SVN::Client::get_simple_prompt_provider(
\&Git::SVN::Prompt::simple, 2),
+ SVN::Client::get_ssl_client_cert_file_provider(),
SVN::Client::get_ssl_client_cert_prompt_provider(
\&Git::SVN::Prompt::ssl_client_cert, 2),
SVN::Client::get_ssl_client_cert_pw_prompt_provider(
\&Git::SVN::Prompt::username, 2),
]);
my $config = SVN::Core::config_get_config($config_dir);
+ $RA = undef;
my $self = SVN::Ra->new(url => $url, auth => $baton,
config => $config,
pool => SVN::Pool->new,
$editor->{git_commit_ok};
}
-sub gs_fetch_loop_common {
- my ($self, $base, $head, $gsv, $globs) = @_;
- return if ($base > $head);
- my $inc = $_log_window_size;
- my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
+sub longest_common_path {
+ my ($gsv, $globs) = @_;
my %common;
my $common_max = scalar @$gsv;
last;
}
}
+ $longest_path;
+}
+
+sub gs_fetch_loop_common {
+ my ($self, $base, $head, $gsv, $globs) = @_;
+ return if ($base > $head);
+ my $inc = $_log_window_size;
+ my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
+ my $longest_path = longest_common_path($gsv, $globs);
while (1) {
my %revs;
my $err;
if ($node_kind eq $SVN::Node::dir) {
$srcpath =~ s#/*$#/#;
}
-
+
my $pid = open my $f,'-|';
die $! unless defined $pid;
if (!$pid) {
} else {
$p = $path;
}
- push(@$new,[$mode,$sha1,$p]);
+ push(@$new,[$mode,$sha1,$p]);
}
close($f) or
print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n";
or die "Cannot write branch $dest for update: $!\n";
}
- if($tag) {
- my($in, $out) = ('','');
+ if ($tag) {
$last_rev = "-" if %$changed_paths;
# the tag was 'complex', i.e. did not refer to a "real" revision
$dest =~ tr/_/\./ if $opt_u;
- $branch = $dest;
-
- my $pid = open2($in, $out, 'git-mktag');
- print $out ("object $cid\n".
- "type commit\n".
- "tag $dest\n".
- "tagger $committer_name <$committer_email> 0 +0000\n") and
- close($out)
- or die "Cannot create tag object $dest: $!\n";
-
- my $tagobj = <$in>;
- chomp $tagobj;
-
- if ( !close($in) or waitpid($pid, 0) != $pid or
- $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
- die "Cannot create tag object $dest: $!\n";
- }
- open(C,">$git_dir/refs/tags/$dest") and
- print C ("$tagobj\n") and
- close(C)
- or die "Cannot create tag $branch: $!\n";
+ system('git-tag', $dest, $cid) == 0
+ or die "Cannot create tag $dest: $!\n";
print "Created tag '$dest' on '$branch'\n" if $opt_v;
}
#!/bin/sh
# Copyright (c) 2005 Linus Torvalds
-USAGE='-l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]'
+USAGE='[-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]'
SUBDIRECTORY_OK='Yes'
. git-sh-setup
username=
list=
verify=
+LINES=0
while case "$#" in 0) break ;; esac
do
case "$1" in
-a)
annotate=1
+ shift
;;
-s)
annotate=1
signed=1
+ shift
;;
-f)
force=1
+ shift
;;
- -l)
- case "$#" in
- 1)
- set x . ;;
+ -n)
+ case "$#,$2" in
+ 1,* | *,-*)
+ LINES=1 # no argument
+ ;;
+ *) shift
+ LINES=$(expr "$1" : '\([0-9]*\)')
+ [ -z "$LINES" ] && LINES=1 # 1 line is default when -n is used
+ ;;
esac
shift
- git rev-parse --symbolic --tags | sort | grep "$@"
- exit $?
+ ;;
+ -l)
+ list=1
+ shift
+ case $# in
+ 0) PATTERN=
+ ;;
+ *)
+ PATTERN="$1" # select tags by shell pattern, not re
+ shift
+ ;;
+ esac
+ git rev-parse --symbolic --tags | sort |
+ while read TAG
+ do
+ case "$TAG" in
+ *$PATTERN*) ;;
+ *) continue ;;
+ esac
+ [ "$LINES" -le 0 ] && { echo "$TAG"; continue ;}
+ OBJTYPE=$(git cat-file -t "$TAG")
+ case $OBJTYPE in
+ tag)
+ ANNOTATION=$(git cat-file tag "$TAG" |
+ sed -e '1,/^$/d' |
+ sed -n -e "
+ /^-----BEGIN PGP SIGNATURE-----\$/q
+ 2,\$s/^/ /
+ p
+ ${LINES}q
+ ")
+ printf "%-15s %s\n" "$TAG" "$ANNOTATION"
+ ;;
+ *) echo "$TAG"
+ ;;
+ esac
+ done
;;
-m)
- annotate=1
+ annotate=1
shift
message="$1"
if test "$#" = "0"; then
die "error: option -m needs an argument"
else
+ message="$1"
message_given=1
+ shift
fi
;;
-F)
else
message="$(cat "$1")"
message_given=1
+ shift
fi
;;
-u)
annotate=1
signed=1
shift
- username="$1"
+ if test "$#" = "0"; then
+ die "error: option -u needs an argument"
+ else
+ username="$1"
+ shift
+ fi
;;
-d)
- shift
+ shift
had_error=0
for tag
do
- cur=$(git-show-ref --verify --hash -- "refs/tags/$tag") || {
+ cur=$(git show-ref --verify --hash -- "refs/tags/$tag") || {
echo >&2 "Seriously, what tag are you talking about?"
had_error=1
continue
}
- git-update-ref -m 'tag: delete' -d "refs/tags/$tag" "$cur" || {
+ git update-ref -m 'tag: delete' -d "refs/tags/$tag" "$cur" || {
had_error=1
continue
}
-v)
shift
tag_name="$1"
- tag=$(git-show-ref --verify --hash -- "refs/tags/$tag_name") ||
+ tag=$(git show-ref --verify --hash -- "refs/tags/$tag_name") ||
die "Seriously, what tag are you talking about?"
git-verify-tag -v "$tag"
exit $?
break
;;
esac
- shift
done
+[ -n "$list" ] && exit 0
+
name="$1"
[ "$name" ] || usage
prev=0000000000000000000000000000000000000000
-if git-show-ref --verify --quiet -- "refs/tags/$name"
+if git show-ref --verify --quiet -- "refs/tags/$name"
then
test -n "$force" || die "tag '$name' already exists"
prev=`git rev-parse "refs/tags/$name"`
fi
shift
-git-check-ref-format "tags/$name" ||
+git check-ref-format "tags/$name" ||
die "we do not like '$name' as a tag name."
-object=$(git-rev-parse --verify --default HEAD "$@") || exit 1
-type=$(git-cat-file -t $object) || exit 1
+object=$(git rev-parse --verify --default HEAD "$@") || exit 1
+type=$(git cat-file -t $object) || exit 1
tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
test -n "$username" ||
- username=$(git-repo-config user.signingkey) ||
+ username=$(git repo-config user.signingkey) ||
username=$(expr "z$tagger" : 'z\(.*>\)')
trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
( echo "#"
echo "# Write a tag message"
echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
- ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR"/TAG_EDITMSG || exit
+ git_editor "$GIT_DIR"/TAG_EDITMSG || exit
else
printf '%s\n' "$message" >"$GIT_DIR"/TAG_EDITMSG
fi
grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
- git-stripspace >"$GIT_DIR"/TAG_FINALMSG
+ git stripspace >"$GIT_DIR"/TAG_FINALMSG
[ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || {
echo >&2 "No tag message?"
fi
git update-ref "refs/tags/$name" "$object" "$prev"
-
usage
fi
-type="$(git-cat-file -t "$1" 2>/dev/null)" ||
+type="$(git cat-file -t "$1" 2>/dev/null)" ||
die "$1: no such object."
test "$type" = tag ||
case "$verbose" in
t)
- git-cat-file -p "$1" |
+ git cat-file -p "$1" |
sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p
;;
esac
trap 'rm -f "$GIT_DIR/.tmp-vtag"' 0
-git-cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1
-
-cat "$GIT_DIR/.tmp-vtag" |
-sed '/-----BEGIN PGP/Q' |
+git cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1
+sed -n -e '
+ /^-----BEGIN PGP SIGNATURE-----$/q
+ p
+' <"$GIT_DIR/.tmp-vtag" |
gpg --verify "$GIT_DIR/.tmp-vtag" - || exit 1
rm -f "$GIT_DIR/.tmp-vtag"
-
#include "quote.h"
const char git_usage_string[] =
- "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]";
+ "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
static void prepend_to_path(const char *dir, int len)
{
free(path);
}
-static int handle_options(const char*** argv, int* argc)
+static int handle_options(const char*** argv, int* argc, int* envchanged)
{
int handled = 0;
usage(git_usage_string);
}
setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1);
+ if (envchanged)
+ *envchanged = 1;
(*argv)++;
(*argc)--;
handled++;
} else if (!prefixcmp(cmd, "--git-dir=")) {
setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
+ if (envchanged)
+ *envchanged = 1;
+ } else if (!strcmp(cmd, "--work-tree")) {
+ if (*argc < 2) {
+ fprintf(stderr, "No directory given for --work-tree.\n" );
+ usage(git_usage_string);
+ }
+ setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1);
+ if (envchanged)
+ *envchanged = 1;
+ (*argv)++;
+ (*argc)--;
+ } else if (!prefixcmp(cmd, "--work-tree=")) {
+ setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
+ if (envchanged)
+ *envchanged = 1;
} else if (!strcmp(cmd, "--bare")) {
static char git_dir[PATH_MAX+1];
setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
+ if (envchanged)
+ *envchanged = 1;
} else {
fprintf(stderr, "Unknown option: %s\n", cmd);
usage(git_usage_string);
static int handle_alias(int *argcp, const char ***argv)
{
- int nongit = 0, ret = 0, saved_errno = errno;
+ int nongit = 0, envchanged = 0, ret = 0, saved_errno = errno;
const char *subdir;
int count, option_count;
const char** new_argv;
git_config(git_alias_config);
if (alias_string) {
if (alias_string[0] == '!') {
+ if (*argcp > 1) {
+ int i, sz = PATH_MAX;
+ char *s = xmalloc(sz), *new_alias = s;
+
+ add_to_string(&s, &sz, alias_string, 0);
+ free(alias_string);
+ alias_string = new_alias;
+ for (i = 1; i < *argcp &&
+ !add_to_string(&s, &sz, " ", 0) &&
+ !add_to_string(&s, &sz, (*argv)[i], 1)
+ ; i++)
+ ; /* do nothing */
+ if (!sz)
+ die("Too many or long arguments");
+ }
trace_printf("trace: alias to shell cmd: %s => %s\n",
alias_command, alias_string + 1);
ret = system(alias_string + 1);
alias_string + 1, alias_command);
}
count = split_cmdline(alias_string, &new_argv);
- option_count = handle_options(&new_argv, &count);
+ option_count = handle_options(&new_argv, &count, &envchanged);
+ if (envchanged)
+ die("alias '%s' changes environment variables\n"
+ "You can use '!git' in the alias to do this.",
+ alias_command);
memmove(new_argv - option_count, new_argv,
count * sizeof(char *));
new_argv -= option_count;
* require working tree to be present -- anything uses this needs
* RUN_SETUP for reading from the configuration file.
*/
-#define NOT_BARE (1<<2)
+#define NEED_WORK_TREE (1<<2)
-static void handle_internal_command(int argc, const char **argv, char **envp)
+struct cmd_struct {
+ const char *cmd;
+ int (*fn)(int, const char **, const char *);
+ int option;
+};
+
+static int run_command(struct cmd_struct *p, int argc, const char **argv)
+{
+ int status;
+ struct stat st;
+ const char *prefix;
+
+ prefix = NULL;
+ if (p->option & RUN_SETUP)
+ prefix = setup_git_directory();
+ if (p->option & USE_PAGER)
+ setup_pager();
+ if (p->option & NEED_WORK_TREE) {
+ const char *work_tree = get_git_work_tree();
+ const char *git_dir = get_git_dir();
+ if (!is_absolute_path(git_dir))
+ set_git_dir(make_absolute_path(git_dir));
+ if (!work_tree || chdir(work_tree))
+ die("%s must be run in a work tree", p->cmd);
+ }
+ trace_argv_printf(argv, argc, "trace: built-in: git");
+
+ status = p->fn(argc, argv, prefix);
+ if (status)
+ return status;
+
+ /* Somebody closed stdout? */
+ if (fstat(fileno(stdout), &st))
+ return 0;
+ /* Ignore write errors for pipes and sockets.. */
+ if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode))
+ return 0;
+
+ /* Check for ENOSPC and EIO errors.. */
+ if (fflush(stdout))
+ die("write failure on standard output: %s", strerror(errno));
+ if (ferror(stdout))
+ die("unknown write failure on standard output");
+ if (fclose(stdout))
+ die("close failed on standard output: %s", strerror(errno));
+ return 0;
+}
+
+static void handle_internal_command(int argc, const char **argv)
{
const char *cmd = argv[0];
- static struct cmd_struct {
- const char *cmd;
- int (*fn)(int, const char **, const char *);
- int option;
- } commands[] = {
- { "add", cmd_add, RUN_SETUP | NOT_BARE },
- { "annotate", cmd_annotate, RUN_SETUP | USE_PAGER },
+ static struct cmd_struct commands[] = {
+ { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
+ { "annotate", cmd_annotate, RUN_SETUP },
{ "apply", cmd_apply },
{ "archive", cmd_archive },
{ "blame", cmd_blame, RUN_SETUP },
{ "branch", cmd_branch, RUN_SETUP },
{ "bundle", cmd_bundle },
{ "cat-file", cmd_cat_file, RUN_SETUP },
- { "checkout-index", cmd_checkout_index, RUN_SETUP },
+ { "checkout-index", cmd_checkout_index,
+ RUN_SETUP | NEED_WORK_TREE},
{ "check-ref-format", cmd_check_ref_format },
- { "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
+ { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
{ "cherry", cmd_cherry, RUN_SETUP },
- { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
+ { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config },
{ "count-objects", cmd_count_objects, RUN_SETUP },
{ "mailsplit", cmd_mailsplit },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
- { "mv", cmd_mv, RUN_SETUP | NOT_BARE },
+ { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
- { "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER },
+ { "pickaxe", cmd_blame, RUN_SETUP },
{ "prune", cmd_prune, RUN_SETUP },
{ "prune-packed", cmd_prune_packed, RUN_SETUP },
{ "push", cmd_push, RUN_SETUP },
{ "rerere", cmd_rerere, RUN_SETUP },
{ "rev-list", cmd_rev_list, RUN_SETUP },
{ "rev-parse", cmd_rev_parse, RUN_SETUP },
- { "revert", cmd_revert, RUN_SETUP | NOT_BARE },
- { "rm", cmd_rm, RUN_SETUP | NOT_BARE },
- { "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE },
+ { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
+ { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
+ { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
{ "show-branch", cmd_show_branch, RUN_SETUP },
{ "show", cmd_show, RUN_SETUP | USE_PAGER },
for (i = 0; i < ARRAY_SIZE(commands); i++) {
struct cmd_struct *p = commands+i;
- const char *prefix;
if (strcmp(p->cmd, cmd))
continue;
-
- prefix = NULL;
- if (p->option & RUN_SETUP)
- prefix = setup_git_directory();
- if (p->option & USE_PAGER)
- setup_pager();
- if ((p->option & NOT_BARE) &&
- (is_bare_repository() || is_inside_git_dir()))
- die("%s must be run in a work tree", cmd);
- trace_argv_printf(argv, argc, "trace: built-in: git");
-
- exit(p->fn(argc, argv, prefix));
+ exit(run_command(p, argc, argv));
}
}
-int main(int argc, const char **argv, char **envp)
+int main(int argc, const char **argv)
{
const char *cmd = argv[0] ? argv[0] : "git-help";
char *slash = strrchr(cmd, '/');
if (!prefixcmp(cmd, "git-")) {
cmd += 4;
argv[0] = cmd;
- handle_internal_command(argc, argv, envp);
+ handle_internal_command(argc, argv);
die("cannot handle %s internally", cmd);
}
/* Look for flags.. */
argv++;
argc--;
- handle_options(&argv, &argc);
+ handle_options(&argv, &argc, NULL);
if (argc > 0) {
if (!prefixcmp(argv[0], "--"))
argv[0] += 2;
cmd = argv[0];
/*
- * We search for git commands in the following order:
- * - git_exec_path()
- * - the path of the "git" command if we could find it
- * in $0
- * - the regular PATH.
+ * We execute external git command via execv_git_cmd(),
+ * which looks at "--exec-path" option, GIT_EXEC_PATH
+ * environment, and $(gitexecdir) in Makefile while built,
+ * in this order. For scripted commands, we prepend
+ * the value of the exec_path variable to the PATH.
*/
if (exec_path)
prepend_to_path(exec_path, strlen(exec_path));
while (1) {
/* See if it's an internal command */
- handle_internal_command(argc, argv, envp);
+ handle_internal_command(argc, argv);
/* .. then try the external ones */
execv_git_cmd(argv);
Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, git-p4, perl-Git
+Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, perl-Git
%description
Git is a fast, scalable, distributed revision control system with an
%description arch
Git tools for importing Arch repositories.
-%package p4
-Summary: Git tools for importing Perforce repositories
-Group: Development/Tools
-Requires: git-core = %{version}-%{release}, python
-%description p4
-Git tools for importing Perforce repositories.
-
%package email
Summary: Git tools for sending email
Group: Development/Tools
-Requires: git-core = %{version}-%{release}
+Requires: git-core = %{version}-%{release}
%description email
Git tools for sending email.
%setup -q
%build
-make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_P4IMPORT=YesPlease \
+make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" \
ETC_GITCONFIG=/etc/gitconfig \
- prefix=%{_prefix} PYTHON_PATH=%{python_path} all %{!?_without_docs: doc}
+ prefix=%{_prefix} all %{!?_without_docs: doc}
%install
rm -rf $RPM_BUILD_ROOT
make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \
- WITH_P4IMPORT=YesPlease prefix=%{_prefix} mandir=%{_mandir} \
+ prefix=%{_prefix} mandir=%{_mandir} \
ETC_GITCONFIG=/etc/gitconfig \
PYTHON_PATH=%{python_path} \
INSTALLDIRS=vendor install %{!?_without_docs: install-doc}
find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
-(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "p4import|archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files
(find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files
%if %{!?_without_docs:1}0
-(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "p4import|archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
%else
rm -rf $RPM_BUILD_ROOT%{_mandir}
%endif
%{!?_without_docs: %{_mandir}/man1/git-archimport.1*}
%{!?_without_docs: %doc Documentation/git-archimport.html }
-%files p4
-%defattr(-,root,root)
-%doc Documentation/git-p4import.txt
-%{_bindir}/git-p4import
-%{!?_without_docs: %{_mandir}/man1/git-p4import.1*}
-%{!?_without_docs: %doc Documentation/git-p4import.html }
-
%files email
%defattr(-,root,root)
%doc Documentation/*email*.txt
%{_bindir}/git-gui
%{_bindir}/git-citool
%{_datadir}/git-gui/
-# Not Yet...
-# %{!?_without_docs: %{_mandir}/man1/git-gui.1}
-# %{!?_without_docs: %doc Documentation/git-gui.html}
-# %{!?_without_docs: %{_mandir}/man1/git-citool.1}
-# %{!?_without_docs: %doc Documentation/git-citool.html}
+%{!?_without_docs: %{_mandir}/man1/git-gui.1*}
+%{!?_without_docs: %doc Documentation/git-gui.html}
+%{!?_without_docs: %{_mandir}/man1/git-citool.1*}
+%{!?_without_docs: %doc Documentation/git-citool.html}
%files -n gitk
%defattr(-,root,root)
%{!?_without_docs: %doc Documentation/technical}
%changelog
+* Sun Jul 15 2007 Sean Estabrooks <seanlkml@sympatico.ca>
+- Removed p4import.
+
+* Tue Jun 26 2007 Quy Tonthat <qtonthat@gmail.com>
+- Fixed problems looking for wrong manpages.
+
+* Thu Jun 21 2007 Shawn O. Pearce <spearce@spearce.org>
+- Added documentation files for git-gui
+
* Tue May 13 2007 Quy Tonthat <qtonthat@gmail.com>
- Added lib files for git-gui
- Added Documentation/technical (As needed by Git Users Manual)
}
}
+# A simple scheduler for compute-intensive stuff.
+# The aim is to make sure that event handlers for GUI actions can
+# run at least every 50-100 ms. Unfortunately fileevent handlers are
+# run before X event handlers, so reading from a fast source can
+# make the GUI completely unresponsive.
+proc run args {
+ global isonrunq runq
+
+ set script $args
+ if {[info exists isonrunq($script)]} return
+ if {$runq eq {}} {
+ after idle dorunq
+ }
+ lappend runq [list {} $script]
+ set isonrunq($script) 1
+}
+
+proc filerun {fd script} {
+ fileevent $fd readable [list filereadable $fd $script]
+}
+
+proc filereadable {fd script} {
+ global runq
+
+ fileevent $fd readable {}
+ if {$runq eq {}} {
+ after idle dorunq
+ }
+ lappend runq [list $fd $script]
+}
+
+proc dorunq {} {
+ global isonrunq runq
+
+ set tstart [clock clicks -milliseconds]
+ set t0 $tstart
+ while {$runq ne {}} {
+ set fd [lindex $runq 0 0]
+ set script [lindex $runq 0 1]
+ set repeat [eval $script]
+ set t1 [clock clicks -milliseconds]
+ set t [expr {$t1 - $t0}]
+ set runq [lrange $runq 1 end]
+ if {$repeat ne {} && $repeat} {
+ if {$fd eq {} || $repeat == 2} {
+ # script returns 1 if it wants to be readded
+ # file readers return 2 if they could do more straight away
+ lappend runq [list $fd $script]
+ } else {
+ fileevent $fd readable [list filereadable $fd $script]
+ }
+ } elseif {$fd eq {}} {
+ unset isonrunq($script)
+ }
+ set t0 $t1
+ if {$t1 - $tstart >= 80} break
+ }
+ if {$runq ne {}} {
+ after idle dorunq
+ }
+}
+
+# Start off a git rev-list process and arrange to read its output
proc start_rev_list {view} {
- global startmsecs nextupdate
+ global startmsecs
global commfd leftover tclencoding datemode
global viewargs viewfiles commitidx
+ global lookingforhead showlocalchanges
set startmsecs [clock clicks -milliseconds]
- set nextupdate [expr {$startmsecs + 100}]
set commitidx($view) 0
- set args $viewargs($view)
- if {$viewfiles($view) ne {}} {
- set args [concat $args "--" $viewfiles($view)]
- }
set order "--topo-order"
if {$datemode} {
set order "--date-order"
}
if {[catch {
- set fd [open [concat | git rev-list --header $order \
- --parents --boundary --default HEAD $args] r]
+ set fd [open [concat | git log -z --pretty=raw $order --parents \
+ --boundary $viewargs($view) "--" $viewfiles($view)] r]
} err]} {
- puts stderr "Error executing git rev-list: $err"
+ error_popup "Error executing git rev-list: $err"
exit 1
}
set commfd($view) $fd
set leftover($view) {}
- fconfigure $fd -blocking 0 -translation lf
+ set lookingforhead $showlocalchanges
+ fconfigure $fd -blocking 0 -translation lf -eofchar {}
if {$tclencoding != {}} {
fconfigure $fd -encoding $tclencoding
}
- fileevent $fd readable [list getcommitlines $fd $view]
+ filerun $fd [list getcommitlines $fd $view]
nowbusy $view
}
}
proc getcommitlines {fd view} {
- global commitlisted nextupdate
+ global commitlisted
global leftover commfd
global displayorder commitidx commitrow commitdata
- global parentlist childlist children curview hlview
- global vparentlist vchildlist vdisporder vcmitlisted
+ global parentlist children curview hlview
+ global vparentlist vdisporder vcmitlisted
set stuff [read $fd 500000]
+ # git log doesn't terminate the last commit with a null...
+ if {$stuff == {} && $leftover($view) ne {} && [eof $fd]} {
+ set stuff "\0"
+ }
if {$stuff == {}} {
- if {![eof $fd]} return
+ if {![eof $fd]} {
+ return 1
+ }
global viewname
unset commfd($view)
notbusy $view
error_popup $err
}
if {$view == $curview} {
- after idle finishcommits
+ run chewcommits $view
}
- return
+ return 0
}
set start 0
set gotsome 0
set j [string first "\n" $cmit]
set ok 0
set listed 1
- if {$j >= 0} {
- set ids [string range $cmit 0 [expr {$j - 1}]]
- if {[string range $ids 0 0] == "-"} {
- set listed 0
+ if {$j >= 0 && [string match "commit *" $cmit]} {
+ set ids [string range $cmit 7 [expr {$j - 1}]]
+ if {[string match {[-<>]*} $ids]} {
+ switch -- [string index $ids 0] {
+ "-" {set listed 0}
+ "<" {set listed 2}
+ ">" {set listed 3}
+ }
set ids [string range $ids 1 end]
}
set ok 1
if {[string length $shortcmit] > 80} {
set shortcmit "[string range $shortcmit 0 80]..."
}
- error_popup "Can't parse git rev-list output: {$shortcmit}"
+ error_popup "Can't parse git log output: {$shortcmit}"
exit 1
}
set id [lindex $ids 0]
incr commitidx($view)
if {$view == $curview} {
lappend parentlist $olds
- lappend childlist $children($view,$id)
lappend displayorder $id
lappend commitlisted $listed
} else {
lappend vparentlist($view) $olds
- lappend vchildlist($view) $children($view,$id)
lappend vdisporder($view) $id
lappend vcmitlisted($view) $listed
}
set gotsome 1
}
if {$gotsome} {
- if {$view == $curview} {
- while {[layoutmore $nextupdate]} doupdate
- } elseif {[info exists hlview] && $view == $hlview} {
- vhighlightmore
- }
- }
- if {[clock clicks -milliseconds] >= $nextupdate} {
- doupdate
+ run chewcommits $view
}
+ return 2
}
-proc doupdate {} {
- global commfd nextupdate numcommits
+proc chewcommits {view} {
+ global curview hlview commfd
+ global selectedline pending_select
- foreach v [array names commfd] {
- fileevent $commfd($v) readable {}
+ set more 0
+ if {$view == $curview} {
+ set allread [expr {![info exists commfd($view)]}]
+ set tlimit [expr {[clock clicks -milliseconds] + 50}]
+ set more [layoutmore $tlimit $allread]
+ if {$allread && !$more} {
+ global displayorder commitidx phase
+ global numcommits startmsecs
+
+ if {[info exists pending_select]} {
+ set row [first_real_row]
+ selectline $row 1
+ }
+ if {$commitidx($curview) > 0} {
+ #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
+ #puts "overall $ms ms for $numcommits commits"
+ } else {
+ show_status "No commits selected"
+ }
+ notbusy layout
+ set phase {}
+ }
}
- update
- set nextupdate [expr {[clock clicks -milliseconds] + 100}]
- foreach v [array names commfd] {
- set fd $commfd($v)
- fileevent $fd readable [list getcommitlines $fd $v]
+ if {[info exists hlview] && $view == $hlview} {
+ vhighlightmore
}
+ return $more
}
proc readcommit {id} {
catch {unset selectedline}
catch {unset thickerline}
catch {unset viewdata($n)}
- discardallcommits
readrefs
+ changedrefs
+ regetallcommits
showview $n
}
}
}
set headline {}
- # take the first line of the comment as the headline
- set i [string first "\n" $comment]
+ # take the first non-blank line of the comment as the headline
+ set headline [string trimleft $comment]
+ set i [string first "\n" $headline]
if {$i >= 0} {
- set headline [string trim [string range $comment 0 $i]]
- } else {
- set headline $comment
+ set headline [string range $headline 0 $i]
+ }
+ set headline [string trimright $headline]
+ set i [string first "\r" $headline]
+ if {$i >= 0} {
+ set headline [string trimright [string range $headline 0 $i]]
}
if {!$listed} {
# git rev-list indents the comment by 4 spaces;
}
proc readrefs {} {
- global tagids idtags headids idheads tagcontents
- global otherrefids idotherrefs mainhead
+ global tagids idtags headids idheads tagobjid
+ global otherrefids idotherrefs mainhead mainheadid
foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
catch {unset $v}
}
- set refd [open [list | git show-ref] r]
- while {0 <= [set n [gets $refd line]]} {
- if {![regexp {^([0-9a-f]{40}) refs/([^^]*)$} $line \
- match id path]} {
- continue
- }
- if {[regexp {^remotes/.*/HEAD$} $path match]} {
- continue
- }
- if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
- set type others
- set name $path
- }
- if {[regexp {^remotes/} $path match]} {
- set type heads
- }
- if {$type == "tags"} {
- set tagids($name) $id
- lappend idtags($id) $name
- set obj {}
- set type {}
- set tag {}
- catch {
- set commit [exec git rev-parse "$id^0"]
- if {$commit != $id} {
- set tagids($name) $commit
- lappend idtags($commit) $name
- }
- }
- catch {
- set tagcontents($name) [exec git cat-file tag $id]
+ set refd [open [list | git show-ref -d] r]
+ while {[gets $refd line] >= 0} {
+ if {[string index $line 40] ne " "} continue
+ set id [string range $line 0 39]
+ set ref [string range $line 41 end]
+ if {![string match "refs/*" $ref]} continue
+ set name [string range $ref 5 end]
+ if {[string match "remotes/*" $name]} {
+ if {![string match "*/HEAD" $name]} {
+ set headids($name) $id
+ lappend idheads($id) $name
}
- } elseif { $type == "heads" } {
+ } elseif {[string match "heads/*" $name]} {
+ set name [string range $name 6 end]
set headids($name) $id
lappend idheads($id) $name
+ } elseif {[string match "tags/*" $name]} {
+ # this lets refs/tags/foo^{} overwrite refs/tags/foo,
+ # which is what we want since the former is the commit ID
+ set name [string range $name 5 end]
+ if {[string match "*^{}" $name]} {
+ set name [string range $name 0 end-3]
+ } else {
+ set tagobjid($name) $id
+ }
+ set tagids($name) $id
+ lappend idtags($id) $name
} else {
set otherrefids($name) $id
lappend idotherrefs($id) $name
}
close $refd
set mainhead {}
+ set mainheadid {}
catch {
set thehead [exec git symbolic-ref HEAD]
if {[string match "refs/heads/*" $thehead]} {
set mainhead [string range $thehead 11 end]
+ if {[info exists headids($mainhead)]} {
+ set mainheadid $headids($mainhead)
+ }
+ }
+ }
+}
+
+# skip over fake commits
+proc first_real_row {} {
+ global nullid nullid2 displayorder numcommits
+
+ for {set row 0} {$row < $numcommits} {incr row} {
+ set id [lindex $displayorder $row]
+ if {$id ne $nullid && $id ne $nullid2} {
+ break
+ }
+ }
+ return $row
+}
+
+# update things for a head moved to a child of its previous location
+proc movehead {id name} {
+ global headids idheads
+
+ removehead $headids($name) $name
+ set headids($name) $id
+ lappend idheads($id) $name
+}
+
+# update things when a head has been removed
+proc removehead {id name} {
+ global headids idheads
+
+ if {$idheads($id) eq $name} {
+ unset idheads($id)
+ } else {
+ set i [lsearch -exact $idheads($id) $name]
+ if {$i >= 0} {
+ set idheads($id) [lreplace $idheads($id) $i $i]
}
}
+ unset headids($name)
}
proc show_error {w top msg} {
proc makewindow {} {
global canv canv2 canv3 linespc charspc ctext cflist
- global textfont mainfont uifont
+ global textfont mainfont uifont tabstop
global findtype findtypemenu findloc findstring fstring geometry
global entries sha1entry sha1string sha1but
global maincursor textcursor curtextcursor
- global rowctxmenu mergemax wrapcomment
+ global rowctxmenu fakerowmenu mergemax wrapcomment
global highlight_files gdttype
global searchstring sstring
- global bgcolor fgcolor bglist fglist diffcolors
+ global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
global headctxmenu
menu .bar
set cscroll .tf.histframe.csb
set canv .tf.histframe.pwclist.canv
canvas $canv \
+ -selectbackground $selectbgcolor \
-background $bgcolor -bd 0 \
-yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
.tf.histframe.pwclist add $canv
set canv2 .tf.histframe.pwclist.canv2
canvas $canv2 \
+ -selectbackground $selectbgcolor \
-background $bgcolor -bd 0 -yscrollincr $linespc
.tf.histframe.pwclist add $canv2
set canv3 .tf.histframe.pwclist.canv3
canvas $canv3 \
+ -selectbackground $selectbgcolor \
-background $bgcolor -bd 0 -yscrollincr $linespc
.tf.histframe.pwclist add $canv3
eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
set ctext .bleft.ctext
text $ctext -background $bgcolor -foreground $fgcolor \
+ -tabs "[expr {$tabstop * $charspc}]" \
-state disabled -font $textfont \
-yscrollcommand scrolltext -wrap none
scrollbar .bleft.sb -command "$ctext yview"
set cflist .bright.cfiles
set indent [font measure $mainfont "nn"]
text $cflist \
+ -selectbackground $selectbgcolor \
-background $bgcolor -foreground $fgcolor \
-font $mainfont \
-tabs [list $indent [expr {2 * $indent}]] \
wm geometry . "$geometry(main)"
}
+ if {[tk windowingsystem] eq {aqua}} {
+ set M1B M1
+ } else {
+ set M1B Control
+ }
+
bind .pwbottom <Configure> {resizecdetpanes %W %w}
pack .ctop -fill both -expand 1
bindall <1> {selcanvline %W %x %y}
bindkey <Key-Left> "goback"
bind . <Key-Prior> "selnextpage -1"
bind . <Key-Next> "selnextpage 1"
- bind . <Control-Home> "allcanvs yview moveto 0.0"
- bind . <Control-End> "allcanvs yview moveto 1.0"
- bind . <Control-Key-Up> "allcanvs yview scroll -1 units"
- bind . <Control-Key-Down> "allcanvs yview scroll 1 units"
- bind . <Control-Key-Prior> "allcanvs yview scroll -1 pages"
- bind . <Control-Key-Next> "allcanvs yview scroll 1 pages"
+ bind . <$M1B-Home> "allcanvs yview moveto 0.0"
+ bind . <$M1B-End> "allcanvs yview moveto 1.0"
+ bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
+ bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
+ bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
+ bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
bindkey <Key-Delete> "$ctext yview scroll -1 pages"
bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
bindkey <Key-space> "$ctext yview scroll 1 pages"
bindkey ? findprev
bindkey f nextfile
bindkey <F5> updatecommits
- bind . <Control-q> doquit
- bind . <Control-f> dofind
- bind . <Control-g> {findnext 0}
- bind . <Control-r> dosearchback
- bind . <Control-s> dosearch
- bind . <Control-equal> {incrfont 1}
- bind . <Control-KP_Add> {incrfont 1}
- bind . <Control-minus> {incrfont -1}
- bind . <Control-KP_Subtract> {incrfont -1}
+ bind . <$M1B-q> doquit
+ bind . <$M1B-f> dofind
+ bind . <$M1B-g> {findnext 0}
+ bind . <$M1B-r> dosearchback
+ bind . <$M1B-s> dosearch
+ bind . <$M1B-equal> {incrfont 1}
+ bind . <$M1B-KP_Add> {incrfont 1}
+ bind . <$M1B-minus> {incrfont -1}
+ bind . <$M1B-KP_Subtract> {incrfont -1}
wm protocol . WM_DELETE_WINDOW doquit
bind . <Button-1> "click %W"
bind $fstring <Key-Return> dofind
$rowctxmenu add command -label "Create new branch" -command mkbranch
$rowctxmenu add command -label "Cherry-pick this commit" \
-command cherrypick
+ $rowctxmenu add command -label "Reset HEAD branch to here" \
+ -command resethead
+
+ set fakerowmenu .fakerowmenu
+ menu $fakerowmenu -tearoff 0
+ $fakerowmenu add command -label "Diff this -> selected" \
+ -command {diffvssel 0}
+ $fakerowmenu add command -label "Diff selected -> this" \
+ -command {diffvssel 1}
+ $fakerowmenu add command -label "Make patch" -command mkpatch
+# $fakerowmenu add command -label "Commit" -command {mkcommit 0}
+# $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
+# $fakerowmenu add command -label "Revert local changes" -command revertlocal
set headctxmenu .headctxmenu
menu $headctxmenu -tearoff 0
}
proc savestuff {w} {
- global canv canv2 canv3 ctext cflist mainfont textfont uifont
+ global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
global stuffsaved findmergefiles maxgraphpct
- global maxwidth showneartags
+ global maxwidth showneartags showlocalchanges
global viewname viewfiles viewargs viewperm nextviewnum
global cmitmode wrapcomment
- global colors bgcolor fgcolor diffcolors
+ global colors bgcolor fgcolor diffcolors selectbgcolor
if {$stuffsaved} return
if {![winfo viewable .]} return
puts $f [list set mainfont $mainfont]
puts $f [list set textfont $textfont]
puts $f [list set uifont $uifont]
+ puts $f [list set tabstop $tabstop]
puts $f [list set findmergefiles $findmergefiles]
puts $f [list set maxgraphpct $maxgraphpct]
puts $f [list set maxwidth $maxwidth]
puts $f [list set cmitmode $cmitmode]
puts $f [list set wrapcomment $wrapcomment]
puts $f [list set showneartags $showneartags]
+ puts $f [list set showlocalchanges $showlocalchanges]
puts $f [list set bgcolor $bgcolor]
puts $f [list set fgcolor $fgcolor]
puts $f [list set colors $colors]
puts $f [list set diffcolors $diffcolors]
+ puts $f [list set selectbgcolor $selectbgcolor]
puts $f "set geometry(main) [wm geometry .]"
puts $f "set geometry(topwidth) [winfo width .tf]"
raise $w
return
}
+ if {[tk windowingsystem] eq {aqua}} {
+ set M1T Cmd
+ } else {
+ set M1T Ctrl
+ }
toplevel $w
wm title $w "Gitk key bindings"
- message $w.m -text {
+ message $w.m -text "
Gitk key bindings:
-<Ctrl-Q> Quit
+<$M1T-Q> Quit
<Home> Move to first commit
<End> Move to last commit
<Up>, p, i Move up one commit
<Right>, x, l Go forward in history list
<PageUp> Move up one page in commit list
<PageDown> Move down one page in commit list
-<Ctrl-Home> Scroll to top of commit list
-<Ctrl-End> Scroll to bottom of commit list
-<Ctrl-Up> Scroll commit list up one line
-<Ctrl-Down> Scroll commit list down one line
-<Ctrl-PageUp> Scroll commit list up one page
-<Ctrl-PageDown> Scroll commit list down one page
+<$M1T-Home> Scroll to top of commit list
+<$M1T-End> Scroll to bottom of commit list
+<$M1T-Up> Scroll commit list up one line
+<$M1T-Down> Scroll commit list down one line
+<$M1T-PageUp> Scroll commit list up one page
+<$M1T-PageDown> Scroll commit list down one page
<Shift-Up> Move to previous highlighted line
<Shift-Down> Move to next highlighted line
<Delete>, b Scroll diff view up one page
<Space> Scroll diff view down one page
u Scroll diff view up 18 lines
d Scroll diff view down 18 lines
-<Ctrl-F> Find
-<Ctrl-G> Move to next find hit
+<$M1T-F> Find
+<$M1T-G> Move to next find hit
<Return> Move to next find hit
/ Move to next find hit, or redo find
? Move to previous find hit
f Scroll diff view to next file
-<Ctrl-S> Search for next hit in diff view
-<Ctrl-R> Search for previous hit in diff view
-<Ctrl-KP+> Increase font size
-<Ctrl-plus> Increase font size
-<Ctrl-KP-> Decrease font size
-<Ctrl-minus> Decrease font size
+<$M1T-S> Search for next hit in diff view
+<$M1T-R> Search for previous hit in diff view
+<$M1T-KP+> Increase font size
+<$M1T-plus> Increase font size
+<$M1T-KP-> Decrease font size
+<$M1T-minus> Decrease font size
<F5> Update
-} \
+" \
-justify left -bg white -border 2 -relief groove
pack $w.m -side top -fill both -padx 2 -pady 2
$w.m configure -font $uifont
set treeheight($prefix) $ht
incr ht [lindex $htstack end]
set htstack [lreplace $htstack end end]
+ set prefixend [lindex $prefendstack end]
+ set prefendstack [lreplace $prefendstack end end]
+ set prefix [string range $prefix 0 $prefixend]
}
$w conf -state disabled
}
set viewargs($n) $newargs
addviewmenu $n
if {!$newishighlight} {
- after idle showview $n
+ run showview $n
} else {
- after idle addvhighlight $n
+ run addvhighlight $n
}
} else {
# editing an existing view
set viewfiles($n) $files
set viewargs($n) $newargs
if {$curview == $n} {
- after idle updatecommits
+ run updatecommits
}
}
}
proc showview {n} {
global curview viewdata viewfiles
- global displayorder parentlist childlist rowidlist rowoffsets
+ global displayorder parentlist rowidlist rowoffsets
global colormap rowtextx commitrow nextcolor canvxmax
- global numcommits rowrangelist commitlisted idrowranges
+ global numcommits rowrangelist commitlisted idrowranges rowchk
global selectedline currentid canv canvy0
- global matchinglines treediffs
+ global treediffs
global pending_select phase
- global commitidx rowlaidout rowoptim linesegends
- global commfd nextupdate
- global selectedview
- global vparentlist vchildlist vdisporder vcmitlisted
+ global commitidx rowlaidout rowoptim
+ global commfd
+ global selectedview selectfirst
+ global vparentlist vdisporder vcmitlisted
global hlview selectedhlview
if {$n == $curview} return
} else {
set yscreen [expr {($ybot - $ytop) / 2}]
}
+ } elseif {[info exists pending_select]} {
+ set selid $pending_select
+ unset pending_select
}
unselectline
normalline
- stopfindproc
if {$curview >= 0} {
set vparentlist($curview) $parentlist
- set vchildlist($curview) $childlist
set vdisporder($curview) $displayorder
set vcmitlisted($curview) $commitlisted
if {$phase ne {}} {
set viewdata($curview) \
[list $phase $rowidlist $rowoffsets $rowrangelist \
[flatten idrowranges] [flatten idinlist] \
- $rowlaidout $rowoptim $numcommits $linesegends]
+ $rowlaidout $rowoptim $numcommits]
} elseif {![info exists viewdata($curview)]
|| [lindex $viewdata($curview) 0] ne {}} {
set viewdata($curview) \
[list {} $rowidlist $rowoffsets $rowrangelist]
}
}
- catch {unset matchinglines}
catch {unset treediffs}
clear_display
if {[info exists hlview] && $hlview == $n} {
.bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
if {![info exists viewdata($n)]} {
- set pending_select $selid
+ if {$selid ne {}} {
+ set pending_select $selid
+ }
getcommits
return
}
set phase [lindex $v 0]
set displayorder $vdisporder($n)
set parentlist $vparentlist($n)
- set childlist $vchildlist($n)
set commitlisted $vcmitlisted($n)
set rowidlist [lindex $v 1]
set rowoffsets [lindex $v 2]
set rowlaidout [lindex $v 6]
set rowoptim [lindex $v 7]
set numcommits [lindex $v 8]
- set linesegends [lindex $v 9]
+ catch {unset rowchk}
}
catch {unset colormap}
set row 0
setcanvscroll
set yf 0
- set row 0
+ set row {}
+ set selectfirst 0
if {$selid ne {} && [info exists commitrow($n,$selid)]} {
set row $commitrow($n,$selid)
# try to get the selected row in the same position on the screen
}
allcanvs yview moveto $yf
drawvisible
- selectline $row 0
+ if {$row ne {}} {
+ selectline $row 0
+ } elseif {$selid ne {}} {
+ set pending_select $selid
+ } else {
+ set row [first_real_row]
+ if {$row < $numcommits} {
+ selectline $row 0
+ } else {
+ set selectfirst 1
+ }
+ }
if {$phase ne {}} {
if {$phase eq "getcommits"} {
show_status "Reading commits..."
}
- if {[info exists commfd($n)]} {
- layoutmore {}
- } else {
- finishcommits
- }
+ run chewcommits $n
} elseif {$numcommits == 0} {
show_status "No commits selected"
}
if {$n != $curview && ![info exists viewdata($n)]} {
set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
set vparentlist($n) {}
- set vchildlist($n) {}
set vdisporder($n) {}
set vcmitlisted($n) {}
start_rev_list $n
set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
set filehighlight [open $cmd r+]
fconfigure $filehighlight -blocking 0
- fileevent $filehighlight readable readfhighlight
+ filerun $filehighlight readfhighlight
set fhl_list {}
drawvisible
flushhighlights
global filehighlight fhighlights commitrow curview mainfont iddrawn
global fhl_list
- while {[gets $filehighlight line] >= 0} {
+ if {![info exists filehighlight]} {
+ return 0
+ }
+ set nr 0
+ while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
set line [string trim $line]
set i [lsearch -exact $fhl_list $line]
if {$i < 0} continue
puts "oops, git diff-tree died"
catch {close $filehighlight}
unset filehighlight
+ return 0
}
next_hlcont
+ return 1
}
proc find_change {name ix op} {
set boldnamerows {}
catch {unset nhighlights}
unbolden
+ unmarkmatches
if {$findtype ne "Regexp"} {
set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
$findstring]
drawvisible
}
+proc doesmatch {f} {
+ global findtype findstring findpattern
+
+ if {$findtype eq "Regexp"} {
+ return [regexp $findstring $f]
+ } elseif {$findtype eq "IgnCase"} {
+ return [string match -nocase $findpattern $f]
+ } else {
+ return [string match $findpattern $f]
+ }
+}
+
proc askfindhighlight {row id} {
global nhighlights commitinfo iddrawn mainfont
- global findstring findtype findloc findpattern
+ global findloc
+ global markingmatches
if {![info exists commitinfo($id)]} {
getcommit $id
set isbold 0
set fldtypes {Headline Author Date Committer CDate Comments}
foreach f $info ty $fldtypes {
- if {$findloc ne "All fields" && $findloc ne $ty} {
- continue
- }
- if {$findtype eq "Regexp"} {
- set doesmatch [regexp $findstring $f]
- } elseif {$findtype eq "IgnCase"} {
- set doesmatch [string match -nocase $findpattern $f]
- } else {
- set doesmatch [string match $findpattern $f]
- }
- if {$doesmatch} {
+ if {($findloc eq "All fields" || $findloc eq $ty) &&
+ [doesmatch $f]} {
if {$ty eq "Author"} {
set isbold 2
- } else {
- set isbold 1
+ break
}
+ set isbold 1
}
}
- if {[info exists iddrawn($id)]} {
- if {$isbold && ![ishighlighted $row]} {
- bolden $row [concat $mainfont bold]
+ if {$isbold && [info exists iddrawn($id)]} {
+ set f [concat $mainfont bold]
+ if {![ishighlighted $row]} {
+ bolden $row $f
+ if {$isbold > 1} {
+ bolden_name $row $f
+ }
}
- if {$isbold >= 2} {
- bolden_name $row [concat $mainfont bold]
+ if {$markingmatches} {
+ markrowmatches $row $id
}
}
set nhighlights($row) $isbold
}
+proc markrowmatches {row id} {
+ global canv canv2 linehtag linentag commitinfo findloc
+
+ set headline [lindex $commitinfo($id) 0]
+ set author [lindex $commitinfo($id) 1]
+ $canv delete match$row
+ $canv2 delete match$row
+ if {$findloc eq "All fields" || $findloc eq "Headline"} {
+ set m [findmatches $headline]
+ if {$m ne {}} {
+ markmatches $canv $row $headline $linehtag($row) $m \
+ [$canv itemcget $linehtag($row) -font] $row
+ }
+ }
+ if {$findloc eq "All fields" || $findloc eq "Author"} {
+ set m [findmatches $author]
+ if {$m ne {}} {
+ markmatches $canv2 $row $author $linentag($row) $m \
+ [$canv2 itemcget $linentag($row) -font] $row
+ }
+ }
+}
+
proc vrel_change {name ix op} {
global highlight_related
rhighlight_none
if {$highlight_related ne "None"} {
- after idle drawvisible
+ run drawvisible
}
}
set anc_todo [list $a]
if {$highlight_related ne "None"} {
rhighlight_none
- after idle drawvisible
+ run drawvisible
}
}
}
proc usedinrange {id l1 l2} {
- global children commitrow childlist curview
+ global children commitrow curview
if {[info exists commitrow($curview,$id)]} {
set r $commitrow($curview,$id)
if {$l1 <= $r && $r <= $l2} {
return [expr {$r - $l1 + 1}]
}
- set kids [lindex $childlist $r]
- } else {
- set kids $children($curview,$id)
}
+ set kids $children($curview,$id)
foreach c $kids {
set r $commitrow($curview,$c)
if {$l1 <= $r && $r <= $l2} {
}
proc makeuparrow {oid x y z} {
- global rowidlist rowoffsets uparrowlen idrowranges
+ global rowidlist rowoffsets uparrowlen idrowranges displayorder
for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
incr y -1
}
set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
- lappend idrowranges($oid) $y
+ lappend idrowranges($oid) [lindex $displayorder $y]
}
proc initlayout {} {
global idinlist rowchk rowrangelist idrowranges
global numcommits canvxmax canv
global nextcolor
- global parentlist childlist children
+ global parentlist
global colormap rowtextx
- global linesegends
+ global selectfirst
set numcommits 0
set displayorder {}
set commitlisted {}
set parentlist {}
- set childlist {}
set rowrangelist {}
set nextcolor 0
set rowidlist {{}}
catch {unset colormap}
catch {unset rowtextx}
catch {unset idrowranges}
- set linesegends {}
+ set selectfirst 1
}
proc setcanvscroll {} {
return [list $r0 $r1]
}
-proc layoutmore {tmax} {
+proc layoutmore {tmax allread} {
global rowlaidout rowoptim commitidx numcommits optim_delay
- global uparrowlen curview
+ global uparrowlen curview rowidlist idinlist
+ set showlast 0
+ set showdelay $optim_delay
+ set optdelay [expr {$uparrowlen + 1}]
while {1} {
- if {$rowoptim - $optim_delay > $numcommits} {
- showstuff [expr {$rowoptim - $optim_delay}]
- } elseif {$rowlaidout - $uparrowlen - 1 > $rowoptim} {
- set nr [expr {$rowlaidout - $uparrowlen - 1 - $rowoptim}]
+ if {$rowoptim - $showdelay > $numcommits} {
+ showstuff [expr {$rowoptim - $showdelay}] $showlast
+ } elseif {$rowlaidout - $optdelay > $rowoptim} {
+ set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
if {$nr > 100} {
set nr 100
}
set nr 150
}
set row $rowlaidout
- set rowlaidout [layoutrows $row [expr {$row + $nr}] 0]
+ set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
if {$rowlaidout == $row} {
return 0
}
+ } elseif {$allread} {
+ set optdelay 0
+ set nrows $commitidx($curview)
+ if {[lindex $rowidlist $nrows] ne {} ||
+ [array names idinlist] ne {}} {
+ layouttail
+ set rowlaidout $commitidx($curview)
+ } elseif {$rowoptim == $nrows} {
+ set showdelay 0
+ set showlast 1
+ if {$numcommits == $nrows} {
+ return 0
+ }
+ }
} else {
return 0
}
}
}
-proc showstuff {canshow} {
- global numcommits commitrow pending_select selectedline
- global linesegends idrowranges idrangedrawn curview
+proc showstuff {canshow last} {
+ global numcommits commitrow pending_select selectedline curview
+ global lookingforhead mainheadid displayorder selectfirst
+ global lastscrollset
if {$numcommits == 0} {
global phase
set phase "incrdraw"
allcanvs delete all
}
- set row $numcommits
+ set r0 $numcommits
+ set prev $numcommits
set numcommits $canshow
- setcanvscroll
+ set t [clock clicks -milliseconds]
+ if {$prev < 100 || $last || $t - $lastscrollset > 500} {
+ set lastscrollset $t
+ setcanvscroll
+ }
set rows [visiblerows]
- set r0 [lindex $rows 0]
set r1 [lindex $rows 1]
- set selrow -1
- for {set r $row} {$r < $canshow} {incr r} {
- foreach id [lindex $linesegends [expr {$r+1}]] {
- set i -1
- foreach {s e} [rowranges $id] {
- incr i
- if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
- && ![info exists idrangedrawn($id,$i)]} {
- drawlineseg $id $i
- set idrangedrawn($id,$i) 1
- }
- }
- }
- }
- if {$canshow > $r1} {
- set canshow $r1
+ if {$r1 >= $canshow} {
+ set r1 [expr {$canshow - 1}]
}
- while {$row < $canshow} {
- drawcmitrow $row
- incr row
+ if {$r0 <= $r1} {
+ drawcommits $r0 $r1
}
if {[info exists pending_select] &&
[info exists commitrow($curview,$pending_select)] &&
$commitrow($curview,$pending_select) < $numcommits} {
selectline $commitrow($curview,$pending_select) 1
}
- if {![info exists selectedline] && ![info exists pending_select]} {
- selectline 0 1
+ if {$selectfirst} {
+ if {[info exists selectedline] || [info exists pending_select]} {
+ set selectfirst 0
+ } else {
+ set l [first_real_row]
+ selectline $l 1
+ set selectfirst 0
+ }
+ }
+ if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
+ && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
+ set lookingforhead 0
+ dodiffindex
+ }
+}
+
+proc doshowlocalchanges {} {
+ global lookingforhead curview mainheadid phase commitrow
+
+ if {[info exists commitrow($curview,$mainheadid)] &&
+ ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
+ dodiffindex
+ } elseif {$phase ne {}} {
+ set lookingforhead 1
+ }
+}
+
+proc dohidelocalchanges {} {
+ global lookingforhead localfrow localirow lserial
+
+ set lookingforhead 0
+ if {$localfrow >= 0} {
+ removerow $localfrow
+ set localfrow -1
+ if {$localirow > 0} {
+ incr localirow -1
+ }
+ }
+ if {$localirow >= 0} {
+ removerow $localirow
+ set localirow -1
+ }
+ incr lserial
+}
+
+# spawn off a process to do git diff-index --cached HEAD
+proc dodiffindex {} {
+ global localirow localfrow lserial
+
+ incr lserial
+ set localfrow -1
+ set localirow -1
+ set fd [open "|git diff-index --cached HEAD" r]
+ fconfigure $fd -blocking 0
+ filerun $fd [list readdiffindex $fd $lserial]
+}
+
+proc readdiffindex {fd serial} {
+ global localirow commitrow mainheadid nullid2 curview
+ global commitinfo commitdata lserial
+
+ set isdiff 1
+ if {[gets $fd line] < 0} {
+ if {![eof $fd]} {
+ return 1
+ }
+ set isdiff 0
+ }
+ # we only need to see one line and we don't really care what it says...
+ close $fd
+
+ # now see if there are any local changes not checked in to the index
+ if {$serial == $lserial} {
+ set fd [open "|git diff-files" r]
+ fconfigure $fd -blocking 0
+ filerun $fd [list readdifffiles $fd $serial]
+ }
+
+ if {$isdiff && $serial == $lserial && $localirow == -1} {
+ # add the line for the changes in the index to the graph
+ set localirow $commitrow($curview,$mainheadid)
+ set hl "Local changes checked in to index but not committed"
+ set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
+ set commitdata($nullid2) "\n $hl\n"
+ insertrow $localirow $nullid2
+ }
+ return 0
+}
+
+proc readdifffiles {fd serial} {
+ global localirow localfrow commitrow mainheadid nullid curview
+ global commitinfo commitdata lserial
+
+ set isdiff 1
+ if {[gets $fd line] < 0} {
+ if {![eof $fd]} {
+ return 1
+ }
+ set isdiff 0
+ }
+ # we only need to see one line and we don't really care what it says...
+ close $fd
+
+ if {$isdiff && $serial == $lserial && $localfrow == -1} {
+ # add the line for the local diff to the graph
+ if {$localirow >= 0} {
+ set localfrow $localirow
+ incr localirow
+ } else {
+ set localfrow $commitrow($curview,$mainheadid)
+ }
+ set hl "Local uncommitted changes, not checked in to index"
+ set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
+ set commitdata($nullid) "\n $hl\n"
+ insertrow $localfrow $nullid
}
+ return 0
}
proc layoutrows {row endrow last} {
global rowidlist rowoffsets displayorder
global uparrowlen downarrowlen maxwidth mingaplen
- global childlist parentlist
- global idrowranges linesegends
+ global children parentlist
+ global idrowranges
global commitidx curview
global idinlist rowchk rowrangelist
lappend oldolds $p
}
}
- set lse {}
set nev [expr {[llength $idlist] + [llength $newolds]
+ [llength $oldolds] - $maxwidth + 1}]
if {$nev > 0} {
set offs [incrange $offs $x 1]
set idinlist($i) 0
set rm1 [expr {$row - 1}]
- lappend lse $i
- lappend idrowranges($i) $rm1
+ lappend idrowranges($i) [lindex $displayorder $rm1]
if {[incr nev -1] <= 0} break
continue
}
lset rowidlist $row $idlist
lset rowoffsets $row $offs
}
- lappend linesegends $lse
set col [lsearch -exact $idlist $id]
if {$col < 0} {
set col [llength $idlist]
lappend idlist $id
lset rowidlist $row $idlist
set z {}
- if {[lindex $childlist $row] ne {}} {
+ if {$children($curview,$id) ne {}} {
set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
unset idinlist($id)
}
set ranges {}
if {[info exists idrowranges($id)]} {
set ranges $idrowranges($id)
- lappend ranges $row
+ lappend ranges $id
unset idrowranges($id)
}
lappend rowrangelist $ranges
}
foreach i $newolds {
set idinlist($i) 1
- set idrowranges($i) $row
+ set idrowranges($i) $id
}
incr col $l
foreach oid $oldolds {
proc addextraid {id row} {
global displayorder commitrow commitinfo
global commitidx commitlisted
- global parentlist childlist children curview
+ global parentlist children curview
incr commitidx($curview)
lappend displayorder $id
if {![info exists children($curview,$id)]} {
set children($curview,$id) {}
}
- lappend childlist $children($curview,$id)
}
proc layouttail {} {
set id [lindex $idlist $col]
addextraid $id $row
unset idinlist($id)
- lappend idrowranges($id) $row
+ lappend idrowranges($id) $id
lappend rowrangelist $idrowranges($id)
unset idrowranges($id)
incr row
}
foreach id [array names idinlist] {
+ unset idinlist($id)
addextraid $id $row
lset rowidlist $row [list $id]
lset rowoffsets $row 0
makeuparrow $id 0 $row 0
- lappend idrowranges($id) $row
+ lappend idrowranges($id) $id
lappend rowrangelist $idrowranges($id)
unset idrowranges($id)
incr row
}
proc optimize_rows {row col endrow} {
- global rowidlist rowoffsets idrowranges displayorder
+ global rowidlist rowoffsets displayorder
for {} {$row < $endrow} {incr row} {
set idlist [lindex $rowidlist $row]
set isarrow 1
}
}
+ # Looking at lines from this row to the previous row,
+ # make them go straight up if they end in an arrow on
+ # the previous row; otherwise make them go straight up
+ # or at 45 degrees.
if {$z < -1 || ($z < 0 && $isarrow)} {
+ # Line currently goes left too much;
+ # insert pads in the previous row, then optimize it
set npad [expr {-1 - $z + $isarrow}]
set offs [incrange $offs $col $npad]
insert_pad $y0 $x0 $npad
set x0 [expr {$col + $z}]
set z0 [lindex $rowoffsets $y0 $x0]
} elseif {$z > 1 || ($z > 0 && $isarrow)} {
+ # Line currently goes right too much;
+ # insert pads in this line and adjust the next's rowoffsets
set npad [expr {$z - 1 + $isarrow}]
set y1 [expr {$row + 1}]
set offs2 [lindex $rowoffsets $y1]
set z0 [expr {$xc - $x0}]
}
}
+ # avoid lines jigging left then immediately right
if {$z0 ne {} && $z < 0 && $z0 > 0} {
insert_pad $y0 $x0 1
set offs [incrange $offs $col 1]
}
if {!$haspad} {
set o {}
+ # Find the first column that doesn't have a line going right
for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
set o [lindex $offs $col]
if {$o eq {}} {
}
if {$o eq {} || $o <= 0} break
}
+ # Insert a pad at that column as long as it has a line and
+ # isn't the last column, and adjust the next row' offsets
if {$o ne {} && [incr col] < [llength $idlist]} {
set y1 [expr {$row + 1}]
set offs2 [lindex $rowoffsets $y1]
} elseif {[info exists idrowranges($id)]} {
set ranges $idrowranges($id)
}
- return $ranges
+ set linenos {}
+ foreach rid $ranges {
+ lappend linenos $commitrow($curview,$rid)
+ }
+ if {$linenos ne {}} {
+ lset linenos 0 [expr {[lindex $linenos 0] + 1}]
+ }
+ return $linenos
}
-proc drawlineseg {id i} {
- global rowoffsets rowidlist
- global displayorder
- global canv colormap linespc
- global numcommits commitrow curview
+# work around tk8.4 refusal to draw arrows on diagonal segments
+proc adjarrowhigh {coords} {
+ global linespc
- set ranges [rowranges $id]
- set downarrow 1
- if {[info exists commitrow($curview,$id)]
- && $commitrow($curview,$id) < $numcommits} {
- set downarrow [expr {$i < [llength $ranges] / 2 - 1}]
- } else {
- set downarrow 1
- }
- set startrow [lindex $ranges [expr {2 * $i}]]
- set row [lindex $ranges [expr {2 * $i + 1}]]
- if {$startrow == $row} return
- assigncolor $id
- set coords {}
- set col [lsearch -exact [lindex $rowidlist $row] $id]
- if {$col < 0} {
- puts "oops: drawline: id $id not on row $row"
- return
+ set x0 [lindex $coords 0]
+ set x1 [lindex $coords 2]
+ if {$x0 != $x1} {
+ set y0 [lindex $coords 1]
+ set y1 [lindex $coords 3]
+ if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
+ # we have a nearby vertical segment, just trim off the diag bit
+ set coords [lrange $coords 2 end]
+ } else {
+ set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
+ set xi [expr {$x0 - $slope * $linespc / 2}]
+ set yi [expr {$y0 - $linespc / 2}]
+ set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
+ }
}
- set lasto {}
- set ns 0
+ return $coords
+}
+
+proc drawlineseg {id row endrow arrowlow} {
+ global rowidlist displayorder iddrawn linesegs
+ global canv colormap linespc curview maxlinelen
+
+ set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
+ set le [expr {$row + 1}]
+ set arrowhigh 1
while {1} {
- set o [lindex $rowoffsets $row $col]
- if {$o eq {}} break
- if {$o ne $lasto} {
- # changing direction
- set x [xc $row $col]
- set y [yc $row]
- lappend coords $x $y
- set lasto $o
+ set c [lsearch -exact [lindex $rowidlist $le] $id]
+ if {$c < 0} {
+ incr le -1
+ break
+ }
+ lappend cols $c
+ set x [lindex $displayorder $le]
+ if {$x eq $id} {
+ set arrowhigh 0
+ break
+ }
+ if {[info exists iddrawn($x)] || $le == $endrow} {
+ set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
+ if {$c >= 0} {
+ lappend cols $c
+ set arrowhigh 0
+ }
+ break
}
- incr col $o
- incr row -1
+ incr le
}
- set x [xc $row $col]
- set y [yc $row]
- lappend coords $x $y
- if {$i == 0} {
- # draw the link to the first child as part of this line
- incr row -1
- set child [lindex $displayorder $row]
- set ccol [lsearch -exact [lindex $rowidlist $row] $child]
- if {$ccol >= 0} {
- set x [xc $row $ccol]
- set y [yc $row]
- if {$ccol < $col - 1} {
- lappend coords [xc $row [expr {$col - 1}]] [yc $row]
- } elseif {$ccol > $col + 1} {
- lappend coords [xc $row [expr {$col + 1}]] [yc $row]
- }
- lappend coords $x $y
- }
- }
- if {[llength $coords] < 4} return
- if {$downarrow} {
- # This line has an arrow at the lower end: check if the arrow is
- # on a diagonal segment, and if so, work around the Tk 8.4
- # refusal to draw arrows on diagonal lines.
- set x0 [lindex $coords 0]
- set x1 [lindex $coords 2]
- if {$x0 != $x1} {
- set y0 [lindex $coords 1]
- set y1 [lindex $coords 3]
- if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
- # we have a nearby vertical segment, just trim off the diag bit
- set coords [lrange $coords 2 end]
+ if {$le <= $row} {
+ return $row
+ }
+
+ set lines {}
+ set i 0
+ set joinhigh 0
+ if {[info exists linesegs($id)]} {
+ set lines $linesegs($id)
+ foreach li $lines {
+ set r0 [lindex $li 0]
+ if {$r0 > $row} {
+ if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
+ set joinhigh 1
+ }
+ break
+ }
+ incr i
+ }
+ }
+ set joinlow 0
+ if {$i > 0} {
+ set li [lindex $lines [expr {$i-1}]]
+ set r1 [lindex $li 1]
+ if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
+ set joinlow 1
+ }
+ }
+
+ set x [lindex $cols [expr {$le - $row}]]
+ set xp [lindex $cols [expr {$le - 1 - $row}]]
+ set dir [expr {$xp - $x}]
+ if {$joinhigh} {
+ set ith [lindex $lines $i 2]
+ set coords [$canv coords $ith]
+ set ah [$canv itemcget $ith -arrow]
+ set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
+ set x2 [lindex $cols [expr {$le + 1 - $row}]]
+ if {$x2 ne {} && $x - $x2 == $dir} {
+ set coords [lrange $coords 0 end-2]
+ }
+ } else {
+ set coords [list [xc $le $x] [yc $le]]
+ }
+ if {$joinlow} {
+ set itl [lindex $lines [expr {$i-1}] 2]
+ set al [$canv itemcget $itl -arrow]
+ set arrowlow [expr {$al eq "last" || $al eq "both"}]
+ } elseif {$arrowlow &&
+ [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
+ set arrowlow 0
+ }
+ set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
+ for {set y $le} {[incr y -1] > $row} {} {
+ set x $xp
+ set xp [lindex $cols [expr {$y - 1 - $row}]]
+ set ndir [expr {$xp - $x}]
+ if {$dir != $ndir || $xp < 0} {
+ lappend coords [xc $y $x] [yc $y]
+ }
+ set dir $ndir
+ }
+ if {!$joinlow} {
+ if {$xp < 0} {
+ # join parent line to first child
+ set ch [lindex $displayorder $row]
+ set xc [lsearch -exact [lindex $rowidlist $row] $ch]
+ if {$xc < 0} {
+ puts "oops: drawlineseg: child $ch not on row $row"
+ } else {
+ if {$xc < $x - 1} {
+ lappend coords [xc $row [expr {$x-1}]] [yc $row]
+ } elseif {$xc > $x + 1} {
+ lappend coords [xc $row [expr {$x+1}]] [yc $row]
+ }
+ set x $xc
+ }
+ lappend coords [xc $row $x] [yc $row]
+ } else {
+ set xn [xc $row $xp]
+ set yn [yc $row]
+ # work around tk8.4 refusal to draw arrows on diagonal segments
+ if {$arrowlow && $xn != [lindex $coords end-1]} {
+ if {[llength $coords] < 4 ||
+ [lindex $coords end-3] != [lindex $coords end-1] ||
+ [lindex $coords end] - $yn > 2 * $linespc} {
+ set xn [xc $row [expr {$xp - 0.5 * $dir}]]
+ set yo [yc [expr {$row + 0.5}]]
+ lappend coords $xn $yo $xn $yn
+ }
} else {
- set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
- set xi [expr {$x0 - $slope * $linespc / 2}]
- set yi [expr {$y0 - $linespc / 2}]
- set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
+ lappend coords $xn $yn
}
}
+ if {!$joinhigh} {
+ if {$arrowhigh} {
+ set coords [adjarrowhigh $coords]
+ }
+ assigncolor $id
+ set t [$canv create line $coords -width [linewidth $id] \
+ -fill $colormap($id) -tags lines.$id -arrow $arrow]
+ $canv lower $t
+ bindline $t $id
+ set lines [linsert $lines $i [list $row $le $t]]
+ } else {
+ $canv coords $ith $coords
+ if {$arrow ne $ah} {
+ $canv itemconf $ith -arrow $arrow
+ }
+ lset lines $i 0 $row
+ }
+ } else {
+ set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
+ set ndir [expr {$xo - $xp}]
+ set clow [$canv coords $itl]
+ if {$dir == $ndir} {
+ set clow [lrange $clow 2 end]
+ }
+ set coords [concat $coords $clow]
+ if {!$joinhigh} {
+ lset lines [expr {$i-1}] 1 $le
+ if {$arrowhigh} {
+ set coords [adjarrowhigh $coords]
+ }
+ } else {
+ # coalesce two pieces
+ $canv delete $ith
+ set b [lindex $lines [expr {$i-1}] 0]
+ set e [lindex $lines $i 1]
+ set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
+ }
+ $canv coords $itl $coords
+ if {$arrow ne $al} {
+ $canv itemconf $itl -arrow $arrow
+ }
}
- set arrow [expr {2 * ($i > 0) + $downarrow}]
- set arrow [lindex {none first last both} $arrow]
- set t [$canv create line $coords -width [linewidth $id] \
- -fill $colormap($id) -tags lines.$id -arrow $arrow]
- $canv lower $t
- bindline $t $id
+
+ set linesegs($id) $lines
+ return $le
}
-proc drawparentlinks {id row col olds} {
- global rowidlist canv colormap
+proc drawparentlinks {id row} {
+ global rowidlist canv colormap curview parentlist
+ global idpos
+ set rowids [lindex $rowidlist $row]
+ set col [lsearch -exact $rowids $id]
+ if {$col < 0} return
+ set olds [lindex $parentlist $row]
set row2 [expr {$row + 1}]
set x [xc $row $col]
set y [yc $row]
if {$x2 > $rmx} {
set rmx $x2
}
- set ranges [rowranges $p]
- if {$ranges ne {} && $row2 == [lindex $ranges 0]
- && $row2 < [lindex $ranges 1]} {
+ if {[lsearch -exact $rowids $p] < 0} {
# drawlineseg will do this one for us
continue
}
$canv lower $t
bindline $t $p
}
- return $rmx
+ if {$rmx > [lindex $idpos($id) 1]} {
+ lset idpos($id) 1 $rmx
+ redrawtags $id
+ }
}
proc drawlines {id} {
- global colormap canv
- global idrangedrawn
- global children iddrawn commitrow rowidlist curview
+ global canv
- $canv delete lines.$id
- set nr [expr {[llength [rowranges $id]] / 2}]
- for {set i 0} {$i < $nr} {incr i} {
- if {[info exists idrangedrawn($id,$i)]} {
- drawlineseg $id $i
- }
- }
- foreach child $children($curview,$id) {
- if {[info exists iddrawn($child)]} {
- set row $commitrow($curview,$child)
- set col [lsearch -exact [lindex $rowidlist $row] $child]
- if {$col >= 0} {
- drawparentlinks $child $row $col [list $id]
- }
- }
- }
+ $canv itemconf lines.$id -width [linewidth $id]
}
-proc drawcmittext {id row col rmx} {
- global linespc canv canv2 canv3 canvy0 fgcolor
- global commitlisted commitinfo rowidlist
+proc drawcmittext {id row col} {
+ global linespc canv canv2 canv3 canvy0 fgcolor curview
+ global commitlisted commitinfo rowidlist parentlist
global rowtextx idpos idtags idheads idotherrefs
global linehtag linentag linedtag
- global mainfont canvxmax boldrows boldnamerows fgcolor
-
- set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
+ global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2
+
+ # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
+ set listed [lindex $commitlisted $row]
+ if {$id eq $nullid} {
+ set ofill red
+ } elseif {$id eq $nullid2} {
+ set ofill green
+ } else {
+ set ofill [expr {$listed != 0? "blue": "white"}]
+ }
set x [xc $row $col]
set y [yc $row]
set orad [expr {$linespc / 3}]
- set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
- [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
- -fill $ofill -outline $fgcolor -width 1 -tags circle]
+ if {$listed <= 1} {
+ 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]
+ } elseif {$listed == 2} {
+ # 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]
+ } 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]
+ }
$canv raise $t
$canv bind $t <1> {selcanvline {} %x %y}
- set xt [xc $row [llength [lindex $rowidlist $row]]]
- if {$xt < $rmx} {
- set xt $rmx
+ set rmx [llength [lindex $rowidlist $row]]
+ set olds [lindex $parentlist $row]
+ if {$olds ne {}} {
+ set nextids [lindex $rowidlist [expr {$row + 1}]]
+ foreach p $olds {
+ set i [lsearch -exact $nextids $p]
+ if {$i > $rmx} {
+ set rmx $i
+ }
+ }
}
+ set xt [xc $row $rmx]
set rowtextx($row) $xt
set idpos($id) [list $x $xt $y]
if {[info exists idtags($id)] || [info exists idheads($id)]
proc drawcmitrow {row} {
global displayorder rowidlist
- global idrangedrawn iddrawn
+ global iddrawn markingmatches
global commitinfo parentlist numcommits
global filehighlight fhighlights findstring nhighlights
global hlview vhighlights
global highlight_related rhighlights
if {$row >= $numcommits} return
- foreach id [lindex $rowidlist $row] {
- if {$id eq {}} continue
- set i -1
- foreach {s e} [rowranges $id] {
- incr i
- if {$row < $s} continue
- if {$e eq {}} break
- if {$row <= $e} {
- if {$e < $numcommits && ![info exists idrangedrawn($id,$i)]} {
- drawlineseg $id $i
- set idrangedrawn($id,$i) 1
- }
- break
- }
- }
- }
set id [lindex $displayorder $row]
if {[info exists hlview] && ![info exists vhighlights($row)]} {
if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
askrelhighlight $row $id
}
- if {[info exists iddrawn($id)]} return
- set col [lsearch -exact [lindex $rowidlist $row] $id]
- if {$col < 0} {
- puts "oops, row $row id $id not in list"
- return
+ if {![info exists iddrawn($id)]} {
+ set col [lsearch -exact [lindex $rowidlist $row] $id]
+ if {$col < 0} {
+ puts "oops, row $row id $id not in list"
+ return
+ }
+ if {![info exists commitinfo($id)]} {
+ getcommit $id
+ }
+ assigncolor $id
+ drawcmittext $id $row $col
+ set iddrawn($id) 1
}
- if {![info exists commitinfo($id)]} {
- getcommit $id
+ if {$markingmatches} {
+ markrowmatches $row $id
}
- assigncolor $id
- set olds [lindex $parentlist $row]
- if {$olds ne {}} {
- set rmx [drawparentlinks $id $row $col $olds]
- } else {
- set rmx 0
+}
+
+proc drawcommits {row {endrow {}}} {
+ global numcommits iddrawn displayorder curview
+ global parentlist rowidlist
+
+ if {$row < 0} {
+ set row 0
+ }
+ if {$endrow eq {}} {
+ set endrow $row
+ }
+ if {$endrow >= $numcommits} {
+ set endrow [expr {$numcommits - 1}]
+ }
+
+ # make the lines join to already-drawn rows either side
+ set r [expr {$row - 1}]
+ if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
+ set r $row
+ }
+ set er [expr {$endrow + 1}]
+ if {$er >= $numcommits ||
+ ![info exists iddrawn([lindex $displayorder $er])]} {
+ set er $endrow
+ }
+ for {} {$r <= $er} {incr r} {
+ set id [lindex $displayorder $r]
+ set wasdrawn [info exists iddrawn($id)]
+ drawcmitrow $r
+ if {$r == $er} break
+ set nextid [lindex $displayorder [expr {$r + 1}]]
+ if {$wasdrawn && [info exists iddrawn($nextid)]} {
+ catch {unset prevlines}
+ continue
+ }
+ drawparentlinks $id $r
+
+ if {[info exists lineends($r)]} {
+ foreach lid $lineends($r) {
+ unset prevlines($lid)
+ }
+ }
+ set rowids [lindex $rowidlist $r]
+ foreach lid $rowids {
+ if {$lid eq {}} continue
+ if {$lid eq $id} {
+ # see if this is the first child of any of its parents
+ foreach p [lindex $parentlist $r] {
+ if {[lsearch -exact $rowids $p] < 0} {
+ # make this line extend up to the child
+ set le [drawlineseg $p $r $er 0]
+ lappend lineends($le) $p
+ set prevlines($p) 1
+ }
+ }
+ } elseif {![info exists prevlines($lid)]} {
+ set le [drawlineseg $lid $r $er 1]
+ lappend lineends($le) $lid
+ set prevlines($lid) 1
+ }
+ }
}
- drawcmittext $id $row $col $rmx
- set iddrawn($id) 1
}
proc drawfrac {f0 f1} {
- global numcommits canv
- global linespc
+ global canv linespc
set ymax [lindex [$canv cget -scrollregion] 3]
if {$ymax eq {} || $ymax == 0} return
set y0 [expr {int($f0 * $ymax)}]
set row [expr {int(($y0 - 3) / $linespc) - 1}]
- if {$row < 0} {
- set row 0
- }
set y1 [expr {int($f1 * $ymax)}]
set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
- if {$endrow >= $numcommits} {
- set endrow [expr {$numcommits - 1}]
- }
- for {} {$row <= $endrow} {incr row} {
- drawcmitrow $row
- }
+ drawcommits $row $endrow
}
proc drawvisible {} {
}
proc clear_display {} {
- global iddrawn idrangedrawn
+ global iddrawn linesegs
global vhighlights fhighlights nhighlights rhighlights
allcanvs delete all
catch {unset iddrawn}
- catch {unset idrangedrawn}
+ catch {unset linesegs}
catch {unset vhighlights}
catch {unset fhighlights}
catch {unset nhighlights}
-tags text -fill $fgcolor
}
-proc finishcommits {} {
- global commitidx phase curview
- global pending_select
-
- if {$commitidx($curview) > 0} {
- drawrest
- } else {
- show_status "No commits selected"
- }
- set phase {}
- catch {unset pending_select}
-}
-
# Insert a new commit as the child of the commit on row $row.
# The new commit will be displayed on row $row and the commits
# on that row and below will move down one row.
proc insertrow {row newcmit} {
- global displayorder parentlist childlist commitlisted
+ global displayorder parentlist commitlisted children
global commitrow curview rowidlist rowoffsets numcommits
- global rowrangelist idrowranges rowlaidout rowoptim numcommits
- global linesegends selectedline
+ global rowrangelist rowlaidout rowoptim numcommits
+ global selectedline rowchk commitidx
if {$row >= $numcommits} {
puts "oops, inserting new row $row but only have $numcommits rows"
set p [lindex $displayorder $row]
set displayorder [linsert $displayorder $row $newcmit]
set parentlist [linsert $parentlist $row $p]
- set kids [lindex $childlist $row]
+ set kids $children($curview,$p)
lappend kids $newcmit
- lset childlist $row $kids
- set childlist [linsert $childlist $row {}]
+ set children($curview,$p) $kids
+ set children($curview,$newcmit) {}
set commitlisted [linsert $commitlisted $row 1]
set l [llength $displayorder]
for {set r $row} {$r < $l} {incr r} {
set id [lindex $displayorder $r]
set commitrow($curview,$id) $r
}
+ incr commitidx($curview)
set idlist [lindex $rowidlist $row]
set offs [lindex $rowoffsets $row]
set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
set rowrangelist [linsert $rowrangelist $row {}]
- set l [llength $rowrangelist]
- for {set r 0} {$r < $l} {incr r} {
- set ranges [lindex $rowrangelist $r]
- if {$ranges ne {} && [lindex $ranges end] >= $row} {
- set newranges {}
- foreach x $ranges {
- if {$x >= $row} {
- lappend newranges [expr {$x + 1}]
- } else {
- lappend newranges $x
- }
- }
- lset rowrangelist $r $newranges
- }
- }
if {[llength $kids] > 1} {
set rp1 [expr {$row + 1}]
set ranges [lindex $rowrangelist $rp1]
if {$ranges eq {}} {
- set ranges [list $row $rp1]
- } elseif {[lindex $ranges end-1] == $rp1} {
- lset ranges end-1 $row
+ set ranges [list $newcmit $p]
+ } elseif {[lindex $ranges end-1] eq $p} {
+ lset ranges end-1 $newcmit
}
lset rowrangelist $rp1 $ranges
}
- foreach id [array names idrowranges] {
- set ranges $idrowranges($id)
- if {$ranges ne {} && [lindex $ranges end] >= $row} {
- set newranges {}
- foreach x $ranges {
- if {$x >= $row} {
- lappend newranges [expr {$x + 1}]
- } else {
- lappend newranges $x
- }
- }
- set idrowranges($id) $newranges
- }
- }
- set linesegends [linsert $linesegends $row {}]
+ catch {unset rowchk}
incr rowlaidout
incr rowoptim
redisplay
}
+# Remove a commit that was inserted with insertrow on row $row.
+proc removerow {row} {
+ global displayorder parentlist commitlisted children
+ global commitrow curview rowidlist rowoffsets numcommits
+ global rowrangelist idrowranges rowlaidout rowoptim numcommits
+ global linesegends selectedline rowchk commitidx
+
+ if {$row >= $numcommits} {
+ puts "oops, removing row $row but only have $numcommits rows"
+ return
+ }
+ set rp1 [expr {$row + 1}]
+ set id [lindex $displayorder $row]
+ set p [lindex $parentlist $row]
+ set displayorder [lreplace $displayorder $row $row]
+ set parentlist [lreplace $parentlist $row $row]
+ set commitlisted [lreplace $commitlisted $row $row]
+ set kids $children($curview,$p)
+ set i [lsearch -exact $kids $id]
+ if {$i >= 0} {
+ set kids [lreplace $kids $i $i]
+ set children($curview,$p) $kids
+ }
+ set l [llength $displayorder]
+ for {set r $row} {$r < $l} {incr r} {
+ set id [lindex $displayorder $r]
+ set commitrow($curview,$id) $r
+ }
+ incr commitidx($curview) -1
+
+ set rowidlist [lreplace $rowidlist $row $row]
+ set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
+ if {$kids ne {}} {
+ set offs [lindex $rowoffsets $row]
+ set offs [lreplace $offs end end]
+ lset rowoffsets $row $offs
+ }
+
+ set rowrangelist [lreplace $rowrangelist $row $row]
+ if {[llength $kids] > 0} {
+ set ranges [lindex $rowrangelist $row]
+ if {[lindex $ranges end-1] eq $id} {
+ set ranges [lreplace $ranges end-1 end]
+ lset rowrangelist $row $ranges
+ }
+ }
+
+ catch {unset rowchk}
+
+ incr rowlaidout -1
+ incr rowoptim -1
+ incr numcommits -1
+
+ if {[info exists selectedline] && $selectedline > $row} {
+ incr selectedline -1
+ }
+ redisplay
+}
+
# Don't change the text pane cursor if it is currently the hand cursor,
# showing that we are over a sha1 ID link.
proc settextcursor {c} {
}
}
-proc drawrest {} {
- global startmsecs
- global rowlaidout commitidx curview
- global pending_select
-
- set row $rowlaidout
- layoutrows $rowlaidout $commitidx($curview) 1
- layouttail
- optimize_rows $row 0 $commitidx($curview)
- showstuff $commitidx($curview)
- if {[info exists pending_select]} {
- selectline 0 1
- }
-
- set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
- #global numcommits
- #puts "overall $drawmsecs ms for $numcommits commits"
-}
-
proc findmatches {f} {
- global findtype foundstring foundstrlen
+ global findtype findstring
if {$findtype == "Regexp"} {
- set matches [regexp -indices -all -inline $foundstring $f]
+ set matches [regexp -indices -all -inline $findstring $f]
} else {
+ set fs $findstring
if {$findtype == "IgnCase"} {
- set str [string tolower $f]
- } else {
- set str $f
+ set f [string tolower $f]
+ set fs [string tolower $fs]
}
set matches {}
set i 0
- while {[set j [string first $foundstring $str $i]] >= 0} {
- lappend matches [list $j [expr {$j+$foundstrlen-1}]]
- set i [expr {$j + $foundstrlen}]
+ set l [string length $fs]
+ while {[set j [string first $fs $f $i]] >= 0} {
+ lappend matches [list $j [expr {$j+$l-1}]]
+ set i [expr {$j + $l}]
}
}
return $matches
}
-proc dofind {} {
- global findtype findloc findstring markedmatches commitinfo
- global numcommits displayorder linehtag linentag linedtag
- global mainfont canv canv2 canv3 selectedline
- global matchinglines foundstring foundstrlen matchstring
- global commitdata
+proc dofind {{rev 0}} {
+ global findstring findstartline findcurline selectedline numcommits
- stopfindproc
unmarkmatches
cancel_next_highlight
focus .
- set matchinglines {}
- if {$findtype == "IgnCase"} {
- set foundstring [string tolower $findstring]
+ if {$findstring eq {} || $numcommits == 0} return
+ if {![info exists selectedline]} {
+ set findstartline [lindex [visiblerows] $rev]
} else {
- set foundstring $findstring
+ set findstartline $selectedline
}
- set foundstrlen [string length $findstring]
- if {$foundstrlen == 0} return
- regsub -all {[*?\[\\]} $foundstring {\\&} matchstring
- set matchstring "*$matchstring*"
- if {![info exists selectedline]} {
- set oldsel -1
+ set findcurline $findstartline
+ nowbusy finding
+ if {!$rev} {
+ run findmore
} else {
- set oldsel $selectedline
+ if {$findcurline == 0} {
+ set findcurline $numcommits
+ }
+ incr findcurline -1
+ run findmorerev
}
- set didsel 0
- set fldtypes {Headline Author Date Committer CDate Comments}
- set l -1
- foreach id $displayorder {
- set d $commitdata($id)
- incr l
- if {$findtype == "Regexp"} {
- set doesmatch [regexp $foundstring $d]
- } elseif {$findtype == "IgnCase"} {
- set doesmatch [string match -nocase $matchstring $d]
+}
+
+proc findnext {restart} {
+ global findcurline
+ if {![info exists findcurline]} {
+ if {$restart} {
+ dofind
} else {
- set doesmatch [string match $matchstring $d]
+ bell
}
- if {!$doesmatch} continue
+ } else {
+ run findmore
+ nowbusy finding
+ }
+}
+
+proc findprev {} {
+ global findcurline
+ if {![info exists findcurline]} {
+ dofind 1
+ } else {
+ run findmorerev
+ nowbusy finding
+ }
+}
+
+proc findmore {} {
+ global commitdata commitinfo numcommits findstring findpattern findloc
+ global findstartline findcurline displayorder
+
+ set fldtypes {Headline Author Date Committer CDate Comments}
+ set l [expr {$findcurline + 1}]
+ if {$l >= $numcommits} {
+ set l 0
+ }
+ if {$l <= $findstartline} {
+ set lim [expr {$findstartline + 1}]
+ } else {
+ set lim $numcommits
+ }
+ if {$lim - $l > 500} {
+ set lim [expr {$l + 500}]
+ }
+ set last 0
+ for {} {$l < $lim} {incr l} {
+ set id [lindex $displayorder $l]
+ # shouldn't happen unless git log doesn't give all the commits...
+ if {![info exists commitdata($id)]} continue
+ if {![doesmatch $commitdata($id)]} continue
if {![info exists commitinfo($id)]} {
getcommit $id
}
set info $commitinfo($id)
- set doesmatch 0
foreach f $info ty $fldtypes {
- if {$findloc != "All fields" && $findloc != $ty} {
- continue
+ if {($findloc eq "All fields" || $findloc eq $ty) &&
+ [doesmatch $f]} {
+ findselectline $l
+ notbusy finding
+ return 0
}
- set matches [findmatches $f]
- if {$matches == {}} continue
- set doesmatch 1
- if {$ty == "Headline"} {
- drawcmitrow $l
- markmatches $canv $l $f $linehtag($l) $matches $mainfont
- } elseif {$ty == "Author"} {
- drawcmitrow $l
- markmatches $canv2 $l $f $linentag($l) $matches $mainfont
- } elseif {$ty == "Date"} {
- drawcmitrow $l
- markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
- }
- }
- if {$doesmatch} {
- lappend matchinglines $l
- if {!$didsel && $l > $oldsel} {
+ }
+ }
+ if {$l == $findstartline + 1} {
+ bell
+ unset findcurline
+ notbusy finding
+ return 0
+ }
+ set findcurline [expr {$l - 1}]
+ return 1
+}
+
+proc findmorerev {} {
+ global commitdata commitinfo numcommits findstring findpattern findloc
+ global findstartline findcurline displayorder
+
+ set fldtypes {Headline Author Date Committer CDate Comments}
+ set l $findcurline
+ if {$l == 0} {
+ set l $numcommits
+ }
+ incr l -1
+ if {$l >= $findstartline} {
+ set lim [expr {$findstartline - 1}]
+ } else {
+ set lim -1
+ }
+ if {$l - $lim > 500} {
+ set lim [expr {$l - 500}]
+ }
+ set last 0
+ for {} {$l > $lim} {incr l -1} {
+ set id [lindex $displayorder $l]
+ if {![doesmatch $commitdata($id)]} continue
+ if {![info exists commitinfo($id)]} {
+ getcommit $id
+ }
+ set info $commitinfo($id)
+ foreach f $info ty $fldtypes {
+ if {($findloc eq "All fields" || $findloc eq $ty) &&
+ [doesmatch $f]} {
findselectline $l
- set didsel 1
+ notbusy finding
+ return 0
}
}
}
- if {$matchinglines == {}} {
+ if {$l == -1} {
bell
- } elseif {!$didsel} {
- findselectline [lindex $matchinglines 0]
+ unset findcurline
+ notbusy finding
+ return 0
}
+ set findcurline [expr {$l + 1}]
+ return 1
}
proc findselectline {l} {
- global findloc commentend ctext
+ global findloc commentend ctext findcurline markingmatches
+
+ set markingmatches 1
+ set findcurline $l
selectline $l 1
if {$findloc == "All fields" || $findloc == "Comments"} {
# highlight the matches in the comments
$ctext tag add found "1.0 + $start c" "1.0 + $end c"
}
}
+ drawvisible
}
-proc findnext {restart} {
- global matchinglines selectedline
- if {![info exists matchinglines]} {
- if {$restart} {
- dofind
- }
- return
- }
- if {![info exists selectedline]} return
- foreach l $matchinglines {
- if {$l > $selectedline} {
- findselectline $l
- return
- }
- }
- bell
-}
-
-proc findprev {} {
- global matchinglines selectedline
- if {![info exists matchinglines]} {
- dofind
- return
- }
- if {![info exists selectedline]} return
- set prev {}
- foreach l $matchinglines {
- if {$l >= $selectedline} break
- set prev $l
- }
- if {$prev != {}} {
- findselectline $prev
- } else {
- bell
- }
-}
-
-proc stopfindproc {{done 0}} {
- global findprocpid findprocfile findids
- global ctext findoldcursor phase maincursor textcursor
- global findinprogress
-
- catch {unset findids}
- if {[info exists findprocpid]} {
- if {!$done} {
- catch {exec kill $findprocpid}
- }
- catch {close $findprocfile}
- unset findprocpid
- }
- catch {unset findinprogress}
- notbusy find
-}
-
-# mark a commit as matching by putting a yellow background
-# behind the headline
-proc markheadline {l id} {
- global canv mainfont linehtag
-
- drawcmitrow $l
- set bbox [$canv bbox $linehtag($l)]
- set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
- $canv lower $t
-}
+# mark the bits of a headline or author that match a find string
+proc markmatches {canv l str tag matches font row} {
+ global selectedline
-# mark the bits of a headline, author or date that match a find string
-proc markmatches {canv l str tag matches font} {
set bbox [$canv bbox $tag]
set x0 [lindex $bbox 0]
set y0 [lindex $bbox 1]
set xlen [font measure $font [string range $str 0 [expr {$end}]]]
set t [$canv create rect [expr {$x0+$xoff}] $y0 \
[expr {$x0+$xlen+2}] $y1 \
- -outline {} -tags matches -fill yellow]
+ -outline {} -tags [list match$l matches] -fill yellow]
$canv lower $t
+ if {[info exists selectedline] && $row == $selectedline} {
+ $canv raise $t secsel
+ }
}
}
proc unmarkmatches {} {
- global matchinglines findids
+ global findids markingmatches findcurline
+
allcanvs delete matches
- catch {unset matchinglines}
catch {unset findids}
+ set markingmatches 0
+ catch {unset findcurline}
}
proc selcanvline {w x y} {
# add a list of tag or branch names at position pos
# returns the number of names inserted
-proc appendrefs {pos tags var} {
- global ctext commitrow linknum curview $var
+proc appendrefs {pos ids var} {
+ global ctext commitrow linknum curview $var maxrefs
if {[catch {$ctext index $pos}]} {
return 0
}
- set tags [lsort $tags]
- set sep {}
- foreach tag $tags {
- set id [set $var\($tag\)]
- set lk link$linknum
- incr linknum
- $ctext insert $pos $sep
- $ctext insert $pos $tag $lk
- $ctext tag conf $lk -foreground blue
- if {[info exists commitrow($curview,$id)]} {
- $ctext tag bind $lk <1> \
- [list selectline $commitrow($curview,$id) 1]
- $ctext tag conf $lk -underline 1
- $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
- $ctext tag bind $lk <Leave> { %W configure -cursor $curtextcursor }
+ $ctext conf -state normal
+ $ctext delete $pos "$pos lineend"
+ set tags {}
+ foreach id $ids {
+ foreach tag [set $var\($id\)] {
+ lappend tags [list $tag $id]
}
- set sep ", "
}
+ if {[llength $tags] > $maxrefs} {
+ $ctext insert $pos "many ([llength $tags])"
+ } else {
+ set tags [lsort -index 0 -decreasing $tags]
+ set sep {}
+ foreach ti $tags {
+ set id [lindex $ti 1]
+ set lk link$linknum
+ incr linknum
+ $ctext tag delete $lk
+ $ctext insert $pos $sep
+ $ctext insert $pos [lindex $ti 0] $lk
+ if {[info exists commitrow($curview,$id)]} {
+ $ctext tag conf $lk -foreground blue
+ $ctext tag bind $lk <1> \
+ [list selectline $commitrow($curview,$id) 1]
+ $ctext tag conf $lk -underline 1
+ $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
+ $ctext tag bind $lk <Leave> \
+ { %W configure -cursor $curtextcursor }
+ }
+ set sep ", "
+ }
+ }
+ $ctext conf -state disabled
return [llength $tags]
}
-proc taglist {ids} {
- global idtags
+# called when we have finished computing the nearby tags
+proc dispneartags {delay} {
+ global selectedline currentid showneartags tagphase
- set tags {}
- foreach id $ids {
- foreach tag $idtags($id) {
- lappend tags $tag
- }
+ if {![info exists selectedline] || !$showneartags} return
+ after cancel dispnexttag
+ if {$delay} {
+ after 200 dispnexttag
+ set tagphase -1
+ } else {
+ after idle dispnexttag
+ set tagphase 0
}
- return $tags
}
-# called when we have finished computing the nearby tags
-proc dispneartags {} {
- global selectedline currentid ctext anc_tags desc_tags showneartags
- global desc_heads
+proc dispnexttag {} {
+ global selectedline currentid showneartags tagphase ctext
if {![info exists selectedline] || !$showneartags} return
- set id $currentid
- $ctext conf -state normal
- if {[info exists desc_heads($id)]} {
- if {[appendrefs branch $desc_heads($id) headids] > 1} {
- $ctext insert "branch -2c" "es"
+ switch -- $tagphase {
+ 0 {
+ set dtags [desctags $currentid]
+ if {$dtags ne {}} {
+ appendrefs precedes $dtags idtags
+ }
+ }
+ 1 {
+ set atags [anctags $currentid]
+ if {$atags ne {}} {
+ appendrefs follows $atags idtags
+ }
+ }
+ 2 {
+ set dheads [descheads $currentid]
+ if {$dheads ne {}} {
+ if {[appendrefs branch $dheads idheads] > 1
+ && [$ctext get "branch -3c"] eq "h"} {
+ # turn "Branch" into "Branches"
+ $ctext conf -state normal
+ $ctext insert "branch -2c" "es"
+ $ctext conf -state disabled
+ }
+ }
}
}
- if {[info exists anc_tags($id)]} {
- appendrefs follows [taglist $anc_tags($id)] tagids
- }
- if {[info exists desc_tags($id)]} {
- appendrefs precedes [taglist $desc_tags($id)] tagids
+ if {[incr tagphase] <= 2} {
+ after idle dispnexttag
}
- $ctext conf -state disabled
}
proc selectline {l isnew} {
global canv canv2 canv3 ctext commitinfo selectedline
global displayorder linehtag linentag linedtag
- global canvy0 linespc parentlist childlist
+ global canvy0 linespc parentlist children curview
global currentid sha1entry
global commentend idtags linknum
global mergemax numcommits pending_select
- global cmitmode desc_tags anc_tags showneartags allcommits desc_heads
+ global cmitmode showneartags allcommits
catch {unset pending_select}
$canv delete hover
}
}
- foreach c [lindex $childlist $l] {
+ foreach c $children($curview,$id) {
append headers "Child: [commit_descriptor $c]"
}
$ctext insert end "Branch: "
$ctext mark set branch "end -1c"
$ctext mark gravity branch left
- if {[info exists desc_heads($id)]} {
- if {[appendrefs branch $desc_heads($id) headids] > 1} {
- # turn "Branch" into "Branches"
- $ctext insert "branch -2c" "es"
- }
- }
$ctext insert end "\nFollows: "
$ctext mark set follows "end -1c"
$ctext mark gravity follows left
- if {[info exists anc_tags($id)]} {
- appendrefs follows [taglist $anc_tags($id)] tagids
- }
$ctext insert end "\nPrecedes: "
$ctext mark set precedes "end -1c"
$ctext mark gravity precedes left
- if {[info exists desc_tags($id)]} {
- appendrefs precedes [taglist $desc_tags($id)] tagids
- }
$ctext insert end "\n"
+ dispneartags 1
}
$ctext insert end "\n"
- appendwithlinks [lindex $info 5] {comment}
+ set comment [lindex $info 5]
+ if {[string first "\r" $comment] >= 0} {
+ set comment [string map {"\r" "\n "} $comment]
+ }
+ appendwithlinks $comment {comment}
- $ctext tag delete Comments
$ctext tag remove found 1.0 end
$ctext conf -state disabled
set commentend [$ctext index "end - 1c"]
proc gettree {id} {
global treefilelist treeidlist diffids diffmergeid treepending
+ global nullid nullid2
set diffids $id
catch {unset diffmergeid}
if {![info exists treefilelist($id)]} {
if {![info exists treepending]} {
- if {[catch {set gtf [open [concat | git ls-tree -r $id] r]}]} {
+ if {$id eq $nullid} {
+ set cmd [list | git ls-files]
+ } elseif {$id eq $nullid2} {
+ set cmd [list | git ls-files --stage -t]
+ } else {
+ set cmd [list | git ls-tree -r $id]
+ }
+ if {[catch {set gtf [open $cmd r]}]} {
return
}
set treepending $id
set treefilelist($id) {}
set treeidlist($id) {}
fconfigure $gtf -blocking 0
- fileevent $gtf readable [list gettreeline $gtf $id]
+ filerun $gtf [list gettreeline $gtf $id]
}
} else {
setfilelist $id
}
proc gettreeline {gtf id} {
- global treefilelist treeidlist treepending cmitmode diffids
+ global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
- while {[gets $gtf line] >= 0} {
- if {[lindex $line 1] ne "blob"} continue
- set sha1 [lindex $line 2]
- set fname [lindex $line 3]
+ set nl 0
+ while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
+ if {$diffids eq $nullid} {
+ set fname $line
+ } else {
+ if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
+ set i [string first "\t" $line]
+ if {$i < 0} continue
+ set sha1 [lindex $line 2]
+ set fname [string range $line [expr {$i+1}] end]
+ if {[string index $fname 0] eq "\""} {
+ set fname [lindex $fname 0]
+ }
+ lappend treeidlist($id) $sha1
+ }
lappend treefilelist($id) $fname
- lappend treeidlist($id) $sha1
}
- if {![eof $gtf]} return
+ if {![eof $gtf]} {
+ return [expr {$nl >= 1000? 2: 1}]
+ }
close $gtf
unset treepending
if {$cmitmode ne "tree"} {
} else {
setfilelist $id
}
+ return 0
}
proc showfile {f} {
- global treefilelist treeidlist diffids
+ global treefilelist treeidlist diffids nullid nullid2
global ctext commentend
set i [lsearch -exact $treefilelist($diffids) $f]
puts "oops, $f not in list for id $diffids"
return
}
- set blob [lindex $treeidlist($diffids) $i]
- if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
- puts "oops, error reading blob $blob: $err"
- return
+ if {$diffids eq $nullid} {
+ if {[catch {set bf [open $f r]} err]} {
+ puts "oops, can't read $f: $err"
+ return
+ }
+ } else {
+ set blob [lindex $treeidlist($diffids) $i]
+ if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
+ puts "oops, error reading blob $blob: $err"
+ return
+ }
}
fconfigure $bf -blocking 0
- fileevent $bf readable [list getblobline $bf $diffids]
+ filerun $bf [list getblobline $bf $diffids]
$ctext config -state normal
clear_ctext $commentend
$ctext insert end "\n"
if {$id ne $diffids || $cmitmode ne "tree"} {
catch {close $bf}
- return
+ return 0
}
$ctext config -state normal
- while {[gets $bf line] >= 0} {
+ set nl 0
+ while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
$ctext insert end "$line\n"
}
if {[eof $bf]} {
# delete last newline
$ctext delete "end - 2c" "end - 1c"
close $bf
+ return 0
}
$ctext config -state disabled
+ return [expr {$nl >= 1000? 2: 1}]
}
proc mergediff {id l} {
fconfigure $mdf -blocking 0
set mdifffd($id) $mdf
set np [llength [lindex $parentlist $l]]
- fileevent $mdf readable [list getmergediffline $mdf $id $np]
- set nextupdate [expr {[clock clicks -milliseconds] + 100}]
+ filerun $mdf [list getmergediffline $mdf $id $np]
}
proc getmergediffline {mdf id np} {
- global diffmergeid ctext cflist nextupdate mergemax
+ global diffmergeid ctext cflist mergemax
global difffilestart mdifffd
- set n [gets $mdf line]
- if {$n < 0} {
- if {[eof $mdf]} {
+ $ctext conf -state normal
+ set nr 0
+ while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
+ if {![info exists diffmergeid] || $id != $diffmergeid
+ || $mdf != $mdifffd($id)} {
close $mdf
+ return 0
}
- return
- }
- if {![info exists diffmergeid] || $id != $diffmergeid
- || $mdf != $mdifffd($id)} {
- return
- }
- $ctext conf -state normal
- if {[regexp {^diff --cc (.*)} $line match fname]} {
- # start of a new file
- $ctext insert end "\n"
- set here [$ctext index "end - 1c"]
- lappend difffilestart $here
- add_flist [list $fname]
- set l [expr {(78 - [string length $fname]) / 2}]
- set pad [string range "----------------------------------------" 1 $l]
- $ctext insert end "$pad $fname $pad\n" filesep
- } elseif {[regexp {^@@} $line]} {
- $ctext insert end "$line\n" hunksep
- } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
- # do nothing
- } else {
- # parse the prefix - one ' ', '-' or '+' for each parent
- set spaces {}
- set minuses {}
- set pluses {}
- set isbad 0
- for {set j 0} {$j < $np} {incr j} {
- set c [string range $line $j $j]
- if {$c == " "} {
- lappend spaces $j
- } elseif {$c == "-"} {
- lappend minuses $j
- } elseif {$c == "+"} {
- lappend pluses $j
- } else {
- set isbad 1
- break
+ if {[regexp {^diff --cc (.*)} $line match fname]} {
+ # start of a new file
+ $ctext insert end "\n"
+ set here [$ctext index "end - 1c"]
+ lappend difffilestart $here
+ add_flist [list $fname]
+ set l [expr {(78 - [string length $fname]) / 2}]
+ set pad [string range "----------------------------------------" 1 $l]
+ $ctext insert end "$pad $fname $pad\n" filesep
+ } elseif {[regexp {^@@} $line]} {
+ $ctext insert end "$line\n" hunksep
+ } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
+ # do nothing
+ } else {
+ # parse the prefix - one ' ', '-' or '+' for each parent
+ set spaces {}
+ set minuses {}
+ set pluses {}
+ set isbad 0
+ for {set j 0} {$j < $np} {incr j} {
+ set c [string range $line $j $j]
+ if {$c == " "} {
+ lappend spaces $j
+ } elseif {$c == "-"} {
+ lappend minuses $j
+ } elseif {$c == "+"} {
+ lappend pluses $j
+ } else {
+ set isbad 1
+ break
+ }
}
- }
- set tags {}
- set num {}
- if {!$isbad && $minuses ne {} && $pluses eq {}} {
- # line doesn't appear in result, parents in $minuses have the line
- set num [lindex $minuses 0]
- } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
- # line appears in result, parents in $pluses don't have the line
- lappend tags mresult
- set num [lindex $spaces 0]
- }
- if {$num ne {}} {
- if {$num >= $mergemax} {
- set num "max"
+ set tags {}
+ set num {}
+ if {!$isbad && $minuses ne {} && $pluses eq {}} {
+ # line doesn't appear in result, parents in $minuses have the line
+ set num [lindex $minuses 0]
+ } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
+ # line appears in result, parents in $pluses don't have the line
+ lappend tags mresult
+ set num [lindex $spaces 0]
+ }
+ if {$num ne {}} {
+ if {$num >= $mergemax} {
+ set num "max"
+ }
+ lappend tags m$num
}
- lappend tags m$num
+ $ctext insert end "$line\n" $tags
}
- $ctext insert end "$line\n" $tags
}
$ctext conf -state disabled
- if {[clock clicks -milliseconds] >= $nextupdate} {
- incr nextupdate 100
- fileevent $mdf readable {}
- update
- fileevent $mdf readable [list getmergediffline $mdf $id $np]
+ if {[eof $mdf]} {
+ close $mdf
+ return 0
}
+ return [expr {$nr >= 1000? 2: 1}]
}
proc startdiff {ids} {
- global treediffs diffids treepending diffmergeid
+ global treediffs diffids treepending diffmergeid nullid nullid2
set diffids $ids
catch {unset diffmergeid}
- if {![info exists treediffs($ids)]} {
+ if {![info exists treediffs($ids)] ||
+ [lsearch -exact $ids $nullid] >= 0 ||
+ [lsearch -exact $ids $nullid2] >= 0} {
if {![info exists treepending]} {
gettreediffs $ids
}
getblobdiffs $ids
}
-proc gettreediffs {ids} {
- global treediff treepending
- set treepending $ids
- set treediff {}
- if {[catch \
- {set gdtf [open [concat | git diff-tree --no-commit-id -r $ids] r]} \
- ]} return
- fconfigure $gdtf -blocking 0
- fileevent $gdtf readable [list gettreediffline $gdtf $ids]
-}
+proc diffcmd {ids flags} {
+ global nullid nullid2
-proc gettreediffline {gdtf ids} {
+ set i [lsearch -exact $ids $nullid]
+ set j [lsearch -exact $ids $nullid2]
+ if {$i >= 0} {
+ if {[llength $ids] > 1 && $j < 0} {
+ # comparing working directory with some specific revision
+ set cmd [concat | git diff-index $flags]
+ if {$i == 0} {
+ lappend cmd -R [lindex $ids 1]
+ } else {
+ lappend cmd [lindex $ids 0]
+ }
+ } else {
+ # comparing working directory with index
+ set cmd [concat | git diff-files $flags]
+ if {$j == 1} {
+ lappend cmd -R
+ }
+ }
+ } elseif {$j >= 0} {
+ set cmd [concat | git diff-index --cached $flags]
+ if {[llength $ids] > 1} {
+ # comparing index with specific revision
+ if {$i == 0} {
+ lappend cmd -R [lindex $ids 1]
+ } else {
+ lappend cmd [lindex $ids 0]
+ }
+ } else {
+ # comparing index with HEAD
+ lappend cmd HEAD
+ }
+ } else {
+ set cmd [concat | git diff-tree -r $flags $ids]
+ }
+ return $cmd
+}
+
+proc gettreediffs {ids} {
+ global treediff treepending
+
+ set treepending $ids
+ set treediff {}
+ if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
+ fconfigure $gdtf -blocking 0
+ filerun $gdtf [list gettreediffline $gdtf $ids]
+}
+
+proc gettreediffline {gdtf ids} {
global treediff treediffs treepending diffids diffmergeid
global cmitmode
- set n [gets $gdtf line]
- if {$n < 0} {
- if {![eof $gdtf]} return
- close $gdtf
- set treediffs($ids) $treediff
- unset treepending
- if {$cmitmode eq "tree"} {
- gettree $diffids
- } elseif {$ids != $diffids} {
- if {![info exists diffmergeid]} {
- gettreediffs $diffids
+ set nr 0
+ while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
+ set i [string first "\t" $line]
+ if {$i >= 0} {
+ set file [string range $line [expr {$i+1}] end]
+ if {[string index $file 0] eq "\""} {
+ set file [lindex $file 0]
}
- } else {
- addtocflist $ids
+ lappend treediff $file
}
- return
}
- set file [lindex $line 5]
- lappend treediff $file
+ if {![eof $gdtf]} {
+ return [expr {$nr >= 1000? 2: 1}]
+ }
+ close $gdtf
+ set treediffs($ids) $treediff
+ unset treepending
+ if {$cmitmode eq "tree"} {
+ gettree $diffids
+ } elseif {$ids != $diffids} {
+ if {![info exists diffmergeid]} {
+ gettreediffs $diffids
+ }
+ } else {
+ addtocflist $ids
+ }
+ return 0
}
proc getblobdiffs {ids} {
- global diffopts blobdifffd diffids env curdifftag curtagstart
- global nextupdate diffinhdr treediffs
+ global diffopts blobdifffd diffids env
+ global diffinhdr treediffs
set env(GIT_DIFF_OPTS) $diffopts
- set cmd [concat | git diff-tree --no-commit-id -r -p -C $ids]
- if {[catch {set bdf [open $cmd r]} err]} {
+ if {[catch {set bdf [open [diffcmd $ids {-p -C --no-commit-id}] r]} err]} {
puts "error getting diffs: $err"
return
}
set diffinhdr 0
fconfigure $bdf -blocking 0
set blobdifffd($ids) $bdf
- set curdifftag Comments
- set curtagstart 0.0
- fileevent $bdf readable [list getblobdiffline $bdf $diffids]
- set nextupdate [expr {[clock clicks -milliseconds] + 100}]
+ filerun $bdf [list getblobdiffline $bdf $diffids]
}
proc setinlist {var i val} {
}
}
+proc makediffhdr {fname ids} {
+ global ctext curdiffstart treediffs
+
+ set i [lsearch -exact $treediffs($ids) $fname]
+ if {$i >= 0} {
+ setinlist difffilestart $i $curdiffstart
+ }
+ set l [expr {(78 - [string length $fname]) / 2}]
+ set pad [string range "----------------------------------------" 1 $l]
+ $ctext insert $curdiffstart "$pad $fname $pad" filesep
+}
+
proc getblobdiffline {bdf ids} {
- global diffids blobdifffd ctext curdifftag curtagstart
+ global diffids blobdifffd ctext curdiffstart
global diffnexthead diffnextnote difffilestart
- global nextupdate diffinhdr treediffs
+ global diffinhdr treediffs
- set n [gets $bdf line]
- if {$n < 0} {
- if {[eof $bdf]} {
+ set nr 0
+ $ctext conf -state normal
+ while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
+ if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
close $bdf
- if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
- $ctext tag add $curdifftag $curtagstart end
- }
+ return 0
}
- return
- }
- if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
- return
- }
- $ctext conf -state normal
- if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
- # start of a new file
- $ctext insert end "\n"
- $ctext tag add $curdifftag $curtagstart end
- set here [$ctext index "end - 1c"]
- set curtagstart $here
- set header $newname
- set i [lsearch -exact $treediffs($ids) $fname]
- if {$i >= 0} {
- setinlist difffilestart $i $here
- }
- if {$newname ne $fname} {
- set i [lsearch -exact $treediffs($ids) $newname]
- if {$i >= 0} {
- setinlist difffilestart $i $here
- }
- }
- set curdifftag "f:$fname"
- $ctext tag delete $curdifftag
- set l [expr {(78 - [string length $header]) / 2}]
- set pad [string range "----------------------------------------" 1 $l]
- $ctext insert end "$pad $header $pad\n" filesep
- set diffinhdr 1
- } elseif {$diffinhdr && [string compare -length 3 $line "---"] == 0} {
- # do nothing
- } elseif {$diffinhdr && [string compare -length 3 $line "+++"] == 0} {
- set diffinhdr 0
- } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
- $line match f1l f1c f2l f2c rest]} {
- $ctext insert end "$line\n" hunksep
- set diffinhdr 0
- } else {
- set x [string range $line 0 0]
- if {$x == "-" || $x == "+"} {
- set tag [expr {$x == "+"}]
- $ctext insert end "$line\n" d$tag
- } elseif {$x == " "} {
- $ctext insert end "$line\n"
- } elseif {$diffinhdr || $x == "\\"} {
- # e.g. "\ No newline at end of file"
+ if {![string compare -length 11 "diff --git " $line]} {
+ # trim off "diff --git "
+ set line [string range $line 11 end]
+ set diffinhdr 1
+ # start of a new file
+ $ctext insert end "\n"
+ set curdiffstart [$ctext index "end - 1c"]
+ $ctext insert end "\n" filesep
+ # If the name hasn't changed the length will be odd,
+ # the middle char will be a space, and the two bits either
+ # side will be a/name and b/name, or "a/name" and "b/name".
+ # If the name has changed we'll get "rename from" and
+ # "rename to" lines following this, and we'll use them
+ # to get the filenames.
+ # This complexity is necessary because spaces in the filename(s)
+ # don't get escaped.
+ set l [string length $line]
+ set i [expr {$l / 2}]
+ if {!(($l & 1) && [string index $line $i] eq " " &&
+ [string range $line 2 [expr {$i - 1}]] eq \
+ [string range $line [expr {$i + 3}] end])} {
+ continue
+ }
+ # unescape if quoted and chop off the a/ from the front
+ if {[string index $line 0] eq "\""} {
+ set fname [string range [lindex $line 0] 2 end]
+ } else {
+ set fname [string range $line 2 [expr {$i - 1}]]
+ }
+ makediffhdr $fname $ids
+
+ } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
+ $line match f1l f1c f2l f2c rest]} {
+ $ctext insert end "$line\n" hunksep
+ set diffinhdr 0
+
+ } elseif {$diffinhdr} {
+ if {![string compare -length 12 "rename from " $line]} {
+ set fname [string range $line 12 end]
+ if {[string index $fname 0] eq "\""} {
+ set fname [lindex $fname 0]
+ }
+ set i [lsearch -exact $treediffs($ids) $fname]
+ if {$i >= 0} {
+ setinlist difffilestart $i $curdiffstart
+ }
+ } elseif {![string compare -length 10 $line "rename to "]} {
+ set fname [string range $line 10 end]
+ if {[string index $fname 0] eq "\""} {
+ set fname [lindex $fname 0]
+ }
+ makediffhdr $fname $ids
+ } elseif {[string compare -length 3 $line "---"] == 0} {
+ # do nothing
+ continue
+ } elseif {[string compare -length 3 $line "+++"] == 0} {
+ set diffinhdr 0
+ continue
+ }
$ctext insert end "$line\n" filesep
+
} else {
- # Something else we don't recognize
- if {$curdifftag != "Comments"} {
- $ctext insert end "\n"
- $ctext tag add $curdifftag $curtagstart end
- set curtagstart [$ctext index "end - 1c"]
- set curdifftag Comments
+ set x [string range $line 0 0]
+ if {$x == "-" || $x == "+"} {
+ set tag [expr {$x == "+"}]
+ $ctext insert end "$line\n" d$tag
+ } elseif {$x == " "} {
+ $ctext insert end "$line\n"
+ } else {
+ # "\ No newline at end of file",
+ # or something else we don't recognize
+ $ctext insert end "$line\n" hunksep
}
- $ctext insert end "$line\n" filesep
}
}
$ctext conf -state disabled
- if {[clock clicks -milliseconds] >= $nextupdate} {
- incr nextupdate 100
- fileevent $bdf readable {}
- update
- fileevent $bdf readable "getblobdiffline $bdf {$ids}"
+ if {[eof $bdf]} {
+ close $bdf
+ return 0
}
+ return [expr {$nr >= 1000? 2: 1}]
}
proc changediffdisp {} {
}
proc incrfont {inc} {
- global mainfont textfont ctext canv phase
+ global mainfont textfont ctext canv phase cflist
+ global charspc tabstop
global stopped entries
unmarkmatches
set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
setcoords
- $ctext conf -font $textfont
+ $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
+ $cflist conf -font $textfont
$ctext tag conf filesep -font [concat $textfont bold]
foreach e $entries {
$e conf -font $mainfont
proc rowmenu {x y id} {
global rowctxmenu commitrow selectedline rowmenuid curview
+ global nullid nullid2 fakerowmenu mainhead
+ set rowmenuid $id
if {![info exists selectedline]
|| $commitrow($curview,$id) eq $selectedline} {
set state disabled
} else {
set state normal
}
- $rowctxmenu entryconfigure "Diff this*" -state $state
- $rowctxmenu entryconfigure "Diff selected*" -state $state
- $rowctxmenu entryconfigure "Make patch" -state $state
- set rowmenuid $id
- tk_popup $rowctxmenu $x $y
+ if {$id ne $nullid && $id ne $nullid2} {
+ set menu $rowctxmenu
+ $menu entryconfigure 7 -label "Reset $mainhead branch to here"
+ } else {
+ set menu $fakerowmenu
+ }
+ $menu entryconfigure "Diff this*" -state $state
+ $menu entryconfigure "Diff selected*" -state $state
+ $menu entryconfigure "Make patch" -state $state
+ tk_popup $menu $x $y
}
proc diffvssel {dirn} {
$ctext insert end [lindex $commitinfo($newid) 0]
$ctext insert end "\n"
$ctext conf -state disabled
- $ctext tag delete Comments
$ctext tag remove found 1.0 end
startdiff [list $oldid $newid]
}
}
proc mkpatchgo {} {
- global patchtop
+ global patchtop nullid nullid2
set oldid [$patchtop.fromsha1 get]
set newid [$patchtop.tosha1 get]
set fname [$patchtop.fname get]
- if {[catch {exec git diff-tree -p $oldid $newid >$fname &} err]} {
+ set cmd [diffcmd [list $oldid $newid] -p]
+ lappend cmd >$fname &
+ if {[catch {eval exec $cmd} err]} {
error_popup "Error creating patch: $err"
}
catch {destroy $patchtop}
proc redrawtags {id} {
global canv linehtag commitrow idpos selectedline curview
- global mainfont canvxmax
+ global mainfont canvxmax iddrawn
if {![info exists commitrow($curview,$id)]} return
- drawcmitrow $commitrow($curview,$id)
+ if {![info exists iddrawn($id)]} return
+ drawcommits $commitrow($curview,$id)
$canv delete tag.$id
set xt [eval drawtags $id $idpos($id)]
$canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
notbusy newbranch
error_popup $err
} else {
+ set headids($name) $id
+ lappend idheads($id) $name
addedhead $id $name
- # XXX should update list of heads displayed for selected commit
notbusy newbranch
redrawtags $id
+ dispneartags 0
}
}
proc cherrypick {} {
global rowmenuid curview commitrow
- global mainhead desc_heads anc_tags desc_tags allparents allchildren
+ global mainhead
- if {[info exists desc_heads($rowmenuid)]
- && [lsearch -exact $desc_heads($rowmenuid) $mainhead] >= 0} {
+ set oldhead [exec git rev-parse HEAD]
+ set dheads [descheads $rowmenuid]
+ if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
included in branch $mainhead -- really re-apply it?"]
if {!$ok} return
}
nowbusy cherrypick
update
- set oldhead [exec git rev-parse HEAD]
# Unfortunately git-cherry-pick writes stuff to stderr even when
# no error occurs, and exec takes that as an indication of error...
if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
error_popup "No changes committed"
return
}
- set allparents($newhead) $oldhead
- lappend allchildren($oldhead) $newhead
- set desc_heads($newhead) $mainhead
- if {[info exists anc_tags($oldhead)]} {
- set anc_tags($newhead) $anc_tags($oldhead)
- }
- set desc_tags($newhead) {}
+ addnewchild $newhead $oldhead
if {[info exists commitrow($curview,$oldhead)]} {
insertrow $commitrow($curview,$oldhead) $newhead
if {$mainhead ne {}} {
+ movehead $newhead $mainhead
movedhead $newhead $mainhead
}
redrawtags $oldhead
notbusy cherrypick
}
+proc resethead {} {
+ global mainheadid mainhead rowmenuid confirm_ok resettype
+ global showlocalchanges
+
+ set confirm_ok 0
+ set w ".confirmreset"
+ toplevel $w
+ wm transient $w .
+ wm title $w "Confirm reset"
+ message $w.m -text \
+ "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
+ -justify center -aspect 1000
+ pack $w.m -side top -fill x -padx 20 -pady 20
+ frame $w.f -relief sunken -border 2
+ message $w.f.rt -text "Reset type:" -aspect 1000
+ grid $w.f.rt -sticky w
+ set resettype mixed
+ radiobutton $w.f.soft -value soft -variable resettype -justify left \
+ -text "Soft: Leave working tree and index untouched"
+ grid $w.f.soft -sticky w
+ radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
+ -text "Mixed: Leave working tree untouched, reset index"
+ grid $w.f.mixed -sticky w
+ radiobutton $w.f.hard -value hard -variable resettype -justify left \
+ -text "Hard: Reset working tree and index\n(discard ALL local changes)"
+ grid $w.f.hard -sticky w
+ pack $w.f -side top -fill x
+ button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
+ pack $w.ok -side left -fill x -padx 20 -pady 20
+ button $w.cancel -text Cancel -command "destroy $w"
+ pack $w.cancel -side right -fill x -padx 20 -pady 20
+ bind $w <Visibility> "grab $w; focus $w"
+ tkwait window $w
+ if {!$confirm_ok} return
+ if {[catch {set fd [open \
+ [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
+ error_popup $err
+ } else {
+ dohidelocalchanges
+ set w ".resetprogress"
+ filerun $fd [list readresetstat $fd $w]
+ toplevel $w
+ wm transient $w
+ wm title $w "Reset progress"
+ message $w.m -text "Reset in progress, please wait..." \
+ -justify center -aspect 1000
+ pack $w.m -side top -fill x -padx 20 -pady 5
+ canvas $w.c -width 150 -height 20 -bg white
+ $w.c create rect 0 0 0 20 -fill green -tags rect
+ pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
+ nowbusy reset
+ }
+}
+
+proc readresetstat {fd w} {
+ global mainhead mainheadid showlocalchanges
+
+ if {[gets $fd line] >= 0} {
+ if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
+ set x [expr {($m * 150) / $n}]
+ $w.c coords rect 0 0 $x 20
+ }
+ return 1
+ }
+ destroy $w
+ notbusy reset
+ if {[catch {close $fd} err]} {
+ error_popup $err
+ }
+ set oldhead $mainheadid
+ set newhead [exec git rev-parse HEAD]
+ if {$newhead ne $oldhead} {
+ movehead $newhead $mainhead
+ movedhead $newhead $mainhead
+ set mainheadid $newhead
+ redrawtags $oldhead
+ redrawtags $newhead
+ }
+ if {$showlocalchanges} {
+ doshowlocalchanges
+ }
+ return 0
+}
+
# context menu for a head
proc headmenu {x y id head} {
- global headmenuid headmenuhead headctxmenu
+ global headmenuid headmenuhead headctxmenu mainhead
set headmenuid $id
set headmenuhead $head
+ set state normal
+ if {$head eq $mainhead} {
+ set state disabled
+ }
+ $headctxmenu entryconfigure 0 -state $state
+ $headctxmenu entryconfigure 1 -state $state
tk_popup $headctxmenu $x $y
}
proc cobranch {} {
global headmenuid headmenuhead mainhead headids
+ global showlocalchanges mainheadid
# check the tree is clean first??
set oldmainhead $mainhead
nowbusy checkout
update
+ dohidelocalchanges
if {[catch {
- exec git checkout $headmenuhead
+ exec git checkout -q $headmenuhead
} err]} {
notbusy checkout
error_popup $err
} else {
notbusy checkout
set mainhead $headmenuhead
+ set mainheadid $headmenuid
if {[info exists headids($oldmainhead)]} {
redrawtags $headids($oldmainhead)
}
redrawtags $headmenuid
}
+ if {$showlocalchanges} {
+ dodiffindex
+ }
}
proc rmbranch {} {
- global desc_heads headmenuid headmenuhead mainhead
+ global headmenuid headmenuhead mainhead
global headids idheads
set head $headmenuhead
set id $headmenuid
+ # this check shouldn't be needed any more...
if {$head eq $mainhead} {
error_popup "Cannot delete the currently checked-out branch"
return
}
- if {$desc_heads($id) eq $head} {
+ set dheads [descheads $id]
+ if {$dheads eq $headids($head)} {
# the stuff on this branch isn't on any other branch
if {![confirm_popup "The commits on branch $head aren't on any other\
branch.\nReally delete branch $head?"]} return
error_popup $err
return
}
+ removehead $id $head
removedhead $id $head
redrawtags $id
notbusy rmbranch
+ dispneartags 0
}
# Stuff for finding nearby tags
proc getallcommits {} {
- global allcstart allcommits allcfd allids
+ global allcommits allids nbmp nextarc seeds
set allids {}
- set fd [open [concat | git rev-list --all --topo-order --parents] r]
- set allcfd $fd
+ set nbmp 0
+ set nextarc 0
+ set allcommits 0
+ set seeds {}
+ regetallcommits
+}
+
+# Called when the graph might have changed
+proc regetallcommits {} {
+ global allcommits seeds
+
+ set cmd [concat | git rev-list --all --parents]
+ foreach id $seeds {
+ lappend cmd "^$id"
+ }
+ set fd [open $cmd r]
fconfigure $fd -blocking 0
- set allcommits "reading"
+ incr allcommits
nowbusy allcommits
- restartgetall $fd
-}
+ filerun $fd [list getallclines $fd]
+}
+
+# Since most commits have 1 parent and 1 child, we group strings of
+# such commits into "arcs" joining branch/merge points (BMPs), which
+# are commits that either don't have 1 parent or don't have 1 child.
+#
+# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
+# arcout(id) - outgoing arcs for BMP
+# arcids(a) - list of IDs on arc including end but not start
+# arcstart(a) - BMP ID at start of arc
+# arcend(a) - BMP ID at end of arc
+# growing(a) - arc a is still growing
+# arctags(a) - IDs out of arcids (excluding end) that have tags
+# archeads(a) - IDs out of arcids (excluding end) that have heads
+# The start of an arc is at the descendent end, so "incoming" means
+# coming from descendents, and "outgoing" means going towards ancestors.
-proc discardallcommits {} {
- global allparents allchildren allcommits allcfd
- global desc_tags anc_tags alldtags tagisdesc allids desc_heads
+proc getallclines {fd} {
+ global allids allparents allchildren idtags idheads nextarc nbmp
+ global arcnos arcids arctags arcout arcend arcstart archeads growing
+ global seeds allcommits
+
+ set nid 0
+ while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
+ set id [lindex $line 0]
+ if {[info exists allparents($id)]} {
+ # seen it already
+ continue
+ }
+ lappend allids $id
+ set olds [lrange $line 1 end]
+ set allparents($id) $olds
+ if {![info exists allchildren($id)]} {
+ set allchildren($id) {}
+ set arcnos($id) {}
+ lappend seeds $id
+ } else {
+ set a $arcnos($id)
+ if {[llength $olds] == 1 && [llength $a] == 1} {
+ lappend arcids($a) $id
+ if {[info exists idtags($id)]} {
+ lappend arctags($a) $id
+ }
+ if {[info exists idheads($id)]} {
+ lappend archeads($a) $id
+ }
+ if {[info exists allparents($olds)]} {
+ # seen parent already
+ if {![info exists arcout($olds)]} {
+ splitarc $olds
+ }
+ lappend arcids($a) $olds
+ set arcend($a) $olds
+ unset growing($a)
+ }
+ lappend allchildren($olds) $id
+ lappend arcnos($olds) $a
+ continue
+ }
+ }
+ incr nbmp
+ foreach a $arcnos($id) {
+ lappend arcids($a) $id
+ set arcend($a) $id
+ unset growing($a)
+ }
- if {![info exists allcommits]} return
- if {$allcommits eq "reading"} {
- catch {close $allcfd}
+ set ao {}
+ foreach p $olds {
+ lappend allchildren($p) $id
+ set a [incr nextarc]
+ set arcstart($a) $id
+ set archeads($a) {}
+ set arctags($a) {}
+ set archeads($a) {}
+ set arcids($a) {}
+ lappend ao $a
+ set growing($a) 1
+ if {[info exists allparents($p)]} {
+ # seen it already, may need to make a new branch
+ if {![info exists arcout($p)]} {
+ splitarc $p
+ }
+ lappend arcids($a) $p
+ set arcend($a) $p
+ unset growing($a)
+ }
+ lappend arcnos($p) $a
+ }
+ set arcout($id) $ao
}
- foreach v {allcommits allchildren allparents allids desc_tags anc_tags
- alldtags tagisdesc desc_heads} {
- catch {unset $v}
+ if {$nid > 0} {
+ global cached_dheads cached_dtags cached_atags
+ catch {unset cached_dheads}
+ catch {unset cached_dtags}
+ catch {unset cached_atags}
+ }
+ if {![eof $fd]} {
+ return [expr {$nid >= 1000? 2: 1}]
+ }
+ close $fd
+ if {[incr allcommits -1] == 0} {
+ notbusy allcommits
}
+ dispneartags 0
+ return 0
}
-proc restartgetall {fd} {
- global allcstart
+proc recalcarc {a} {
+ global arctags archeads arcids idtags idheads
- fileevent $fd readable [list getallclines $fd]
- set allcstart [clock clicks -milliseconds]
+ set at {}
+ set ah {}
+ foreach id [lrange $arcids($a) 0 end-1] {
+ if {[info exists idtags($id)]} {
+ lappend at $id
+ }
+ if {[info exists idheads($id)]} {
+ lappend ah $id
+ }
+ }
+ set arctags($a) $at
+ set archeads($a) $ah
}
-proc combine_dtags {l1 l2} {
- global tagisdesc notfirstd
+proc splitarc {p} {
+ global arcnos arcids nextarc nbmp arctags archeads idtags idheads
+ global arcstart arcend arcout allparents growing
- set res [lsort -unique [concat $l1 $l2]]
- for {set i 0} {$i < [llength $res]} {incr i} {
- set x [lindex $res $i]
- for {set j [expr {$i+1}]} {$j < [llength $res]} {} {
- set y [lindex $res $j]
- if {[info exists tagisdesc($x,$y)]} {
- if {$tagisdesc($x,$y) > 0} {
- # x is a descendent of y, exclude x
- set res [lreplace $res $i $i]
- incr i -1
- break
- } else {
- # y is a descendent of x, exclude y
- set res [lreplace $res $j $j]
- }
- } else {
- # no relation, keep going
- incr j
+ set a $arcnos($p)
+ if {[llength $a] != 1} {
+ puts "oops splitarc called but [llength $a] arcs already"
+ return
+ }
+ set a [lindex $a 0]
+ set i [lsearch -exact $arcids($a) $p]
+ if {$i < 0} {
+ puts "oops splitarc $p not in arc $a"
+ return
+ }
+ set na [incr nextarc]
+ if {[info exists arcend($a)]} {
+ set arcend($na) $arcend($a)
+ } else {
+ set l [lindex $allparents([lindex $arcids($a) end]) 0]
+ set j [lsearch -exact $arcnos($l) $a]
+ set arcnos($l) [lreplace $arcnos($l) $j $j $na]
+ }
+ set tail [lrange $arcids($a) [expr {$i+1}] end]
+ set arcids($a) [lrange $arcids($a) 0 $i]
+ set arcend($a) $p
+ set arcstart($na) $p
+ set arcout($p) $na
+ set arcids($na) $tail
+ if {[info exists growing($a)]} {
+ set growing($na) 1
+ unset growing($a)
+ }
+ incr nbmp
+
+ foreach id $tail {
+ if {[llength $arcnos($id)] == 1} {
+ set arcnos($id) $na
+ } else {
+ set j [lsearch -exact $arcnos($id) $a]
+ set arcnos($id) [lreplace $arcnos($id) $j $j $na]
+ }
+ }
+
+ # reconstruct tags and heads lists
+ if {$arctags($a) ne {} || $archeads($a) ne {}} {
+ recalcarc $a
+ recalcarc $na
+ } else {
+ set arctags($na) {}
+ set archeads($na) {}
+ }
+}
+
+# Update things for a new commit added that is a child of one
+# existing commit. Used when cherry-picking.
+proc addnewchild {id p} {
+ global allids allparents allchildren idtags nextarc nbmp
+ global arcnos arcids arctags arcout arcend arcstart archeads growing
+ global seeds
+
+ lappend allids $id
+ set allparents($id) [list $p]
+ set allchildren($id) {}
+ set arcnos($id) {}
+ lappend seeds $id
+ incr nbmp
+ lappend allchildren($p) $id
+ set a [incr nextarc]
+ set arcstart($a) $id
+ set archeads($a) {}
+ set arctags($a) {}
+ set arcids($a) [list $p]
+ set arcend($a) $p
+ if {![info exists arcout($p)]} {
+ splitarc $p
+ }
+ lappend arcnos($p) $a
+ set arcout($id) [list $a]
+}
+
+# Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
+# or 0 if neither is true.
+proc anc_or_desc {a b} {
+ global arcout arcstart arcend arcnos cached_isanc
+
+ if {$arcnos($a) eq $arcnos($b)} {
+ # Both are on the same arc(s); either both are the same BMP,
+ # or if one is not a BMP, the other is also not a BMP or is
+ # the BMP at end of the arc (and it only has 1 incoming arc).
+ # Or both can be BMPs with no incoming arcs.
+ if {$a eq $b || $arcnos($a) eq {}} {
+ return 0
+ }
+ # assert {[llength $arcnos($a)] == 1}
+ set arc [lindex $arcnos($a) 0]
+ set i [lsearch -exact $arcids($arc) $a]
+ set j [lsearch -exact $arcids($arc) $b]
+ if {$i < 0 || $i > $j} {
+ return 1
+ } else {
+ return -1
+ }
+ }
+
+ if {![info exists arcout($a)]} {
+ set arc [lindex $arcnos($a) 0]
+ if {[info exists arcend($arc)]} {
+ set aend $arcend($arc)
+ } else {
+ set aend {}
+ }
+ set a $arcstart($arc)
+ } else {
+ set aend $a
+ }
+ if {![info exists arcout($b)]} {
+ set arc [lindex $arcnos($b) 0]
+ if {[info exists arcend($arc)]} {
+ set bend $arcend($arc)
+ } else {
+ set bend {}
+ }
+ set b $arcstart($arc)
+ } else {
+ set bend $b
+ }
+ if {$a eq $bend} {
+ return 1
+ }
+ if {$b eq $aend} {
+ return -1
+ }
+ if {[info exists cached_isanc($a,$bend)]} {
+ if {$cached_isanc($a,$bend)} {
+ return 1
+ }
+ }
+ if {[info exists cached_isanc($b,$aend)]} {
+ if {$cached_isanc($b,$aend)} {
+ return -1
+ }
+ if {[info exists cached_isanc($a,$bend)]} {
+ return 0
+ }
+ }
+
+ set todo [list $a $b]
+ set anc($a) a
+ set anc($b) b
+ for {set i 0} {$i < [llength $todo]} {incr i} {
+ set x [lindex $todo $i]
+ if {$anc($x) eq {}} {
+ continue
+ }
+ foreach arc $arcnos($x) {
+ set xd $arcstart($arc)
+ if {$xd eq $bend} {
+ set cached_isanc($a,$bend) 1
+ set cached_isanc($b,$aend) 0
+ return 1
+ } elseif {$xd eq $aend} {
+ set cached_isanc($b,$aend) 1
+ set cached_isanc($a,$bend) 0
+ return -1
+ }
+ if {![info exists anc($xd)]} {
+ set anc($xd) $anc($x)
+ lappend todo $xd
+ } elseif {$anc($xd) ne $anc($x)} {
+ set anc($xd) {}
}
}
}
- return $res
+ set cached_isanc($a,$bend) 0
+ set cached_isanc($b,$aend) 0
+ return 0
}
-proc combine_atags {l1 l2} {
- global tagisdesc
-
- set res [lsort -unique [concat $l1 $l2]]
- for {set i 0} {$i < [llength $res]} {incr i} {
- set x [lindex $res $i]
- for {set j [expr {$i+1}]} {$j < [llength $res]} {} {
- set y [lindex $res $j]
- if {[info exists tagisdesc($x,$y)]} {
- if {$tagisdesc($x,$y) < 0} {
- # x is an ancestor of y, exclude x
- set res [lreplace $res $i $i]
- incr i -1
- break
- } else {
- # y is an ancestor of x, exclude y
- set res [lreplace $res $j $j]
+# This identifies whether $desc has an ancestor that is
+# a growing tip of the graph and which is not an ancestor of $anc
+# and returns 0 if so and 1 if not.
+# If we subsequently discover a tag on such a growing tip, and that
+# turns out to be a descendent of $anc (which it could, since we
+# don't necessarily see children before parents), then $desc
+# isn't a good choice to display as a descendent tag of
+# $anc (since it is the descendent of another tag which is
+# a descendent of $anc). Similarly, $anc isn't a good choice to
+# display as a ancestor tag of $desc.
+#
+proc is_certain {desc anc} {
+ global arcnos arcout arcstart arcend growing problems
+
+ set certain {}
+ if {[llength $arcnos($anc)] == 1} {
+ # tags on the same arc are certain
+ if {$arcnos($desc) eq $arcnos($anc)} {
+ return 1
+ }
+ if {![info exists arcout($anc)]} {
+ # if $anc is partway along an arc, use the start of the arc instead
+ set a [lindex $arcnos($anc) 0]
+ set anc $arcstart($a)
+ }
+ }
+ if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
+ set x $desc
+ } else {
+ set a [lindex $arcnos($desc) 0]
+ set x $arcend($a)
+ }
+ if {$x == $anc} {
+ return 1
+ }
+ set anclist [list $x]
+ set dl($x) 1
+ set nnh 1
+ set ngrowanc 0
+ for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
+ set x [lindex $anclist $i]
+ if {$dl($x)} {
+ incr nnh -1
+ }
+ set done($x) 1
+ foreach a $arcout($x) {
+ if {[info exists growing($a)]} {
+ if {![info exists growanc($x)] && $dl($x)} {
+ set growanc($x) 1
+ incr ngrowanc
}
} else {
- # no relation, keep going
- incr j
+ set y $arcend($a)
+ if {[info exists dl($y)]} {
+ if {$dl($y)} {
+ if {!$dl($x)} {
+ set dl($y) 0
+ if {![info exists done($y)]} {
+ incr nnh -1
+ }
+ if {[info exists growanc($x)]} {
+ incr ngrowanc -1
+ }
+ set xl [list $y]
+ for {set k 0} {$k < [llength $xl]} {incr k} {
+ set z [lindex $xl $k]
+ foreach c $arcout($z) {
+ if {[info exists arcend($c)]} {
+ set v $arcend($c)
+ if {[info exists dl($v)] && $dl($v)} {
+ set dl($v) 0
+ if {![info exists done($v)]} {
+ incr nnh -1
+ }
+ if {[info exists growanc($v)]} {
+ incr ngrowanc -1
+ }
+ lappend xl $v
+ }
+ }
+ }
+ }
+ }
+ }
+ } elseif {$y eq $anc || !$dl($x)} {
+ set dl($y) 0
+ lappend anclist $y
+ } else {
+ set dl($y) 1
+ lappend anclist $y
+ incr nnh
+ }
}
}
}
- return $res
+ foreach x [array names growanc] {
+ if {$dl($x)} {
+ return 0
+ }
+ return 0
+ }
+ return 1
}
-proc forward_pass {id children} {
- global idtags desc_tags idheads desc_heads alldtags tagisdesc
+proc validate_arctags {a} {
+ global arctags idtags
- set dtags {}
- set dheads {}
- foreach child $children {
- if {[info exists idtags($child)]} {
- set ctags [list $child]
- } else {
- set ctags $desc_tags($child)
+ set i -1
+ set na $arctags($a)
+ foreach id $arctags($a) {
+ incr i
+ if {![info exists idtags($id)]} {
+ set na [lreplace $na $i $i]
+ incr i -1
+ }
+ }
+ set arctags($a) $na
+}
+
+proc validate_archeads {a} {
+ global archeads idheads
+
+ set i -1
+ set na $archeads($a)
+ foreach id $archeads($a) {
+ incr i
+ if {![info exists idheads($id)]} {
+ set na [lreplace $na $i $i]
+ incr i -1
+ }
+ }
+ set archeads($a) $na
+}
+
+# Return the list of IDs that have tags that are descendents of id,
+# ignoring IDs that are descendents of IDs already reported.
+proc desctags {id} {
+ global arcnos arcstart arcids arctags idtags allparents
+ global growing cached_dtags
+
+ if {![info exists allparents($id)]} {
+ return {}
+ }
+ set t1 [clock clicks -milliseconds]
+ set argid $id
+ if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+ # part-way along an arc; check that arc first
+ set a [lindex $arcnos($id) 0]
+ if {$arctags($a) ne {}} {
+ validate_arctags $a
+ set i [lsearch -exact $arcids($a) $id]
+ set tid {}
+ foreach t $arctags($a) {
+ set j [lsearch -exact $arcids($a) $t]
+ if {$j >= $i} break
+ set tid $t
+ }
+ if {$tid ne {}} {
+ return $tid
+ }
+ }
+ set id $arcstart($a)
+ if {[info exists idtags($id)]} {
+ return $id
+ }
+ }
+ if {[info exists cached_dtags($id)]} {
+ return $cached_dtags($id)
+ }
+
+ set origid $id
+ set todo [list $id]
+ set queued($id) 1
+ set nc 1
+ for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
+ set id [lindex $todo $i]
+ set done($id) 1
+ set ta [info exists hastaggedancestor($id)]
+ if {!$ta} {
+ incr nc -1
+ }
+ # ignore tags on starting node
+ if {!$ta && $i > 0} {
+ if {[info exists idtags($id)]} {
+ set tagloc($id) $id
+ set ta 1
+ } elseif {[info exists cached_dtags($id)]} {
+ set tagloc($id) $cached_dtags($id)
+ set ta 1
+ }
+ }
+ foreach a $arcnos($id) {
+ set d $arcstart($a)
+ if {!$ta && $arctags($a) ne {}} {
+ validate_arctags $a
+ if {$arctags($a) ne {}} {
+ lappend tagloc($id) [lindex $arctags($a) end]
+ }
+ }
+ if {$ta || $arctags($a) ne {}} {
+ set tomark [list $d]
+ for {set j 0} {$j < [llength $tomark]} {incr j} {
+ set dd [lindex $tomark $j]
+ if {![info exists hastaggedancestor($dd)]} {
+ if {[info exists done($dd)]} {
+ foreach b $arcnos($dd) {
+ lappend tomark $arcstart($b)
+ }
+ if {[info exists tagloc($dd)]} {
+ unset tagloc($dd)
+ }
+ } elseif {[info exists queued($dd)]} {
+ incr nc -1
+ }
+ set hastaggedancestor($dd) 1
+ }
+ }
+ }
+ if {![info exists queued($d)]} {
+ lappend todo $d
+ set queued($d) 1
+ if {![info exists hastaggedancestor($d)]} {
+ incr nc
+ }
+ }
}
- if {$dtags eq {}} {
- set dtags $ctags
- } elseif {$ctags ne $dtags} {
- set dtags [combine_dtags $dtags $ctags]
+ }
+ set tags {}
+ foreach id [array names tagloc] {
+ if {![info exists hastaggedancestor($id)]} {
+ foreach t $tagloc($id) {
+ if {[lsearch -exact $tags $t] < 0} {
+ lappend tags $t
+ }
+ }
}
- set cheads $desc_heads($child)
- if {$dheads eq {}} {
- set dheads $cheads
- } elseif {$cheads ne $dheads} {
- set dheads [lsort -unique [concat $dheads $cheads]]
+ }
+ set t2 [clock clicks -milliseconds]
+ set loopix $i
+
+ # remove tags that are descendents of other tags
+ for {set i 0} {$i < [llength $tags]} {incr i} {
+ set a [lindex $tags $i]
+ for {set j 0} {$j < $i} {incr j} {
+ set b [lindex $tags $j]
+ set r [anc_or_desc $a $b]
+ if {$r == 1} {
+ set tags [lreplace $tags $j $j]
+ incr j -1
+ incr i -1
+ } elseif {$r == -1} {
+ set tags [lreplace $tags $i $i]
+ incr i -1
+ break
+ }
}
}
- set desc_tags($id) $dtags
- if {[info exists idtags($id)]} {
- set adt $dtags
- foreach tag $dtags {
- set adt [concat $adt $alldtags($tag)]
+
+ if {[array names growing] ne {}} {
+ # graph isn't finished, need to check if any tag could get
+ # eclipsed by another tag coming later. Simply ignore any
+ # tags that could later get eclipsed.
+ set ctags {}
+ foreach t $tags {
+ if {[is_certain $t $origid]} {
+ lappend ctags $t
+ }
}
- set adt [lsort -unique $adt]
- set alldtags($id) $adt
- foreach tag $adt {
- set tagisdesc($id,$tag) -1
- set tagisdesc($tag,$id) 1
+ if {$tags eq $ctags} {
+ set cached_dtags($origid) $tags
+ } else {
+ set tags $ctags
}
+ } else {
+ set cached_dtags($origid) $tags
}
- if {[info exists idheads($id)]} {
- set dheads [concat $dheads $idheads($id)]
+ set t3 [clock clicks -milliseconds]
+ if {0 && $t3 - $t1 >= 100} {
+ puts "iterating descendents ($loopix/[llength $todo] nodes) took\
+ [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
}
- set desc_heads($id) $dheads
+ return $tags
}
-proc getallclines {fd} {
- global allparents allchildren allcommits allcstart
- global desc_tags anc_tags idtags tagisdesc allids
- global idheads travindex
-
- while {[gets $fd line] >= 0} {
- set id [lindex $line 0]
- lappend allids $id
- set olds [lrange $line 1 end]
- set allparents($id) $olds
- if {![info exists allchildren($id)]} {
- set allchildren($id) {}
+proc anctags {id} {
+ global arcnos arcids arcout arcend arctags idtags allparents
+ global growing cached_atags
+
+ if {![info exists allparents($id)]} {
+ return {}
+ }
+ set t1 [clock clicks -milliseconds]
+ set argid $id
+ if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+ # part-way along an arc; check that arc first
+ set a [lindex $arcnos($id) 0]
+ if {$arctags($a) ne {}} {
+ validate_arctags $a
+ set i [lsearch -exact $arcids($a) $id]
+ foreach t $arctags($a) {
+ set j [lsearch -exact $arcids($a) $t]
+ if {$j > $i} {
+ return $t
+ }
+ }
}
- foreach p $olds {
- lappend allchildren($p) $id
+ if {![info exists arcend($a)]} {
+ return {}
+ }
+ set id $arcend($a)
+ if {[info exists idtags($id)]} {
+ return $id
+ }
+ }
+ if {[info exists cached_atags($id)]} {
+ return $cached_atags($id)
+ }
+
+ set origid $id
+ set todo [list $id]
+ set queued($id) 1
+ set taglist {}
+ set nc 1
+ for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
+ set id [lindex $todo $i]
+ set done($id) 1
+ set td [info exists hastaggeddescendent($id)]
+ if {!$td} {
+ incr nc -1
+ }
+ # ignore tags on starting node
+ if {!$td && $i > 0} {
+ if {[info exists idtags($id)]} {
+ set tagloc($id) $id
+ set td 1
+ } elseif {[info exists cached_atags($id)]} {
+ set tagloc($id) $cached_atags($id)
+ set td 1
+ }
}
- # compute nearest tagged descendents as we go
- # also compute descendent heads
- forward_pass $id $allchildren($id)
- if {[clock clicks -milliseconds] - $allcstart >= 50} {
- fileevent $fd readable {}
- after idle restartgetall $fd
- return
+ foreach a $arcout($id) {
+ if {!$td && $arctags($a) ne {}} {
+ validate_arctags $a
+ if {$arctags($a) ne {}} {
+ lappend tagloc($id) [lindex $arctags($a) 0]
+ }
+ }
+ if {![info exists arcend($a)]} continue
+ set d $arcend($a)
+ if {$td || $arctags($a) ne {}} {
+ set tomark [list $d]
+ for {set j 0} {$j < [llength $tomark]} {incr j} {
+ set dd [lindex $tomark $j]
+ if {![info exists hastaggeddescendent($dd)]} {
+ if {[info exists done($dd)]} {
+ foreach b $arcout($dd) {
+ if {[info exists arcend($b)]} {
+ lappend tomark $arcend($b)
+ }
+ }
+ if {[info exists tagloc($dd)]} {
+ unset tagloc($dd)
+ }
+ } elseif {[info exists queued($dd)]} {
+ incr nc -1
+ }
+ set hastaggeddescendent($dd) 1
+ }
+ }
+ }
+ if {![info exists queued($d)]} {
+ lappend todo $d
+ set queued($d) 1
+ if {![info exists hastaggeddescendent($d)]} {
+ incr nc
+ }
+ }
}
}
- if {[eof $fd]} {
- set travindex [llength $allids]
- set allcommits "traversing"
- after idle restartatags
- if {[catch {close $fd} err]} {
- error_popup "Error reading full commit graph: $err.\n\
- Results may be incomplete."
+ set t2 [clock clicks -milliseconds]
+ set loopix $i
+ set tags {}
+ foreach id [array names tagloc] {
+ if {![info exists hastaggeddescendent($id)]} {
+ foreach t $tagloc($id) {
+ if {[lsearch -exact $tags $t] < 0} {
+ lappend tags $t
+ }
+ }
}
}
-}
-
-# walk backward through the tree and compute nearest tagged ancestors
-proc restartatags {} {
- global allids allparents idtags anc_tags travindex
- set t0 [clock clicks -milliseconds]
- set i $travindex
- while {[incr i -1] >= 0} {
- set id [lindex $allids $i]
- set atags {}
- foreach p $allparents($id) {
- if {[info exists idtags($p)]} {
- set ptags [list $p]
- } else {
- set ptags $anc_tags($p)
+ # remove tags that are ancestors of other tags
+ for {set i 0} {$i < [llength $tags]} {incr i} {
+ set a [lindex $tags $i]
+ for {set j 0} {$j < $i} {incr j} {
+ set b [lindex $tags $j]
+ set r [anc_or_desc $a $b]
+ if {$r == -1} {
+ set tags [lreplace $tags $j $j]
+ incr j -1
+ incr i -1
+ } elseif {$r == 1} {
+ set tags [lreplace $tags $i $i]
+ incr i -1
+ break
}
- if {$atags eq {}} {
- set atags $ptags
- } elseif {$ptags ne $atags} {
- set atags [combine_atags $atags $ptags]
+ }
+ }
+
+ if {[array names growing] ne {}} {
+ # graph isn't finished, need to check if any tag could get
+ # eclipsed by another tag coming later. Simply ignore any
+ # tags that could later get eclipsed.
+ set ctags {}
+ foreach t $tags {
+ if {[is_certain $origid $t]} {
+ lappend ctags $t
}
}
- set anc_tags($id) $atags
- if {[clock clicks -milliseconds] - $t0 >= 50} {
- set travindex $i
- after idle restartatags
- return
+ if {$tags eq $ctags} {
+ set cached_atags($origid) $tags
+ } else {
+ set tags $ctags
}
+ } else {
+ set cached_atags($origid) $tags
+ }
+ set t3 [clock clicks -milliseconds]
+ if {0 && $t3 - $t1 >= 100} {
+ puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
+ [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
}
- set allcommits "done"
- set travindex 0
- notbusy allcommits
- dispneartags
+ return $tags
}
-# update the desc_tags and anc_tags arrays for a new tag just added
-proc addedtag {id} {
- global desc_tags anc_tags allparents allchildren allcommits
- global idtags tagisdesc alldtags
-
- if {![info exists desc_tags($id)]} return
- set adt $desc_tags($id)
- foreach t $desc_tags($id) {
- set adt [concat $adt $alldtags($t)]
- }
- set adt [lsort -unique $adt]
- set alldtags($id) $adt
- foreach t $adt {
- set tagisdesc($id,$t) -1
- set tagisdesc($t,$id) 1
- }
- if {[info exists anc_tags($id)]} {
- set todo $anc_tags($id)
- while {$todo ne {}} {
- set do [lindex $todo 0]
- set todo [lrange $todo 1 end]
- if {[info exists tagisdesc($id,$do)]} continue
- set tagisdesc($do,$id) -1
- set tagisdesc($id,$do) 1
- if {[info exists anc_tags($do)]} {
- set todo [concat $todo $anc_tags($do)]
- }
- }
- }
-
- set lastold $desc_tags($id)
- set lastnew [list $id]
- set nup 0
- set nch 0
- set todo $allparents($id)
- while {$todo ne {}} {
- set do [lindex $todo 0]
- set todo [lrange $todo 1 end]
- if {![info exists desc_tags($do)]} continue
- if {$desc_tags($do) ne $lastold} {
- set lastold $desc_tags($do)
- set lastnew [combine_dtags $lastold [list $id]]
- incr nch
- }
- if {$lastold eq $lastnew} continue
- set desc_tags($do) $lastnew
- incr nup
- if {![info exists idtags($do)]} {
- set todo [concat $todo $allparents($do)]
- }
- }
-
- if {![info exists anc_tags($id)]} return
- set lastold $anc_tags($id)
- set lastnew [list $id]
- set nup 0
- set nch 0
- set todo $allchildren($id)
- while {$todo ne {}} {
- set do [lindex $todo 0]
- set todo [lrange $todo 1 end]
- if {![info exists anc_tags($do)]} continue
- if {$anc_tags($do) ne $lastold} {
- set lastold $anc_tags($do)
- set lastnew [combine_atags $lastold [list $id]]
- incr nch
- }
- if {$lastold eq $lastnew} continue
- set anc_tags($do) $lastnew
- incr nup
- if {![info exists idtags($do)]} {
- set todo [concat $todo $allchildren($do)]
- }
- }
-}
-
-# update the desc_heads array for a new head just added
-proc addedhead {hid head} {
- global desc_heads allparents headids idheads
-
- set headids($head) $hid
- lappend idheads($hid) $head
-
- set todo [list $hid]
- while {$todo ne {}} {
- set do [lindex $todo 0]
- set todo [lrange $todo 1 end]
- if {![info exists desc_heads($do)] ||
- [lsearch -exact $desc_heads($do) $head] >= 0} continue
- set oldheads $desc_heads($do)
- lappend desc_heads($do) $head
- set heads $desc_heads($do)
- while {1} {
- set p $allparents($do)
- if {[llength $p] != 1 || ![info exists desc_heads($p)] ||
- $desc_heads($p) ne $oldheads} break
- set do $p
- set desc_heads($do) $heads
+# Return the list of IDs that have heads that are descendents of id,
+# including id itself if it has a head.
+proc descheads {id} {
+ global arcnos arcstart arcids archeads idheads cached_dheads
+ global allparents
+
+ if {![info exists allparents($id)]} {
+ return {}
+ }
+ set aret {}
+ if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+ # part-way along an arc; check it first
+ set a [lindex $arcnos($id) 0]
+ if {$archeads($a) ne {}} {
+ validate_archeads $a
+ set i [lsearch -exact $arcids($a) $id]
+ foreach t $archeads($a) {
+ set j [lsearch -exact $arcids($a) $t]
+ if {$j > $i} break
+ lappend aret $t
+ }
+ }
+ set id $arcstart($a)
+ }
+ set origid $id
+ set todo [list $id]
+ set seen($id) 1
+ set ret {}
+ for {set i 0} {$i < [llength $todo]} {incr i} {
+ set id [lindex $todo $i]
+ if {[info exists cached_dheads($id)]} {
+ set ret [concat $ret $cached_dheads($id)]
+ } else {
+ if {[info exists idheads($id)]} {
+ lappend ret $id
+ }
+ foreach a $arcnos($id) {
+ if {$archeads($a) ne {}} {
+ validate_archeads $a
+ if {$archeads($a) ne {}} {
+ set ret [concat $ret $archeads($a)]
+ }
+ }
+ set d $arcstart($a)
+ if {![info exists seen($d)]} {
+ lappend todo $d
+ set seen($d) 1
+ }
+ }
}
- set todo [concat $todo $p]
}
+ set ret [lsort -unique $ret]
+ set cached_dheads($origid) $ret
+ return [concat $ret $aret]
}
-# update the desc_heads array for a head just removed
-proc removedhead {hid head} {
- global desc_heads allparents headids idheads
+proc addedtag {id} {
+ global arcnos arcout cached_dtags cached_atags
- unset headids($head)
- if {$idheads($hid) eq $head} {
- unset idheads($hid)
- } else {
- set i [lsearch -exact $idheads($hid) $head]
- if {$i >= 0} {
- set idheads($hid) [lreplace $idheads($hid) $i $i]
- }
+ if {![info exists arcnos($id)]} return
+ if {![info exists arcout($id)]} {
+ recalcarc [lindex $arcnos($id) 0]
}
+ catch {unset cached_dtags}
+ catch {unset cached_atags}
+}
- set todo [list $hid]
- while {$todo ne {}} {
- set do [lindex $todo 0]
- set todo [lrange $todo 1 end]
- if {![info exists desc_heads($do)]} continue
- set i [lsearch -exact $desc_heads($do) $head]
- if {$i < 0} continue
- set oldheads $desc_heads($do)
- set heads [lreplace $desc_heads($do) $i $i]
- while {1} {
- set desc_heads($do) $heads
- set p $allparents($do)
- if {[llength $p] != 1 || ![info exists desc_heads($p)] ||
- $desc_heads($p) ne $oldheads} break
- set do $p
- }
- set todo [concat $todo $p]
+proc addedhead {hid head} {
+ global arcnos arcout cached_dheads
+
+ if {![info exists arcnos($hid)]} return
+ if {![info exists arcout($hid)]} {
+ recalcarc [lindex $arcnos($hid) 0]
}
+ catch {unset cached_dheads}
}
-# update things for a head moved to a child of its previous location
-proc movedhead {id name} {
- global headids idheads
+proc removedhead {hid head} {
+ global cached_dheads
- set oldid $headids($name)
- set headids($name) $id
- if {$idheads($oldid) eq $name} {
- unset idheads($oldid)
- } else {
- set i [lsearch -exact $idheads($oldid) $name]
- if {$i >= 0} {
- set idheads($oldid) [lreplace $idheads($oldid) $i $i]
- }
- }
- lappend idheads($id) $name
+ catch {unset cached_dheads}
}
-proc changedrefs {} {
- global desc_heads desc_tags anc_tags allcommits allids
- global allchildren allparents idtags travindex
+proc movedhead {hid head} {
+ global arcnos arcout cached_dheads
- if {![info exists allcommits]} return
- catch {unset desc_heads}
- catch {unset desc_tags}
- catch {unset anc_tags}
- catch {unset alldtags}
- catch {unset tagisdesc}
- foreach id $allids {
- forward_pass $id $allchildren($id)
+ if {![info exists arcnos($hid)]} return
+ if {![info exists arcout($hid)]} {
+ recalcarc [lindex $arcnos($hid) 0]
}
- if {$allcommits ne "reading"} {
- set travindex [llength $allids]
- if {$allcommits ne "traversing"} {
- set allcommits "traversing"
- after idle restartatags
+ catch {unset cached_dheads}
+}
+
+proc changedrefs {} {
+ global cached_dheads cached_dtags cached_atags
+ global arctags archeads arcnos arcout idheads idtags
+
+ foreach id [concat [array names idheads] [array names idtags]] {
+ if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
+ set a [lindex $arcnos($id) 0]
+ if {![info exists donearc($a)]} {
+ recalcarc $a
+ set donearc($a) 1
+ }
}
}
+ catch {unset cached_dtags}
+ catch {unset cached_atags}
+ catch {unset cached_dheads}
}
proc rereadrefs {} {
}
proc showtag {tag isnew} {
- global ctext tagcontents tagids linknum
+ global ctext tagcontents tagids linknum tagobjid
if {$isnew} {
addtohistory [list showtag $tag 0]
$ctext conf -state normal
clear_ctext
set linknum 0
+ if {![info exists tagcontents($tag)]} {
+ catch {
+ set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
+ }
+ }
if {[info exists tagcontents($tag)]} {
set text $tagcontents($tag)
} else {
proc doprefs {} {
global maxwidth maxgraphpct diffopts
- global oldprefs prefstop showneartags
- global bgcolor fgcolor ctext diffcolors
- global uifont
+ global oldprefs prefstop showneartags showlocalchanges
+ global bgcolor fgcolor ctext diffcolors selectbgcolor
+ global uifont tabstop
set top .gitkprefs
set prefstop $top
raise $top
return
}
- foreach v {maxwidth maxgraphpct diffopts showneartags} {
+ foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
set oldprefs($v) [set $v]
}
toplevel $top
-font optionfont
spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
grid x $top.maxpctl $top.maxpct -sticky w
+ frame $top.showlocal
+ label $top.showlocal.l -text "Show local changes" -font optionfont
+ checkbutton $top.showlocal.b -variable showlocalchanges
+ pack $top.showlocal.b $top.showlocal.l -side left
+ grid x $top.showlocal -sticky w
label $top.ddisp -text "Diff display options"
$top.ddisp configure -font $uifont
checkbutton $top.ntag.b -variable showneartags
pack $top.ntag.b $top.ntag.l -side left
grid x $top.ntag -sticky w
+ label $top.tabstopl -text "tabstop" -font optionfont
+ spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
+ grid x $top.tabstopl $top.tabstop -sticky w
label $top.cdisp -text "Colors: press to choose"
$top.cdisp configure -font $uifont
"diff hunk header" \
[list $ctext tag conf hunksep -foreground]]
grid x $top.hunksepbut $top.hunksep -sticky w
+ label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
+ button $top.selbgbut -text "Select bg" -font optionfont \
+ -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
+ grid x $top.selbgbut $top.selbgsep -sticky w
frame $top.buts
button $top.buts.ok -text "OK" -command prefsok -default active
eval $cmd $c
}
+proc setselbg {c} {
+ global bglist cflist
+ foreach w $bglist {
+ $w configure -selectbackground $c
+ }
+ $cflist tag configure highlight \
+ -background [$cflist cget -selectbackground]
+ allcanvs itemconf secsel -fill $c
+}
+
proc setbg {c} {
global bglist
proc prefscan {} {
global maxwidth maxgraphpct diffopts
- global oldprefs prefstop showneartags
+ global oldprefs prefstop showneartags showlocalchanges
- foreach v {maxwidth maxgraphpct diffopts showneartags} {
+ foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
set $v $oldprefs($v)
}
catch {destroy $prefstop}
proc prefsok {} {
global maxwidth maxgraphpct
- global oldprefs prefstop showneartags
+ global oldprefs prefstop showneartags showlocalchanges
+ global charspc ctext tabstop
catch {destroy $prefstop}
unset prefstop
+ $ctext configure -tabs "[expr {$tabstop * $charspc}]"
+ if {$showlocalchanges != $oldprefs(showlocalchanges)} {
+ if {$showlocalchanges} {
+ doshowlocalchanges
+ } else {
+ dohidelocalchanges
+ }
+ }
if {$maxwidth != $oldprefs(maxwidth)
|| $maxgraphpct != $oldprefs(maxgraphpct)} {
redisplay
}
proc formatdate {d} {
- return [clock format $d -format "%Y-%m-%d %H:%M:%S"]
+ if {$d ne {}} {
+ set d [clock format $d -format "%Y-%m-%d %H:%M:%S"]
+ }
+ return $d
}
# This list of encoding names and aliases is distilled from
set mainfont {Helvetica 9}
set textfont {Courier 9}
set uifont {Helvetica 9 bold}
+set tabstop 8
set findmergefiles 0
set maxgraphpct 50
set maxwidth 16
set cmitmode "patch"
set wrapcomment "none"
set showneartags 1
+set maxrefs 20
+set maxlinelen 200
+set showlocalchanges 1
set colors {green red blue magenta darkgrey brown orange}
set bgcolor white
set fgcolor black
set diffcolors {red "#00a000" blue}
+set selectbgcolor gray85
catch {source ~/.gitk}
font create optionfont -family sans-serif -size -12
-set revtreeargs {}
-foreach arg $argv {
- switch -regexp -- $arg {
- "^$" { }
- "^-d" { set datemode 1 }
- default {
- lappend revtreeargs $arg
- }
- }
-}
-
# check that we can find a .git directory somewhere...
set gitdir [gitdir]
if {![file isdirectory $gitdir]} {
exit 1
}
+set revtreeargs {}
set cmdline_files {}
-set i [lsearch -exact $revtreeargs "--"]
-if {$i >= 0} {
- set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
- set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
-} elseif {$revtreeargs ne {}} {
+set i 0
+foreach arg $argv {
+ switch -- $arg {
+ "" { }
+ "-d" { set datemode 1 }
+ "--" {
+ set cmdline_files [lrange $argv [expr {$i + 1}] end]
+ break
+ }
+ default {
+ lappend revtreeargs $arg
+ }
+ }
+ incr i
+}
+
+if {$i >= [llength $argv] && $revtreeargs ne {}} {
+ # no -- on command line, but some arguments (other than -d)
if {[catch {
set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
set cmdline_files [split $f "\n"]
set n [llength $cmdline_files]
set revtreeargs [lrange $revtreeargs 0 end-$n]
+ # Unfortunately git rev-parse doesn't produce an error when
+ # something is both a revision and a filename. To be consistent
+ # with git log and git rev-list, check revtreeargs for filenames.
+ foreach arg $revtreeargs {
+ if {[file exists $arg]} {
+ show_error {} . "Ambiguous argument '$arg': both revision\
+ and filename"
+ exit 1
+ }
+ }
} err]} {
# unfortunately we get both stdout and stderr in $err,
# so look for "fatal:".
}
}
+set nullid "0000000000000000000000000000000000000000"
+set nullid2 "0000000000000000000000000000000000000001"
+
+
+set runq {}
set history {}
set historyindex 0
set fh_serial 0
set boldrows {}
set boldnamerows {}
set diffelide {0 0}
+set markingmatches 0
set optim_delay 16
set stopped 0
set stuffsaved 0
set patchnum 0
+set lookingforhead 0
+set localirow -1
+set localfrow -1
+set lserial 0
setcoords
makewindow
+# wait for the window to become visible
+tkwait visibility .
wm title . "[file tail $argv0]: [file tail [pwd]]"
readrefs
Any comment/question/concern to:
Git mailing list <git@vger.kernel.org>
-
font-family: monospace;
}
+table.combined.diff_tree th {
+ text-align: center;
+}
+
table.combined.diff_tree td {
padding-right: 24px;
}
+table.combined.diff_tree th.link,
table.combined.diff_tree td.link {
padding: 0px 2px;
}
color: #70a070;
}
+/* noage: "No commits" */
+table.project_list td.noage {
+ color: #808080;
+ font-style: italic;
+}
+
/* age2: 60*60*24*2 <= age */
table.project_list td.age2, table.blame td.age2 {
font-style: italic;
# source of projects list
our $projects_list = "++GITWEB_LIST++";
+# the width (in characters) of the projects list "Description" column
+our $projects_list_description_width = 25;
+
# default order of projects list
# valid values are none, project, descr, owner, and age
our $default_projects_order = "project";
# (relative to the current git repository)
our $mimetypes_file = undef;
+# assume this charset if line contains non-UTF-8 characters;
+# it should be valid encoding (see Encoding::Supported(3pm) for list),
+# for which encoding all byte sequences are valid, for example
+# 'iso-8859-1' aka 'latin1' (it is decoded without checking, so it
+# could be even 'utf-8' for the old behavior)
+our $fallback_encoding = 'latin1';
+
+# rename detection options for git-diff and git-diff-tree
+# - default is '-M', with the cost proportional to
+# (number of removed files) * (number of new files).
+# - more costly is '-C' (which implies '-M'), with the cost proportional to
+# (number of changed files + number of removed files) * (number of new files)
+# - even more costly is '-C', '--find-copies-harder' with cost
+# (number of files in the original tree) * (number of new files)
+# - one might want to include '-B' option, e.g. '-B', '-M'
+our @diff_opts = ('-M'); # taken from git_commit
+
+# information about snapshot formats that gitweb is capable of serving
+our %known_snapshot_formats = (
+ # name => {
+ # 'display' => display name,
+ # 'type' => mime type,
+ # 'suffix' => filename suffix,
+ # 'format' => --format for git-archive,
+ # 'compressor' => [compressor command and arguments]
+ # (array reference, optional)}
+ #
+ 'tgz' => {
+ 'display' => 'tar.gz',
+ 'type' => 'application/x-gzip',
+ 'suffix' => '.tar.gz',
+ 'format' => 'tar',
+ 'compressor' => ['gzip']},
+
+ 'tbz2' => {
+ 'display' => 'tar.bz2',
+ 'type' => 'application/x-bzip2',
+ 'suffix' => '.tar.bz2',
+ 'format' => 'tar',
+ 'compressor' => ['bzip2']},
+
+ 'zip' => {
+ 'display' => 'zip',
+ 'type' => 'application/x-zip',
+ 'suffix' => '.zip',
+ 'format' => 'zip'},
+);
+
+# Aliases so we understand old gitweb.snapshot values in repository
+# configuration.
+our %known_snapshot_format_aliases = (
+ 'gzip' => 'tgz',
+ 'bzip2' => 'tbz2',
+
+ # backward compatibility: legacy gitweb config support
+ 'x-gzip' => undef, 'gz' => undef,
+ 'x-bzip2' => undef, 'bz2' => undef,
+ 'x-zip' => undef, '' => undef,
+);
+
# You define site-wide feature defaults here; override them with
# $GITWEB_CONFIG as necessary.
our %feature = (
'override' => 0,
'default' => [0]},
- # Enable the 'snapshot' link, providing a compressed tarball of any
+ # Enable the 'snapshot' link, providing a compressed archive of any
# tree. This can potentially generate high traffic if you have large
# project.
+ # Value is a list of formats defined in %known_snapshot_formats that
+ # you wish to offer.
# To disable system wide have in $GITWEB_CONFIG
- # $feature{'snapshot'}{'default'} = [undef];
+ # $feature{'snapshot'}{'default'} = [];
# To have project specific config enable override in $GITWEB_CONFIG
# $feature{'snapshot'}{'override'} = 1;
- # and in project config gitweb.snapshot = none|gzip|bzip2;
+ # and in project config, a comma-separated list of formats or "none"
+ # to disable. Example: gitweb.snapshot = tbz2,zip;
'snapshot' => {
'sub' => \&feature_snapshot,
'override' => 0,
- # => [content-encoding, suffix, program]
- 'default' => ['x-gzip', 'gz', 'gzip']},
+ 'default' => ['tgz']},
# Enable text search, which will list the commits which match author,
# committer or commit text to a given string. Enabled by default.
}
sub feature_snapshot {
- my ($ctype, $suffix, $command) = @_;
+ my (@fmts) = @_;
my ($val) = git_get_project_config('snapshot');
- if ($val eq 'gzip') {
- return ('x-gzip', 'gz', 'gzip');
- } elsif ($val eq 'bzip2') {
- return ('x-bzip2', 'bz2', 'bzip2');
- } elsif ($val eq 'none') {
- return ();
+ if ($val) {
+ @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
}
- return ($ctype, $suffix, $command);
-}
-
-sub gitweb_have_snapshot {
- my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
- my $have_snapshot = (defined $ctype && defined $suffix);
-
- return $have_snapshot;
+ return @fmts;
}
sub feature_grep {
(!$export_ok || -e "$dir/$export_ok"));
}
-# rename detection options for git-diff and git-diff-tree
-# - default is '-M', with the cost proportional to
-# (number of removed files) * (number of new files).
-# - more costly is '-C' (or '-C', '-M'), with the cost proportional to
-# (number of changed files + number of removed files) * (number of new files)
-# - even more costly is '-C', '--find-copies-harder' with cost
-# (number of files in the original tree) * (number of new files)
-# - one might want to include '-B' option, e.g. '-B', '-M'
-our @diff_opts = ('-M'); # taken from git_commit
+# process alternate names for backward compatibility
+# filter out unsupported (unknown) snapshot formats
+sub filter_snapshot_fmts {
+ my @fmts = @_;
+
+ @fmts = map {
+ exists $known_snapshot_format_aliases{$_} ?
+ $known_snapshot_format_aliases{$_} : $_} @fmts;
+ @fmts = grep(exists $known_snapshot_formats{$_}, @fmts);
+
+}
our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
do $GITWEB_CONFIG if -e $GITWEB_CONFIG;
}
}
+my %allowed_options = (
+ "--no-merges" => [ qw(rss atom log shortlog history) ],
+);
+
+our @extra_options = $cgi->param('opt');
+if (defined @extra_options) {
+ foreach my $opt (@extra_options) {
+ if (not exists $allowed_options{$opt}) {
+ die_error(undef, "Invalid option parameter");
+ }
+ if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
+ die_error(undef, "Invalid option parameter for this action");
+ }
+ }
+}
+
our $hash_parent_base = $cgi->param('hpb');
if (defined $hash_parent_base) {
if (!validate_refname($hash_parent_base)) {
order => "o",
searchtext => "s",
searchtype => "st",
+ snapshot_format => "sf",
+ extra_options => "opt",
);
my %mapping = @mapping;
for (my $i = 0; $i < @mapping; $i += 2) {
my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]);
if (defined $params{$name}) {
- push @result, $symbol . "=" . esc_param($params{$name});
+ if (ref($params{$name}) eq "ARRAY") {
+ foreach my $par (@{$params{$name}}) {
+ push @result, $symbol . "=" . esc_param($par);
+ }
+ } else {
+ push @result, $symbol . "=" . esc_param($params{$name});
+ }
}
}
$href .= "?" . join(';', @result) if scalar @result;
return $input;
}
+# decode sequences of octets in utf8 into Perl's internal form,
+# which is utf-8 with utf8 flag set if needed. gitweb writes out
+# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
+sub to_utf8 {
+ my $str = shift;
+ my $res;
+ eval { $res = decode_utf8($str, Encode::FB_CROAK); };
+ if (defined $res) {
+ return $res;
+ } else {
+ return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
+ }
+}
+
# quote unsafe chars, but keep the slash, even when it's not
# correct, but quoted slashes look too horrible in bookmarks
sub esc_param {
my $str = shift;
my %opts = @_;
- $str = decode_utf8($str);
+ $str = to_utf8($str);
$str = $cgi->escapeHTML($str);
if ($opts{'-nbsp'}) {
$str =~ s/ / /g;
my $str = shift;
my %opts = @_;
- $str = decode_utf8($str);
+ $str = to_utf8($str);
$str = $cgi->escapeHTML($str);
if ($opts{'-nbsp'}) {
$str =~ s/ / /g;
return $age_str;
}
+use constant {
+ S_IFINVALID => 0030000,
+ S_IFGITLINK => 0160000,
+};
+
+# submodule/subproject, a commit object reference
+sub S_ISGITLINK($) {
+ my $mode = shift;
+
+ return (($mode & S_IFMT) == S_IFGITLINK)
+}
+
# convert file mode in octal to symbolic file mode string
sub mode_str {
my $mode = oct shift;
- if (S_ISDIR($mode & S_IFMT)) {
+ if (S_ISGITLINK($mode)) {
+ return 'm---------';
+ } elsif (S_ISDIR($mode & S_IFMT)) {
return 'drwxr-xr-x';
} elsif (S_ISLNK($mode)) {
return 'lrwxrwxrwx';
$mode = oct $mode;
}
- if (S_ISDIR($mode & S_IFMT)) {
+ if (S_ISGITLINK($mode)) {
+ return "submodule";
+ } elsif (S_ISDIR($mode & S_IFMT)) {
return "directory";
} elsif (S_ISLNK($mode)) {
return "symlink";
$mode = oct $mode;
}
- if (S_ISDIR($mode & S_IFMT)) {
+ if (S_ISGITLINK($mode)) {
+ return "submodule";
+ } elsif (S_ISDIR($mode & S_IFMT)) {
return "directory";
} elsif (S_ISLNK($mode)) {
return "symlink";
if (length($short) < length($long)) {
return $cgi->a({-href => $href, -class => "list subject",
- -title => decode_utf8($long)},
+ -title => to_utf8($long)},
esc_html($short) . $extra);
} else {
return $cgi->a({-href => $href, -class => "list subject"},
}
}
-# format patch (diff) line (rather not to be used for diff headers)
+# format git diff header line, i.e. "diff --(git|combined|cc) ..."
+sub format_git_diff_header_line {
+ my $line = shift;
+ my $diffinfo = shift;
+ my ($from, $to) = @_;
+
+ if ($diffinfo->{'nparents'}) {
+ # combined diff
+ $line =~ s!^(diff (.*?) )"?.*$!$1!;
+ if ($to->{'href'}) {
+ $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
+ esc_path($to->{'file'}));
+ } else { # file was deleted (no href)
+ $line .= esc_path($to->{'file'});
+ }
+ } else {
+ # "ordinary" diff
+ $line =~ s!^(diff (.*?) )"?a/.*$!$1!;
+ if ($from->{'href'}) {
+ $line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
+ 'a/' . esc_path($from->{'file'}));
+ } else { # file was added (no href)
+ $line .= 'a/' . esc_path($from->{'file'});
+ }
+ $line .= ' ';
+ if ($to->{'href'}) {
+ $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
+ 'b/' . esc_path($to->{'file'}));
+ } else { # file was deleted
+ $line .= 'b/' . esc_path($to->{'file'});
+ }
+ }
+
+ return "<div class=\"diff header\">$line</div>\n";
+}
+
+# format extended diff header line, before patch itself
+sub format_extended_diff_header_line {
+ my $line = shift;
+ my $diffinfo = shift;
+ my ($from, $to) = @_;
+
+ # match <path>
+ if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
+ $line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
+ esc_path($from->{'file'}));
+ }
+ if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
+ $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
+ esc_path($to->{'file'}));
+ }
+ # match single <mode>
+ if ($line =~ m/\s(\d{6})$/) {
+ $line .= '<span class="info"> (' .
+ file_type_long($1) .
+ ')</span>';
+ }
+ # match <hash>
+ if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
+ # can match only for combined diff
+ $line = 'index ';
+ for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+ if ($from->{'href'}[$i]) {
+ $line .= $cgi->a({-href=>$from->{'href'}[$i],
+ -class=>"hash"},
+ substr($diffinfo->{'from_id'}[$i],0,7));
+ } else {
+ $line .= '0' x 7;
+ }
+ # separator
+ $line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
+ }
+ $line .= '..';
+ if ($to->{'href'}) {
+ $line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
+ substr($diffinfo->{'to_id'},0,7));
+ } else {
+ $line .= '0' x 7;
+ }
+
+ } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
+ # can match only for ordinary diff
+ my ($from_link, $to_link);
+ if ($from->{'href'}) {
+ $from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
+ substr($diffinfo->{'from_id'},0,7));
+ } else {
+ $from_link = '0' x 7;
+ }
+ if ($to->{'href'}) {
+ $to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
+ substr($diffinfo->{'to_id'},0,7));
+ } else {
+ $to_link = '0' x 7;
+ }
+ my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
+ $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
+ }
+
+ return $line . "<br/>\n";
+}
+
+# format from-file/to-file diff header
+sub format_diff_from_to_header {
+ my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
+ my $line;
+ my $result = '';
+
+ $line = $from_line;
+ #assert($line =~ m/^---/) if DEBUG;
+ # no extra formatting for "^--- /dev/null"
+ if (! $diffinfo->{'nparents'}) {
+ # ordinary (single parent) diff
+ if ($line =~ m!^--- "?a/!) {
+ if ($from->{'href'}) {
+ $line = '--- a/' .
+ $cgi->a({-href=>$from->{'href'}, -class=>"path"},
+ esc_path($from->{'file'}));
+ } else {
+ $line = '--- a/' .
+ esc_path($from->{'file'});
+ }
+ }
+ $result .= qq!<div class="diff from_file">$line</div>\n!;
+
+ } else {
+ # combined diff (merge commit)
+ for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+ if ($from->{'href'}[$i]) {
+ $line = '--- ' .
+ $cgi->a({-href=>href(action=>"blobdiff",
+ hash_parent=>$diffinfo->{'from_id'}[$i],
+ hash_parent_base=>$parents[$i],
+ file_parent=>$from->{'file'}[$i],
+ hash=>$diffinfo->{'to_id'},
+ hash_base=>$hash,
+ file_name=>$to->{'file'}),
+ -class=>"path",
+ -title=>"diff" . ($i+1)},
+ $i+1) .
+ '/' .
+ $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
+ esc_path($from->{'file'}[$i]));
+ } else {
+ $line = '--- /dev/null';
+ }
+ $result .= qq!<div class="diff from_file">$line</div>\n!;
+ }
+ }
+
+ $line = $to_line;
+ #assert($line =~ m/^\+\+\+/) if DEBUG;
+ # no extra formatting for "^+++ /dev/null"
+ if ($line =~ m!^\+\+\+ "?b/!) {
+ if ($to->{'href'}) {
+ $line = '+++ b/' .
+ $cgi->a({-href=>$to->{'href'}, -class=>"path"},
+ esc_path($to->{'file'}));
+ } else {
+ $line = '+++ b/' .
+ esc_path($to->{'file'});
+ }
+ }
+ $result .= qq!<div class="diff to_file">$line</div>\n!;
+
+ return $result;
+}
+
+# create note for patch simplified by combined diff
+sub format_diff_cc_simplified {
+ my ($diffinfo, @parents) = @_;
+ my $result = '';
+
+ $result .= "<div class=\"diff header\">" .
+ "diff --cc ";
+ if (!is_deleted($diffinfo)) {
+ $result .= $cgi->a({-href => href(action=>"blob",
+ hash_base=>$hash,
+ hash=>$diffinfo->{'to_id'},
+ file_name=>$diffinfo->{'to_file'}),
+ -class => "path"},
+ esc_path($diffinfo->{'to_file'}));
+ } else {
+ $result .= esc_path($diffinfo->{'to_file'});
+ }
+ $result .= "</div>\n" . # class="diff header"
+ "<div class=\"diff nodifferences\">" .
+ "Simple merge" .
+ "</div>\n"; # class="diff nodifferences"
+
+ return $result;
+}
+
+# format patch (diff) line (not to be used for diff headers)
sub format_diff_line {
my $line = shift;
my ($from, $to) = @_;
return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
}
+# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
+# linked. Pass the hash of the tree/commit to snapshot.
+sub format_snapshot_links {
+ my ($hash) = @_;
+ my @snapshot_fmts = gitweb_check_feature('snapshot');
+ @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+ my $num_fmts = @snapshot_fmts;
+ if ($num_fmts > 1) {
+ # A parenthesized list of links bearing format names.
+ # e.g. "snapshot (_tar.gz_ _zip_)"
+ return "snapshot (" . join(' ', map
+ $cgi->a({
+ -href => href(
+ action=>"snapshot",
+ hash=>$hash,
+ snapshot_format=>$_
+ )
+ }, $known_snapshot_formats{$_}{'display'})
+ , @snapshot_fmts) . ")";
+ } elsif ($num_fmts == 1) {
+ # A single "snapshot" link whose tooltip bears the format name.
+ # i.e. "_snapshot_"
+ my ($fmt) = @snapshot_fmts;
+ return
+ $cgi->a({
+ -href => href(
+ action=>"snapshot",
+ hash=>$hash,
+ snapshot_format=>$fmt
+ ),
+ -title => "in format: $known_snapshot_formats{$fmt}{'display'}"
+ }, "snapshot");
+ } else { # $num_fmts == 0
+ return undef;
+ }
+}
+
## ----------------------------------------------------------------------
## git utility subroutines, invoking git commands
File::Find::find({
follow_fast => 1, # follow symbolic links
+ follow_skip => 2, # ignore duplicates
dangling_symlinks => 0, # ignore dangling symlinks, silently
wanted => sub {
# skip project-list toplevel, if we get it.
if (check_export_ok("$projectroot/$path")) {
my $pr = {
path => $path,
- owner => decode_utf8($owner),
+ owner => to_utf8($owner),
};
push @list, $pr;
(my $forks_path = $path) =~ s/\.git$//;
return @list;
}
-sub git_get_project_owner {
- my $project = shift;
- my $owner;
+our $gitweb_project_owner = undef;
+sub git_get_project_list_from_file {
- return undef unless $project;
+ return if (defined $gitweb_project_owner);
+ $gitweb_project_owner = {};
# read from file (url-encoded):
# 'git%2Fgit.git Linus+Torvalds'
# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
my ($pr, $ow) = split ' ', $line;
$pr = unescape($pr);
$ow = unescape($ow);
- if ($pr eq $project) {
- $owner = decode_utf8($ow);
- last;
- }
+ $gitweb_project_owner->{$pr} = to_utf8($ow);
}
close $fd;
}
+}
+
+sub git_get_project_owner {
+ my $project = shift;
+ my $owner;
+
+ return undef unless $project;
+
+ if (!defined $gitweb_project_owner) {
+ git_get_project_list_from_file();
+ }
+
+ if (exists $gitweb_project_owner->{$project}) {
+ $owner = $gitweb_project_owner->{$project};
+ }
if (!defined $owner) {
$owner = get_file_owner("$projectroot/$project");
}
my $age = time - $timestamp;
return ($age, age_string($age));
}
+ return (undef, undef);
}
sub git_get_references {
($arg ? ($arg) : ()),
("--max-count=" . $maxcount),
("--skip=" . $skip),
+ @extra_options,
$commit_id,
"--",
($filename ? ($filename) : ())
return wantarray ? %res : \%res;
}
+# generates _two_ hashes, references to which are passed as 2 and 3 argument
+sub parse_from_to_diffinfo {
+ my ($diffinfo, $from, $to, @parents) = @_;
+
+ if ($diffinfo->{'nparents'}) {
+ # combined diff
+ $from->{'file'} = [];
+ $from->{'href'} = [];
+ fill_from_file_info($diffinfo, @parents)
+ unless exists $diffinfo->{'from_file'};
+ for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+ $from->{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
+ if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
+ $from->{'href'}[$i] = href(action=>"blob",
+ hash_base=>$parents[$i],
+ hash=>$diffinfo->{'from_id'}[$i],
+ file_name=>$from->{'file'}[$i]);
+ } else {
+ $from->{'href'}[$i] = undef;
+ }
+ }
+ } else {
+ $from->{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
+ if ($diffinfo->{'status'} ne "A") { # not new (added) file
+ $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
+ hash=>$diffinfo->{'from_id'},
+ file_name=>$from->{'file'});
+ } else {
+ delete $from->{'href'};
+ }
+ }
+
+ $to->{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
+ if (!is_deleted($diffinfo)) { # file exists in result
+ $to->{'href'} = href(action=>"blob", hash_base=>$hash,
+ hash=>$diffinfo->{'to_id'},
+ file_name=>$to->{'file'});
+ } else {
+ delete $to->{'href'};
+ }
+}
+
## ......................................................................
## parse to array of hashes functions
}
my $owner = $gcos;
$owner =~ s/[,;].*$//;
- return decode_utf8($owner);
+ return to_utf8($owner);
}
## ......................................................................
my $title = "$site_name";
if (defined $project) {
- $title .= " - " . decode_utf8($project);
+ $title .= " - " . to_utf8($project);
if (defined $action) {
$title .= "/$action";
if (defined $file_name) {
printf('<link rel="alternate" title="%s log RSS feed" '.
'href="%s" type="application/rss+xml" />'."\n",
esc_param($project), href(action=>"rss"));
+ printf('<link rel="alternate" title="%s log RSS feed (no merges)" '.
+ 'href="%s" type="application/rss+xml" />'."\n",
+ esc_param($project), href(action=>"rss",
+ extra_options=>"--no-merges"));
printf('<link rel="alternate" title="%s log Atom feed" '.
'href="%s" type="application/atom+xml" />'."\n",
esc_param($project), href(action=>"atom"));
+ printf('<link rel="alternate" title="%s log Atom feed (no merges)" '.
+ 'href="%s" type="application/atom+xml" />'."\n",
+ esc_param($project), href(action=>"atom",
+ extra_options=>"--no-merges"));
} else {
printf('<link rel="alternate" title="%s projects list" '.
'href="%s" type="text/plain; charset=utf-8"/>'."\n",
} else {
$search_hash = "HEAD";
}
+ my $action = $my_uri;
+ my ($use_pathinfo) = gitweb_check_feature('pathinfo');
+ if ($use_pathinfo) {
+ $action .= "/$project";
+ } else {
+ $cgi->param("p", $project);
+ }
$cgi->param("a", "search");
$cgi->param("h", $search_hash);
- $cgi->param("p", $project);
- print $cgi->startform(-method => "get", -action => $my_uri) .
+ print $cgi->startform(-method => "get", -action => $action) .
"<div class=\"search\">\n" .
- $cgi->hidden(-name => "p") . "\n" .
+ (!$use_pathinfo && $cgi->hidden(-name => "p") . "\n") .
$cgi->hidden(-name => "a") . "\n" .
$cgi->hidden(-name => "h") . "\n" .
$cgi->popup_menu(-name => 'st', -default => 'commit',
print "<div class=\"page_path\">";
print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
- -title => 'tree root'}, decode_utf8("[$project]"));
+ -title => 'tree root'}, to_utf8("[$project]"));
print " / ";
if (defined $name) {
my @dirname = split '/', $name;
"history");
}
print "</td>\n";
+ } else {
+ # unknown object: we can only present history for it
+ # (this includes 'commit' object, i.e. submodule support)
+ print "<td class=\"list\">" .
+ esc_path($t->{'name'}) .
+ "</td>\n";
+ print "<td class=\"link\">";
+ if (defined $hash_base) {
+ print $cgi->a({-href => href(action=>"history",
+ hash_base=>$hash_base,
+ file_name=>"$basedir$t->{'name'}")},
+ "history");
+ }
+ print "</td>\n";
}
}
}
}
+sub is_deleted {
+ my $diffinfo = shift;
+
+ return $diffinfo->{'to_id'} eq ('0' x 40);
+}
sub git_difftree_body {
my ($difftree, $hash, @parents) = @_;
print "<table class=\"" .
(@parents > 1 ? "combined " : "") .
"diff_tree\">\n";
+
+ # header only for combined diff in 'commitdiff' view
+ my $has_header = @parents > 1 && $action eq 'commitdiff';
+ if ($has_header) {
+ # table header
+ print "<thead><tr>\n" .
+ "<th></th><th></th>\n"; # filename, patchN link
+ for (my $i = 0; $i < @parents; $i++) {
+ my $par = $parents[$i];
+ print "<th>" .
+ $cgi->a({-href => href(action=>"commitdiff",
+ hash=>$hash, hash_parent=>$par),
+ -title => 'commitdiff to parent number ' .
+ ($i+1) . ': ' . substr($par,0,7)},
+ $i+1) .
+ " </th>\n";
+ }
+ print "</tr></thead>\n<tbody>\n";
+ }
+
my $alternate = 1;
my $patchno = 0;
foreach my $line (@{$difftree}) {
fill_from_file_info($diff, @parents)
unless exists $diff->{'from_file'};
- if ($diff->{'to_id'} ne ('0' x 40)) {
+ if (!is_deleted($diff)) {
# file exists in the result (child) commit
print "<td>" .
$cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
} # we should not encounter Unmerged (U) or Unknown (X) status
print "</tr>\n";
}
+ print "</tbody>" if $has_header;
print "</table>\n";
}
# advance raw git-diff output if needed
$patch_idx++ if defined $diffinfo;
- # read and prepare patch information
- if (ref($difftree->[$patch_idx]) eq "HASH") {
- # pre-parsed (or generated by hand)
- $diffinfo = $difftree->[$patch_idx];
- } else {
- $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
+ # compact combined diff output can have some patches skipped
+ # find which patch (using pathname of result) we are at now
+ my $to_name;
+ if ($diff_header[0] =~ m!^diff --cc "?(.*)"?$!) {
+ $to_name = $1;
}
+
+ do {
+ # read and prepare patch information
+ if (ref($difftree->[$patch_idx]) eq "HASH") {
+ # pre-parsed (or generated by hand)
+ $diffinfo = $difftree->[$patch_idx];
+ } else {
+ $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
+ }
+
+ # check if current raw line has no patch (it got simplified)
+ if (defined $to_name && $to_name ne $diffinfo->{'to_file'}) {
+ print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
+ format_diff_cc_simplified($diffinfo, @hash_parents) .
+ "</div>\n"; # class="patch"
+
+ $patch_idx++;
+ $patch_number++;
+ }
+ } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} ||
+ $patch_idx > $#$difftree);
+ # modifies %from, %to hashes
+ parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
if ($diffinfo->{'nparents'}) {
# combined diff
$from{'file'} = [];
}
$to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
- if ($diffinfo->{'to_id'} ne ('0' x 40)) { # file exists in result
+ if (!is_deleted($diffinfo)) { # file exists in result
$to{'href'} = href(action=>"blob", hash_base=>$hash,
hash=>$diffinfo->{'to_id'},
file_name=>$to{'file'});
# print "git diff" header
$patch_line = shift @diff_header;
- if ($diffinfo->{'nparents'}) {
-
- # combined diff
- $patch_line =~ s!^(diff (.*?) )"?.*$!$1!;
- if ($to{'href'}) {
- $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
- esc_path($to{'file'}));
- } else { # file was deleted
- $patch_line .= esc_path($to{'file'});
- }
-
- } else {
-
- $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
- if ($from{'href'}) {
- $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
- 'a/' . esc_path($from{'file'}));
- } else { # file was added
- $patch_line .= 'a/' . esc_path($from{'file'});
- }
- $patch_line .= ' ';
- if ($to{'href'}) {
- $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
- 'b/' . esc_path($to{'file'}));
- } else { # file was deleted
- $patch_line .= 'b/' . esc_path($to{'file'});
- }
-
- }
- print "<div class=\"diff header\">$patch_line</div>\n";
+ print format_git_diff_header_line($patch_line, $diffinfo,
+ \%from, \%to);
# print extended diff header
print "<div class=\"diff extended_header\">\n" if (@diff_header > 0);
EXTENDED_HEADER:
foreach $patch_line (@diff_header) {
- # match <path>
- if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) {
- $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"},
- esc_path($from{'file'}));
- }
- if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) {
- $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"path"},
- esc_path($to{'file'}));
- }
- # match single <mode>
- if ($patch_line =~ m/\s(\d{6})$/) {
- $patch_line .= '<span class="info"> (' .
- file_type_long($1) .
- ')</span>';
- }
- # match <hash>
- if ($patch_line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
- # can match only for combined diff
- $patch_line = 'index ';
- for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
- if ($from{'href'}[$i]) {
- $patch_line .= $cgi->a({-href=>$from{'href'}[$i],
- -class=>"hash"},
- substr($diffinfo->{'from_id'}[$i],0,7));
- } else {
- $patch_line .= '0' x 7;
- }
- # separator
- $patch_line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
- }
- $patch_line .= '..';
- if ($to{'href'}) {
- $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"hash"},
- substr($diffinfo->{'to_id'},0,7));
- } else {
- $patch_line .= '0' x 7;
- }
-
- } elsif ($patch_line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
- # can match only for ordinary diff
- my ($from_link, $to_link);
- if ($from{'href'}) {
- $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
- substr($diffinfo->{'from_id'},0,7));
- } else {
- $from_link = '0' x 7;
- }
- if ($to{'href'}) {
- $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
- substr($diffinfo->{'to_id'},0,7));
- } else {
- $to_link = '0' x 7;
- }
- #affirm {
- # my ($from_hash, $to_hash) =
- # ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/);
- # my ($from_id, $to_id) =
- # ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
- # ($from_hash eq $from_id) && ($to_hash eq $to_id);
- #} if DEBUG;
- my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
- $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
- }
- print $patch_line . "<br/>\n";
+ print format_extended_diff_header_line($patch_line, $diffinfo,
+ \%from, \%to);
}
print "</div>\n" if (@diff_header > 0); # class="diff extended_header"
}
next PATCH if ($patch_line =~ m/^diff /);
#assert($patch_line =~ m/^---/) if DEBUG;
- if (!$diffinfo->{'nparents'} && # not from-file line for combined diff
- $from{'href'} && $patch_line =~ m!^--- "?a/!) {
- $patch_line = '--- a/' .
- $cgi->a({-href=>$from{'href'}, -class=>"path"},
- esc_path($from{'file'}));
- }
- print "<div class=\"diff from_file\">$patch_line</div>\n";
+ #assert($patch_line eq $last_patch_line) if DEBUG;
$patch_line = <$fd>;
chomp $patch_line;
+ #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
- #assert($patch_line =~ m/^+++/) if DEBUG;
- if ($to{'href'} && $patch_line =~ m!^\+\+\+ "?b/!) {
- $patch_line = '+++ b/' .
- $cgi->a({-href=>$to{'href'}, -class=>"path"},
- esc_path($to{'file'}));
- }
- print "<div class=\"diff to_file\">$patch_line</div>\n";
+ print format_diff_from_to_header($last_patch_line, $patch_line,
+ $diffinfo, \%from, \%to,
+ @hash_parents);
# the patch itself
LINE:
print "</div>\n"; # class="patch"
}
+ # for compact combined (--cc) format, with chunk and patch simpliciaction
+ # patchset might be empty, but there might be unprocessed raw lines
+ for ($patch_idx++ if $patch_number > 0;
+ $patch_idx < @$difftree;
+ $patch_idx++) {
+ # read and prepare patch information
+ if (ref($difftree->[$patch_idx]) eq "HASH") {
+ # pre-parsed (or generated by hand)
+ $diffinfo = $difftree->[$patch_idx];
+ } else {
+ $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
+ }
+
+ # generate anchor for "patch" links in difftree / whatchanged part
+ print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
+ format_diff_cc_simplified($diffinfo, @hash_parents) .
+ "</div>\n"; # class="patch"
+
+ $patch_number++;
+ }
+
if ($patch_number == 0) {
if (@hash_parents > 1) {
print "<div class=\"diff nodifferences\">Trivial merge</div>\n";
($pr->{'age'}, $pr->{'age_string'}) = @aa;
if (!defined $pr->{'descr'}) {
my $descr = git_get_project_description($pr->{'path'}) || "";
- $pr->{'descr_long'} = decode_utf8($descr);
- $pr->{'descr'} = chop_str($descr, 25, 5);
+ $pr->{'descr_long'} = to_utf8($descr);
+ $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
}
if (!defined $pr->{'owner'}) {
- $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
+ $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
}
if ($check_forks) {
my $pname = $pr->{'path'};
# uses global variable $project
my ($commitlist, $from, $to, $refs, $extra) = @_;
- my $have_snapshot = gitweb_have_snapshot();
-
$from = 0 unless defined $from;
$to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
$cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
$cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
$cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
- if ($have_snapshot) {
- print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
+ my $snapshot_links = format_snapshot_links($commit);
+ if (defined $snapshot_links) {
+ print " | " . $snapshot_links;
}
print "</td>\n" .
"</tr>\n";
foreach my $pr (@projects) {
if (!exists $pr->{'owner'}) {
- $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}");
+ $pr->{'owner'} = git_get_project_owner("$pr->{'path'}");
}
my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
}
sub git_tree {
- my $have_snapshot = gitweb_have_snapshot();
-
if (!defined $hash_base) {
$hash_base = "HEAD";
}
hash_base=>"HEAD", file_name=>$file_name)},
"HEAD"),
}
- if ($have_snapshot) {
+ my $snapshot_links = format_snapshot_links($hash);
+ if (defined $snapshot_links) {
# FIXME: Should be available when we have no hash base as well.
- push @views_nav,
- $cgi->a({-href => href(action=>"snapshot", hash=>$hash)},
- "snapshot");
+ push @views_nav, $snapshot_links;
}
git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
}
sub git_snapshot {
- my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
- my $have_snapshot = (defined $ctype && defined $suffix);
- if (!$have_snapshot) {
+ my @supported_fmts = gitweb_check_feature('snapshot');
+ @supported_fmts = filter_snapshot_fmts(@supported_fmts);
+
+ my $format = $cgi->param('sf');
+ if (!@supported_fmts) {
die_error('403 Permission denied', "Permission denied");
}
+ # default to first supported snapshot format
+ $format ||= $supported_fmts[0];
+ if ($format !~ m/^[a-z0-9]+$/) {
+ die_error(undef, "Invalid snapshot format parameter");
+ } elsif (!exists($known_snapshot_formats{$format})) {
+ die_error(undef, "Unknown snapshot format");
+ } elsif (!grep($_ eq $format, @supported_fmts)) {
+ die_error(undef, "Unsupported snapshot format");
+ }
if (!defined $hash) {
$hash = git_get_head_hash($project);
}
- my $filename = decode_utf8(basename($project)) . "-$hash.tar.$suffix";
+ my $git_command = git_cmd_str();
+ my $name = $project;
+ $name =~ s,([^/])/*\.git$,$1,;
+ $name = basename($name);
+ my $filename = to_utf8($name);
+ $name =~ s/\047/\047\\\047\047/g;
+ my $cmd;
+ $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}";
+ $cmd = "$git_command archive " .
+ "--format=$known_snapshot_formats{$format}{'format'} " .
+ "--prefix=\'$name\'/ $hash";
+ if (exists $known_snapshot_formats{$format}{'compressor'}) {
+ $cmd .= ' | ' . join ' ', @{$known_snapshot_formats{$format}{'compressor'}};
+ }
print $cgi->header(
- -type => "application/$ctype",
+ -type => $known_snapshot_formats{$format}{'type'},
-content_disposition => 'inline; filename="' . "$filename" . '"',
-status => '200 OK');
- my $git = git_cmd_str();
- my $name = $project;
- $name =~ s/\047/\047\\\047\047/g;
- open my $fd, "-|",
- "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
- or die_error(undef, "Execute git-tar-tree failed");
+ open my $fd, "-|", $cmd
+ or die_error(undef, "Execute git-archive failed");
binmode STDOUT, ':raw';
print <$fd>;
binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
close $fd;
-
}
sub git_log {
my $refs = git_get_references();
my $ref = format_ref_marker($refs, $co{'id'});
- my $have_snapshot = gitweb_have_snapshot();
-
git_header_html(undef, $expires);
git_print_page_nav('commit', '',
$hash, $co{'tree'}, $hash,
"<td class=\"link\">" .
$cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
"tree");
- if ($have_snapshot) {
- print " | " .
- $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot");
+ my $snapshot_links = format_snapshot_links($hash);
+ if (defined $snapshot_links) {
+ print " | " . $snapshot_links;
}
print "</td>" .
"</tr>\n";
die_error(undef, "Unknown commit object");
}
- # we need to prepare $formats_nav before any parameter munging
+ # choose format for commitdiff for merge
+ if (! defined $hash_parent && @{$co{'parents'}} > 1) {
+ $hash_parent = '--cc';
+ }
+ # we need to prepare $formats_nav before almost any parameter munging
my $formats_nav;
if ($format eq 'html') {
$formats_nav =
hash=>$hash, hash_parent=>$hash_parent)},
"raw");
- if (defined $hash_parent) {
+ if (defined $hash_parent &&
+ $hash_parent ne '-c' && $hash_parent ne '--cc') {
# commitdiff with two commits given
my $hash_parent_short = $hash_parent;
if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
$hash_parent_short = substr($hash_parent, 0, 7);
}
$formats_nav .=
- ' (from: ' .
+ ' (from';
+ for (my $i = 0; $i < @{$co{'parents'}}; $i++) {
+ if ($co{'parents'}[$i] eq $hash_parent) {
+ $formats_nav .= ' parent ' . ($i+1);
+ last;
+ }
+ }
+ $formats_nav .= ': ' .
$cgi->a({-href => href(action=>"commitdiff",
hash=>$hash_parent)},
esc_html($hash_parent_short)) .
')';
} else {
# merge commit
+ if ($hash_parent eq '--cc') {
+ $formats_nav .= ' | ' .
+ $cgi->a({-href => href(action=>"commitdiff",
+ hash=>$hash, hash_parent=>'-c')},
+ 'combined');
+ } else { # $hash_parent eq '-c'
+ $formats_nav .= ' | ' .
+ $cgi->a({-href => href(action=>"commitdiff",
+ hash=>$hash, hash_parent=>'--cc')},
+ 'compact');
+ }
$formats_nav .=
' (merge: ' .
join(' ', map {
}
my $hash_parent_param = $hash_parent;
- if (!defined $hash_parent) {
+ if (!defined $hash_parent_param) {
+ # --cc for multiple parents, --root for parentless
$hash_parent_param =
- @{$co{'parents'}} > 1 ? '-c' : $co{'parent'} || '--root';
+ @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root';
}
# read commitdiff
# write patch
if ($format eq 'html') {
- git_difftree_body(\@difftree, $hash, $hash_parent || @{$co{'parents'}});
+ my $use_parents = !defined $hash_parent ||
+ $hash_parent eq '-c' || $hash_parent eq '--cc';
+ git_difftree_body(\@difftree, $hash,
+ $use_parents ? @{$co{'parents'}} : $hash_parent);
print "<br/>\n";
- git_patchset_body($fd, \@difftree, $hash, $hash_parent || @{$co{'parents'}});
+ git_patchset_body($fd, \@difftree, $hash,
+ $use_parents ? @{$co{'parents'}} : $hash_parent);
close $fd;
print "</div>\n"; # class="page_body"
git_footer_html();
# log/feed of current (HEAD) branch, log of given branch, history of file/directory
my $head = $hash || 'HEAD';
- my @commitlist = parse_commits($head, 150);
+ my @commitlist = parse_commits($head, 150, 0, undef, $file_name);
my %latest_commit;
my %latest_date;
}
unlink(obj_req->tmpfile);
if (obj_req->slot) {
- release_active_slot(obj_req->slot);
+ release_active_slot(obj_req->slot);
obj_req->slot = NULL;
}
release_object_request(obj_req);
#include "diff.h"
#include "revision.h"
#include "exec_cmd.h"
+#include "remote.h"
#include <expat.h>
request->buffer.size = stream.total_out;
request->buffer.posn = 0;
- request->url = xmalloc(strlen(remote->url) +
+ request->url = xmalloc(strlen(remote->url) +
strlen(request->lock->token) + 51);
strcpy(request->url, remote->url);
posn = request->url + strlen(remote->url);
return 0;
}
-#ifdef USE_CURL_MULTI
+#ifdef USE_CURL_MULTI
if (!strcmp("http.maxrequests", var)) {
if (max_requests == -1)
max_requests = git_config_int(var, value);
}
if (!git_default_email[0]) {
- if (!pw)
- pw = getpwuid(getuid());
- if (!pw)
- die("You don't exist. Go away!");
- copy_email(pw);
+ const char *email = getenv("EMAIL");
+
+ if (email && email[0])
+ strlcpy(git_default_email, email,
+ sizeof(git_default_email));
+ else {
+ if (!pw)
+ pw = getpwuid(getuid());
+ if (!pw)
+ die("You don't exist. Go away!");
+ copy_email(pw);
+ }
}
/* And set the default date */
name = git_default_name;
if (!email)
email = git_default_email;
- if (!email)
- email = getenv("EMAIL");
if (!*name) {
struct passwd *pw;
msg->data[ msg->len ] = 0;
*ofs += msg->len;
- return 1;
+ return 1;
}
static imap_server_conf_t server =
struct object_entry
{
- off_t offset;
+ struct pack_idx_entry idx;
unsigned long size;
unsigned int hdr_size;
- uint32_t crc32;
enum object_type type;
enum object_type real_type;
- unsigned char sha1[20];
};
union delta_base {
unsigned shift;
void *data;
- obj->offset = consumed_bytes;
+ obj->idx.offset = consumed_bytes;
input_crc32 = crc32(0, Z_NULL, 0);
p = fill(1);
while (c & 128) {
base_offset += 1;
if (!base_offset || MSB(base_offset, 7))
- bad_object(obj->offset, "offset value overflow for delta base object");
+ bad_object(obj->idx.offset, "offset value overflow for delta base object");
p = fill(1);
c = *p;
use(1);
base_offset = (base_offset << 7) + (c & 127);
}
- delta_base->offset = obj->offset - base_offset;
- if (delta_base->offset >= obj->offset)
- bad_object(obj->offset, "delta base offset is out of bound");
+ delta_base->offset = obj->idx.offset - base_offset;
+ if (delta_base->offset >= obj->idx.offset)
+ bad_object(obj->idx.offset, "delta base offset is out of bound");
break;
case OBJ_COMMIT:
case OBJ_TREE:
case OBJ_TAG:
break;
default:
- bad_object(obj->offset, "unknown object type %d", obj->type);
+ bad_object(obj->idx.offset, "unknown object type %d", obj->type);
}
- obj->hdr_size = consumed_bytes - obj->offset;
+ obj->hdr_size = consumed_bytes - obj->idx.offset;
- data = unpack_entry_data(obj->offset, obj->size);
- obj->crc32 = input_crc32;
+ data = unpack_entry_data(obj->idx.offset, obj->size);
+ obj->idx.crc32 = input_crc32;
return data;
}
static void *get_data_from_pack(struct object_entry *obj)
{
- unsigned long from = obj[0].offset + obj[0].hdr_size;
- unsigned long len = obj[1].offset - from;
+ unsigned long from = obj[0].idx.offset + obj[0].hdr_size;
+ unsigned long len = obj[1].idx.offset - from;
unsigned long rdy = 0;
unsigned char *src, *data;
z_stream stream;
&result_size);
free(delta_data);
if (!result)
- bad_object(delta_obj->offset, "failed to apply delta");
- sha1_object(result, result_size, type, delta_obj->sha1);
+ bad_object(delta_obj->idx.offset, "failed to apply delta");
+ sha1_object(result, result_size, type, delta_obj->idx.sha1);
nr_resolved_deltas++;
- hashcpy(delta_base.sha1, delta_obj->sha1);
+ hashcpy(delta_base.sha1, delta_obj->idx.sha1);
if (!find_delta_children(&delta_base, &first, &last)) {
for (j = first; j <= last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
}
memset(&delta_base, 0, sizeof(delta_base));
- delta_base.offset = delta_obj->offset;
+ delta_base.offset = delta_obj->idx.offset;
if (!find_delta_children(&delta_base, &first, &last)) {
for (j = first; j <= last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
delta->obj_no = i;
delta++;
} else
- sha1_object(data, obj->size, obj->type, obj->sha1);
+ sha1_object(data, obj->size, obj->type, obj->idx.sha1);
free(data);
if (verbose)
display_progress(&progress, i+1);
}
- objects[i].offset = consumed_bytes;
+ objects[i].idx.offset = consumed_bytes;
if (verbose)
stop_progress(&progress);
if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
continue;
- hashcpy(base.sha1, obj->sha1);
+ hashcpy(base.sha1, obj->idx.sha1);
ref = !find_delta_children(&base, &ref_first, &ref_last);
memset(&base, 0, sizeof(base));
- base.offset = obj->offset;
+ base.offset = obj->idx.offset;
ofs = !find_delta_children(&base, &ofs_first, &ofs_last);
if (!ref && !ofs)
continue;
}
header[n++] = c;
write_or_die(output_fd, header, n);
- obj[0].crc32 = crc32(0, Z_NULL, 0);
- obj[0].crc32 = crc32(obj[0].crc32, header, n);
- obj[1].offset = obj[0].offset + n;
- obj[1].offset += write_compressed(output_fd, buf, size, &obj[0].crc32);
- hashcpy(obj->sha1, sha1);
+ obj[0].idx.crc32 = crc32(0, Z_NULL, 0);
+ obj[0].idx.crc32 = crc32(obj[0].idx.crc32, header, n);
+ obj[1].idx.offset = obj[0].idx.offset + n;
+ obj[1].idx.offset += write_compressed(output_fd, buf, size, &obj[0].idx.crc32);
+ hashcpy(obj->idx.sha1, sha1);
}
static int delta_pos_compare(const void *_a, const void *_b)
free(sorted_by_pos);
}
-static uint32_t index_default_version = 1;
-static uint32_t index_off32_limit = 0x7fffffff;
-
-static int sha1_compare(const void *_a, const void *_b)
-{
- struct object_entry *a = *(struct object_entry **)_a;
- struct object_entry *b = *(struct object_entry **)_b;
- return hashcmp(a->sha1, b->sha1);
-}
-
-/*
- * On entry *sha1 contains the pack content SHA1 hash, on exit it is
- * the SHA1 hash of sorted object names.
- */
-static const char *write_index_file(const char *index_name, unsigned char *sha1)
-{
- struct sha1file *f;
- struct object_entry **sorted_by_sha, **list, **last;
- uint32_t array[256];
- int i, fd;
- SHA_CTX ctx;
- uint32_t index_version;
-
- if (nr_objects) {
- sorted_by_sha =
- xcalloc(nr_objects, sizeof(struct object_entry *));
- list = sorted_by_sha;
- last = sorted_by_sha + nr_objects;
- for (i = 0; i < nr_objects; ++i)
- sorted_by_sha[i] = &objects[i];
- qsort(sorted_by_sha, nr_objects, sizeof(sorted_by_sha[0]),
- sha1_compare);
- }
- else
- sorted_by_sha = list = last = NULL;
-
- if (!index_name) {
- static char tmpfile[PATH_MAX];
- snprintf(tmpfile, sizeof(tmpfile),
- "%s/tmp_idx_XXXXXX", get_object_directory());
- fd = mkstemp(tmpfile);
- index_name = xstrdup(tmpfile);
- } else {
- unlink(index_name);
- fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
- }
- if (fd < 0)
- die("unable to create %s: %s", index_name, strerror(errno));
- f = sha1fd(fd, index_name);
-
- /* if last object's offset is >= 2^31 we should use index V2 */
- index_version = (objects[nr_objects-1].offset >> 31) ? 2 : index_default_version;
-
- /* index versions 2 and above need a header */
- if (index_version >= 2) {
- struct pack_idx_header hdr;
- hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
- hdr.idx_version = htonl(index_version);
- sha1write(f, &hdr, sizeof(hdr));
- }
-
- /*
- * Write the first-level table (the list is sorted,
- * but we use a 256-entry lookup to be able to avoid
- * having to do eight extra binary search iterations).
- */
- for (i = 0; i < 256; i++) {
- struct object_entry **next = list;
- while (next < last) {
- struct object_entry *obj = *next;
- if (obj->sha1[0] != i)
- break;
- next++;
- }
- array[i] = htonl(next - sorted_by_sha);
- list = next;
- }
- sha1write(f, array, 256 * 4);
-
- /* compute the SHA1 hash of sorted object names. */
- SHA1_Init(&ctx);
-
- /*
- * Write the actual SHA1 entries..
- */
- list = sorted_by_sha;
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *obj = *list++;
- if (index_version < 2) {
- uint32_t offset = htonl(obj->offset);
- sha1write(f, &offset, 4);
- }
- sha1write(f, obj->sha1, 20);
- SHA1_Update(&ctx, obj->sha1, 20);
- }
-
- if (index_version >= 2) {
- unsigned int nr_large_offset = 0;
-
- /* write the crc32 table */
- list = sorted_by_sha;
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *obj = *list++;
- uint32_t crc32_val = htonl(obj->crc32);
- sha1write(f, &crc32_val, 4);
- }
-
- /* write the 32-bit offset table */
- list = sorted_by_sha;
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *obj = *list++;
- uint32_t offset = (obj->offset <= index_off32_limit) ?
- obj->offset : (0x80000000 | nr_large_offset++);
- offset = htonl(offset);
- sha1write(f, &offset, 4);
- }
-
- /* write the large offset table */
- list = sorted_by_sha;
- while (nr_large_offset) {
- struct object_entry *obj = *list++;
- uint64_t offset = obj->offset;
- if (offset > index_off32_limit) {
- uint32_t split[2];
- split[0] = htonl(offset >> 32);
- split[1] = htonl(offset & 0xffffffff);
- sha1write(f, split, 8);
- nr_large_offset--;
- }
- }
- }
-
- sha1write(f, sha1, 20);
- sha1close(f, NULL, 1);
- free(sorted_by_sha);
- SHA1_Final(sha1, &ctx);
- return index_name;
-}
-
static void final(const char *final_pack_name, const char *curr_pack_name,
const char *final_index_name, const char *curr_index_name,
const char *keep_name, const char *keep_msg,
write_or_die(keep_fd, keep_msg, keep_msg_len);
write_or_die(keep_fd, "\n", 1);
}
- close(keep_fd);
+ if (close(keep_fd) != 0)
+ die("cannot write keep file");
report = "keep";
}
}
const char *curr_index, *index_name = NULL;
const char *keep_name = NULL, *keep_msg = NULL;
char *index_name_buf = NULL, *keep_name_buf = NULL;
+ struct pack_idx_entry **idx_objects;
unsigned char sha1[20];
for (i = 1; i < argc; i++) {
index_name = argv[++i];
} else if (!prefixcmp(arg, "--index-version=")) {
char *c;
- index_default_version = strtoul(arg + 16, &c, 10);
- if (index_default_version > 2)
+ pack_idx_default_version = strtoul(arg + 16, &c, 10);
+ if (pack_idx_default_version > 2)
die("bad %s", arg);
if (*c == ',')
- index_off32_limit = strtoul(c+1, &c, 0);
- if (*c || index_off32_limit & 0x80000000)
+ pack_idx_off32_limit = strtoul(c+1, &c, 0);
+ if (*c || pack_idx_off32_limit & 0x80000000)
die("bad %s", arg);
} else
usage(index_pack_usage);
nr_deltas - nr_resolved_deltas);
}
free(deltas);
- curr_index = write_index_file(index_name, sha1);
+
+ idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
+ for (i = 0; i < nr_objects; i++)
+ idx_objects[i] = &objects[i].idx;
+ curr_index = write_idx_file(index_name, idx_objects, nr_objects, sha1);
+ free(idx_objects);
+
final(pack_name, curr_pack,
index_name, curr_index,
keep_name, keep_msg,
* { "%%", "%"},
* }
*
- * Returns 1 on a successful substitution pass that fits in result,
- * Returns 0 on a failed or overflowing substitution pass.
+ * Returns 0 on a successful substitution pass that fits in result,
+ * Returns a number of bytes needed to hold the full substituted
+ * string otherwise.
*/
-int interpolate(char *result, int reslen,
+unsigned long interpolate(char *result, unsigned long reslen,
const char *orig,
const struct interp *interps, int ninterps)
{
const char *src = orig;
char *dest = result;
- int newlen = 0;
+ unsigned long newlen = 0;
const char *name, *value;
- int namelen, valuelen;
+ unsigned long namelen, valuelen;
int i;
char c;
memset(result, 0, reslen);
- while ((c = *src) && newlen < reslen - 1) {
+ while ((c = *src)) {
if (c == '%') {
/* Try to match an interpolation string. */
for (i = 0; i < ninterps; i++) {
name = interps[i].name;
namelen = strlen(name);
- if (strncmp(src, name, namelen) == 0) {
+ if (strncmp(src, name, namelen) == 0)
break;
- }
}
/* Check for valid interpolation. */
value = interps[i].value;
valuelen = strlen(value);
- if (newlen + valuelen < reslen - 1) {
+ if (newlen + valuelen + 1 < reslen) {
/* Substitute. */
strncpy(dest, value, valuelen);
- newlen += valuelen;
dest += valuelen;
- src += namelen;
- } else {
- /* Something's not fitting. */
- return 0;
}
-
- } else {
- /* Skip bogus interpolation. */
- *dest++ = *src++;
- newlen++;
+ newlen += valuelen;
+ src += namelen;
+ continue;
}
-
- } else {
- /* Straight copy one non-interpolation character. */
- *dest++ = *src++;
- newlen++;
}
+ /* Straight copy one non-interpolation character. */
+ if (newlen + 1 < reslen)
+ *dest++ = *src;
+ src++;
+ newlen++;
}
- return newlen < reslen - 1;
+ if (newlen + 1 < reslen)
+ return 0;
+ else
+ return newlen + 2;
}
extern void interp_set_entry(struct interp *table, int slot, const char *value);
extern void interp_clear_table(struct interp *table, int ninterps);
-extern int interpolate(char *result, int reslen,
- const char *orig,
- const struct interp *interps, int ninterps);
+extern unsigned long interpolate(char *result, unsigned long reslen,
+ const char *orig,
+ const struct interp *interps, int ninterps);
#endif /* INTERPOLATE_H */
process_tree(revs,
lookup_tree(entry.sha1),
p, &me, entry.path);
- else if (S_ISDIRLNK(entry.mode))
+ else if (S_ISGITLINK(entry.mode))
process_gitlink(revs, entry.sha1,
p, &me, entry.path);
else
return -1;
target = find_sha1_pack(sha1, packs);
if (!target)
- return error("Couldn't find %s: not separate or in any pack",
+ return error("Couldn't find %s: not separate or in any pack",
sha1_to_hex(sha1));
if (get_verbosely) {
fprintf(stderr, "Getting pack %s\n",
fprintf(stderr, " which contains %s\n",
sha1_to_hex(sha1));
}
- sprintf(filename, "%s/objects/pack/pack-%s.pack",
+ sprintf(filename, "%s/objects/pack/pack-%s.pack",
path, sha1_to_hex(target->sha1));
copy_file(filename, sha1_pack_name(target->sha1),
sha1_to_hex(target->sha1), 1);
- sprintf(filename, "%s/objects/pack/pack-%s.idx",
+ sprintf(filename, "%s/objects/pack/pack-%s.idx",
path, sha1_to_hex(target->sha1));
copy_file(filename, sha1_pack_index_name(target->sha1),
sha1_to_hex(target->sha1), 1);
char *hex = sha1_to_hex(sha1);
char *dest_filename = sha1_file_name(sha1);
- if (object_name_start < 0) {
+ if (object_name_start < 0) {
strcpy(filename, path); /* e.g. git.git */
strcat(filename, "/objects/");
object_name_start = strlen(filename);
raise(signo);
}
+/*
+ * p = absolute or relative path name
+ *
+ * Return a pointer into p showing the beginning of the last path name
+ * element. If p is empty or the root directory ("/"), just return p.
+ */
+static char *last_path_elm(char *p)
+{
+ /* r starts pointing to null at the end of the string */
+ char *r = strchr(p, '\0');
+
+ if (r == p)
+ return p; /* just return empty string */
+
+ r--; /* back up to last non-null character */
+
+ /* back up past trailing slashes, if any */
+ while (r > p && *r == '/')
+ r--;
+
+ /*
+ * then go backwards until I hit a slash, or the beginning of
+ * the string
+ */
+ while (r > p && *(r-1) != '/')
+ r--;
+ return r;
+}
+
+
+/* We allow "recursive" symbolic links. Only within reason, though */
+#define MAXDEPTH 5
+
+/*
+ * p = path that may be a symlink
+ * s = full size of p
+ *
+ * If p is a symlink, attempt to overwrite p with a path to the real
+ * file or directory (which may or may not exist), following a chain of
+ * symlinks if necessary. Otherwise, leave p unmodified.
+ *
+ * This is a best-effort routine. If an error occurs, p will either be
+ * left unmodified or will name a different symlink in a symlink chain
+ * that started with p's initial contents.
+ *
+ * Always returns p.
+ */
+
+static char *resolve_symlink(char *p, size_t s)
+{
+ int depth = MAXDEPTH;
+
+ while (depth--) {
+ char link[PATH_MAX];
+ int link_len = readlink(p, link, sizeof(link));
+ if (link_len < 0) {
+ /* not a symlink anymore */
+ return p;
+ }
+ else if (link_len < sizeof(link))
+ /* readlink() never null-terminates */
+ link[link_len] = '\0';
+ else {
+ warning("%s: symlink too long", p);
+ return p;
+ }
+
+ if (link[0] == '/') {
+ /* absolute path simply replaces p */
+ if (link_len < s)
+ strcpy(p, link);
+ else {
+ warning("%s: symlink too long", p);
+ return p;
+ }
+ } else {
+ /*
+ * link is a relative path, so I must replace the
+ * last element of p with it.
+ */
+ char *r = (char*)last_path_elm(p);
+ if (r - p + link_len < s)
+ strcpy(r, link);
+ else {
+ warning("%s: symlink too long", p);
+ return p;
+ }
+ }
+ }
+ return p;
+}
+
+
static int lock_file(struct lock_file *lk, const char *path)
{
int fd;
- sprintf(lk->filename, "%s.lock", path);
+
+ 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
+ */
+ resolve_symlink(lk->filename, sizeof(lk->filename)-5);
+ strcat(lk->filename, ".lock");
fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
if (0 <= fd) {
+ if (!lock_file_list) {
+ signal(SIGINT, remove_lock_file_on_signal);
+ atexit(remove_lock_file);
+ }
lk->owner = getpid();
if (!lk->on_list) {
lk->next = lock_file_list;
lock_file_list = lk;
lk->on_list = 1;
}
- if (lock_file_list) {
- signal(SIGINT, remove_lock_file_on_signal);
- atexit(remove_lock_file);
- }
if (adjust_shared_perm(lk->filename))
return error("cannot fix permission bits on %s",
lk->filename);
unlink(lk->filename);
lk->filename[0] = 0;
}
-
return seen_head && seen_name;
}
-static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
+static unsigned long append_signoff(char **buf_p, unsigned long *buf_sz_p,
+ unsigned long at, const char *signoff)
{
static const char signed_off_by[] = "Signed-off-by: ";
- int signoff_len = strlen(signoff);
+ size_t signoff_len = strlen(signoff);
int has_signoff = 0;
- char *cp = buf;
-
- /* Do we have enough space to add it? */
- if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3)
- return at;
+ char *cp;
+ char *buf;
+ unsigned long buf_sz;
+
+ buf = *buf_p;
+ buf_sz = *buf_sz_p;
+ if (buf_sz <= at + strlen(signed_off_by) + signoff_len + 3) {
+ buf_sz += strlen(signed_off_by) + signoff_len + 3;
+ buf = xrealloc(buf, buf_sz);
+ *buf_p = buf;
+ *buf_sz_p = buf_sz;
+ }
+ cp = buf;
/* First see if we already have the sign-off by the signer */
while ((cp = strstr(cp, signed_off_by))) {
void show_log(struct rev_info *opt, const char *sep)
{
- static char this_header[16384];
+ char *msgbuf = NULL;
+ unsigned long msgbuf_len = 0;
struct log_info *log = opt->loginfo;
struct commit *commit = log->commit, *parent = log->parent;
int abbrev = opt->diffopt.abbrev;
digits_in_number(opt->total),
opt->nr, opt->total);
subject = buffer;
- } else if (opt->total == 0) {
+ } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
static char buffer[256];
snprintf(buffer, sizeof(buffer),
"Subject: [%s] ",
/*
* And then the pretty-printed message itself
*/
- len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header,
- sizeof(this_header), abbrev, subject,
+ len = pretty_print_commit(opt->commit_format, commit, ~0u,
+ &msgbuf, &msgbuf_len, abbrev, subject,
extra_headers, opt->date_mode);
if (opt->add_signoff)
- len = append_signoff(this_header, sizeof(this_header), len,
+ len = append_signoff(&msgbuf, &msgbuf_len, len,
opt->add_signoff);
- printf("%s%s%s", this_header, extra, sep);
+ printf("%s%s%s", msgbuf, extra, sep);
+ free(msgbuf);
}
int log_tree_diff_flush(struct rev_info *opt)
shown = 1;
}
opt->loginfo = NULL;
+ maybe_flush_or_die(stdout, "stdout");
return shown;
}
splice_tree(hash1, add_prefix, hash2, shifted);
}
-
xdemitcb_t ecb;
xpp.flags = XDF_NEED_MINIMAL;
+ memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
xecfg.flags = XDL_EMIT_COMMON;
ecb.outf = common_outf;
static int merge_entry(int pos, const char *path)
{
int found;
-
+
if (pos >= active_nr)
die("git-merge-index: %s not in the cache", path);
arguments[0] = pgm;
va_end(args);
}
-static void flush_output()
+static void flush_output(void)
{
struct output_buffer *b, *n;
for (b = output_list; b; b = n) {
xdemitcb_t ecb;
xpp.flags = XDF_NEED_MINIMAL;
+ memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
- xecfg.flags = 0;
ecb.outf = show_outf;
ecb.priv = NULL;
* The first three lines are guaranteed to be at least 63 bytes:
* "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
* shortest possible type-line, and "tag .\n" at 6 bytes is the
- * shortest single-character-tag line.
- *
- * We also artificially limit the size of the full object to 8kB.
- * Just because I'm a lazy bastard, and if you can't fit a signature
- * in that size, you're doing something wrong.
+ * shortest single-character-tag line.
*/
-/* Some random size */
-#define MAXSIZE (8192)
-
/*
* We refuse to tag something we can't verify. Just because.
*/
setup_git_directory();
- if (read_pipe(0, &buffer, &size)) {
+ if (read_fd(0, &buffer, &size)) {
free(buffer);
die("could not read from stdin");
}
-/*
+/*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
- *
+ *
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
- *
+ *
* The Original Code is SHA 180-1 Reference Implementation (Compact version)
- *
+ *
* The Initial Developer of the Original Code is Paul Kocher of
- * Cryptography Research. Portions created by Paul Kocher are
+ * Cryptography Research. Portions created by Paul Kocher are
* Copyright (C) 1995-9 by Cryptography Research, Inc. All
* Rights Reserved.
- *
+ *
* Contributor(s):
*
* Paul Kocher
- *
+ *
* Alternatively, the contents of this file may be used under the
* terms of the GNU General Public License Version 2 or later (the
- * "GPL"), in which case the provisions of the GPL are applicable
- * instead of those above. If you wish to allow use of your
+ * "GPL"), in which case the provisions of the GPL are applicable
+ * instead of those above. If you wish to allow use of your
* version of this file only under the terms of the GPL and not to
* allow others to use your version of this file under the MPL,
* indicate your decision by deleting the provisions above and
ctx->H[3] += D;
ctx->H[4] += E;
}
-
-/*
+/*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
- *
+ *
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
- *
+ *
* The Original Code is SHA 180-1 Header File
- *
+ *
* The Initial Developer of the Original Code is Paul Kocher of
- * Cryptography Research. Portions created by Paul Kocher are
+ * Cryptography Research. Portions created by Paul Kocher are
* Copyright (C) 1995-9 by Cryptography Research, Inc. All
* Rights Reserved.
- *
+ *
* Contributor(s):
*
* Paul Kocher
- *
+ *
* Alternatively, the contents of this file may be used under the
* terms of the GNU General Public License Version 2 or later (the
- * "GPL"), in which case the provisions of the GPL are applicable
- * instead of those above. If you wish to allow use of your
+ * "GPL"), in which case the provisions of the GPL are applicable
+ * instead of those above. If you wish to allow use of your
* version of this file only under the terms of the GPL and not to
* allow others to use your version of this file under the MPL,
* indicate your decision by deleting the provisions above and
mark_reachable(refs->ref[i], mask);
}
}
-
-
void mark_reachable(struct object *obj, unsigned int mask);
-struct object_list *object_list_insert(struct object *item,
+struct object_list *object_list_insert(struct object *item,
struct object_list **list_p);
void object_list_append(struct object *item,
#include "cache.h"
#include "pack.h"
+struct idx_entry
+{
+ const unsigned char *sha1;
+ off_t offset;
+};
+
+static int compare_entries(const void *e1, const void *e2)
+{
+ const struct idx_entry *entry1 = e1;
+ const struct idx_entry *entry2 = e2;
+ if (entry1->offset < entry2->offset)
+ return -1;
+ if (entry1->offset > entry2->offset)
+ return 1;
+ return 0;
+}
+
static int verify_packfile(struct packed_git *p,
struct pack_window **w_curs)
{
off_t offset = 0, pack_sig = p->pack_size - 20;
uint32_t nr_objects, i;
int err;
+ struct idx_entry *entries;
/* Note that the pack header checks are actually performed by
* use_pack when it first opens the pack file. If anything
* we do not do scan-streaming check on the pack file.
*/
nr_objects = p->num_objects;
+ entries = xmalloc(nr_objects * sizeof(*entries));
+ /* first sort entries by pack offset, since unpacking them is more efficient that way */
+ for (i = 0; i < nr_objects; i++) {
+ entries[i].sha1 = nth_packed_object_sha1(p, i);
+ if (!entries[i].sha1)
+ die("internal error pack-check nth-packed-object");
+ entries[i].offset = find_pack_entry_one(entries[i].sha1, p);
+ if (!entries[i].offset)
+ die("internal error pack-check find-pack-entry-one");
+ }
+ qsort(entries, nr_objects, sizeof(*entries), compare_entries);
+
for (i = 0, err = 0; i < nr_objects; i++) {
- const unsigned char *sha1;
void *data;
enum object_type type;
unsigned long size;
- off_t offset;
- sha1 = nth_packed_object_sha1(p, i);
- if (!sha1)
- die("internal error pack-check nth-packed-object");
- offset = find_pack_entry_one(sha1, p);
- if (!offset)
- die("internal error pack-check find-pack-entry-one");
- data = unpack_entry(p, offset, &type, &size);
+ data = unpack_entry(p, entries[i].offset, &type, &size);
if (!data) {
err = error("cannot unpack %s from %s",
- sha1_to_hex(sha1), p->pack_name);
+ sha1_to_hex(entries[i].sha1), p->pack_name);
continue;
}
- if (check_sha1_signature(sha1, data, size, typename(type))) {
+ if (check_sha1_signature(entries[i].sha1, data, size, typename(type))) {
err = error("packed %s from %s is corrupt",
- sha1_to_hex(sha1), p->pack_name);
+ sha1_to_hex(entries[i].sha1), p->pack_name);
free(data);
continue;
}
free(data);
}
+ free(entries);
return err;
}
-#define MAX_CHAIN 40
+#define MAX_CHAIN 50
static void show_pack_info(struct packed_git *p)
{
- uint32_t nr_objects, i, chain_histogram[MAX_CHAIN];
-
+ uint32_t nr_objects, i, chain_histogram[MAX_CHAIN+1];
nr_objects = p->num_objects;
memset(chain_histogram, 0, sizeof(chain_histogram));
printf("%-6s %lu %"PRIuMAX" %u %s\n",
type, size, (uintmax_t)offset,
delta_chain_length, sha1_to_hex(base_sha1));
- if (delta_chain_length < MAX_CHAIN)
+ if (delta_chain_length <= MAX_CHAIN)
chain_histogram[delta_chain_length]++;
else
chain_histogram[0]++;
}
}
- for (i = 0; i < MAX_CHAIN; i++) {
+ for (i = 0; i <= MAX_CHAIN; i++) {
if (!chain_histogram[i])
continue;
- printf("chain length %s %d: %d object%s\n",
- i ? "=" : ">=",
- i ? i : MAX_CHAIN,
- chain_histogram[i],
- 1 < chain_histogram[i] ? "s" : "");
+ printf("chain length = %d: %d object%s\n", i,
+ chain_histogram[i], chain_histogram[i] > 1 ? "s" : "");
}
+ if (chain_histogram[0])
+ printf("chain length > %d: %d object%s\n", MAX_CHAIN,
+ chain_histogram[0], chain_histogram[0] > 1 ? "s" : "");
}
int verify_pack(struct packed_git *p, int verbose)
{
- off_t index_size = p->index_size;
- const unsigned char *index_base = p->index_data;
+ off_t index_size;
+ const unsigned char *index_base;
SHA_CTX ctx;
unsigned char sha1[20];
int ret;
+ if (open_pack_index(p))
+ return error("packfile %s index not opened", p->pack_name);
+ index_size = p->index_size;
+ index_base = p->index_data;
+
ret = 0;
/* Verify SHA1 sum of the index file */
SHA1_Init(&ctx);
{
struct llist *ret;
struct llist_item *new, *old, *prev;
-
+
llist_init(&ret);
if ((ret->size = list->size) == 0)
}
new->next = NULL;
ret->back = new;
-
+
return ret;
}
l.pack = p;
llist_init(&l.all_objects);
+ if (open_pack_index(p))
+ return NULL;
+
base = p->index_data;
base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
step = (p->index_version < 2) ? 24 : 20;
#include "cache.h"
#include "pack.h"
+#include "csum-file.h"
+
+uint32_t pack_idx_default_version = 1;
+uint32_t pack_idx_off32_limit = 0x7fffffff;
+
+static int sha1_compare(const void *_a, const void *_b)
+{
+ struct pack_idx_entry *a = *(struct pack_idx_entry **)_a;
+ struct pack_idx_entry *b = *(struct pack_idx_entry **)_b;
+ return hashcmp(a->sha1, b->sha1);
+}
+
+/*
+ * On entry *sha1 contains the pack content SHA1 hash, on exit it is
+ * the SHA1 hash of sorted object names. The objects array passed in
+ * will be sorted by SHA1 on exit.
+ */
+const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1)
+{
+ struct sha1file *f;
+ struct pack_idx_entry **sorted_by_sha, **list, **last;
+ off_t last_obj_offset = 0;
+ uint32_t array[256];
+ int i, fd;
+ SHA_CTX ctx;
+ uint32_t index_version;
+
+ if (nr_objects) {
+ sorted_by_sha = objects;
+ list = sorted_by_sha;
+ last = sorted_by_sha + nr_objects;
+ for (i = 0; i < nr_objects; ++i) {
+ if (objects[i]->offset > last_obj_offset)
+ last_obj_offset = objects[i]->offset;
+ }
+ qsort(sorted_by_sha, nr_objects, sizeof(sorted_by_sha[0]),
+ sha1_compare);
+ }
+ else
+ sorted_by_sha = list = last = NULL;
+
+ if (!index_name) {
+ static char tmpfile[PATH_MAX];
+ snprintf(tmpfile, sizeof(tmpfile),
+ "%s/tmp_idx_XXXXXX", get_object_directory());
+ fd = mkstemp(tmpfile);
+ index_name = xstrdup(tmpfile);
+ } else {
+ unlink(index_name);
+ fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+ }
+ if (fd < 0)
+ die("unable to create %s: %s", index_name, strerror(errno));
+ f = sha1fd(fd, index_name);
+
+ /* if last object's offset is >= 2^31 we should use index V2 */
+ index_version = (last_obj_offset >> 31) ? 2 : pack_idx_default_version;
+
+ /* index versions 2 and above need a header */
+ if (index_version >= 2) {
+ struct pack_idx_header hdr;
+ hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
+ hdr.idx_version = htonl(index_version);
+ sha1write(f, &hdr, sizeof(hdr));
+ }
+
+ /*
+ * Write the first-level table (the list is sorted,
+ * but we use a 256-entry lookup to be able to avoid
+ * having to do eight extra binary search iterations).
+ */
+ for (i = 0; i < 256; i++) {
+ struct pack_idx_entry **next = list;
+ while (next < last) {
+ struct pack_idx_entry *obj = *next;
+ if (obj->sha1[0] != i)
+ break;
+ next++;
+ }
+ array[i] = htonl(next - sorted_by_sha);
+ list = next;
+ }
+ sha1write(f, array, 256 * 4);
+
+ /* compute the SHA1 hash of sorted object names. */
+ SHA1_Init(&ctx);
+
+ /*
+ * Write the actual SHA1 entries..
+ */
+ list = sorted_by_sha;
+ for (i = 0; i < nr_objects; i++) {
+ struct pack_idx_entry *obj = *list++;
+ if (index_version < 2) {
+ uint32_t offset = htonl(obj->offset);
+ sha1write(f, &offset, 4);
+ }
+ sha1write(f, obj->sha1, 20);
+ SHA1_Update(&ctx, obj->sha1, 20);
+ }
+
+ if (index_version >= 2) {
+ unsigned int nr_large_offset = 0;
+
+ /* write the crc32 table */
+ list = sorted_by_sha;
+ for (i = 0; i < nr_objects; i++) {
+ struct pack_idx_entry *obj = *list++;
+ uint32_t crc32_val = htonl(obj->crc32);
+ sha1write(f, &crc32_val, 4);
+ }
+
+ /* write the 32-bit offset table */
+ list = sorted_by_sha;
+ for (i = 0; i < nr_objects; i++) {
+ struct pack_idx_entry *obj = *list++;
+ uint32_t offset = (obj->offset <= pack_idx_off32_limit) ?
+ obj->offset : (0x80000000 | nr_large_offset++);
+ offset = htonl(offset);
+ sha1write(f, &offset, 4);
+ }
+
+ /* write the large offset table */
+ list = sorted_by_sha;
+ while (nr_large_offset) {
+ struct pack_idx_entry *obj = *list++;
+ uint64_t offset = obj->offset;
+ if (offset > pack_idx_off32_limit) {
+ uint32_t split[2];
+ split[0] = htonl(offset >> 32);
+ split[1] = htonl(offset & 0xffffffff);
+ sha1write(f, split, 8);
+ nr_large_offset--;
+ }
+ }
+ }
+
+ sha1write(f, sha1, 20);
+ sha1close(f, NULL, 1);
+ SHA1_Final(sha1, &ctx);
+ return index_name;
+}
void fixup_pack_header_footer(int pack_fd,
unsigned char *pack_file_sha1,
*/
#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */
+/* These may be overridden by command-line parameters */
+extern uint32_t pack_idx_default_version;
+extern uint32_t pack_idx_off32_limit;
+
/*
* Packed object index header
*/
uint32_t idx_version;
};
+/*
+ * Common part of object structure used for write_idx_file
+ */
+struct pack_idx_entry {
+ unsigned char sha1[20];
+ uint32_t crc32;
+ off_t offset;
+};
+
+extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
extern int verify_pack(struct packed_git *, int);
extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t);
if (!isatty(1))
return;
+ if (!pager)
+ pager = pager_program;
if (!pager)
pager = getenv("PAGER");
if (!pager)
generate_id_list();
return 0;
-}
+}
for (i = 0; i < p->nr; i++)
printf("%s:%p\n", p->items[i].path, p->items[i].util);
}
-
/* git_mkstemp() - create tmp file honoring TMPDIR variable */
int git_mkstemp(char *path, size_t len, const char *template)
{
- char *env, *pch = path;
-
- if ((env = getenv("TMPDIR")) == NULL) {
- strcpy(pch, "/tmp/");
- len -= 5;
- pch += 5;
- } else {
- size_t n = snprintf(pch, len, "%s/", env);
-
- len -= n;
- pch += n;
+ const char *tmp;
+ size_t n;
+
+ tmp = getenv("TMPDIR");
+ if (!tmp)
+ tmp = "/tmp";
+ n = snprintf(path, len, "%s/%s", tmp, template);
+ if (len <= n) {
+ errno = ENAMETOOLONG;
+ return -1;
}
-
- strlcpy(pch, template, len);
-
return mkstemp(path);
}
if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
validate_headref("HEAD") == 0) {
- setenv("GIT_DIR", ".", 1);
+ setenv(GIT_DIR_ENVIRONMENT, ".", 1);
check_repository_format();
return path;
}
return -2;
return 0;
}
+
+/* We allow "recursive" symbolic links. Only within reason, though. */
+#define MAXDEPTH 5
+
+const char *make_absolute_path(const char *path)
+{
+ static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1];
+ char cwd[1024] = "";
+ int buf_index = 1, len;
+
+ int depth = MAXDEPTH;
+ char *last_elem = NULL;
+ struct stat st;
+
+ if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
+ die ("Too long path: %.*s", 60, path);
+
+ while (depth--) {
+ if (stat(buf, &st) || !S_ISDIR(st.st_mode)) {
+ char *last_slash = strrchr(buf, '/');
+ if (last_slash) {
+ *last_slash = '\0';
+ last_elem = xstrdup(last_slash + 1);
+ } else
+ last_elem = xstrdup(buf);
+ }
+
+ if (*buf) {
+ if (!*cwd && !getcwd(cwd, sizeof(cwd)))
+ die ("Could not get current working directory");
+
+ if (chdir(buf))
+ die ("Could not switch to '%s'", buf);
+ }
+ if (!getcwd(buf, PATH_MAX))
+ die ("Could not get current working directory");
+
+ if (last_elem) {
+ int len = strlen(buf);
+ if (len + strlen(last_elem) + 2 > PATH_MAX)
+ die ("Too long path name: '%s/%s'",
+ buf, last_elem);
+ buf[len] = '/';
+ strcpy(buf + len + 1, last_elem);
+ free(last_elem);
+ last_elem = NULL;
+ }
+
+ if (!lstat(buf, &st) && S_ISLNK(st.st_mode)) {
+ len = readlink(buf, next_buf, PATH_MAX);
+ if (len < 0)
+ die ("Invalid symlink: %s", buf);
+ next_buf[len] = '\0';
+ buf = next_buf;
+ buf_index = 1 - buf_index;
+ next_buf = bufs[buf_index];
+ } else
+ break;
+ }
+
+ if (*cwd && chdir(cwd))
+ die ("Could not change back to '%s'", cwd);
+
+ return buf;
+}
if (!dest || i != argc - 1)
usage(peek_remote_usage);
- pid = git_connect(fd, dest, uploadpack);
+ pid = git_connect(fd, dest, uploadpack, 0);
if (pid < 0)
return 1;
ret = peek_remote(fd, flags);
# (even though GIT-CFLAGS aren't used yet. If ever)
../GIT-CFLAGS:
$(MAKE) -C .. GIT-CFLAGS
-
* Write a packetized stream, where each line is preceded by
* its length (including the header) as a 4-byte hex number.
* A length of 'zero' means end of stream (and a length of 1-3
- * would be an error).
+ * would be an error).
*
* This is all pretty stupid, but we use this packetized line
* format to make a streaming format possible without ever
p += nb;
}
return 0;
-}
+}
int SHA1_Final(unsigned char *hash, SHA_CTX *c)
{
fprintf(stderr, "%s%4u%% (%u/%u) done\r",
progress->prefix, percent, n, progress->total);
progress_update = 0;
+ progress->need_lf = 1;
return 1;
}
} else if (progress_update) {
fprintf(stderr, "%s%u\r", progress->prefix, n);
progress_update = 0;
+ progress->need_lf = 1;
return 1;
}
return 0;
progress->total = total;
progress->last_percent = -1;
progress->delay = 0;
+ progress->need_lf = 0;
if (snprintf(buf, sizeof(buf), title, total))
fprintf(stderr, "%s\n", buf);
set_progress_signal();
progress->delayed_percent_treshold = percent_treshold;
progress->delayed_title = title;
progress->delay = delay;
+ progress->need_lf = 0;
set_progress_signal();
}
void stop_progress(struct progress *progress)
{
clear_progress_signal();
- if (progress->total)
+ if (progress->need_lf)
fputc('\n', stderr);
}
unsigned delay;
unsigned delayed_percent_treshold;
const char *delayed_title;
+ int need_lf;
};
int display_progress(struct progress *progress, unsigned n);
return (c == '\'' || c == '!');
}
-size_t sq_quote_buf(char *dst, size_t n, const char *src)
+static size_t sq_quote_buf(char *dst, size_t n, const char *src)
{
char c;
char *bp = dst;
fputc('\'', stream);
}
-char *sq_quote(const char *src)
-{
- char *buf;
- size_t cnt;
-
- cnt = sq_quote_buf(NULL, 0, src) + 1;
- buf = xmalloc(cnt);
- sq_quote_buf(buf, cnt, src);
-
- return buf;
-}
-
char *sq_quote_argv(const char** argv, int count)
{
char *buf, *to;
#define EMITQ() EMIT('\\')
const char *sp;
- int ch, count = 0, needquote = 0;
+ unsigned char ch;
+ int count = 0, needquote = 0;
if (!no_dq)
EMIT('"');
if (!ch)
break;
if ((ch < ' ') || (ch == '"') || (ch == '\\') ||
- (ch >= 0177)) {
+ (quote_path_fully && (ch >= 0177))) {
needquote = 1;
switch (ch) {
case '\a': EMITQ(); ch = 'a'; break;
* excluding the final null regardless of the buffer size.
*/
-extern char *sq_quote(const char *src);
extern void sq_quote_print(FILE *stream, const char *src);
-extern size_t sq_quote_buf(char *dst, size_t n, const char *src);
extern char *sq_quote_argv(const char** argv, int count);
/*
while (tree_entry(&desc, &entry)) {
if (S_ISDIR(entry.mode))
process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
- else if (S_ISDIRLNK(entry.mode))
+ else if (S_ISGITLINK(entry.mode))
process_gitlink(entry.sha1, p, &me, entry.path);
else
process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
* lookup_blob() on them, to avoid populating the hash table
* with invalid information
*/
- if (S_ISDIRLNK(ntohl(active_cache[i]->ce_mode)))
+ if (S_ISGITLINK(ntohl(active_cache[i]->ce_mode)))
continue;
lookup_blob(active_cache[i]->sha1);
/*
* We don't actually require that the .git directory
- * under DIRLNK directory be a valid git directory. It
+ * under GITLINK directory be a valid git directory. It
* might even be missing (in case nobody populated that
* sub-project).
*
return DATA_CHANGED;
break;
case S_IFDIR:
- if (S_ISDIRLNK(ntohl(ce->ce_mode)))
+ if (S_ISGITLINK(ntohl(ce->ce_mode)))
return 0;
default:
return TYPE_CHANGED;
(has_symlinks || !S_ISREG(st->st_mode)))
changed |= TYPE_CHANGED;
break;
- case S_IFDIRLNK:
+ case S_IFGITLINK:
if (!S_ISDIR(st->st_mode))
changed |= TYPE_CHANGED;
else if (ce_compare_gitlink(ce))
changed |= MTIME_CHANGED;
if (ce->ce_ctime.nsec != htonl(st->st_ctim.tv_nsec))
changed |= CTIME_CHANGED;
-#endif
+#endif
if (ce->ce_uid != htonl(st->st_uid) ||
ce->ce_gid != htonl(st->st_gid))
return 0;
}
+static int compare_name(struct cache_entry *ce, const char *path, int namelen)
+{
+ return namelen != ce_namelen(ce) || memcmp(path, ce->name, namelen);
+}
+
+static int index_name_pos_also_unmerged(struct index_state *istate,
+ const char *path, int namelen)
+{
+ int pos = index_name_pos(istate, path, namelen);
+ struct cache_entry *ce;
+
+ if (pos >= 0)
+ return pos;
+
+ /* maybe unmerged? */
+ pos = -1 - pos;
+ if (pos >= istate->cache_nr ||
+ compare_name((ce = istate->cache[pos]), path, namelen))
+ return -1;
+
+ /* order of preference: stage 2, 1, 3 */
+ if (ce_stage(ce) == 1 && pos + 1 < istate->cache_nr &&
+ ce_stage((ce = istate->cache[pos + 1])) == 2 &&
+ !compare_name(ce, path, namelen))
+ pos++;
+ return pos;
+}
+
int add_file_to_index(struct index_state *istate, const char *path, int verbose)
{
- int size, namelen;
+ int size, namelen, pos;
struct stat st;
struct cache_entry *ce;
* from it, otherwise assume unexecutable regular file.
*/
struct cache_entry *ent;
- int pos = index_name_pos(istate, path, namelen);
+ int pos = index_name_pos_also_unmerged(istate, path, namelen);
ent = (0 <= pos) ? istate->cache[pos] : NULL;
ce->ce_mode = ce_mode_from_stat(ent, st.st_mode);
}
+ pos = index_name_pos(istate, ce->name, namelen);
+ if (0 <= pos &&
+ !ce_stage(istate->cache[pos]) &&
+ !ie_modified(istate, istate->cache[pos], &st, 1)) {
+ /* Nothing changed, really */
+ free(ce);
+ return 0;
+ }
+
if (index_path(ce->sha1, path, &st, 1))
die("unable to index file %s", path);
if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
* is being added, or we already have path and path/file is being
* added. Either one would result in a nonsense tree that has path
* twice when git-write-tree tries to write it out. Prevent it.
- *
+ *
* If ok-to-replace is specified, we remove the conflicting entries
* from the cache so the caller should recompute the insert position.
* When this happens, we return non-zero.
write_buffer_len = buffered;
len -= partial;
data = (char *) data + partial;
- }
- return 0;
+ }
+ return 0;
}
static int write_index_ext_header(SHA_CTX *context, int fd,
* size to zero here, then the object name recorded
* in index is the 6-byte file but the cached stat information
* becomes zero --- which would then match what we would
- * obtain from the filesystem next time we stat("frotz").
+ * obtain from the filesystem next time we stat("frotz").
*
* However, the second update-index, before calling
* this function, notices that the cached size is 6
return NULL; /* good */
}
else {
- lock = lock_any_ref_for_update(name, old_sha1);
+ lock = lock_any_ref_for_update(name, old_sha1, 0);
if (!lock) {
error("failed to lock %s", name);
return "failed to lock";
*info = xcalloc(sizeof(struct reflog_walk_info), 1);
}
-void add_reflog_for_walk(struct reflog_walk_info *info,
+int add_reflog_for_walk(struct reflog_walk_info *info,
struct commit *commit, const char *name)
{
unsigned long timestamp = 0;
}
}
if (!reflogs || reflogs->nr == 0)
- die("No reflogs found for '%s'", branch);
+ return -1;
path_list_insert(branch, &info->complete_reflogs)->util
= reflogs;
}
if (commit_reflog->recno < 0) {
free(branch);
free(commit_reflog);
- return;
+ return -1;
}
} else
commit_reflog->recno = reflogs->nr - recno - 1;
commit_reflog->reflogs = reflogs;
add_commit_info(commit, commit_reflog, &info->reflogs);
+ return 0;
}
void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
#define REFLOG_WALK_H
extern void init_reflog_walk(struct reflog_walk_info** info);
-extern void add_reflog_for_walk(struct reflog_walk_info *info,
+extern int add_reflog_for_walk(struct reflog_walk_info *info,
struct commit *commit, const char *name);
extern void fake_reflog_parent(struct reflog_walk_info *info,
struct commit *commit);
* Future: need to be in "struct repository"
* when doing a full libification.
*/
-struct cached_refs {
+static struct cached_refs {
char did_loose;
char did_packed;
struct ref_list *loose;
static inline int bad_ref_char(int ch)
{
- return (((unsigned) ch) <= ' ' ||
- ch == '~' || ch == '^' || ch == ':' ||
- /* 2.13 Pattern Matching Notation */
- ch == '?' || ch == '*' || ch == '[');
+ if (((unsigned) ch) <= ' ' ||
+ ch == '~' || ch == '^' || ch == ':')
+ return 1;
+ /* 2.13 Pattern Matching Notation */
+ if (ch == '?' || ch == '[') /* Unsupported */
+ return 1;
+ if (ch == '*') /* Supported at the end */
+ return 2;
+ return 0;
}
int check_ref_format(const char *ref)
{
- int ch, level;
+ int ch, level, bad_type;
const char *cp = ref;
level = 0;
return -1; /* should not end with slashes */
/* we are at the beginning of the path component */
- if (ch == '.' || bad_ref_char(ch))
+ if (ch == '.')
return -1;
+ bad_type = bad_ref_char(ch);
+ if (bad_type) {
+ return (bad_type == 2 && !*cp) ? -3 : -1;
+ }
/* scan the rest of the path component */
while ((ch = *cp++) != 0) {
- if (bad_ref_char(ch))
- return -1;
+ bad_type = bad_ref_char(ch);
+ if (bad_type) {
+ return (bad_type == 2 && !*cp) ? -3 : -1;
+ }
if (ch == '/')
break;
if (ch == '.' && *cp == '.')
return 1;
}
-static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag)
+static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int flags, int *type_p)
{
char *ref_file;
const char *orig_ref = ref;
struct ref_lock *lock;
struct stat st;
int last_errno = 0;
+ int type;
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
- ref = resolve_ref(ref, lock->old_sha1, mustexist, flag);
+ ref = resolve_ref(ref, lock->old_sha1, mustexist, &type);
if (!ref && errno == EISDIR) {
/* we are trying to lock foo but we used to
* have foo/bar which now does not exist;
error("there are still refs under '%s'", orig_ref);
goto error_return;
}
- ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag);
+ ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, &type);
}
+ if (type_p)
+ *type_p = type;
if (!ref) {
last_errno = errno;
error("unable to resolve reference %s: %s",
lock->lk = xcalloc(1, sizeof(struct lock_file));
+ if (flags & REF_NODEREF)
+ ref = orig_ref;
lock->ref_name = xstrdup(ref);
lock->orig_ref_name = xstrdup(orig_ref);
ref_file = git_path("%s", ref);
- lock->force_write = lstat(ref_file, &st) && errno == ENOENT;
+ if (lstat(ref_file, &st) && errno == ENOENT)
+ lock->force_write = 1;
+ if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
+ lock->force_write = 1;
if (safe_create_leading_directories(ref_file)) {
last_errno = errno;
if (check_ref_format(ref))
return NULL;
strcpy(refpath, mkpath("refs/%s", ref));
- return lock_ref_sha1_basic(refpath, old_sha1, NULL);
+ return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
}
-struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1)
+struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags)
{
if (check_ref_format(ref) == -1)
return NULL;
- return lock_ref_sha1_basic(ref, old_sha1, NULL);
+ return lock_ref_sha1_basic(ref, old_sha1, flags, NULL);
}
static struct lock_file packlock;
struct ref_lock *lock;
int err, i, ret = 0, flag = 0;
- lock = lock_ref_sha1_basic(refname, sha1, &flag);
+ lock = lock_ref_sha1_basic(refname, sha1, 0, &flag);
if (!lock)
return 1;
if (!(flag & REF_ISPACKED)) {
if (!is_refname_available(newref, oldref, get_loose_refs(), 0))
return 1;
- lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL);
+ lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL);
if (!lock)
return error("unable to lock %s", renamed_ref);
lock->force_write = 1;
}
logmoved = log;
- lock = lock_ref_sha1_basic(newref, NULL, NULL);
+ lock = lock_ref_sha1_basic(newref, NULL, 0, NULL);
if (!lock) {
error("unable to lock %s for update", newref);
goto rollback;
return 0;
rollback:
- lock = lock_ref_sha1_basic(oldref, NULL, NULL);
+ lock = lock_ref_sha1_basic(oldref, NULL, 0, NULL);
if (!lock) {
error("unable to lock %s for rollback", oldref);
goto rollbacklog;
free(lock);
}
+/*
+ * copy the reflog message msg to buf, which has been allocated sufficiently
+ * large, while cleaning up the whitespaces. Especially, convert LF to space,
+ * because reflog file is one line per entry.
+ */
+static int copy_msg(char *buf, const char *msg)
+{
+ char *cp = buf;
+ char c;
+ int wasspace = 1;
+
+ *cp++ = '\t';
+ while ((c = *msg++)) {
+ if (wasspace && isspace(c))
+ continue;
+ wasspace = isspace(c);
+ if (wasspace)
+ c = ' ';
+ *cp++ = c;
+ }
+ while (buf < cp && isspace(cp[-1]))
+ cp--;
+ *cp++ = '\n';
+ return cp - buf;
+}
+
static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
const unsigned char *new_sha1, const char *msg)
{
adjust_shared_perm(log_file);
- msglen = 0;
- if (msg) {
- /* clean up the message and make sure it is a single line */
- for ( ; *msg; msg++)
- if (!isspace(*msg))
- break;
- if (*msg) {
- const char *ep = strchr(msg, '\n');
- if (ep)
- msglen = ep - msg;
- else
- msglen = strlen(msg);
- }
- }
-
+ msglen = msg ? strlen(msg) : 0;
committer = git_committer_info(-1);
maxlen = strlen(committer) + msglen + 100;
logrec = xmalloc(maxlen);
sha1_to_hex(new_sha1),
committer);
if (msglen)
- len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1;
+ len += copy_msg(logrec + len - 1, msg) - 1;
written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
free(logrec);
- close(logfd);
- if (written != len)
+ if (close(logfd) != 0 || written != len)
return error("Unable to append to %s", log_file);
return 0;
}
goto error_free_return;
}
written = write_in_full(fd, ref, len);
- close(fd);
- if (written != len) {
+ if (close(fd) != 0 || written != len) {
error("Unable to write to %s", lockpath);
goto error_unlink_return;
}
if (hashcmp(logged_sha1, sha1)) {
fprintf(stderr,
"warning: Log %s has gap after %s.\n",
- logfile, show_rfc2822_date(date, tz));
+ logfile, show_date(date, tz, DATE_RFC2822));
}
}
else if (date == at_time) {
if (hashcmp(logged_sha1, sha1)) {
fprintf(stderr,
"warning: Log %s unexpectedly ended on %s.\n",
- logfile, show_rfc2822_date(date, tz));
+ logfile, show_date(date, tz, DATE_RFC2822));
}
}
munmap(log_mapped, mapsz);
extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
/** Locks any ref (for 'HEAD' type refs). */
-extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1);
+#define REF_NODEREF 0x01
+extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags);
/** Release any lock taken but not written. **/
extern void unlock_ref(struct ref_lock *lock);
--- /dev/null
+#include "cache.h"
+#include "remote.h"
+#include "refs.h"
+
+static struct remote **remotes;
+static int allocated_remotes;
+
+#define BUF_SIZE (2048)
+static char buffer[BUF_SIZE];
+
+static void add_push_refspec(struct remote *remote, const char *ref)
+{
+ int nr = remote->push_refspec_nr + 1;
+ remote->push_refspec =
+ xrealloc(remote->push_refspec, nr * sizeof(char *));
+ remote->push_refspec[nr-1] = ref;
+ remote->push_refspec_nr = nr;
+}
+
+static void add_fetch_refspec(struct remote *remote, const char *ref)
+{
+ int nr = remote->fetch_refspec_nr + 1;
+ remote->fetch_refspec =
+ xrealloc(remote->fetch_refspec, nr * sizeof(char *));
+ remote->fetch_refspec[nr-1] = ref;
+ remote->fetch_refspec_nr = nr;
+}
+
+static void add_uri(struct remote *remote, const char *uri)
+{
+ int nr = remote->uri_nr + 1;
+ remote->uri =
+ xrealloc(remote->uri, nr * sizeof(char *));
+ remote->uri[nr-1] = uri;
+ remote->uri_nr = nr;
+}
+
+static struct remote *make_remote(const char *name, int len)
+{
+ int i, empty = -1;
+
+ for (i = 0; i < allocated_remotes; i++) {
+ if (!remotes[i]) {
+ if (empty < 0)
+ empty = i;
+ } else {
+ if (len ? (!strncmp(name, remotes[i]->name, len) &&
+ !remotes[i]->name[len]) :
+ !strcmp(name, remotes[i]->name))
+ return remotes[i];
+ }
+ }
+
+ if (empty < 0) {
+ empty = allocated_remotes;
+ allocated_remotes += allocated_remotes ? allocated_remotes : 1;
+ remotes = xrealloc(remotes,
+ sizeof(*remotes) * allocated_remotes);
+ memset(remotes + empty, 0,
+ (allocated_remotes - empty) * sizeof(*remotes));
+ }
+ remotes[empty] = xcalloc(1, sizeof(struct remote));
+ if (len)
+ remotes[empty]->name = xstrndup(name, len);
+ else
+ remotes[empty]->name = xstrdup(name);
+ return remotes[empty];
+}
+
+static void read_remotes_file(struct remote *remote)
+{
+ FILE *f = fopen(git_path("remotes/%s", remote->name), "r");
+
+ if (!f)
+ return;
+ while (fgets(buffer, BUF_SIZE, f)) {
+ int value_list;
+ char *s, *p;
+
+ if (!prefixcmp(buffer, "URL:")) {
+ value_list = 0;
+ s = buffer + 4;
+ } else if (!prefixcmp(buffer, "Push:")) {
+ value_list = 1;
+ s = buffer + 5;
+ } else if (!prefixcmp(buffer, "Pull:")) {
+ value_list = 2;
+ s = buffer + 5;
+ } else
+ continue;
+
+ while (isspace(*s))
+ s++;
+ if (!*s)
+ continue;
+
+ p = s + strlen(s);
+ while (isspace(p[-1]))
+ *--p = 0;
+
+ switch (value_list) {
+ case 0:
+ add_uri(remote, xstrdup(s));
+ break;
+ case 1:
+ add_push_refspec(remote, xstrdup(s));
+ break;
+ case 2:
+ add_fetch_refspec(remote, xstrdup(s));
+ break;
+ }
+ }
+ fclose(f);
+}
+
+static void read_branches_file(struct remote *remote)
+{
+ const char *slash = strchr(remote->name, '/');
+ int n = slash ? slash - remote->name : 1000;
+ FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r");
+ char *s, *p;
+ int len;
+
+ if (!f)
+ return;
+ s = fgets(buffer, BUF_SIZE, f);
+ fclose(f);
+ if (!s)
+ return;
+ while (isspace(*s))
+ s++;
+ if (!*s)
+ return;
+ p = s + strlen(s);
+ 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);
+ add_uri(remote, p);
+}
+
+static char *default_remote_name = NULL;
+static const char *current_branch = NULL;
+static int current_branch_len = 0;
+
+static int handle_config(const char *key, const char *value)
+{
+ const char *name;
+ const char *subkey;
+ struct remote *remote;
+ if (!prefixcmp(key, "branch.") && current_branch &&
+ !strncmp(key + 7, current_branch, current_branch_len) &&
+ !strcmp(key + 7 + current_branch_len, ".remote")) {
+ free(default_remote_name);
+ default_remote_name = xstrdup(value);
+ }
+ if (prefixcmp(key, "remote."))
+ return 0;
+ name = key + 7;
+ subkey = strrchr(name, '.');
+ if (!subkey)
+ return error("Config with no key for remote %s", name);
+ if (*subkey == '/') {
+ warning("Config remote shorthand cannot begin with '/': %s", name);
+ return 0;
+ }
+ remote = make_remote(name, subkey - name);
+ if (!value) {
+ /* if we ever have a boolean variable, e.g. "remote.*.disabled"
+ * [remote "frotz"]
+ * disabled
+ * is a valid way to set it to true; we get NULL in value so
+ * we need to handle it here.
+ *
+ * if (!strcmp(subkey, ".disabled")) {
+ * val = git_config_bool(key, value);
+ * return 0;
+ * } else
+ *
+ */
+ return 0; /* ignore unknown booleans */
+ }
+ if (!strcmp(subkey, ".url")) {
+ add_uri(remote, xstrdup(value));
+ } else if (!strcmp(subkey, ".push")) {
+ add_push_refspec(remote, xstrdup(value));
+ } else if (!strcmp(subkey, ".fetch")) {
+ add_fetch_refspec(remote, xstrdup(value));
+ } else if (!strcmp(subkey, ".receivepack")) {
+ if (!remote->receivepack)
+ remote->receivepack = xstrdup(value);
+ else
+ error("more than one receivepack given, using the first");
+ }
+ return 0;
+}
+
+static void read_config(void)
+{
+ unsigned char sha1[20];
+ const char *head_ref;
+ int flag;
+ if (default_remote_name) // did this already
+ return;
+ default_remote_name = xstrdup("origin");
+ current_branch = NULL;
+ head_ref = resolve_ref("HEAD", sha1, 0, &flag);
+ if (head_ref && (flag & REF_ISSYMREF) &&
+ !prefixcmp(head_ref, "refs/heads/")) {
+ current_branch = head_ref + strlen("refs/heads/");
+ current_branch_len = strlen(current_branch);
+ }
+ git_config(handle_config);
+}
+
+static struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
+{
+ int i;
+ struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec);
+ for (i = 0; i < nr_refspec; i++) {
+ const char *sp, *ep, *gp;
+ sp = refspec[i];
+ if (*sp == '+') {
+ rs[i].force = 1;
+ sp++;
+ }
+ gp = strchr(sp, '*');
+ ep = strchr(sp, ':');
+ if (gp && ep && gp > ep)
+ gp = NULL;
+ if (ep) {
+ if (ep[1]) {
+ const char *glob = strchr(ep + 1, '*');
+ if (!glob)
+ gp = NULL;
+ if (gp)
+ rs[i].dst = xstrndup(ep + 1,
+ glob - ep - 1);
+ else
+ rs[i].dst = xstrdup(ep + 1);
+ }
+ } else {
+ ep = sp + strlen(sp);
+ }
+ if (gp) {
+ rs[i].pattern = 1;
+ ep = gp;
+ }
+ rs[i].src = xstrndup(sp, ep - sp);
+ }
+ return rs;
+}
+
+struct remote *remote_get(const char *name)
+{
+ struct remote *ret;
+
+ read_config();
+ if (!name)
+ name = default_remote_name;
+ ret = make_remote(name, 0);
+ if (name[0] != '/') {
+ if (!ret->uri)
+ read_remotes_file(ret);
+ if (!ret->uri)
+ read_branches_file(ret);
+ }
+ if (!ret->uri)
+ add_uri(ret, name);
+ if (!ret->uri)
+ return NULL;
+ ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec);
+ ret->push = parse_ref_spec(ret->push_refspec_nr, ret->push_refspec);
+ return ret;
+}
+
+int for_each_remote(each_remote_fn fn, void *priv)
+{
+ int i, result = 0;
+ read_config();
+ for (i = 0; i < allocated_remotes && !result; i++) {
+ struct remote *r = remotes[i];
+ if (!r)
+ continue;
+ if (!r->fetch)
+ r->fetch = parse_ref_spec(r->fetch_refspec_nr,
+ r->fetch_refspec);
+ if (!r->push)
+ r->push = parse_ref_spec(r->push_refspec_nr,
+ r->push_refspec);
+ result = fn(r, priv);
+ }
+ return result;
+}
+
+int remote_has_uri(struct remote *remote, const char *uri)
+{
+ int i;
+ for (i = 0; i < remote->uri_nr; i++) {
+ if (!strcmp(remote->uri[i], uri))
+ return 1;
+ }
+ return 0;
+}
+
+int remote_find_tracking(struct remote *remote, struct refspec *refspec)
+{
+ int find_src = refspec->src == NULL;
+ char *needle, **result;
+ int i;
+
+ if (find_src) {
+ if (refspec->dst == NULL)
+ return error("find_tracking: need either src or dst");
+ needle = refspec->dst;
+ result = &refspec->src;
+ } else {
+ needle = refspec->src;
+ result = &refspec->dst;
+ }
+
+ for (i = 0; i < remote->fetch_refspec_nr; i++) {
+ struct refspec *fetch = &remote->fetch[i];
+ const char *key = find_src ? fetch->dst : fetch->src;
+ const char *value = find_src ? fetch->src : fetch->dst;
+ if (!fetch->dst)
+ continue;
+ if (fetch->pattern) {
+ if (!prefixcmp(needle, key)) {
+ *result = xmalloc(strlen(value) +
+ strlen(needle) -
+ strlen(key) + 1);
+ strcpy(*result, value);
+ strcpy(*result + strlen(value),
+ needle + strlen(key));
+ refspec->force = fetch->force;
+ return 0;
+ }
+ } else if (!strcmp(needle, key)) {
+ *result = xstrdup(value);
+ refspec->force = fetch->force;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+struct ref *alloc_ref(unsigned namelen)
+{
+ struct ref *ret = xmalloc(sizeof(struct ref) + namelen);
+ memset(ret, 0, sizeof(struct ref) + namelen);
+ return ret;
+}
+
+void free_refs(struct ref *ref)
+{
+ struct ref *next;
+ while (ref) {
+ next = ref->next;
+ if (ref->peer_ref)
+ free(ref->peer_ref);
+ free(ref);
+ ref = next;
+ }
+}
+
+static int count_refspec_match(const char *pattern,
+ struct ref *refs,
+ struct ref **matched_ref)
+{
+ int patlen = strlen(pattern);
+ struct ref *matched_weak = NULL;
+ struct ref *matched = NULL;
+ int weak_match = 0;
+ int match = 0;
+
+ for (weak_match = match = 0; refs; refs = refs->next) {
+ char *name = refs->name;
+ int namelen = strlen(name);
+
+ if (namelen < patlen ||
+ memcmp(name + namelen - patlen, pattern, patlen))
+ continue;
+ if (namelen != patlen && name[namelen - patlen - 1] != '/')
+ continue;
+
+ /* A match is "weak" if it is with refs outside
+ * heads or tags, and did not specify the pattern
+ * in full (e.g. "refs/remotes/origin/master") or at
+ * least from the toplevel (e.g. "remotes/origin/master");
+ * otherwise "git push $URL master" would result in
+ * ambiguity between remotes/origin/master and heads/master
+ * at the remote site.
+ */
+ if (namelen != patlen &&
+ patlen != namelen - 5 &&
+ prefixcmp(name, "refs/heads/") &&
+ prefixcmp(name, "refs/tags/")) {
+ /* We want to catch the case where only weak
+ * matches are found and there are multiple
+ * matches, and where more than one strong
+ * matches are found, as ambiguous. One
+ * strong match with zero or more weak matches
+ * are acceptable as a unique match.
+ */
+ matched_weak = refs;
+ weak_match++;
+ }
+ else {
+ matched = refs;
+ match++;
+ }
+ }
+ if (!matched) {
+ *matched_ref = matched_weak;
+ return weak_match;
+ }
+ else {
+ *matched_ref = matched;
+ return match;
+ }
+}
+
+static void tail_link_ref(struct ref *ref, struct ref ***tail)
+{
+ **tail = ref;
+ while (ref->next)
+ ref = ref->next;
+ *tail = &ref->next;
+}
+
+static struct ref *try_explicit_object_name(const char *name)
+{
+ unsigned char sha1[20];
+ struct ref *ref;
+ int len;
+
+ if (!*name) {
+ ref = alloc_ref(20);
+ strcpy(ref->name, "(delete)");
+ hashclr(ref->new_sha1);
+ return ref;
+ }
+ if (get_sha1(name, sha1))
+ return NULL;
+ len = strlen(name) + 1;
+ ref = alloc_ref(len);
+ memcpy(ref->name, name, len);
+ hashcpy(ref->new_sha1, sha1);
+ return ref;
+}
+
+static struct ref *make_linked_ref(const char *name, struct ref ***tail)
+{
+ struct ref *ret;
+ size_t len;
+
+ len = strlen(name) + 1;
+ ret = alloc_ref(len);
+ memcpy(ret->name, name, len);
+ tail_link_ref(ret, tail);
+ return ret;
+}
+
+static int match_explicit(struct ref *src, struct ref *dst,
+ struct ref ***dst_tail,
+ struct refspec *rs,
+ int errs)
+{
+ struct ref *matched_src, *matched_dst;
+
+ const char *dst_value = rs->dst;
+
+ if (rs->pattern)
+ return errs;
+
+ matched_src = matched_dst = NULL;
+ switch (count_refspec_match(rs->src, src, &matched_src)) {
+ case 1:
+ break;
+ case 0:
+ /* The source could be in the get_sha1() format
+ * not a reference name. :refs/other is a
+ * way to delete 'other' ref at the remote end.
+ */
+ matched_src = try_explicit_object_name(rs->src);
+ if (matched_src)
+ break;
+ error("src refspec %s does not match any.",
+ rs->src);
+ break;
+ default:
+ matched_src = NULL;
+ error("src refspec %s matches more than one.",
+ rs->src);
+ break;
+ }
+
+ if (!matched_src)
+ errs = 1;
+
+ if (dst_value == NULL)
+ dst_value = matched_src->name;
+
+ switch (count_refspec_match(dst_value, dst, &matched_dst)) {
+ case 1:
+ break;
+ case 0:
+ if (!memcmp(dst_value, "refs/", 5))
+ matched_dst = make_linked_ref(dst_value, dst_tail);
+ else
+ error("dst refspec %s does not match any "
+ "existing ref on the remote and does "
+ "not start with refs/.", dst_value);
+ break;
+ default:
+ matched_dst = NULL;
+ error("dst refspec %s matches more than one.",
+ dst_value);
+ break;
+ }
+ if (errs || matched_dst == NULL)
+ return 1;
+ if (matched_dst->peer_ref) {
+ errs = 1;
+ error("dst ref %s receives from more than one src.",
+ matched_dst->name);
+ }
+ else {
+ matched_dst->peer_ref = matched_src;
+ matched_dst->force = rs->force;
+ }
+ return errs;
+}
+
+static int match_explicit_refs(struct ref *src, struct ref *dst,
+ struct ref ***dst_tail, struct refspec *rs,
+ int rs_nr)
+{
+ int i, errs;
+ for (i = errs = 0; i < rs_nr; i++)
+ errs |= match_explicit(src, dst, dst_tail, &rs[i], errs);
+ return -errs;
+}
+
+static struct ref *find_ref_by_name(struct ref *list, const char *name)
+{
+ for ( ; list; list = list->next)
+ if (!strcmp(list->name, name))
+ return list;
+ return NULL;
+}
+
+static const struct refspec *check_pattern_match(const struct refspec *rs,
+ int rs_nr,
+ const struct ref *src)
+{
+ int i;
+ for (i = 0; i < rs_nr; i++) {
+ if (rs[i].pattern && !prefixcmp(src->name, rs[i].src))
+ return rs + i;
+ }
+ return NULL;
+}
+
+/*
+ * Note. This is used only by "push"; refspec matching rules for
+ * push and fetch are subtly different, so do not try to reuse it
+ * without thinking.
+ */
+int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+ int nr_refspec, char **refspec, int all)
+{
+ struct refspec *rs =
+ parse_ref_spec(nr_refspec, (const char **) refspec);
+
+ if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
+ return -1;
+
+ /* pick the remainder */
+ for ( ; src; src = src->next) {
+ struct ref *dst_peer;
+ const struct refspec *pat = NULL;
+ char *dst_name;
+ if (src->peer_ref)
+ continue;
+ if (nr_refspec) {
+ pat = check_pattern_match(rs, nr_refspec, src);
+ if (!pat)
+ continue;
+ }
+ else if (prefixcmp(src->name, "refs/heads/"))
+ /*
+ * "matching refs"; traditionally we pushed everything
+ * including refs outside refs/heads/ hierarchy, but
+ * that does not make much sense these days.
+ */
+ continue;
+
+ if (pat) {
+ const char *dst_side = pat->dst ? pat->dst : pat->src;
+ dst_name = xmalloc(strlen(dst_side) +
+ strlen(src->name) -
+ strlen(pat->src) + 2);
+ strcpy(dst_name, dst_side);
+ strcat(dst_name, src->name + strlen(pat->src));
+ } else
+ dst_name = xstrdup(src->name);
+ dst_peer = find_ref_by_name(dst, dst_name);
+ if (dst_peer && dst_peer->peer_ref)
+ /* We're already sending something to this ref. */
+ goto free_name;
+ if (!dst_peer && !nr_refspec && !all)
+ /* Remote doesn't have it, and we have no
+ * explicit pattern, and we don't have
+ * --all. */
+ goto free_name;
+ if (!dst_peer) {
+ /* Create a new one and link it */
+ dst_peer = make_linked_ref(dst_name, dst_tail);
+ hashcpy(dst_peer->new_sha1, src->new_sha1);
+ }
+ dst_peer->peer_ref = src;
+ free_name:
+ free(dst_name);
+ }
+ return 0;
+}
--- /dev/null
+#ifndef REMOTE_H
+#define REMOTE_H
+
+struct remote {
+ const char *name;
+
+ const char **uri;
+ int uri_nr;
+
+ const char **push_refspec;
+ struct refspec *push;
+ int push_refspec_nr;
+
+ const char **fetch_refspec;
+ struct refspec *fetch;
+ int fetch_refspec_nr;
+
+ const char *receivepack;
+};
+
+struct remote *remote_get(const char *name);
+
+typedef int each_remote_fn(struct remote *remote, void *priv);
+int for_each_remote(each_remote_fn fn, void *priv);
+
+int remote_has_uri(struct remote *remote, const char *uri);
+
+struct refspec {
+ unsigned force : 1;
+ unsigned pattern : 1;
+
+ char *src;
+ char *dst;
+};
+
+struct ref *alloc_ref(unsigned namelen);
+
+/*
+ * Frees the entire list and peers of elements.
+ */
+void free_refs(struct ref *ref);
+
+int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+ int nr_refspec, char **refspec, int all);
+
+/*
+ * For the given remote, reads the refspec's src and sets the other fields.
+ */
+int remote_find_tracking(struct remote *remote, struct refspec *refspec);
+
+#endif
}
}
-void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
-{
- add_pending_object_with_mode(revs, obj, name, S_IFINVALID);
-}
-
-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 (revs->no_walk && (obj->flags & UNINTERESTING))
die("object ranges do not make sense when not walking revisions");
+ if (revs->reflog_info && obj->type == OBJ_COMMIT &&
+ add_reflog_for_walk(revs->reflog_info,
+ (struct commit *)obj, name))
+ return;
add_object_array_with_mode(obj, name, &revs->pending, mode);
- if (revs->reflog_info && obj->type == OBJ_COMMIT)
- add_reflog_for_walk(revs->reflog_info,
- (struct commit *)obj, 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);
}
static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
options->has_changes = 1;
}
-int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
+static int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
{
if (!t1)
return REV_TREE_NEW;
return tree_difference;
}
-int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
+static int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
{
int retval;
void *tree;
return 0;
}
-static void cherry_pick_list(struct commit_list *list)
+static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
{
struct commit_list *p;
int left_count = 0, right_count = 0;
left_first = left_count < right_count;
init_patch_ids(&ids);
+ if (revs->diffopt.nr_paths) {
+ ids.diffopts.nr_paths = revs->diffopt.nr_paths;
+ ids.diffopts.paths = revs->diffopt.paths;
+ ids.diffopts.pathlens = revs->diffopt.pathlens;
+ }
/* Compute patch-ids for one side */
for (p = list; p; p = p->next) {
p = &commit_list_insert(commit, p)->next;
}
if (revs->cherry_pick)
- cherry_pick_list(newlist);
+ cherry_pick_list(newlist, revs);
revs->commits = newlist;
return 0;
revs->min_age = -1;
revs->skip_count = -1;
revs->max_count = -1;
- revs->subject_prefix = "PATCH";
revs->prune_fn = NULL;
revs->prune_data = NULL;
const char **unrecognized = argv + 1;
int left = 1;
int all_match = 0;
+ int regflags = 0;
/* First, search for "--" */
seen_dashdash = 0;
if (!strncmp(arg, "--date=", 7)) {
if (!strcmp(arg + 7, "relative"))
revs->date_mode = DATE_RELATIVE;
+ else if (!strcmp(arg + 7, "iso8601") ||
+ !strcmp(arg + 7, "iso"))
+ revs->date_mode = DATE_ISO8601;
+ else if (!strcmp(arg + 7, "rfc2822") ||
+ !strcmp(arg + 7, "rfc"))
+ revs->date_mode = DATE_RFC2822;
+ else if (!strcmp(arg + 7, "short"))
+ revs->date_mode = DATE_SHORT;
else if (!strcmp(arg + 7, "local"))
revs->date_mode = DATE_LOCAL;
else if (!strcmp(arg + 7, "default"))
add_message_grep(revs, arg+7);
continue;
}
+ if (!strcmp(arg, "--extended-regexp") ||
+ !strcmp(arg, "-E")) {
+ regflags |= REG_EXTENDED;
+ continue;
+ }
+ if (!strcmp(arg, "--regexp-ignore-case") ||
+ !strcmp(arg, "-i")) {
+ regflags |= REG_ICASE;
+ continue;
+ }
if (!strcmp(arg, "--all-match")) {
all_match = 1;
continue;
revs->reverse ^= 1;
continue;
}
+ if (!strcmp(arg, "--no-walk")) {
+ revs->no_walk = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--do-walk")) {
+ revs->no_walk = 0;
+ continue;
+ }
opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
if (opts > 0) {
}
}
+ if (revs->grep_filter)
+ revs->grep_filter->regflags |= regflags;
+
if (show_merge)
prepare_show_merge(revs);
if (def && !revs->pending.nr) {
if (revs->prune_data) {
diff_tree_setup_paths(revs->prune_data, &revs->pruning);
- revs->prune_fn = try_to_simplify_commit;
+ /* Can't prune commits with rename following: the paths change.. */
+ if (!revs->diffopt.follow_renames)
+ revs->prune_fn = try_to_simplify_commit;
if (!revs->full_diff)
diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
}
}
}
+static void remove_duplicate_parents(struct commit *commit)
+{
+ struct commit_list **pp, *p;
+
+ /* Examine existing parents while marking ones we have seen... */
+ pp = &commit->parents;
+ while ((p = *pp) != NULL) {
+ struct commit *parent = p->item;
+ if (parent->object.flags & TMP_MARK) {
+ *pp = p->next;
+ continue;
+ }
+ parent->object.flags |= TMP_MARK;
+ pp = &p->next;
+ }
+ /* ... and clear the temporary mark */
+ for (p = commit->parents; p; p = p->next)
+ p->item->object.flags &= ~TMP_MARK;
+}
+
static int rewrite_parents(struct rev_info *revs, struct commit *commit)
{
struct commit_list **pp = &commit->parents;
}
pp = &parent->next;
}
+ remove_duplicate_parents(commit);
return 0;
}
#define REV_TREE_DIFFERENT 2
/* revision.c */
-extern int rev_same_tree_as_empty(struct rev_info *, struct tree *t1);
-extern int rev_compare_tree(struct rev_info *, struct tree *t1, struct tree *t2);
extern void init_revisions(struct rev_info *revs, const char *prefix);
extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
const char *name);
extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
-extern void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode);
#endif
#ifndef RSH_H
#define RSH_H
-int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
+int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
char *url, int rmt_argc, char **rmt_argv);
#endif
close(cmd->out);
}
+ if (cmd->dir && chdir(cmd->dir))
+ die("exec %s: cd to %s failed (%s)", cmd->argv[0],
+ cmd->dir, strerror(errno));
+ if (cmd->env) {
+ for (; *cmd->env; cmd->env++) {
+ if (strchr(*cmd->env, '='))
+ putenv((char*)*cmd->env);
+ else
+ unsetenv(*cmd->env);
+ }
+ }
if (cmd->git_cmd) {
execv_git_cmd(cmd->argv);
} else {
return finish_command(cmd);
}
+static void prepare_run_command_v_opt(struct child_process *cmd,
+ const char **argv,
+ int opt)
+{
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->argv = argv;
+ cmd->no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;
+ cmd->git_cmd = opt & RUN_GIT_CMD ? 1 : 0;
+ cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
+}
+
int run_command_v_opt(const char **argv, int opt)
{
struct child_process cmd;
- memset(&cmd, 0, sizeof(cmd));
- cmd.argv = argv;
- cmd.no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;
- cmd.git_cmd = opt & RUN_GIT_CMD ? 1 : 0;
- cmd.stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
+ prepare_run_command_v_opt(&cmd, argv, opt);
+ return run_command(&cmd);
+}
+
+int run_command_v_opt_cd(const char **argv, int opt, const char *dir)
+{
+ struct child_process cmd;
+ prepare_run_command_v_opt(&cmd, argv, opt);
+ cmd.dir = dir;
+ return run_command(&cmd);
+}
+
+int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)
+{
+ struct child_process cmd;
+ prepare_run_command_v_opt(&cmd, argv, opt);
+ cmd.dir = dir;
+ cmd.env = env;
return run_command(&cmd);
}
pid_t pid;
int in;
int out;
+ const char *dir;
+ const char *const *env;
unsigned close_in:1;
unsigned close_out:1;
unsigned no_stdin:1;
#define RUN_GIT_CMD 2 /*If this is to be git sub-command */
#define RUN_COMMAND_STDOUT_TO_STDERR 4
int run_command_v_opt(const char **argv, int opt);
+int run_command_v_opt_cd(const char **argv, int opt, const char *dir);
+
+/*
+ * env (the environment) is to be formatted like environ: "VAR=VALUE".
+ * To unset an environment variable use just "VAR".
+ */
+int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);
#endif
#include "refs.h"
#include "pkt-line.h"
#include "run-command.h"
+#include "remote.h"
static const char send_pack_usage[] =
"git-send-pack [--all] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
return ret;
}
-static int send_pack(int in, int out, int nr_refspec, char **refspec)
+static int send_pack(int in, int out, struct remote *remote, int nr_refspec, char **refspec)
{
struct ref *ref;
int new_refs;
new_refs = 0;
for (ref = remote_refs; ref; ref = ref->next) {
char old_hex[60], *new_hex;
- int delete_ref;
+ int will_delete_ref;
if (!ref->peer_ref)
continue;
- delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
- if (delete_ref && !allow_deleting_refs) {
+
+ will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
+ if (will_delete_ref && !allow_deleting_refs) {
error("remote does not support deleting refs");
ret = -2;
continue;
}
- if (!delete_ref &&
+ if (!will_delete_ref &&
!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
if (verbose)
fprintf(stderr, "'%s': up-to-date\n", ref->name);
*/
if (!force_update &&
- !delete_ref &&
+ !will_delete_ref &&
!is_null_sha1(ref->old_sha1) &&
!ref->force) {
if (!has_sha1_file(ref->old_sha1) ||
}
}
hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
- if (!delete_ref)
+ if (!will_delete_ref)
new_refs++;
strcpy(old_hex, sha1_to_hex(ref->old_sha1));
new_hex = sha1_to_hex(ref->new_sha1);
else
packet_write(out, "%s %s %s",
old_hex, new_hex, ref->name);
- if (delete_ref)
+ if (will_delete_ref)
fprintf(stderr, "deleting '%s'\n", ref->name);
else {
fprintf(stderr, "updating '%s'", ref->name);
fprintf(stderr, "\n from %s\n to %s\n",
old_hex, new_hex);
}
+ if (remote) {
+ struct refspec rs;
+ rs.src = ref->name;
+ rs.dst = NULL;
+ if (!remote_find_tracking(remote, &rs)) {
+ struct ref_lock *lock;
+ fprintf(stderr, " Also local %s\n", rs.dst);
+ if (will_delete_ref) {
+ if (delete_ref(rs.dst, NULL)) {
+ error("Failed to delete");
+ }
+ } else {
+ lock = lock_any_ref_for_update(rs.dst, NULL, 0);
+ if (!lock)
+ error("Failed to lock");
+ else
+ write_ref_sha1(lock, ref->new_sha1,
+ "update by push");
+ }
+ free(rs.dst);
+ }
+ }
}
packet_flush(out);
case -2: /* ok but a single level -- that is fine for
* a match pattern.
*/
+ case -3: /* ok but ends with a pattern-match character */
continue;
}
die("remote part of refspec is not a valid name in %s",
char **heads = NULL;
int fd[2], ret;
pid_t pid;
+ char *remote_name = NULL;
+ struct remote *remote = NULL;
setup_git_directory();
git_config(git_default_config);
receivepack = arg + 7;
continue;
}
+ if (!prefixcmp(arg, "--remote=")) {
+ remote_name = arg + 9;
+ continue;
+ }
if (!strcmp(arg, "--all")) {
send_all = 1;
continue;
usage(send_pack_usage);
verify_remote_names(nr_heads, heads);
- pid = git_connect(fd, dest, receivepack);
+ if (remote_name) {
+ remote = remote_get(remote_name);
+ if (!remote_has_uri(remote, dest)) {
+ die("Destination %s is not a uri for %s",
+ dest, remote_name);
+ }
+ }
+
+ pid = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0);
if (pid < 0)
return 1;
- ret = send_pack(fd[0], fd[1], nr_heads, heads);
+ ret = send_pack(fd[0], fd[1], remote, nr_heads, heads);
close(fd[0]);
close(fd[1]);
ret |= finish_connect(pid);
return error("unable to update %s", path0);
for_each_ref(add_info_ref, NULL);
fclose(info_ref_fp);
+ adjust_shared_perm(path1);
rename(path1, path0);
free(path0);
free(path1);
return error("cannot open %s", name);
write_pack_info_file(fp);
fclose(fp);
+ adjust_shared_perm(name);
rename(name, infofile);
return 0;
}
#include "cache.h"
+#include "dir.h"
+
+static int inside_git_dir = -1;
+static int inside_work_tree = -1;
const char *prefix_path(const char *prefix, int len, const char *path)
{
if (len) {
int speclen = strlen(path);
char *n = xmalloc(speclen + len + 1);
-
+
memcpy(n, prefix, len);
memcpy(n + len, path, speclen+1);
path = n;
return path;
}
-/*
+/*
* Unlike prefix_path, this should be used if the named file does
* not have to interact with index entry; i.e. name of a random file
* on the filesystem.
const char *name;
struct stat st;
- if (is_inside_git_dir())
+ if (!is_inside_work_tree() || is_inside_git_dir())
return;
if (*arg == '-')
return; /* flag */
return 1;
}
-static int inside_git_dir = -1;
-
int is_inside_git_dir(void)
{
- if (inside_git_dir < 0) {
- char buffer[1024];
-
- if (is_bare_repository())
- return (inside_git_dir = 1);
- if (getcwd(buffer, sizeof(buffer))) {
- const char *git_dir = get_git_dir(), *cwd = buffer;
- while (*git_dir && *git_dir == *cwd) {
- git_dir++;
- cwd++;
- }
- inside_git_dir = !*git_dir;
- } else
- inside_git_dir = 0;
- }
+ if (inside_git_dir < 0)
+ inside_git_dir = is_inside_dir(get_git_dir());
return inside_git_dir;
}
+int is_inside_work_tree(void)
+{
+ if (inside_work_tree < 0)
+ inside_work_tree = is_inside_dir(get_git_work_tree());
+ return inside_work_tree;
+}
+
+/*
+ * If no worktree was given, and we are outside of a default work tree,
+ * now is the time to set it.
+ *
+ * In other words, if the user calls git with something like
+ *
+ * git --git-dir=/some/where/else/.git bla
+ *
+ * default to /some/where/else as working directory; if the specified
+ * git-dir does not end in "/.git", the cwd is used as working directory.
+ */
+const char *set_work_tree(const char *dir)
+{
+ char dir_buffer[PATH_MAX], *rel = NULL;
+ static char buffer[PATH_MAX + 1];
+ int len, suffix_len = strlen(DEFAULT_GIT_DIR_ENVIRONMENT) + 1;
+
+ /* strip the variable 'dir' of the postfix "/.git" if it has it */
+ len = strlen(dir);
+ if (len > suffix_len &&
+ !strcmp(dir + len - suffix_len, "/" DEFAULT_GIT_DIR_ENVIRONMENT)) {
+ if ((len - suffix_len) >= sizeof(dir_buffer))
+ die("directory name too long");
+ memcpy(dir_buffer, dir, len - suffix_len);
+ dir_buffer[len - suffix_len] = '\0';
+
+ /* are we inside the default work tree? */
+ rel = get_relative_cwd(buffer, sizeof(buffer), dir_buffer);
+ }
+
+ /* if rel is set, the cwd is _not_ the current working tree */
+ if (rel && *rel) {
+ if (!is_absolute_path(dir))
+ set_git_dir(make_absolute_path(dir));
+ dir = dir_buffer;
+ if (chdir(dir))
+ die("cannot chdir to %s: %s", dir, strerror(errno));
+ else
+ strcat(rel, "/");
+ inside_git_dir = 0;
+ } else {
+ rel = NULL;
+ dir = getcwd(buffer, sizeof(buffer));
+ }
+ git_work_tree_cfg = xstrdup(dir);
+ inside_work_tree = 1;
+
+ return rel;
+}
+
+/*
+ * We cannot decide in this function whether we are in the work tree or
+ * not, since the config can only be read _after_ this function was called.
+ */
const char *setup_git_directory_gently(int *nongit_ok)
{
+ const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
static char cwd[PATH_MAX+1];
const char *gitdirenv;
int len, offset;
if (gitdirenv) {
if (PATH_MAX - 40 < strlen(gitdirenv))
die("'$%s' too big", GIT_DIR_ENVIRONMENT);
- if (is_git_directory(gitdirenv))
+ if (is_git_directory(gitdirenv)) {
+ if (!work_tree_env)
+ return set_work_tree(gitdirenv);
return NULL;
+ }
if (nongit_ok) {
*nongit_ok = 1;
return NULL;
die("Not a git repository: '%s'", gitdirenv);
}
- if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')
+ if (!getcwd(cwd, sizeof(cwd)-1))
die("Unable to read current working directory");
+ /*
+ * Test in the following order (relative to the cwd):
+ * - .git/
+ * - ./ (bare)
+ * - ../.git/
+ * - ../ (bare)
+ * - ../../.git/
+ * etc.
+ */
offset = len = strlen(cwd);
for (;;) {
- if (is_git_directory(".git"))
+ if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
break;
+ if (is_git_directory(".")) {
+ inside_git_dir = 1;
+ if (!work_tree_env)
+ inside_work_tree = 0;
+ setenv(GIT_DIR_ENVIRONMENT, ".", 1);
+ return NULL;
+ }
chdir("..");
do {
if (!offset) {
- if (is_git_directory(cwd)) {
- if (chdir(cwd))
- die("Cannot come back to cwd");
- setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
- inside_git_dir = 1;
- return NULL;
- }
if (nongit_ok) {
if (chdir(cwd))
die("Cannot come back to cwd");
} while (cwd[--offset] != '/');
}
+ inside_git_dir = 0;
+ if (!work_tree_env)
+ inside_work_tree = 1;
+ git_work_tree_cfg = xstrndup(cwd, offset);
if (offset == len)
return NULL;
offset++;
cwd[len++] = '/';
cwd[len] = 0;
- inside_git_dir = !prefixcmp(cwd + offset, ".git/");
return cwd + offset;
}
int git_config_perm(const char *var, const char *value)
{
if (value) {
+ int i;
if (!strcmp(value, "umask"))
return PERM_UMASK;
if (!strcmp(value, "group"))
!strcmp(value, "world") ||
!strcmp(value, "everybody"))
return PERM_EVERYBODY;
+ i = atoi(value);
+ if (i > 1)
+ return i;
}
return git_config_bool(var, value);
}
int check_repository_format_version(const char *var, const char *value)
{
- if (strcmp(var, "core.repositoryformatversion") == 0)
- repository_format_version = git_config_int(var, value);
+ if (strcmp(var, "core.repositoryformatversion") == 0)
+ repository_format_version = git_config_int(var, value);
else if (strcmp(var, "core.sharedrepository") == 0)
shared_repository = git_config_perm(var, value);
- return 0;
+ else if (strcmp(var, "core.bare") == 0) {
+ is_bare_repository_cfg = git_config_bool(var, value);
+ if (is_bare_repository_cfg == 1)
+ inside_work_tree = -1;
+ } else if (strcmp(var, "core.worktree") == 0) {
+ if (git_work_tree_cfg)
+ free(git_work_tree_cfg);
+ git_work_tree_cfg = xstrdup(value);
+ inside_work_tree = -1;
+ }
+ return 0;
}
int check_repository_format(void)
{
const char *retval = setup_git_directory_gently(NULL);
check_repository_format();
+
+ /* If the work tree is not the default one, recompute prefix */
+ if (inside_work_tree < 0) {
+ static char buffer[PATH_MAX + 1];
+ char *rel;
+ if (retval && chdir(retval))
+ die ("Could not jump back into original cwd");
+ rel = get_relative_cwd(buffer, PATH_MAX, get_git_work_tree());
+ return rel && *rel ? strcat(rel, "/") : NULL;
+ }
+
return retval;
}
*buf++ = hex[val >> 4];
*buf++ = hex[val & 0xf];
}
-
+
return base;
}
*buf++ = hex[val >> 4];
*buf++ = hex[val & 0xf];
}
-
+
return base;
}
{
const char *alt;
+ if (alt_odb_tail)
+ return;
+
alt = getenv(ALTERNATE_DB_ENVIRONMENT);
if (!alt) alt = "";
- if (alt_odb_tail)
- return;
alt_odb_tail = &alt_odb_list;
link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL, 0);
static size_t pack_mapped;
struct packed_git *packed_git;
-void pack_report()
+void pack_report(void)
{
fprintf(stderr,
"pack_report: getpagesize() = %10" SZ_FMT "\n"
return 0;
}
+int open_pack_index(struct packed_git *p)
+{
+ char *idx_name;
+ int ret;
+
+ if (p->index_data)
+ return 0;
+
+ idx_name = xstrdup(p->pack_name);
+ strcpy(idx_name + strlen(idx_name) - strlen(".pack"), ".idx");
+ ret = check_packed_git_idx(idx_name, p);
+ free(idx_name);
+ return ret;
+}
+
static void scan_windows(struct packed_git *p,
struct packed_git **lru_p,
struct pack_window **lru_w,
unsigned char *idx_sha1;
long fd_flag;
+ if (!p->index_data && open_pack_index(p))
+ return error("packfile %s index unavailable", p->pack_name);
+
p->pack_fd = open(p->pack_name, O_RDONLY);
if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
return -1;
return NULL;
memcpy(p->pack_name, path, path_len);
strcpy(p->pack_name + path_len, ".pack");
- if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode) ||
- check_packed_git_idx(path, p)) {
+ if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode)) {
free(p);
return NULL;
}
/* ok, it looks sane as far as we can check without
* actually mapping the pack file.
*/
+ p->index_version = 0;
+ p->index_data = NULL;
+ p->index_size = 0;
+ p->num_objects = 0;
p->pack_size = st.st_size;
p->next = NULL;
p->windows = NULL;
return hashcmp(sha1, real_sha1) ? -1 : 0;
}
-void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
+static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
{
struct stat st;
void *map;
return map;
}
-int legacy_loose_object(unsigned char *map)
+static int legacy_loose_object(unsigned char *map)
{
unsigned int word;
return inflate(stream, 0);
}
+
+ /*
+ * There used to be a second loose object header format which
+ * was meant to mimic the in-pack format, allowing for direct
+ * copy of the object data. This format turned up not to be
+ * really worth it and we don't write it any longer. But we
+ * can still read it.
+ */
used = unpack_object_header_gently(map, mapsize, &type, &size);
if (!used || !valid_loose_object_type[type])
return -1;
unsigned long size;
/*
- * The type can be at most ten bytes (including the
+ * The type can be at most ten bytes (including the
* terminating '\0' that we add), and is followed by
* a space.
*/
return data;
}
-const unsigned char *nth_packed_object_sha1(const struct packed_git *p,
+const unsigned char *nth_packed_object_sha1(struct packed_git *p,
uint32_t n)
{
const unsigned char *index = p->index_data;
+ if (!index) {
+ if (open_pack_index(p))
+ return NULL;
+ index = p->index_data;
+ }
if (n >= p->num_objects)
return NULL;
index += 4 * 256;
const unsigned char *index = p->index_data;
unsigned hi, lo;
+ if (!index) {
+ if (open_pack_index(p))
+ return 0;
+ level1_ofs = p->index_data;
+ index = p->index_data;
+ }
if (p->index_version > 1) {
level1_ofs += 2;
index += 8;
static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed)
{
+ static struct packed_git *last_found = (void *)1;
struct packed_git *p;
off_t offset;
prepare_packed_git();
+ if (!packed_git)
+ return 0;
+ p = (last_found == (void *)1) ? packed_git : last_found;
- for (p = packed_git; p; p = p->next) {
+ do {
if (ignore_packed) {
const char **ig;
for (ig = ignore_packed; *ig; ig++)
if (!matches_pack_name(p, *ig))
break;
if (*ig)
- continue;
+ goto next;
}
+
offset = find_pack_entry_one(sha1, p);
if (offset) {
/*
*/
if (p->pack_fd == -1 && open_packed_git(p)) {
error("packfile %s cannot be accessed", p->pack_name);
- continue;
+ goto next;
}
e->offset = offset;
e->p = p;
hashcpy(e->sha1, sha1);
+ last_found = p;
return 1;
}
- }
+
+ next:
+ if (p == last_found)
+ p = packed_git;
+ else
+ p = p->next;
+ if (p == last_found)
+ p = p->next;
+ } while (p);
return 0;
}
-struct packed_git *find_sha1_pack(const unsigned char *sha1,
+struct packed_git *find_sha1_pack(const unsigned char *sha1,
struct packed_git *packs)
{
struct packed_git *p;
return 0;
}
-static int write_binary_header(unsigned char *hdr, enum object_type type, unsigned long len)
-{
- int hdr_len;
- unsigned char c;
-
- c = (type << 4) | (len & 15);
- len >>= 4;
- hdr_len = 1;
- while (len) {
- *hdr++ = c | 0x80;
- hdr_len++;
- c = (len & 0x7f);
- len >>= 7;
- }
- *hdr = c;
- return hdr_len;
-}
-
-static void setup_object_header(z_stream *stream, const char *type, unsigned long len)
-{
- int obj_type, hdrlen;
-
- if (use_legacy_headers) {
- while (deflate(stream, 0) == Z_OK)
- /* nothing */;
- return;
- }
- obj_type = type_from_string(type);
- hdrlen = write_binary_header(stream->next_out, obj_type, len);
- stream->total_out = hdrlen;
- stream->next_out += hdrlen;
- stream->avail_out -= hdrlen;
-}
-
int hash_sha1_file(const void *buf, unsigned long len, const char *type,
unsigned char *sha1)
{
/* First header.. */
stream.next_in = (unsigned char *)hdr;
stream.avail_in = hdrlen;
- setup_object_header(&stream, type, len);
+ while (deflate(&stream, 0) == Z_OK)
+ /* nothing */;
/* Then the data itself.. */
stream.next_in = buf;
*
* returns 0 if anything went fine and -1 otherwise
*
+ * The buffer is always NUL-terminated, not including it in returned size.
+ *
* NOTE: both buf and size may change, but even when -1 is returned
* you still have to free() it yourself.
*/
-int read_pipe(int fd, char** return_buf, unsigned long* return_size)
+int read_fd(int fd, char **return_buf, unsigned long *return_size)
{
- char* buf = *return_buf;
+ char *buf = *return_buf;
unsigned long size = *return_size;
ssize_t iret;
unsigned long off = 0;
+ if (!buf || size <= 1) {
+ size = 1024;
+ buf = xrealloc(buf, size);
+ }
+
do {
- iret = xread(fd, buf + off, size - off);
+ iret = xread(fd, buf + off, (size - 1) - off);
if (iret > 0) {
off += iret;
- if (off == size) {
- size *= 2;
+ if (off == size - 1) {
+ size = alloc_nr(size);
buf = xrealloc(buf, size);
}
}
} while (iret > 0);
+ buf[off] = '\0';
+
*return_buf = buf;
*return_size = off;
char *buf = xmalloc(size);
int ret;
- if (read_pipe(fd, &buf, &size)) {
+ if (read_fd(fd, &buf, &size)) {
free(buf);
return -1;
}
prepare_packed_git();
for (p = packed_git; p && found < 2; p = p->next) {
- uint32_t num = p->num_objects;
- uint32_t first = 0, last = num;
+ uint32_t num, last;
+ uint32_t first = 0;
+ open_pack_index(p);
+ num = p->num_objects;
+ last = num;
while (first < last) {
uint32_t mid = (first + last) / 2;
const unsigned char *now;
int has_unpacked, has_packed;
unsigned char unpacked_sha1[20], packed_sha1[20];
+ prepare_alt_odb();
has_unpacked = find_short_object_filename(len, canonical, unpacked_sha1);
has_packed = find_short_packed_object(len, res, packed_sha1);
if (!has_unpacked && !has_packed)
fprintf(stderr,
"warning: Log for '%.*s' only goes "
"back to %s.\n", len, str,
- show_rfc2822_date(co_time, co_tz));
+ show_date(co_time, co_tz, DATE_RFC2822));
else
fprintf(stderr,
"warning: Log for '%.*s' only has "
const char *cp;
*mode = S_IFINVALID;
- prepare_alt_odb();
ret = get_sha1_1(name, namelen, sha1);
if (!ret)
return ret;
namelen = namelen - (cp - name);
if (!active_cache)
read_cache();
- if (active_nr < 0)
- return -1;
pos = cache_name_pos(cp, namelen);
if (pos < 0)
pos = -pos - 1;
}
if (!size)
return -1;
-
+
if (verbose)
fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1));
remote = 0;
-
+
if (!has_sha1_file(sha1)) {
fprintf(stderr, "git-ssh-upload: could not find %s\n",
sha1_to_hex(sha1));
remote = -1;
}
-
+
if (write_in_full(fd_out, &remote, 1) != 1)
return 0;
-
+
if (remote < 0)
return 0;
-
+
return write_sha1_to_fd(fd_out, sha1);
}
sb->eof = 1;
strbuf_end(sb);
}
-
#GIT_TEST_OPTS=--verbose --debug
SHELL_PATH ?= $(SHELL)
TAR ?= $(TAR)
+RM ?= rm -f
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
@echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
clean:
- rm -fr trash
+ $(RM) -r trash
# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
full-svn-test:
.PHONY: $(T) clean
.NOTPARALLEL:
-
poke() {
test-chmtime +1 "$1"
}
+
+SVN_HTTPD_MODULE_PATH=${SVN_HTTPD_MODULE_PATH-'/usr/lib/apache2/modules'}
+SVN_HTTPD_PATH=${SVN_HTTPD_PATH-'/usr/sbin/apache2'}
+
+start_httpd () {
+ if test -z "$SVN_HTTPD_PORT"
+ then
+ echo >&2 'SVN_HTTPD_PORT is not defined!'
+ return
+ fi
+
+ mkdir "$GIT_DIR"/logs
+
+ cat > "$GIT_DIR/httpd.conf" <<EOF
+ServerName "git-svn test"
+ServerRoot "$GIT_DIR"
+DocumentRoot "$GIT_DIR"
+PidFile "$GIT_DIR/httpd.pid"
+Listen 127.0.0.1:$SVN_HTTPD_PORT
+LoadModule dav_module $SVN_HTTPD_MODULE_PATH/mod_dav.so
+LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so
+<Location /svn>
+ DAV svn
+ SVNPath $rawsvnrepo
+</Location>
+EOF
+ "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start
+ svnrepo=http://127.0.0.1:$SVN_HTTPD_PORT/svn
+}
+
+stop_httpd () {
+ test -z "$SVN_HTTPD_PORT" && return
+ "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k stop
+}
echo This is Z/$p from the original tree. >Z/$p
test_expect_success \
"adding test file $p and Z/$p" \
- 'git-update-index --add $p &&
- git-update-index --add Z/$p'
+ 'git update-index --add $p &&
+ git update-index --add Z/$p'
done
done
echo This is SS from the original tree. >SS
test_expect_success \
'adding test file SS' \
- 'git-update-index --add SS'
+ 'git update-index --add SS'
cat >TT <<\EOF
This is a trivial merge sample text.
Branch A is expected to upcase this word, here.
EOF
test_expect_success \
'adding test file TT' \
- 'git-update-index --add TT'
+ 'git update-index --add TT'
test_expect_success \
'prepare initial tree' \
- 'tree_O=$(git-write-tree)'
+ 'tree_O=$(git write-tree)'
################################################################
# Branch A and B makes the changes according to the above matrix.
rm -f $to_remove
test_expect_success \
'change in branch A (removal)' \
- 'git-update-index --remove $to_remove'
+ 'git update-index --remove $to_remove'
for p in M? Z/M?
do
echo This is modified $p in the branch A. >$p
test_expect_success \
'change in branch A (modification)' \
- "git-update-index $p"
+ "git update-index $p"
done
for p in AN AA Z/AN Z/AA
echo This is added $p in the branch A. >$p
test_expect_success \
'change in branch A (addition)' \
- "git-update-index --add $p"
+ "git update-index --add $p"
done
echo This is SS from the modified tree. >SS
echo This is LL from the modified tree. >LL
test_expect_success \
'change in branch A (addition)' \
- 'git-update-index --add LL &&
- git-update-index SS'
+ 'git update-index --add LL &&
+ git update-index SS'
mv TT TT-
sed -e '/Branch A/s/word/WORD/g' <TT- >TT
rm -f TT-
test_expect_success \
'change in branch A (edit)' \
- 'git-update-index TT'
+ 'git update-index TT'
mkdir DF
echo Branch A makes a file at DF/DF, creating a directory DF. >DF/DF
test_expect_success \
'change in branch A (change file to directory)' \
- 'git-update-index --add DF/DF'
+ 'git update-index --add DF/DF'
test_expect_success \
'recording branch A tree' \
- 'tree_A=$(git-write-tree)'
-
+ 'tree_A=$(git write-tree)'
+
################################################################
# Branch B
# Start from O
mkdir Z
test_expect_success \
'reading original tree and checking out' \
- 'git-read-tree $tree_O &&
- git-checkout-index -a'
+ 'git read-tree $tree_O &&
+ git checkout-index -a'
to_remove=$(echo ?D Z/?D)
rm -f $to_remove
test_expect_success \
'change in branch B (removal)' \
- "git-update-index --remove $to_remove"
+ "git update-index --remove $to_remove"
for p in ?M Z/?M
do
echo This is modified $p in the branch B. >$p
test_expect_success \
'change in branch B (modification)' \
- "git-update-index $p"
+ "git update-index $p"
done
for p in NA AA Z/NA Z/AA
echo This is added $p in the branch B. >$p
test_expect_success \
'change in branch B (addition)' \
- "git-update-index --add $p"
+ "git update-index --add $p"
done
echo This is SS from the modified tree. >SS
echo This is LL from the modified tree. >LL
test_expect_success \
'change in branch B (addition and modification)' \
- 'git-update-index --add LL &&
- git-update-index SS'
+ 'git update-index --add LL &&
+ git update-index SS'
mv TT TT-
sed -e '/Branch B/s/word/WORD/g' <TT- >TT
rm -f TT-
test_expect_success \
'change in branch B (modification)' \
- 'git-update-index TT'
+ 'git update-index TT'
echo Branch B makes a file at DF. >DF
test_expect_success \
'change in branch B (addition of a file to conflict with directory)' \
- 'git-update-index --add DF'
+ 'git update-index --add DF'
test_expect_success \
'recording branch B tree' \
- 'tree_B=$(git-write-tree)'
+ 'tree_B=$(git write-tree)'
test_expect_success \
'keep contents of 3 trees for easy access' \
'rm -f .git/index &&
- git-read-tree $tree_O &&
+ git read-tree $tree_O &&
mkdir .orig-O &&
- git-checkout-index --prefix=.orig-O/ -f -q -a &&
+ git checkout-index --prefix=.orig-O/ -f -q -a &&
rm -f .git/index &&
- git-read-tree $tree_A &&
+ git read-tree $tree_A &&
mkdir .orig-A &&
- git-checkout-index --prefix=.orig-A/ -f -q -a &&
+ git checkout-index --prefix=.orig-A/ -f -q -a &&
rm -f .git/index &&
- git-read-tree $tree_B &&
+ git read-tree $tree_B &&
mkdir .orig-B &&
- git-checkout-index --prefix=.orig-B/ -f -q -a'
+ git checkout-index --prefix=.orig-B/ -f -q -a'
. ./test-lib.sh
################################################################
-# git-init has been done in an empty repository.
+# git init has been done in an empty repository.
# make sure it is empty.
find .git/objects -type f -print >should-be-empty
test_expect_success \
- '.git/objects should be empty after git-init in an empty repo.' \
- 'cmp -s /dev/null should-be-empty'
+ '.git/objects should be empty after git init in an empty repo.' \
+ 'cmp -s /dev/null should-be-empty'
# also it should have 2 subdirectories; no fan-out anymore, pack, and info.
# 3 is counting "objects" itself
# updating a new file without --add should fail.
test_expect_failure \
- 'git-update-index without --add should fail adding.' \
- 'git-update-index should-be-empty'
+ 'git update-index without --add should fail adding.' \
+ 'git update-index should-be-empty'
# and with --add it should succeed, even if it is empty (it used to fail).
test_expect_success \
- 'git-update-index with --add should succeed.' \
- 'git-update-index --add should-be-empty'
+ 'git update-index with --add should succeed.' \
+ 'git update-index --add should-be-empty'
test_expect_success \
- 'writing tree out with git-write-tree' \
- 'tree=$(git-write-tree)'
+ 'writing tree out with git write-tree' \
+ 'tree=$(git write-tree)'
# we know the shape and contents of the tree and know the object ID for it.
test_expect_success \
# Removing paths.
rm -f should-be-empty full-of-directories
test_expect_failure \
- 'git-update-index without --remove should fail removing.' \
- 'git-update-index should-be-empty'
+ 'git update-index without --remove should fail removing.' \
+ 'git update-index should-be-empty'
test_expect_success \
- 'git-update-index with --remove should be able to remove.' \
- 'git-update-index --remove should-be-empty'
+ 'git update-index with --remove should be able to remove.' \
+ 'git update-index --remove should-be-empty'
# Empty tree can be written with recent write-tree.
test_expect_success \
- 'git-write-tree should be able to write an empty tree.' \
- 'tree=$(git-write-tree)'
+ 'git write-tree should be able to write an empty tree.' \
+ 'tree=$(git write-tree)'
test_expect_success \
'validate object ID of a known tree.' \
ln -s "hello $p" ${p}sym
done
test_expect_success \
- 'adding various types of objects with git-update-index --add.' \
- 'find path* ! -type d -print | xargs git-update-index --add'
+ 'adding various types of objects with git update-index --add.' \
+ 'find path* ! -type d -print | xargs git update-index --add'
# Show them and see that matches what we expect.
test_expect_success \
- 'showing stage with git-ls-files --stage' \
- 'git-ls-files --stage >current'
+ 'showing stage with git ls-files --stage' \
+ 'git ls-files --stage >current'
cat >expected <<\EOF
100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0
120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0 path3/subp3/file3sym
EOF
test_expect_success \
- 'validate git-ls-files output for a known tree.' \
+ 'validate git ls-files output for a known tree.' \
'diff current expected'
test_expect_success \
- 'writing tree out with git-write-tree.' \
- 'tree=$(git-write-tree)'
+ 'writing tree out with git write-tree.' \
+ 'tree=$(git write-tree)'
test_expect_success \
'validate object ID for a known tree.' \
'test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b'
test_expect_success \
- 'showing tree with git-ls-tree' \
- 'git-ls-tree $tree >current'
+ 'showing tree with git ls-tree' \
+ 'git ls-tree $tree >current'
cat >expected <<\EOF
100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3
EOF
test_expect_success \
- 'git-ls-tree output for a known tree.' \
+ 'git ls-tree output for a known tree.' \
'diff current expected'
# This changed in ls-tree pathspec change -- recursive does
# not show tree nodes anymore.
test_expect_success \
- 'showing tree with git-ls-tree -r' \
- 'git-ls-tree -r $tree >current'
+ 'showing tree with git ls-tree -r' \
+ 'git ls-tree -r $tree >current'
cat >expected <<\EOF
100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym
EOF
test_expect_success \
- 'git-ls-tree -r output for a known tree.' \
+ 'git ls-tree -r output for a known tree.' \
'diff current expected'
# But with -r -t we can have both.
test_expect_success \
- 'showing tree with git-ls-tree -r -t' \
- 'git-ls-tree -r -t $tree >current'
+ 'showing tree with git ls-tree -r -t' \
+ 'git ls-tree -r -t $tree >current'
cat >expected <<\EOF
100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym
EOF
test_expect_success \
- 'git-ls-tree -r output for a known tree.' \
+ 'git ls-tree -r output for a known tree.' \
'diff current expected'
test_expect_success \
- 'writing partial tree out with git-write-tree --prefix.' \
- 'ptree=$(git-write-tree --prefix=path3)'
+ 'writing partial tree out with git write-tree --prefix.' \
+ 'ptree=$(git write-tree --prefix=path3)'
test_expect_success \
'validate object ID for a known tree.' \
'test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3'
test_expect_success \
- 'writing partial tree out with git-write-tree --prefix.' \
- 'ptree=$(git-write-tree --prefix=path3/subp3)'
+ 'writing partial tree out with git write-tree --prefix.' \
+ 'ptree=$(git write-tree --prefix=path3/subp3)'
test_expect_success \
'validate object ID for a known tree.' \
'test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2'
rm .git/index
test_expect_success \
'put invalid objects into the index.' \
- 'git-update-index --index-info < badobjects'
+ 'git update-index --index-info < badobjects'
test_expect_failure \
'writing this tree without --missing-ok.' \
- 'git-write-tree'
+ 'git write-tree'
test_expect_success \
'writing this tree with --missing-ok.' \
- 'git-write-tree --missing-ok'
+ 'git write-tree --missing-ok'
################################################################
rm .git/index
test_expect_success \
- 'git-read-tree followed by write-tree should be idempotent.' \
- 'git-read-tree $tree &&
+ 'git read-tree followed by write-tree should be idempotent.' \
+ 'git read-tree $tree &&
test -f .git/index &&
- newtree=$(git-write-tree) &&
+ newtree=$(git write-tree) &&
test "$newtree" = "$tree"'
cat >expected <<\EOF
:120000 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0000000000000000000000000000000000000000 M path3/subp3/file3sym
EOF
test_expect_success \
- 'validate git-diff-files output for a know cache/work tree state.' \
- 'git-diff-files >current && diff >/dev/null -b current expected'
+ 'validate git diff-files output for a know cache/work tree state.' \
+ 'git diff-files >current && diff >/dev/null -b current expected'
test_expect_success \
- 'git-update-index --refresh should succeed.' \
- 'git-update-index --refresh'
+ 'git update-index --refresh should succeed.' \
+ 'git update-index --refresh'
test_expect_success \
- 'no diff after checkout and git-update-index --refresh.' \
- 'git-diff-files >current && cmp -s current /dev/null'
+ 'no diff after checkout and git update-index --refresh.' \
+ 'git diff-files >current && cmp -s current /dev/null'
################################################################
P=087704a96baf1c2d1c869a8b084481e121c88b5b
test_expect_success \
- 'git-commit-tree records the correct tree in a commit.' \
- 'commit0=$(echo NO | git-commit-tree $P) &&
+ 'git commit-tree records the correct tree in a commit.' \
+ 'commit0=$(echo NO | git commit-tree $P) &&
tree=$(git show --pretty=raw $commit0 |
sed -n -e "s/^tree //p" -e "/^author /q") &&
test "z$tree" = "z$P"'
test_expect_success \
- 'git-commit-tree records the correct parent in a commit.' \
- 'commit1=$(echo NO | git-commit-tree $P -p $commit0) &&
+ 'git commit-tree records the correct parent in a commit.' \
+ 'commit1=$(echo NO | git commit-tree $P -p $commit0) &&
parent=$(git show --pretty=raw $commit1 |
sed -n -e "s/^parent //p" -e "/^author /q") &&
test "z$commit0" = "z$parent"'
test_expect_success \
- 'git-commit-tree omits duplicated parent in a commit.' \
- 'commit2=$(echo NO | git-commit-tree $P -p $commit0 -p $commit0) &&
+ 'git commit-tree omits duplicated parent in a commit.' \
+ 'commit2=$(echo NO | git commit-tree $P -p $commit0 -p $commit0) &&
parent=$(git show --pretty=raw $commit2 |
sed -n -e "s/^parent //p" -e "/^author /q" |
sort -u) &&
test $numpath0 = 1
'
+test_expect_success 'absolute path works as expected' '
+ mkdir first &&
+ ln -s ../.git first/.git &&
+ mkdir second &&
+ ln -s ../first second/other &&
+ mkdir third &&
+ dir="$(cd .git; pwd -P)" &&
+ dir2=third/../second/other/.git &&
+ test "$dir" = "$(test-absolute-path $dir2)" &&
+ file="$dir"/index &&
+ test "$file" = "$(test-absolute-path $dir2/index)" &&
+ ln -s ../first/file .git/syml &&
+ sym="$(cd first; pwd -P)"/file &&
+ test "$sym" = "$(test-absolute-path $dir2/syml)"
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='ignore CR in CRLF sequence while computing similiarity'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ cat ../t0022-crlf-rename.sh >sample &&
+ git add sample &&
+
+ test_tick &&
+ git commit -m Initial &&
+
+ sed -e "s/\$/\r/" ../t0022-crlf-rename.sh >elpmas &&
+ git add elpmas &&
+ rm -f sample &&
+
+ test_tick &&
+ git commit -a -m Second
+
+'
+
+test_expect_success 'diff -M' '
+
+ git diff-tree -M -r --name-status HEAD^ HEAD |
+ sed -e "s/R[0-9]*/RNUM/" >actual &&
+ echo "RNUM sample elpmas" >expect &&
+ diff -u expect actual
+
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Carlos Rica
+#
+
+test_description='git stripspace'
+
+. ./test-lib.sh
+
+t40='A quick brown fox jumps over the lazy do'
+s40=' '
+sss="$s40$s40$s40$s40$s40$s40$s40$s40$s40$s40" # 400
+ttt="$t40$t40$t40$t40$t40$t40$t40$t40$t40$t40" # 400
+
+test_expect_success \
+ 'long lines without spaces should be unchanged' '
+ echo "$ttt" >expect &&
+ git stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt" >expect &&
+ git stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt$ttt" >expect &&
+ git stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt$ttt$ttt" >expect &&
+ git stripspace <expect >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'lines with spaces at the beginning should be unchanged' '
+ echo "$sss$ttt" >expect &&
+ git stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$sss$sss$ttt" >expect &&
+ git stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$sss$sss$sss$ttt" >expect &&
+ git stripspace <expect >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'lines with intermediate spaces should be unchanged' '
+ echo "$ttt$sss$ttt" >expect &&
+ git stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$sss$sss$ttt" >expect &&
+ git stripspace <expect >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'consecutive blank lines should be unified' '
+ printf "$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt\n\n\n\n\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt$ttt\n\n\n\n\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt$ttt$ttt\n\n\n\n\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt\n\n\n\n\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$ttt$ttt\n" > expect &&
+ printf "$ttt\n\n\n\n\n$ttt$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$ttt$ttt$ttt\n" > expect &&
+ printf "$ttt\n\n\n\n\n$ttt$ttt$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt\n\t\n \n\n \t\t\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt$ttt\n\t\n \n\n \t\t\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt$ttt$ttt\n\t\n \n\n \t\t\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt\n\t\n \n\n \t\t\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$ttt$ttt\n" > expect &&
+ printf "$ttt\n\t\n \n\n \t\t\n$ttt$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$ttt$ttt$ttt\n" > expect &&
+ printf "$ttt\n\t\n \n\n \t\t\n$ttt$ttt$ttt\n" | git stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'only consecutive blank lines should be completely removed' '
+ > expect &&
+
+ printf "\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "\n\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$sss\n$sss\n$sss\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$sss$sss\n$sss\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "\n$sss\n$sss$sss\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$sss$sss$sss$sss\n\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "\n$sss$sss$sss$sss\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "\n\n$sss$sss$sss$sss\n" | git stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'consecutive blank lines at the beginning should be removed' '
+ printf "$ttt\n" > expect &&
+ printf "\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "\n\n\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n" > expect &&
+ printf "\n\n\n$ttt$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt\n" > expect &&
+ printf "\n\n\n$ttt$ttt$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt$ttt\n" > expect &&
+ printf "\n\n\n$ttt$ttt$ttt$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+
+ printf "$sss\n$sss\n$sss\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "\n$sss\n$sss$sss\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$sss$sss\n$sss\n\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$sss$sss$sss\n\n\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "\n$sss$sss$sss\n\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "\n\n$sss$sss$sss\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'consecutive blank lines at the end should be removed' '
+ printf "$ttt\n" > expect &&
+ printf "$ttt\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$ttt\n\n\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n" > expect &&
+ printf "$ttt$ttt\n\n\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt\n" > expect &&
+ printf "$ttt$ttt$ttt\n\n\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt$ttt\n" > expect &&
+ printf "$ttt$ttt$ttt$ttt\n\n\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+
+ printf "$ttt\n$sss\n$sss\n$sss\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$sss\n$sss$sss\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n$sss$sss\n$sss\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n$sss$sss$sss\n\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$sss$sss$sss\n\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n\n$sss$sss$sss\n" | git stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'text without newline at end should end with newline' '
+ test `printf "$ttt" | git stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt" | git stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt$ttt" | git stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt$ttt$ttt" | git stripspace | wc -l` -gt 0
+'
+
+# text plus spaces at the end:
+
+test_expect_success \
+ 'text plus spaces without newline at end should end with newline' '
+ test `printf "$ttt$sss" | git stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt$sss" | git stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt$ttt$sss" | git stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$sss$sss" | git stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt$sss$sss" | git stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$sss$sss$sss" | git stripspace | wc -l` -gt 0
+'
+
+test_expect_failure \
+ 'text plus spaces without newline at end should not show spaces' '
+ printf "$ttt$sss" | git stripspace | grep -q " " ||
+ printf "$ttt$ttt$sss" | git stripspace | grep -q " " ||
+ printf "$ttt$ttt$ttt$sss" | git stripspace | grep -q " " ||
+ printf "$ttt$sss$sss" | git stripspace | grep -q " " ||
+ printf "$ttt$ttt$sss$sss" | git stripspace | grep -q " " ||
+ printf "$ttt$sss$sss$sss" | git stripspace | grep -q " "
+'
+
+test_expect_success \
+ 'text plus spaces without newline should show the correct lines' '
+ printf "$ttt\n" >expect &&
+ printf "$ttt$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" >expect &&
+ printf "$ttt$sss$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" >expect &&
+ printf "$ttt$sss$sss$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n" >expect &&
+ printf "$ttt$ttt$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n" >expect &&
+ printf "$ttt$ttt$sss$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt\n" >expect &&
+ printf "$ttt$ttt$ttt$sss" | git stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_failure \
+ 'text plus spaces at end should not show spaces' '
+ echo "$ttt$sss" | git stripspace | grep -q " " ||
+ echo "$ttt$ttt$sss" | git stripspace | grep -q " " ||
+ echo "$ttt$ttt$ttt$sss" | git stripspace | grep -q " " ||
+ echo "$ttt$sss$sss" | git stripspace | grep -q " " ||
+ echo "$ttt$ttt$sss$sss" | git stripspace | grep -q " " ||
+ echo "$ttt$sss$sss$sss" | git stripspace | grep -q " "
+'
+
+test_expect_success \
+ 'text plus spaces at end should be cleaned and newline must remain' '
+ echo "$ttt" >expect &&
+ echo "$ttt$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$ttt" >expect &&
+ echo "$ttt$sss$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$ttt" >expect &&
+ echo "$ttt$sss$sss$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt" >expect &&
+ echo "$ttt$ttt$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt" >expect &&
+ echo "$ttt$ttt$sss$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt$ttt" >expect &&
+ echo "$ttt$ttt$ttt$sss" | git stripspace >actual &&
+ git diff expect actual
+'
+
+# spaces only:
+
+test_expect_success \
+ 'spaces with newline at end should be replaced with empty string' '
+ printf "" >expect &&
+
+ echo | git stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$sss$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$sss$sss$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$sss$sss$sss$sss" | git stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_failure \
+ 'spaces without newline at end should not show spaces' '
+ printf "" | git stripspace | grep -q " " ||
+ printf "$sss" | git stripspace | grep -q " " ||
+ printf "$sss$sss" | git stripspace | grep -q " " ||
+ printf "$sss$sss$sss" | git stripspace | grep -q " " ||
+ printf "$sss$sss$sss$sss" | git stripspace | grep -q " "
+'
+
+test_expect_success \
+ 'spaces without newline at end should be replaced with empty string' '
+ printf "" >expect &&
+
+ printf "" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$sss$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$sss$sss$sss" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$sss$sss$sss$sss" | git stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'consecutive text lines should be unchanged' '
+ printf "$ttt$ttt\n$ttt\n" >expect &&
+ printf "$ttt$ttt\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n$ttt$ttt\n$ttt\n" >expect &&
+ printf "$ttt\n$ttt$ttt\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n$ttt\n$ttt\n$ttt$ttt\n" >expect &&
+ printf "$ttt\n$ttt\n$ttt\n$ttt$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n$ttt\n\n$ttt$ttt\n$ttt\n" >expect &&
+ printf "$ttt\n$ttt\n\n$ttt$ttt\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n\n$ttt\n$ttt$ttt\n" >expect &&
+ printf "$ttt$ttt\n\n$ttt\n$ttt$ttt\n" | git stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n$ttt$ttt\n\n$ttt\n" >expect &&
+ printf "$ttt\n$ttt$ttt\n\n$ttt\n" | git stripspace >actual &&
+ git diff expect actual
+'
+
+test_done
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
check_result () {
- git-ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
+ git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
git diff expected current
}
# This is done on an empty work directory, which is the normal
# merge person behaviour.
test_expect_success \
- '3-way merge with git-read-tree -m, empty cache' \
+ '3-way merge with git read-tree -m, empty cache' \
"rm -fr [NDMALTS][NDMALTSF] Z &&
rm .git/index &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
# This starts out with the first head, which is the normal
# patch submitter behaviour.
test_expect_success \
- '3-way merge with git-read-tree -m, match H' \
+ '3-way merge with git read-tree -m, match H' \
"rm -fr [NDMALTS][NDMALTSF] Z &&
rm .git/index &&
- git-read-tree $tree_A &&
- git-checkout-index -f -u -a &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git read-tree $tree_A &&
+ git checkout-index -f -u -a &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
: <<\END_OF_CASE_TABLE
case which are trivial. Make sure index requirements are also
checked.
-"git-read-tree -m O A B"
+"git read-tree -m O A B"
O A B result index requirements
-------------------------------------------------------------------
'1 - must not have an entry not in A.' \
"rm -f .git/index XX &&
echo XX >XX &&
- git-update-index --add XX &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add XX &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'2 - must match B in !O && !A && B case.' \
"rm -f .git/index NA &&
cp .orig-B/NA NA &&
- git-update-index --add NA &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add NA &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'2 - matching B alone is OK in !O && !A && B case.' \
"rm -f .git/index NA &&
cp .orig-B/NA NA &&
- git-update-index --add NA &&
+ git update-index --add NA &&
echo extra >>NA &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'3 - must match A in !O && A && !B case.' \
"rm -f .git/index AN &&
cp .orig-A/AN AN &&
- git-update-index --add AN &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git update-index --add AN &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
'3 - matching A alone is OK in !O && A && !B case.' \
"rm -f .git/index AN &&
cp .orig-A/AN AN &&
- git-update-index --add AN &&
+ git update-index --add AN &&
echo extra >>AN &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_failure \
'3 (fail) - must match A in !O && A && !B case.' \
"rm -f .git/index AN &&
cp .orig-A/AN AN &&
echo extra >>AN &&
- git-update-index --add AN &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add AN &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'4 - must match and be up-to-date in !O && A && B && A!=B case.' \
"rm -f .git/index AA &&
cp .orig-A/AA AA &&
- git-update-index --add AA &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git update-index --add AA &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_failure \
'4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
"rm -f .git/index AA &&
cp .orig-A/AA AA &&
- git-update-index --add AA &&
+ git update-index --add AA &&
echo extra >>AA &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_failure \
'4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
"rm -f .git/index AA &&
cp .orig-A/AA AA &&
echo extra >>AA &&
- git-update-index --add AA &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add AA &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'5 - must match in !O && A && B && A==B case.' \
"rm -f .git/index LL &&
cp .orig-A/LL LL &&
- git-update-index --add LL &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git update-index --add LL &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
'5 - must match in !O && A && B && A==B case.' \
"rm -f .git/index LL &&
cp .orig-A/LL LL &&
- git-update-index --add LL &&
+ git update-index --add LL &&
echo extra >>LL &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_failure \
"rm -f .git/index LL &&
cp .orig-A/LL LL &&
echo extra >>LL &&
- git-update-index --add LL &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add LL &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_failure \
'6 - must not exist in O && !A && !B case' \
"rm -f .git/index DD &&
echo DD >DD
- git-update-index --add DD &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add DD &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_failure \
'7 - must not exist in O && !A && B && O!=B case' \
"rm -f .git/index DM &&
cp .orig-B/DM DM &&
- git-update-index --add DM &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add DM &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_failure \
'8 - must not exist in O && !A && B && O==B case' \
"rm -f .git/index DN &&
cp .orig-B/DN DN &&
- git-update-index --add DN &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add DN &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'9 - must match and be up-to-date in O && A && !B && O!=A case' \
"rm -f .git/index MD &&
cp .orig-A/MD MD &&
- git-update-index --add MD &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git update-index --add MD &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_failure \
'9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
"rm -f .git/index MD &&
cp .orig-A/MD MD &&
- git-update-index --add MD &&
+ git update-index --add MD &&
echo extra >>MD &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_failure \
'9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
"rm -f .git/index MD &&
cp .orig-A/MD MD &&
echo extra >>MD &&
- git-update-index --add MD &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add MD &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'10 - must match and be up-to-date in O && A && !B && O==A case' \
"rm -f .git/index ND &&
cp .orig-A/ND ND &&
- git-update-index --add ND &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git update-index --add ND &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_failure \
'10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
"rm -f .git/index ND &&
cp .orig-A/ND ND &&
- git-update-index --add ND &&
+ git update-index --add ND &&
echo extra >>ND &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_failure \
'10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
"rm -f .git/index ND &&
cp .orig-A/ND ND &&
echo extra >>ND &&
- git-update-index --add ND &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add ND &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
"rm -f .git/index MM &&
cp .orig-A/MM MM &&
- git-update-index --add MM &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git update-index --add MM &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_failure \
'11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
"rm -f .git/index MM &&
cp .orig-A/MM MM &&
- git-update-index --add MM &&
+ git update-index --add MM &&
echo extra >>MM &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_failure \
'11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
"rm -f .git/index MM &&
cp .orig-A/MM MM &&
echo extra >>MM &&
- git-update-index --add MM &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add MM &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'12 - must match A in O && A && B && O!=A && A==B case' \
"rm -f .git/index SS &&
cp .orig-A/SS SS &&
- git-update-index --add SS &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git update-index --add SS &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
'12 - must match A in O && A && B && O!=A && A==B case' \
"rm -f .git/index SS &&
cp .orig-A/SS SS &&
- git-update-index --add SS &&
+ git update-index --add SS &&
echo extra >>SS &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_failure \
"rm -f .git/index SS &&
cp .orig-A/SS SS &&
echo extra >>SS &&
- git-update-index --add SS &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add SS &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'13 - must match A in O && A && B && O!=A && O==B case' \
"rm -f .git/index MN &&
cp .orig-A/MN MN &&
- git-update-index --add MN &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git update-index --add MN &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
'13 - must match A in O && A && B && O!=A && O==B case' \
"rm -f .git/index MN &&
cp .orig-A/MN MN &&
- git-update-index --add MN &&
+ git update-index --add MN &&
echo extra >>MN &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
'14 - must match and be up-to-date in O && A && B && O==A && O!=B case' \
"rm -f .git/index NM &&
cp .orig-A/NM NM &&
- git-update-index --add NM &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git update-index --add NM &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
'14 - may match B in O && A && B && O==A && O!=B case' \
"rm -f .git/index NM &&
cp .orig-B/NM NM &&
- git-update-index --add NM &&
+ git update-index --add NM &&
echo extra >>NM &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_failure \
'14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
"rm -f .git/index NM &&
cp .orig-A/NM NM &&
- git-update-index --add NM &&
+ git update-index --add NM &&
echo extra >>NM &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_failure \
'14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
"rm -f .git/index NM &&
cp .orig-A/NM NM &&
echo extra >>NM &&
- git-update-index --add NM &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add NM &&
+ git read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
'15 - must match A in O && A && B && O==A && O==B case' \
"rm -f .git/index NN &&
cp .orig-A/NN NN &&
- git-update-index --add NN &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git update-index --add NN &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_success \
'15 - must match A in O && A && B && O==A && O==B case' \
"rm -f .git/index NN &&
cp .orig-A/NN NN &&
- git-update-index --add NN &&
+ git update-index --add NN &&
echo extra >>NN &&
- git-read-tree -m $tree_O $tree_A $tree_B &&
+ git read-tree -m $tree_O $tree_A $tree_B &&
check_result"
test_expect_failure \
"rm -f .git/index NN &&
cp .orig-A/NN NN &&
echo extra >>NN &&
- git-update-index --add NN &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git update-index --add NN &&
+ git read-tree -m $tree_O $tree_A $tree_B"
# #16
test_expect_success \
'16 - A matches in one and B matches in another.' \
'rm -f .git/index F16 &&
echo F16 >F16 &&
- git-update-index --add F16 &&
- tree0=`git-write-tree` &&
+ git update-index --add F16 &&
+ tree0=`git write-tree` &&
echo E16 >F16 &&
- git-update-index F16 &&
- tree1=`git-write-tree` &&
- git-read-tree -m $tree0 $tree1 $tree1 $tree0 &&
- git-ls-files --stage'
+ git update-index F16 &&
+ tree1=`git write-tree` &&
+ git read-tree -m $tree0 $tree1 $tree1 $tree0 &&
+ git ls-files --stage'
test_done
simply ahead of H. The index and the work tree contains a state that
is derived from H, but may also have local changes. This test checks
all the combinations described in the two-tree merge "carry forward"
-rules, found in <Documentation/git-read-tree.txt>.
+rules, found in <Documentation/git read-tree.txt>.
In the test, these paths are used:
bozbar - in H, stays in M, modified from bozbar to gnusto
. ./test-lib.sh
read_tree_twoway () {
- git-read-tree -m "$1" "$2" && git-ls-files --stage
+ git read-tree -m "$1" "$2" && git ls-files --stage
}
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
}
check_cache_at () {
- clean_if_empty=`git-diff-files -- "$1"`
+ clean_if_empty=`git diff-files -- "$1"`
case "$clean_if_empty" in
'') echo "$1: clean" ;;
?*) echo "$1: dirty" ;;
cat bozbar-old >bozbar &&
echo rezrov >rezrov &&
echo yomin >yomin &&
- git-update-index --add nitfol bozbar rezrov &&
- treeH=`git-write-tree` &&
+ git update-index --add nitfol bozbar rezrov &&
+ treeH=`git write-tree` &&
echo treeH $treeH &&
- git-ls-tree $treeH &&
+ git ls-tree $treeH &&
cat bozbar-new >bozbar &&
- git-update-index --add frotz bozbar --force-remove rezrov &&
- git-ls-files --stage >M.out &&
- treeM=`git-write-tree` &&
+ git update-index --add frotz bozbar --force-remove rezrov &&
+ git ls-files --stage >M.out &&
+ treeM=`git write-tree` &&
echo treeM $treeM &&
- git-ls-tree $treeM &&
- git-diff-tree $treeH $treeM'
+ git ls-tree $treeM &&
+ git diff-tree $treeH $treeM'
test_expect_success \
'1, 2, 3 - no carry forward' \
'rm -f .git/index &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >1-3.out &&
+ git ls-files --stage >1-3.out &&
git diff M.out 1-3.out &&
check_cache_at bozbar dirty &&
check_cache_at frotz dirty &&
test_expect_success \
'4 - carry forward local addition.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
- git-update-index --add yomin &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
+ git update-index --add yomin &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >4.out || return 1
+ git ls-files --stage >4.out || return 1
git diff M.out 4.out >4diff.out
compare_change 4diff.out expected &&
check_cache_at yomin clean'
test_expect_success \
'5 - carry forward local addition.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo yomin >yomin &&
- git-update-index --add yomin &&
+ git update-index --add yomin &&
echo yomin yomin >yomin &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >5.out || return 1
+ git ls-files --stage >5.out || return 1
git diff M.out 5.out >5diff.out
compare_change 5diff.out expected &&
check_cache_at yomin dirty'
test_expect_success \
'6 - local addition already has the same.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
- git-update-index --add frotz &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
+ git update-index --add frotz &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >6.out &&
+ git ls-files --stage >6.out &&
git diff M.out 6.out &&
check_cache_at frotz clean'
test_expect_success \
'7 - local addition already has the same.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo frotz >frotz &&
- git-update-index --add frotz &&
+ git update-index --add frotz &&
echo frotz frotz >frotz &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >7.out &&
+ git ls-files --stage >7.out &&
git diff M.out 7.out &&
check_cache_at frotz dirty'
test_expect_success \
'8 - conflicting addition.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo frotz frotz >frotz &&
- git-update-index --add frotz &&
+ git update-index --add frotz &&
if read_tree_twoway $treeH $treeM; then false; else :; fi'
test_expect_success \
'9 - conflicting addition.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo frotz frotz >frotz &&
- git-update-index --add frotz &&
+ git update-index --add frotz &&
echo frotz >frotz &&
if read_tree_twoway $treeH $treeM; then false; else :; fi'
test_expect_success \
'10 - path removed.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo rezrov >rezrov &&
- git-update-index --add rezrov &&
+ git update-index --add rezrov &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >10.out &&
+ git ls-files --stage >10.out &&
git diff M.out 10.out'
test_expect_success \
'11 - dirty path removed.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo rezrov >rezrov &&
- git-update-index --add rezrov &&
+ git update-index --add rezrov &&
echo rezrov rezrov >rezrov &&
if read_tree_twoway $treeH $treeM; then false; else :; fi'
test_expect_success \
'12 - unmatching local changes being removed.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo rezrov rezrov >rezrov &&
- git-update-index --add rezrov &&
+ git update-index --add rezrov &&
if read_tree_twoway $treeH $treeM; then false; else :; fi'
test_expect_success \
'13 - unmatching local changes being removed.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo rezrov rezrov >rezrov &&
- git-update-index --add rezrov &&
+ git update-index --add rezrov &&
echo rezrov >rezrov &&
if read_tree_twoway $treeH $treeM; then false; else :; fi'
test_expect_success \
'14 - unchanged in two heads.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo nitfol nitfol >nitfol &&
- git-update-index --add nitfol &&
+ git update-index --add nitfol &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >14.out || return 1
+ git ls-files --stage >14.out || return 1
git diff M.out 14.out >14diff.out
compare_change 14diff.out expected &&
check_cache_at nitfol clean'
test_expect_success \
'15 - unchanged in two heads.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo nitfol nitfol >nitfol &&
- git-update-index --add nitfol &&
+ git update-index --add nitfol &&
echo nitfol nitfol nitfol >nitfol &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >15.out || return 1
+ git ls-files --stage >15.out || return 1
git diff M.out 15.out >15diff.out
compare_change 15diff.out expected &&
check_cache_at nitfol dirty'
test_expect_success \
'16 - conflicting local change.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo bozbar bozbar >bozbar &&
- git-update-index --add bozbar &&
+ git update-index --add bozbar &&
if read_tree_twoway $treeH $treeM; then false; else :; fi'
test_expect_success \
'17 - conflicting local change.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
echo bozbar bozbar >bozbar &&
- git-update-index --add bozbar &&
+ git update-index --add bozbar &&
echo bozbar bozbar bozbar >bozbar &&
if read_tree_twoway $treeH $treeM; then false; else :; fi'
test_expect_success \
'18 - local change already having a good result.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
cat bozbar-new >bozbar &&
- git-update-index --add bozbar &&
+ git update-index --add bozbar &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >18.out &&
+ git ls-files --stage >18.out &&
git diff M.out 18.out &&
check_cache_at bozbar clean'
test_expect_success \
'19 - local change already having a good result, further modified.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
cat bozbar-new >bozbar &&
- git-update-index --add bozbar &&
+ git update-index --add bozbar &&
echo gnusto gnusto >bozbar &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >19.out &&
+ git ls-files --stage >19.out &&
git diff M.out 19.out &&
check_cache_at bozbar dirty'
test_expect_success \
'20 - no local change, use new tree.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
cat bozbar-old >bozbar &&
- git-update-index --add bozbar &&
+ git update-index --add bozbar &&
read_tree_twoway $treeH $treeM &&
- git-ls-files --stage >20.out &&
+ git ls-files --stage >20.out &&
git diff M.out 20.out &&
check_cache_at bozbar dirty'
test_expect_success \
'21 - no local change, dirty cache.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
cat bozbar-old >bozbar &&
- git-update-index --add bozbar &&
+ git update-index --add bozbar &&
echo gnusto gnusto >bozbar &&
if read_tree_twoway $treeH $treeM; then false; else :; fi'
test_expect_success \
'22 - local change cache updated.' \
'rm -f .git/index &&
- git-read-tree $treeH &&
- git-checkout-index -u -f -q -a &&
+ git read-tree $treeH &&
+ git checkout-index -u -f -q -a &&
sed -e "s/such as/SUCH AS/" bozbar-old >bozbar &&
- git-update-index --add bozbar &&
+ git update-index --add bozbar &&
if read_tree_twoway $treeH $treeM; then false; else :; fi'
# Also make sure we did not break DF vs DF/DF case.
'DF vs DF/DF case setup.' \
'rm -f .git/index &&
echo DF >DF &&
- git-update-index --add DF &&
- treeDF=`git-write-tree` &&
+ git update-index --add DF &&
+ treeDF=`git write-tree` &&
echo treeDF $treeDF &&
- git-ls-tree $treeDF &&
+ git ls-tree $treeDF &&
rm -f DF &&
mkdir DF &&
echo DF/DF >DF/DF &&
- git-update-index --add --remove DF DF/DF &&
- treeDFDF=`git-write-tree` &&
+ git update-index --add --remove DF DF/DF &&
+ treeDFDF=`git write-tree` &&
echo treeDFDF $treeDFDF &&
- git-ls-tree $treeDFDF &&
- git-ls-files --stage >DFDF.out'
+ git ls-tree $treeDFDF &&
+ git ls-files --stage >DFDF.out'
test_expect_success \
'DF vs DF/DF case test.' \
'rm -f .git/index &&
rm -fr DF &&
echo DF >DF &&
- git-update-index --add DF &&
+ git update-index --add DF &&
read_tree_twoway $treeDF $treeDFDF &&
- git-ls-files --stage >DFDFcheck.out &&
+ git ls-files --stage >DFDFcheck.out &&
git diff DFDF.out DFDFcheck.out &&
check_cache_at DF/DF dirty &&
:'
}
check_cache_at () {
- clean_if_empty=`git-diff-files -- "$1"`
+ clean_if_empty=`git diff-files -- "$1"`
case "$clean_if_empty" in
'') echo "$1: clean" ;;
?*) echo "$1: dirty" ;;
echo nitfol >nitfol &&
echo bozbar >bozbar &&
echo rezrov >rezrov &&
- git-update-index --add nitfol bozbar rezrov &&
- treeH=`git-write-tree` &&
+ git update-index --add nitfol bozbar rezrov &&
+ treeH=`git write-tree` &&
echo treeH $treeH &&
- git-ls-tree $treeH &&
+ git ls-tree $treeH &&
echo gnusto >bozbar &&
- git-update-index --add frotz bozbar --force-remove rezrov &&
- git-ls-files --stage >M.out &&
- treeM=`git-write-tree` &&
+ git update-index --add frotz bozbar --force-remove rezrov &&
+ git ls-files --stage >M.out &&
+ treeM=`git write-tree` &&
echo treeM $treeM &&
- git-ls-tree $treeM &&
+ git ls-tree $treeM &&
sum bozbar frotz nitfol >M.sum &&
- git-diff-tree $treeH $treeM'
+ git diff-tree $treeH $treeM'
test_expect_success \
'1, 2, 3 - no carry forward' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >1-3.out &&
+ git read-tree --reset -u $treeH &&
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >1-3.out &&
cmp M.out 1-3.out &&
sum bozbar frotz nitfol >actual3.sum &&
cmp M.sum actual3.sum &&
test_expect_success \
'4 - carry forward local addition.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo "+100644 X 0 yomin" >expected &&
echo yomin >yomin &&
- git-update-index --add yomin &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >4.out || return 1
+ git update-index --add yomin &&
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >4.out || return 1
diff -U0 M.out 4.out >4diff.out
compare_change 4diff.out expected &&
check_cache_at yomin clean &&
test_expect_success \
'5 - carry forward local addition.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
- git-read-tree -m -u $treeH &&
+ git read-tree --reset -u $treeH &&
+ git read-tree -m -u $treeH &&
echo yomin >yomin &&
- git-update-index --add yomin &&
+ git update-index --add yomin &&
echo yomin yomin >yomin &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >5.out || return 1
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >5.out || return 1
diff -U0 M.out 5.out >5diff.out
compare_change 5diff.out expected &&
check_cache_at yomin dirty &&
test_expect_success \
'6 - local addition already has the same.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo frotz >frotz &&
- git-update-index --add frotz &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >6.out &&
+ git update-index --add frotz &&
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >6.out &&
diff -U0 M.out 6.out &&
check_cache_at frotz clean &&
sum bozbar frotz nitfol >actual3.sum &&
test_expect_success \
'7 - local addition already has the same.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo frotz >frotz &&
- git-update-index --add frotz &&
+ git update-index --add frotz &&
echo frotz frotz >frotz &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >7.out &&
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >7.out &&
diff -U0 M.out 7.out &&
check_cache_at frotz dirty &&
sum bozbar frotz nitfol >actual7.sum &&
test_expect_success \
'8 - conflicting addition.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo frotz frotz >frotz &&
- git-update-index --add frotz &&
- if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+ git update-index --add frotz &&
+ if git read-tree -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'9 - conflicting addition.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo frotz frotz >frotz &&
- git-update-index --add frotz &&
+ git update-index --add frotz &&
echo frotz >frotz &&
- if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if git read-tree -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'10 - path removed.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo rezrov >rezrov &&
- git-update-index --add rezrov &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >10.out &&
+ git update-index --add rezrov &&
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >10.out &&
cmp M.out 10.out &&
sum bozbar frotz nitfol >actual10.sum &&
cmp M.sum actual10.sum'
test_expect_success \
'11 - dirty path removed.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo rezrov >rezrov &&
- git-update-index --add rezrov &&
+ git update-index --add rezrov &&
echo rezrov rezrov >rezrov &&
- if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if git read-tree -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'12 - unmatching local changes being removed.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo rezrov rezrov >rezrov &&
- git-update-index --add rezrov &&
- if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+ git update-index --add rezrov &&
+ if git read-tree -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'13 - unmatching local changes being removed.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo rezrov rezrov >rezrov &&
- git-update-index --add rezrov &&
+ git update-index --add rezrov &&
echo rezrov >rezrov &&
- if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if git read-tree -m -u $treeH $treeM; then false; else :; fi'
cat >expected <<EOF
-100644 X 0 nitfol
test_expect_success \
'14 - unchanged in two heads.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo nitfol nitfol >nitfol &&
- git-update-index --add nitfol &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >14.out || return 1
+ git update-index --add nitfol &&
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >14.out || return 1
diff -U0 M.out 14.out >14diff.out
compare_change 14diff.out expected &&
sum bozbar frotz >actual14.sum &&
test_expect_success \
'15 - unchanged in two heads.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo nitfol nitfol >nitfol &&
- git-update-index --add nitfol &&
+ git update-index --add nitfol &&
echo nitfol nitfol nitfol >nitfol &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >15.out || return 1
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >15.out || return 1
diff -U0 M.out 15.out >15diff.out
compare_change 15diff.out expected &&
check_cache_at nitfol dirty &&
test_expect_success \
'16 - conflicting local change.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo bozbar bozbar >bozbar &&
- git-update-index --add bozbar &&
- if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+ git update-index --add bozbar &&
+ if git read-tree -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'17 - conflicting local change.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo bozbar bozbar >bozbar &&
- git-update-index --add bozbar &&
+ git update-index --add bozbar &&
echo bozbar bozbar bozbar >bozbar &&
- if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if git read-tree -m -u $treeH $treeM; then false; else :; fi'
test_expect_success \
'18 - local change already having a good result.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo gnusto >bozbar &&
- git-update-index --add bozbar &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >18.out &&
+ git update-index --add bozbar &&
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >18.out &&
diff -U0 M.out 18.out &&
check_cache_at bozbar clean &&
sum bozbar frotz nitfol >actual18.sum &&
test_expect_success \
'19 - local change already having a good result, further modified.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo gnusto >bozbar &&
- git-update-index --add bozbar &&
+ git update-index --add bozbar &&
echo gnusto gnusto >bozbar &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >19.out &&
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >19.out &&
diff -U0 M.out 19.out &&
check_cache_at bozbar dirty &&
sum frotz nitfol >actual19.sum &&
test_expect_success \
'20 - no local change, use new tree.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo bozbar >bozbar &&
- git-update-index --add bozbar &&
- git-read-tree -m -u $treeH $treeM &&
- git-ls-files --stage >20.out &&
+ git update-index --add bozbar &&
+ git read-tree -m -u $treeH $treeM &&
+ git ls-files --stage >20.out &&
diff -U0 M.out 20.out &&
check_cache_at bozbar clean &&
sum bozbar frotz nitfol >actual20.sum &&
test_expect_success \
'21 - no local change, dirty cache.' \
'rm -f .git/index nitfol bozbar rezrov frotz &&
- git-read-tree --reset -u $treeH &&
+ git read-tree --reset -u $treeH &&
echo bozbar >bozbar &&
- git-update-index --add bozbar &&
+ git update-index --add bozbar &&
echo gnusto gnusto >bozbar &&
- if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+ if git read-tree -m -u $treeH $treeM; then false; else :; fi'
# Also make sure we did not break DF vs DF/DF case.
test_expect_success \
'DF vs DF/DF case setup.' \
'rm -f .git/index
echo DF >DF &&
- git-update-index --add DF &&
- treeDF=`git-write-tree` &&
+ git update-index --add DF &&
+ treeDF=`git write-tree` &&
echo treeDF $treeDF &&
- git-ls-tree $treeDF &&
+ git ls-tree $treeDF &&
rm -f DF &&
mkdir DF &&
echo DF/DF >DF/DF &&
- git-update-index --add --remove DF DF/DF &&
- treeDFDF=`git-write-tree` &&
+ git update-index --add --remove DF DF/DF &&
+ treeDFDF=`git write-tree` &&
echo treeDFDF $treeDFDF &&
- git-ls-tree $treeDFDF &&
- git-ls-files --stage >DFDF.out'
+ git ls-tree $treeDFDF &&
+ git ls-files --stage >DFDF.out'
test_expect_success \
'DF vs DF/DF case test.' \
'rm -f .git/index &&
rm -fr DF &&
echo DF >DF &&
- git-update-index --add DF &&
- git-read-tree -m -u $treeDF $treeDFDF &&
- git-ls-files --stage >DFDFcheck.out &&
+ git update-index --add DF &&
+ git read-tree -m -u $treeDF $treeDFDF &&
+ git ls-files --stage >DFDFcheck.out &&
diff -U0 DFDF.out DFDFcheck.out &&
check_cache_at DF/DF clean'
# Copyright (c) 2006 Junio C Hamano
#
-test_description='git-read-tree --prefix test.
+test_description='git read-tree --prefix test.
'
. ./test-lib.sh
test_expect_success setup '
echo hello >one &&
- git-update-index --add one &&
- tree=`git-write-tree` &&
+ git update-index --add one &&
+ tree=`git write-tree` &&
echo tree is $tree
'
two/one' >expect
test_expect_success 'read-tree --prefix' '
- git-read-tree --prefix=two/ $tree &&
- git-ls-files >actual &&
+ git read-tree --prefix=two/ $tree &&
+ git ls-files >actual &&
cmp expect actual
'
echo >file2 file two is untracked on the master side &&
echo >subdir/file2 file two is untracked on the master side &&
- git-read-tree -m -u branch-point master side
+ git read-tree -m -u branch-point master side
'
test_expect_success 'three-way not clobbering a working tree file' '
test_expect_success 'update-index and ls-files' '
cd $HERE &&
- git-update-index --add one &&
- case "`git-ls-files`" in
+ git update-index --add one &&
+ case "`git ls-files`" in
one) echo ok one ;;
*) echo bad one; exit 1 ;;
esac &&
cd dir &&
- git-update-index --add two &&
- case "`git-ls-files`" in
+ git update-index --add two &&
+ case "`git ls-files`" in
two) echo ok two ;;
*) echo bad two; exit 1 ;;
esac &&
cd .. &&
- case "`git-ls-files`" in
+ case "`git ls-files`" in
dir/two"$LF"one) echo ok both ;;
*) echo bad; exit 1 ;;
esac
test_expect_success 'cat-file' '
cd $HERE &&
- two=`git-ls-files -s dir/two` &&
+ two=`git ls-files -s dir/two` &&
two=`expr "$two" : "[0-7]* \\([0-9a-f]*\\)"` &&
echo "$two" &&
- git-cat-file -p "$two" >actual &&
+ git cat-file -p "$two" >actual &&
cmp dir/two actual &&
cd dir &&
- git-cat-file -p "$two" >actual &&
+ git cat-file -p "$two" >actual &&
cmp two actual
'
rm -f actual dir/actual
cd $HERE &&
echo a >>one &&
echo d >>dir/two &&
- case "`git-diff-files --name-only`" in
+ case "`git diff-files --name-only`" in
dir/two"$LF"one) echo ok top ;;
*) echo bad top; exit 1 ;;
esac &&
# diff should not omit leading paths
cd dir &&
- case "`git-diff-files --name-only`" in
+ case "`git diff-files --name-only`" in
dir/two"$LF"one) echo ok subdir ;;
*) echo bad subdir; exit 1 ;;
esac &&
- case "`git-diff-files --name-only .`" in
+ case "`git diff-files --name-only .`" in
dir/two) echo ok subdir limited ;;
*) echo bad subdir limited; exit 1 ;;
esac
test_expect_success 'write-tree' '
cd $HERE &&
- top=`git-write-tree` &&
+ top=`git write-tree` &&
echo $top &&
cd dir &&
- sub=`git-write-tree` &&
+ sub=`git write-tree` &&
echo $sub &&
test "z$top" = "z$sub"
'
test_expect_success 'checkout-index' '
cd $HERE &&
- git-checkout-index -f -u one &&
+ git checkout-index -f -u one &&
cmp one original.one &&
cd dir &&
- git-checkout-index -f -u two &&
+ git checkout-index -f -u two &&
cmp two ../original.two
'
test_expect_success 'read-tree' '
cd $HERE &&
rm -f one dir/two &&
- tree=`git-write-tree` &&
- git-read-tree --reset -u "$tree" &&
+ tree=`git write-tree` &&
+ git read-tree --reset -u "$tree" &&
cmp one original.one &&
cmp dir/two original.two &&
cd dir &&
rm -f two &&
- git-read-tree --reset -u "$tree" &&
+ git read-tree --reset -u "$tree" &&
cmp two ../original.two &&
cmp ../one ../original.one
'
# Copyright (C) 2005 Rene Scharfe
#
-test_description='git-commit-tree options test
+test_description='git commit-tree options test
-This test checks that git-commit-tree can create a specific commit
+This test checks that git commit-tree can create a specific commit
object by defining all environment variables that it understands.
'
test_expect_success \
'test preparation: write empty tree' \
- 'git-write-tree >treeid'
+ 'git write-tree >treeid'
test_expect_success \
'construct commit' \
GIT_COMMITTER_NAME="Committer Name" \
GIT_COMMITTER_EMAIL="committer@email" \
GIT_COMMITTER_DATE="2005-05-26 23:30" \
- TZ=GMT git-commit-tree `cat treeid` >commitid 2>/dev/null'
+ TZ=GMT git commit-tree `cat treeid` >commitid 2>/dev/null'
test_expect_success \
'read commit' \
- 'git-cat-file commit `cat commitid` >commit'
+ 'git cat-file commit `cat commitid` >commit'
test_expect_success \
'compare commit' \
echo "Hello World" > hello
echo "Silly example" > example
-git-update-index --add hello example
+git update-index --add hello example
-test_expect_success 'blob' "test blob = \"$(git-cat-file -t 557db03)\""
+test_expect_success 'blob' "test blob = \"$(git cat-file -t 557db03)\""
-test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git-cat-file blob 557db03)\""
+test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git cat-file blob 557db03)\""
echo "It's a new day for git" >>hello
cat > diff.expect << EOF
Hello World
+It's a new day for git
EOF
-git-diff-files -p > diff.output
-test_expect_success 'git-diff-files -p' 'cmp diff.expect diff.output'
+git diff-files -p > diff.output
+test_expect_success 'git diff-files -p' 'cmp diff.expect diff.output'
git diff > diff.output
test_expect_success 'git diff' 'cmp diff.expect diff.output'
-tree=$(git-write-tree 2>/dev/null)
+tree=$(git write-tree 2>/dev/null)
test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree"
-output="$(echo "Initial commit" | git-commit-tree $(git-write-tree) 2>&1 > .git/refs/heads/master)"
+output="$(echo "Initial commit" | git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master)"
-git-diff-index -p HEAD > diff.output
-test_expect_success 'git-diff-index -p HEAD' 'cmp diff.expect diff.output'
+git diff-index -p HEAD > diff.output
+test_expect_success 'git diff-index -p HEAD' 'cmp diff.expect diff.output'
git diff HEAD > diff.output
test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output'
#rm hello
-#test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\""
+#test_expect_success 'git read-tree --reset HEAD' "git read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git update-index --refresh)\""
cat > whatchanged.expect << EOF
commit VARIABLE
+Hello World
EOF
-git-whatchanged -p --root | \
+git whatchanged -p --root | \
sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \
-e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
> whatchanged.output
-test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
+test_expect_success 'git whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
git tag my-first-tag
test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag'
test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]'
test_done
-
# Copyright (c) 2005 Johannes Schindelin
#
-test_description='Test git-config in different settings'
+test_description='Test git config in different settings'
. ./test-lib.sh
test -f .git/config && rm .git/config
-git-config core.penguin "little blue"
+git config core.penguin "little blue"
cat > expect << EOF
[core]
test_expect_success 'initial' 'cmp .git/config expect'
-git-config Core.Movie BadPhysics
+git config Core.Movie BadPhysics
cat > expect << EOF
[core]
test_expect_success 'mixed case' 'cmp .git/config expect'
-git-config Cores.WhatEver Second
+git config Cores.WhatEver Second
cat > expect << EOF
[core]
test_expect_success 'similar section' 'cmp .git/config expect'
-git-config CORE.UPPERCASE true
+git config CORE.UPPERCASE true
cat > expect << EOF
[core]
test_expect_success 'similar section' 'cmp .git/config expect'
test_expect_success 'replace with non-match' \
- 'git-config core.penguin kingpin !blue'
+ 'git config core.penguin kingpin !blue'
test_expect_success 'replace with non-match (actually matching)' \
- 'git-config core.penguin "very blue" !kingpin'
+ 'git config core.penguin "very blue" !kingpin'
cat > expect << EOF
[core]
cp .git/config .git/config2
test_expect_success 'multiple unset' \
- 'git-config --unset-all beta.haha'
+ 'git config --unset-all beta.haha'
cat > expect << EOF
[beta] ; silly comment # another comment
mv .git/config2 .git/config
test_expect_success '--replace-all' \
- 'git-config --replace-all beta.haha gamma'
+ 'git config --replace-all beta.haha gamma'
cat > expect << EOF
[beta] ; silly comment # another comment
test_expect_success 'all replaced' 'cmp .git/config expect'
-git-config beta.haha alpha
+git config beta.haha alpha
cat > expect << EOF
[beta] ; silly comment # another comment
test_expect_success 'really mean test' 'cmp .git/config expect'
-git-config nextsection.nonewline wow
+git config nextsection.nonewline wow
cat > expect << EOF
[beta] ; silly comment # another comment
test_expect_success 'really really mean test' 'cmp .git/config expect'
-test_expect_success 'get value' 'test alpha = $(git-config beta.haha)'
-git-config --unset beta.haha
+test_expect_success 'get value' 'test alpha = $(git config beta.haha)'
+git config --unset beta.haha
cat > expect << EOF
[beta] ; silly comment # another comment
test_expect_success 'unset' 'cmp .git/config expect'
-git-config nextsection.NoNewLine "wow2 for me" "for me$"
+git config nextsection.NoNewLine "wow2 for me" "for me$"
cat > expect << EOF
[beta] ; silly comment # another comment
test_expect_success 'multivar' 'cmp .git/config expect'
test_expect_success 'non-match' \
- 'git-config --get nextsection.nonewline !for'
+ 'git config --get nextsection.nonewline !for'
test_expect_success 'non-match value' \
- 'test wow = $(git-config --get nextsection.nonewline !for)'
+ 'test wow = $(git config --get nextsection.nonewline !for)'
test_expect_failure 'ambiguous get' \
- 'git-config --get nextsection.nonewline'
+ 'git config --get nextsection.nonewline'
test_expect_success 'get multivar' \
- 'git-config --get-all nextsection.nonewline'
+ 'git config --get-all nextsection.nonewline'
-git-config nextsection.nonewline "wow3" "wow$"
+git config nextsection.nonewline "wow3" "wow$"
cat > expect << EOF
[beta] ; silly comment # another comment
test_expect_success 'multivar replace' 'cmp .git/config expect'
-test_expect_failure 'ambiguous value' 'git-config nextsection.nonewline'
+test_expect_failure 'ambiguous value' 'git config nextsection.nonewline'
test_expect_failure 'ambiguous unset' \
- 'git-config --unset nextsection.nonewline'
+ 'git config --unset nextsection.nonewline'
test_expect_failure 'invalid unset' \
- 'git-config --unset somesection.nonewline'
+ 'git config --unset somesection.nonewline'
-git-config --unset nextsection.nonewline "wow3$"
+git config --unset nextsection.nonewline "wow3$"
cat > expect << EOF
[beta] ; silly comment # another comment
test_expect_success 'multivar unset' 'cmp .git/config expect'
-test_expect_failure 'invalid key' 'git-config inval.2key blabla'
+test_expect_failure 'invalid key' 'git config inval.2key blabla'
-test_expect_success 'correct key' 'git-config 123456.a123 987'
+test_expect_success 'correct key' 'git config 123456.a123 987'
test_expect_success 'hierarchical section' \
- 'git-config Version.1.2.3eX.Alpha beta'
+ 'git config Version.1.2.3eX.Alpha beta'
cat > expect << EOF
[beta] ; silly comment # another comment
EOF
test_expect_success 'working --list' \
- 'git-config --list > output && cmp output expect'
+ 'git config --list > output && cmp output expect'
cat > expect << EOF
beta.noindent sillyValue
EOF
test_expect_success '--get-regexp' \
- 'git-config --get-regexp in > output && cmp output expect'
+ 'git config --get-regexp in > output && cmp output expect'
-git-config --add nextsection.nonewline "wow4 for you"
+git config --add nextsection.nonewline "wow4 for you"
cat > expect << EOF
wow2 for me
EOF
test_expect_success '--add' \
- 'git-config --get-all nextsection.nonewline > output && cmp output expect'
+ 'git config --get-all nextsection.nonewline > output && cmp output expect'
cat > .git/config << EOF
[novalue]
EOF
test_expect_success 'get variable with no value' \
- 'git-config --get novalue.variable ^$'
+ 'git config --get novalue.variable ^$'
echo novalue.variable > expect
test_expect_success 'get-regexp variable with no value' \
- 'git-config --get-regexp novalue > output &&
+ 'git config --get-regexp novalue > output &&
cmp output expect'
-git-config > output 2>&1
+git config > output 2>&1
test_expect_success 'no arguments, but no crash' \
"test $? = 129 && grep usage output"
c = d
EOF
-git-config a.x y
+git config a.x y
cat > expect << EOF
[a.b]
test_expect_success 'new section is partial match of another' 'cmp .git/config expect'
-git-config b.x y
-git-config a.b c
+git config b.x y
+git config a.b c
cat > expect << EOF
[a.b]
test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect'
+test_expect_success 'alternative GIT_CONFIG (non-existing file should fail)' \
+ 'git config --file non-existing-config -l; test $? != 0'
+
cat > other-config << EOF
[ein]
bahn = strasse
ein.bahn=strasse
EOF
-GIT_CONFIG=other-config git-config -l > output
+GIT_CONFIG=other-config git config -l > output
test_expect_success 'alternative GIT_CONFIG' 'cmp output expect'
-GIT_CONFIG=other-config git-config anwohner.park ausweis
+test_expect_success 'alternative GIT_CONFIG (--file)' \
+ 'git config --file other-config -l > output && cmp output expect'
+
+GIT_CONFIG=other-config git config anwohner.park ausweis
cat > expect << EOF
[ein]
EOF
test_expect_success "rename section" \
- "git-config --rename-section branch.eins branch.zwei"
+ "git config --rename-section branch.eins branch.zwei"
cat > expect << EOF
# Hallo
test_expect_success "rename succeeded" "git diff expect .git/config"
test_expect_failure "rename non-existing section" \
- 'git-config --rename-section branch."world domination" branch.drei'
+ 'git config --rename-section branch."world domination" branch.drei'
test_expect_success "rename succeeded" "git diff expect .git/config"
test_expect_success "rename another section" \
- 'git-config --rename-section branch."1 234 blabl/a" branch.drei'
+ 'git config --rename-section branch."1 234 blabl/a" branch.drei'
cat > expect << EOF
# Hallo
test_expect_success 'section ending' '
- git-config gitcvs.enabled true &&
- git-config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
- git-config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
+ git config gitcvs.enabled true &&
+ git config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
+ git config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
cmp .git/config expect
'
test_expect_success numbers '
- git-config kilo.gram 1k &&
- git-config mega.ton 1m &&
- k=$(git-config --int --get kilo.gram) &&
+ git config kilo.gram 1k &&
+ git config mega.ton 1m &&
+ k=$(git config --int --get kilo.gram) &&
test z1024 = "z$k" &&
- m=$(git-config --int --get mega.ton) &&
+ m=$(git config --int --get mega.ton) &&
test z1048576 = "z$m"
'
test_expect_success bool '
- git-config bool.true1 01 &&
- git-config bool.true2 -1 &&
- git-config bool.true3 YeS &&
- git-config bool.true4 true &&
- git-config bool.false1 000 &&
- git-config bool.false2 "" &&
- git-config bool.false3 nO &&
- git-config bool.false4 FALSE &&
+ git config bool.true1 01 &&
+ git config bool.true2 -1 &&
+ git config bool.true3 YeS &&
+ git config bool.true4 true &&
+ git config bool.false1 000 &&
+ git config bool.false2 "" &&
+ git config bool.false3 nO &&
+ git config bool.false4 FALSE &&
rm -f result &&
for i in 1 2 3 4
do
- git-config --bool --get bool.true$i >>result
- git-config --bool --get bool.false$i >>result
+ git config --bool --get bool.true$i >>result
+ git config --bool --get bool.false$i >>result
done &&
cmp expect result'
-test_expect_failure 'invalid bool' '
+test_expect_failure 'invalid bool (--get)' '
+
+ git config bool.nobool foobar &&
+ git config --bool --get bool.nobool'
+
+test_expect_failure 'invalid bool (set)' '
+
+ git config --bool bool.nobool foobar'
+
+rm .git/config
+
+cat > expect <<\EOF
+[bool]
+ true1 = true
+ true2 = true
+ true3 = true
+ true4 = true
+ false1 = false
+ false2 = false
+ false3 = false
+ false4 = false
+EOF
+
+test_expect_success 'set --bool' '
+
+ git config --bool bool.true1 01 &&
+ git config --bool bool.true2 -1 &&
+ git config --bool bool.true3 YeS &&
+ git config --bool bool.true4 true &&
+ git config --bool bool.false1 000 &&
+ git config --bool bool.false2 "" &&
+ git config --bool bool.false3 nO &&
+ git config --bool bool.false4 FALSE &&
+ cmp expect .git/config'
+
+rm .git/config
+
+cat > expect <<\EOF
+[int]
+ val1 = 1
+ val2 = -1
+ val3 = 5242880
+EOF
- git-config bool.nobool foobar &&
- git-config --bool --get bool.nobool'
+test_expect_success 'set --int' '
+
+ git config --int int.val1 01 &&
+ git config --int int.val2 -1 &&
+ git config --int int.val3 5m &&
+ cmp expect .git/config'
rm .git/config
-git-config quote.leading " test"
-git-config quote.ending "test "
-git-config quote.semicolon "test;test"
-git-config quote.hash "test#test"
+git config quote.leading " test"
+git config quote.ending "test "
+git config quote.semicolon "test;test"
+git config quote.hash "test#test"
cat > expect << EOF
[quote]
test_expect_success 'value continued on next line' 'cmp result expect'
-test_done
+cat > .git/config <<\EOF
+[section "sub=section"]
+ val1 = foo=bar
+ val2 = foo\nbar
+ val3 = \n\n
+ val4 =
+ val5
+EOF
+
+cat > expect <<\EOF
+section.sub=section.val1
+foo=barQsection.sub=section.val2
+foo
+barQsection.sub=section.val3
+
+
+Qsection.sub=section.val4
+Qsection.sub=section.val5Q
+EOF
+
+git config --null --list | tr '[\000]' 'Q' > result
+echo >>result
+test_expect_success '--null --list' 'cmp result expect'
+
+git config --null --get-regexp 'val[0-9]' | tr '[\000]' 'Q' > result
+echo >>result
+
+test_expect_success '--null --get-regexp' 'cmp result expect'
+
+test_expect_success 'symlinked configuration' '
+
+ ln -s notyet myconfig &&
+ GIT_CONFIG=myconfig git config test.frotz nitfol &&
+ test -h myconfig &&
+ test -f notyet &&
+ test "z$(GIT_CONFIG=notyet git config test.frotz)" = znitfol &&
+ GIT_CONFIG=myconfig git config test.xyzzy rezrov &&
+ test -h myconfig &&
+ test -f notyet &&
+ test "z$(GIT_CONFIG=notyet git config test.frotz)" = znitfol &&
+ test "z$(GIT_CONFIG=notyet git config test.xyzzy)" = zrezrov
+
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Schindelin
+#
+
+test_description='Test shared repository initialization'
+
+. ./test-lib.sh
+
+test_expect_success 'shared=all' '
+ mkdir sub &&
+ cd sub &&
+ git init --shared=all &&
+ test 2 = $(git config core.sharedrepository)
+'
+
+test_expect_success 'update-server-info honors core.sharedRepository' '
+ : > a1 &&
+ git add a1 &&
+ test_tick &&
+ git commit -m a1 &&
+ umask 0277 &&
+ git update-server-info &&
+ test 444 = $(stat -c %a .git/info/refs)
+'
+
+test_done
# Copyright (c) 2006 Shawn Pearce
#
-test_description='Test git-update-ref and basic ref logging'
+test_description='Test git update-ref and basic ref logging'
. ./test-lib.sh
Z=0000000000000000000000000000000000000000
test_expect_success \
"create $m" \
- "git-update-ref $m $A &&
+ "git update-ref $m $A &&
test $A"' = $(cat .git/'"$m"')'
test_expect_success \
"create $m" \
- "git-update-ref $m $B $A &&
+ "git update-ref $m $B $A &&
test $B"' = $(cat .git/'"$m"')'
rm -f .git/$m
test_expect_success \
"fail to create $n" \
"touch .git/$n_dir
- git-update-ref $n $A >out 2>err"'
+ git update-ref $n $A >out 2>err"'
test $? != 0'
rm -f .git/$n_dir out err
test_expect_success \
"create $m (by HEAD)" \
- "git-update-ref HEAD $A &&
+ "git update-ref HEAD $A &&
test $A"' = $(cat .git/'"$m"')'
test_expect_success \
"create $m (by HEAD)" \
- "git-update-ref HEAD $B $A &&
+ "git update-ref HEAD $B $A &&
test $B"' = $(cat .git/'"$m"')'
rm -f .git/$m
test_expect_failure \
'(not) create HEAD with old sha1' \
- "git-update-ref HEAD $A $B"
+ "git update-ref HEAD $A $B"
test_expect_failure \
"(not) prior created .git/$m" \
"test -f .git/$m"
test_expect_success \
"create HEAD" \
- "git-update-ref HEAD $A"
+ "git update-ref HEAD $A"
test_expect_failure \
'(not) change HEAD with wrong SHA1' \
- "git-update-ref HEAD $B $Z"
+ "git update-ref HEAD $B $Z"
test_expect_failure \
"(not) changed .git/$m" \
"test $B"' = $(cat .git/'"$m"')'
test_expect_success \
"create $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
- git-update-ref HEAD '"$A"' -m "Initial Creation" &&
+ git update-ref HEAD '"$A"' -m "Initial Creation" &&
test '"$A"' = $(cat .git/'"$m"')'
test_expect_success \
"update $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:31" \
- git-update-ref HEAD'" $B $A "'-m "Switch" &&
+ git update-ref HEAD'" $B $A "'-m "Switch" &&
test '"$B"' = $(cat .git/'"$m"')'
test_expect_success \
"set $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:41" \
- git-update-ref HEAD'" $A &&
+ git update-ref HEAD'" $A &&
test $A"' = $(cat .git/'"$m"')'
cat >expect <<EOF
test_expect_success \
'enable core.logAllRefUpdates' \
- 'git-config core.logAllRefUpdates true &&
- test true = $(git-config --bool --get core.logAllRefUpdates)'
+ 'git config core.logAllRefUpdates true &&
+ test true = $(git config --bool --get core.logAllRefUpdates)'
test_expect_success \
"create $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:32" \
- git-update-ref HEAD'" $A "'-m "Initial Creation" &&
+ git update-ref HEAD'" $A "'-m "Initial Creation" &&
test '"$A"' = $(cat .git/'"$m"')'
test_expect_success \
"update $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:33" \
- git-update-ref HEAD'" $B $A "'-m "Switch" &&
+ git update-ref HEAD'" $B $A "'-m "Switch" &&
test '"$B"' = $(cat .git/'"$m"')'
test_expect_success \
"set $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:43" \
- git-update-ref HEAD '"$A &&
+ git update-ref HEAD '"$A &&
test $A"' = $(cat .git/'"$m"')'
cat >expect <<EOF
'diff expect .git/logs/$m'
rm -f .git/$m .git/logs/$m expect
-git-update-ref $m $D
+git update-ref $m $D
cat >.git/logs/$m <<EOF
$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
test_expect_success \
'Query "master@{May 25 2005}" (before history)' \
'rm -f o e
- git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
+ git rev-parse --verify "master@{May 25 2005}" >o 2>e &&
test '"$C"' = $(cat o) &&
test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
test_expect_success \
"Query master@{2005-05-25} (before history)" \
'rm -f o e
- git-rev-parse --verify master@{2005-05-25} >o 2>e &&
+ git rev-parse --verify master@{2005-05-25} >o 2>e &&
test '"$C"' = $(cat o) &&
echo test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
'rm -f o e
- git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
+ git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
test '"$C"' = $(cat o) &&
test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
'rm -f o e
- git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
+ git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
test '"$A"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
'rm -f o e
- git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
+ git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
test '"$B"' = $(cat o) &&
test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
'rm -f o e
- git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
+ git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
test '"$Z"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
'rm -f o e
- git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
+ git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
test '"$E"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-28}" (past end of history)' \
'rm -f o e
- git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
+ git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
test '"$D"' = $(cat o) &&
test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"'
test_expect_success \
'creating initial files' \
'echo TEST >F &&
- git-add F &&
+ git add F &&
GIT_AUTHOR_DATE="2005-05-26 23:30" \
GIT_COMMITTER_DATE="2005-05-26 23:30" git-commit -m add -a &&
- h_TEST=$(git-rev-parse --verify HEAD)
+ h_TEST=$(git rev-parse --verify HEAD)
echo The other day this did not work. >M &&
echo And then Bob told me how to fix it. >>M &&
echo OTHER >F &&
GIT_AUTHOR_DATE="2005-05-26 23:41" \
GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
- h_OTHER=$(git-rev-parse --verify HEAD) &&
+ h_OTHER=$(git rev-parse --verify HEAD) &&
echo FIXED >F &&
GIT_AUTHOR_DATE="2005-05-26 23:44" \
GIT_COMMITTER_DATE="2005-05-26 23:44" git-commit --amend &&
- h_FIXED=$(git-rev-parse --verify HEAD) &&
+ h_FIXED=$(git rev-parse --verify HEAD) &&
echo TEST+FIXED >F &&
echo Merged initial commit and a later commit. >M &&
echo $h_TEST >.git/MERGE_HEAD &&
GIT_AUTHOR_DATE="2005-05-26 23:45" \
GIT_COMMITTER_DATE="2005-05-26 23:45" git-commit -F M &&
- h_MERGED=$(git-rev-parse --verify HEAD)
+ h_MERGED=$(git rev-parse --verify HEAD)
rm -f M'
cat >expect <<EOF
unset h_TEST h_OTHER h_FIXED h_MERGED
test_expect_success \
- 'git-cat-file blob master:F (expect OTHER)' \
- 'test OTHER = $(git-cat-file blob master:F)'
+ 'git cat-file blob master:F (expect OTHER)' \
+ 'test OTHER = $(git cat-file blob master:F)'
test_expect_success \
- 'git-cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
- 'test TEST = $(git-cat-file blob "master@{2005-05-26 23:30}:F")'
+ 'git cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
+ 'test TEST = $(git cat-file blob "master@{2005-05-26 23:30}:F")'
test_expect_success \
- 'git-cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
- 'test OTHER = $(git-cat-file blob "master@{2005-05-26 23:42}:F")'
+ 'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
+ 'test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F")'
test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='Test fsck --lost-found'
+. ./test-lib.sh
+
+test_expect_success setup '
+ git config core.logAllRefUpdates 0 &&
+ : > file1 &&
+ git add file1 &&
+ test_tick &&
+ git commit -m initial &&
+ echo 1 > file1 &&
+ echo 2 > file2 &&
+ git add file1 file2 &&
+ test_tick &&
+ git commit -m second &&
+ echo 3 > file3 &&
+ git add file3
+'
+
+test_expect_success 'lost and found something' '
+ git rev-parse HEAD > lost-commit &&
+ git rev-parse :file3 > lost-other &&
+ test_tick &&
+ git reset --hard HEAD^ &&
+ git fsck --lost-found &&
+ test 2 = $(ls .git/lost-found/*/* | wc -l) &&
+ test -f .git/lost-found/commit/$(cat lost-commit) &&
+ test -f .git/lost-found/other/$(cat lost-other)
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test git rev-parse'
+. ./test-lib.sh
+
+test_rev_parse() {
+ name=$1
+ shift
+
+ test_expect_success "$name: is-bare-repository" \
+ "test '$1' = \"\$(git rev-parse --is-bare-repository)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-git-dir" \
+ "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-work-tree" \
+ "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: prefix" \
+ "test '$1' = \"\$(git rev-parse --show-prefix)\""
+ shift
+ [ $# -eq 0 ] && return
+}
+
+test_rev_parse toplevel false false true ''
+
+cd .git || exit 1
+test_rev_parse .git/ true true false ''
+cd objects || exit 1
+test_rev_parse .git/objects/ true true false ''
+cd ../.. || exit 1
+
+mkdir -p sub/dir || exit 1
+cd sub/dir || exit 1
+test_rev_parse subdirectory false false true sub/dir/
+cd ../.. || exit 1
+
+git config core.bare true
+test_rev_parse 'core.bare = true' true false false
+
+git config --unset core.bare
+test_rev_parse 'core.bare undefined' false false true
+
+mkdir work || exit 1
+cd work || exit 1
+export GIT_DIR=../.git
+export GIT_CONFIG="$(pwd)"/../.git/config
+
+git config core.bare false
+test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true work/
+
+git config core.bare true
+test_rev_parse 'GIT_DIR=../.git, core.bare = true' true false false ''
+
+git config --unset core.bare
+test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true work/
+
+mv ../.git ../repo.git || exit 1
+export GIT_DIR=../repo.git
+export GIT_CONFIG="$(pwd)"/../repo.git/config
+
+git config core.bare false
+test_rev_parse 'GIT_DIR=../repo.git, core.bare = false' false false true ''
+
+git config core.bare true
+test_rev_parse 'GIT_DIR=../repo.git, core.bare = true' true false false ''
+
+git config --unset core.bare
+test_rev_parse 'GIT_DIR=../repo.git, core.bare undefined' false false true ''
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test separate work tree'
+. ./test-lib.sh
+
+test_rev_parse() {
+ name=$1
+ shift
+
+ test_expect_success "$name: is-bare-repository" \
+ "test '$1' = \"\$(git rev-parse --is-bare-repository)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-git-dir" \
+ "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-work-tree" \
+ "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: prefix" \
+ "test '$1' = \"\$(git rev-parse --show-prefix)\""
+ shift
+ [ $# -eq 0 ] && return
+}
+
+mkdir -p work/sub/dir || exit 1
+mv .git repo.git || exit 1
+
+say "core.worktree = relative path"
+export GIT_DIR=repo.git
+export GIT_CONFIG="$(pwd)"/$GIT_DIR/config
+unset GIT_WORK_TREE
+git config core.worktree ../work
+test_rev_parse 'outside' false false false
+cd work || exit 1
+export GIT_DIR=../repo.git
+export GIT_CONFIG="$(pwd)"/$GIT_DIR/config
+test_rev_parse 'inside' false false true ''
+cd sub/dir || exit 1
+export GIT_DIR=../../../repo.git
+export GIT_CONFIG="$(pwd)"/$GIT_DIR/config
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+say "core.worktree = absolute path"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+git config core.worktree "$(pwd)/work"
+test_rev_parse 'outside' false false false
+cd work || exit 1
+test_rev_parse 'inside' false false true ''
+cd sub/dir || exit 1
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+say "GIT_WORK_TREE=relative path (override core.worktree)"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+git config core.worktree non-existent
+export GIT_WORK_TREE=work
+test_rev_parse 'outside' false false false
+cd work || exit 1
+export GIT_WORK_TREE=.
+test_rev_parse 'inside' false false true ''
+cd sub/dir || exit 1
+export GIT_WORK_TREE=../..
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+mv work repo.git/work
+
+say "GIT_WORK_TREE=absolute path, work tree below git dir"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+export GIT_WORK_TREE=$(pwd)/repo.git/work
+test_rev_parse 'outside' false false false
+cd repo.git || exit 1
+test_rev_parse 'in repo.git' false true false
+cd objects || exit 1
+test_rev_parse 'in repo.git/objects' false true false
+cd ../work || exit 1
+test_rev_parse 'in repo.git/work' false true true ''
+cd sub/dir || exit 1
+test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/
+cd ../../../.. || exit 1
+
+test_expect_success 'repo finds its work tree' '
+ (cd repo.git &&
+ : > work/sub/dir/untracked &&
+ test sub/dir/untracked = "$(git ls-files --others)")
+'
+
+test_expect_success 'repo finds its work tree from work tree, too' '
+ (cd repo.git/work/sub/dir &&
+ : > tracked &&
+ git --git-dir=../../.. add tracked &&
+ cd ../../.. &&
+ test sub/dir/tracked = "$(git ls-files)")
+'
+
+test_done
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-checkout-index test.
+test_description='git checkout-index test.
This test registers the following filesystem structure in the
cache:
path0/file0 - a file in a directory
path1 - a file
-The git-checkout-index command should fail when attempting to checkout
+The git checkout-index command should fail when attempting to checkout
path0, finding it is occupied by a directory, and path1/file1, finding
path1 is occupied by a non-directory. With "-f" flag, it should remove
the conflicting paths and succeed.
date >path1/file1
test_expect_success \
- 'git-update-index --add various paths.' \
- 'git-update-index --add path0 path1/file1'
+ 'git update-index --add various paths.' \
+ 'git update-index --add path0 path1/file1'
rm -fr path0 path1
mkdir path0
date >path1
test_expect_failure \
- 'git-checkout-index without -f should fail on conflicting work tree.' \
- 'git-checkout-index -a'
+ 'git checkout-index without -f should fail on conflicting work tree.' \
+ 'git checkout-index -a'
test_expect_success \
- 'git-checkout-index with -f should succeed.' \
- 'git-checkout-index -f -a'
+ 'git checkout-index with -f should succeed.' \
+ 'git checkout-index -f -a'
test_expect_success \
- 'git-checkout-index conflicting paths.' \
+ 'git checkout-index conflicting paths.' \
'test -f path0 && test -d path1 && test -f path1/file1'
test_done
-
-
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-checkout-index test.
+test_description='git checkout-index test.
This test registers the following filesystem structure in the cache:
find path? -ls |
sed -e 's/^[0-9]* * [0-9]* * \([-bcdl]\)[^ ]* *[0-9]* *[^ ]* *[^ ]* *[0-9]* [A-Z][a-z][a-z] [0-9][0-9] [^ ]* /fs: \1 /'
# what's in the cache, just mode and name
- git-ls-files --stage |
+ git ls-files --stage |
sed -e 's/^\([0-9]*\) [0-9a-f]* [0-3] /ca: \1 /'
# what's in the tree, just mode and name.
- git-ls-tree -r "$1" |
+ git ls-tree -r "$1" |
sed -e 's/^\([0-9]*\) [^ ]* [0-9a-f]* /tr: \1 /'
}
mkdir path0
date >path0/file0
test_expect_success \
- 'git-update-index --add path0/file0' \
- 'git-update-index --add path0/file0'
+ 'git update-index --add path0/file0' \
+ 'git update-index --add path0/file0'
test_expect_success \
- 'writing tree out with git-write-tree' \
- 'tree1=$(git-write-tree)'
+ 'writing tree out with git write-tree' \
+ 'tree1=$(git write-tree)'
test_debug 'show_files $tree1'
mkdir path1
date >path1/file1
test_expect_success \
- 'git-update-index --add path1/file1' \
- 'git-update-index --add path1/file1'
+ 'git update-index --add path1/file1' \
+ 'git update-index --add path1/file1'
test_expect_success \
- 'writing tree out with git-write-tree' \
- 'tree2=$(git-write-tree)'
+ 'writing tree out with git write-tree' \
+ 'tree2=$(git write-tree)'
test_debug 'show_files $tree2'
rm -fr path1
test_expect_success \
'read previously written tree and checkout.' \
- 'git-read-tree -m $tree1 && git-checkout-index -f -a'
+ 'git read-tree -m $tree1 && git checkout-index -f -a'
test_debug 'show_files $tree1'
ln -s path0 path1
test_expect_success \
- 'git-update-index --add a symlink.' \
- 'git-update-index --add path1'
+ 'git update-index --add a symlink.' \
+ 'git update-index --add path1'
test_expect_success \
- 'writing tree out with git-write-tree' \
- 'tree3=$(git-write-tree)'
+ 'writing tree out with git write-tree' \
+ 'tree3=$(git write-tree)'
test_debug 'show_files $tree3'
# Morten says "Got that?" here.
test_expect_success \
'read previously written tree and checkout.' \
- 'git-read-tree $tree2 && git-checkout-index -f -a'
+ 'git read-tree $tree2 && git checkout-index -f -a'
test_debug 'show_files $tree2'
test_expect_success \
test ! -h path1/file1 && test -f path1/file1'
test_done
-
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-checkout-index -u test.
+test_description='git checkout-index -u test.
-With -u flag, git-checkout-index internally runs the equivalent of
-git-update-index --refresh on the checked out entry.'
+With -u flag, git checkout-index internally runs the equivalent of
+git update-index --refresh on the checked out entry.'
. ./test-lib.sh
test_expect_success \
'preparation' '
echo frotz >path0 &&
-git-update-index --add path0 &&
-t=$(git-write-tree)'
+git update-index --add path0 &&
+t=$(git write-tree)'
test_expect_failure \
-'without -u, git-checkout-index smudges stat information.' '
+'without -u, git checkout-index smudges stat information.' '
rm -f path0 &&
-git-read-tree $t &&
-git-checkout-index -f -a &&
-git-diff-files | diff - /dev/null'
+git read-tree $t &&
+git checkout-index -f -a &&
+git diff-files | diff - /dev/null'
test_expect_success \
-'with -u, git-checkout-index picks up stat information from new files.' '
+'with -u, git checkout-index picks up stat information from new files.' '
rm -f path0 &&
-git-read-tree $t &&
-git-checkout-index -u -f -a &&
-git-diff-files | diff - /dev/null'
+git read-tree $t &&
+git checkout-index -u -f -a &&
+git diff-files | diff - /dev/null'
test_done
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-checkout-index --prefix test.
+test_description='git checkout-index --prefix test.
This test makes sure that --prefix option works as advertised, and
also verifies that such leading path may contain symlinks, unlike
'mkdir path1 &&
echo frotz >path0 &&
echo rezrov >path1/file1 &&
- git-update-index --add path0 path1/file1'
+ git update-index --add path0 path1/file1'
test_expect_success \
'have symlink in place where dir is expected.' \
'rm -fr path0 path1 &&
mkdir path2 &&
ln -s path2 path1 &&
- git-checkout-index -f -a &&
+ git checkout-index -f -a &&
test ! -h path1 && test -d path1 &&
test -f path1/file1 && test ! -f path2/file1'
'use --prefix=path2/' \
'rm -fr path0 path1 path2 &&
mkdir path2 &&
- git-checkout-index --prefix=path2/ -f -a &&
+ git checkout-index --prefix=path2/ -f -a &&
test -f path2/path0 &&
test -f path2/path1/file1 &&
test ! -f path0 &&
test_expect_success \
'use --prefix=tmp-' \
'rm -fr path0 path1 path2 tmp* &&
- git-checkout-index --prefix=tmp- -f -a &&
+ git checkout-index --prefix=tmp- -f -a &&
test -f tmp-path0 &&
test -f tmp-path1/file1 &&
test ! -f path0 &&
'rm -fr path0 path1 path2 tmp* &&
echo nitfol >tmp-path1 &&
mkdir tmp-path0 &&
- git-checkout-index --prefix=tmp- -f -a &&
+ git checkout-index --prefix=tmp- -f -a &&
test -f tmp-path0 &&
test -f tmp-path1/file1 &&
test ! -f path0 &&
'rm -fr path0 path1 path2 tmp* &&
mkdir tmp1 tmp1/orary &&
ln -s tmp1 tmp &&
- git-checkout-index --prefix=tmp/orary/ -f -a &&
+ git checkout-index --prefix=tmp/orary/ -f -a &&
test -d tmp1/orary &&
test -f tmp1/orary/path0 &&
test -f tmp1/orary/path1/file1 &&
'rm -fr path0 path1 path2 tmp* &&
mkdir tmp1 &&
ln -s tmp1 tmp &&
- git-checkout-index --prefix=tmp/orary- -f -a &&
+ git checkout-index --prefix=tmp/orary- -f -a &&
test -f tmp1/orary-path0 &&
test -f tmp1/orary-path1/file1 &&
test -h tmp'
'rm -fr path0 path1 path2 tmp* &&
mkdir tmp1 &&
ln -s tmp1 tmp-path1 &&
- git-checkout-index --prefix=tmp- -f -a &&
+ git checkout-index --prefix=tmp- -f -a &&
test -f tmp-path0 &&
test ! -h tmp-path1 &&
test -d tmp-path1 &&
# Copyright (c) 2006 Shawn Pearce
#
-test_description='git-checkout-index --temp test.
+test_description='git checkout-index --temp test.
-With --temp flag, git-checkout-index writes to temporary merge files
+With --temp flag, git checkout-index writes to temporary merge files
rather than the tracked path.'
. ./test-lib.sh
echo tree1path3 >path3 &&
echo tree1path4 >path4 &&
echo tree1asubdir/path5 >asubdir/path5 &&
-git-update-index --add path0 path1 path3 path4 asubdir/path5 &&
-t1=$(git-write-tree) &&
+git update-index --add path0 path1 path3 path4 asubdir/path5 &&
+t1=$(git write-tree) &&
rm -f path* .merge_* out .git/index &&
echo tree2path0 >path0 &&
echo tree2path1 >path1 &&
echo tree2path2 >path2 &&
echo tree2path4 >path4 &&
-git-update-index --add path0 path1 path2 path4 &&
-t2=$(git-write-tree) &&
+git update-index --add path0 path1 path2 path4 &&
+t2=$(git write-tree) &&
rm -f path* .merge_* out .git/index &&
echo tree2path0 >path0 &&
echo tree3path1 >path1 &&
echo tree3path2 >path2 &&
echo tree3path3 >path3 &&
-git-update-index --add path0 path1 path2 path3 &&
-t3=$(git-write-tree)'
+git update-index --add path0 path1 path2 path3 &&
+t3=$(git write-tree)'
test_expect_success \
'checkout one stage 0 to temporary file' '
rm -f path* .merge_* out .git/index &&
-git-read-tree $t1 &&
-git-checkout-index --temp -- path1 >out &&
+git read-tree $t1 &&
+git checkout-index --temp -- path1 >out &&
test $(wc -l <out) = 1 &&
test $(cut "-d " -f2 out) = path1 &&
p=$(cut "-d " -f1 out) &&
test_expect_success \
'checkout all stage 0 to temporary files' '
rm -f path* .merge_* out .git/index &&
-git-read-tree $t1 &&
-git-checkout-index -a --temp >out &&
+git read-tree $t1 &&
+git checkout-index -a --temp >out &&
test $(wc -l <out) = 5 &&
for f in path0 path1 path3 path4 asubdir/path5
do
test_expect_success \
'prepare 3-way merge' '
rm -f path* .merge_* out .git/index &&
-git-read-tree -m $t1 $t2 $t3'
+git read-tree -m $t1 $t2 $t3'
test_expect_success \
'checkout one stage 2 to temporary file' '
rm -f path* .merge_* out &&
-git-checkout-index --stage=2 --temp -- path1 >out &&
+git checkout-index --stage=2 --temp -- path1 >out &&
test $(wc -l <out) = 1 &&
test $(cut "-d " -f2 out) = path1 &&
p=$(cut "-d " -f1 out) &&
test_expect_success \
'checkout all stage 2 to temporary files' '
rm -f path* .merge_* out &&
-git-checkout-index --all --stage=2 --temp >out &&
+git checkout-index --all --stage=2 --temp >out &&
test $(wc -l <out) = 3 &&
for f in path1 path2 path4
do
test_expect_success \
'checkout all stages/one file to nothing' '
rm -f path* .merge_* out &&
-git-checkout-index --stage=all --temp -- path0 >out &&
+git checkout-index --stage=all --temp -- path0 >out &&
test $(wc -l <out) = 0'
test_expect_success \
'checkout all stages/one file to temporary files' '
rm -f path* .merge_* out &&
-git-checkout-index --stage=all --temp -- path1 >out &&
+git checkout-index --stage=all --temp -- path1 >out &&
test $(wc -l <out) = 1 &&
test $(cut "-d " -f2 out) = path1 &&
cut "-d " -f1 out | (read s1 s2 s3 &&
test_expect_success \
'checkout some stages/one file to temporary files' '
rm -f path* .merge_* out &&
-git-checkout-index --stage=all --temp -- path2 >out &&
+git checkout-index --stage=all --temp -- path2 >out &&
test $(wc -l <out) = 1 &&
test $(cut "-d " -f2 out) = path2 &&
cut "-d " -f1 out | (read s1 s2 s3 &&
test_expect_success \
'checkout all stages/all files to temporary files' '
rm -f path* .merge_* out &&
-git-checkout-index -a --stage=all --temp >out &&
+git checkout-index -a --stage=all --temp >out &&
test $(wc -l <out) = 5'
test_expect_success \
test_expect_success \
'checkout --temp within subdir' '
(cd asubdir &&
- git-checkout-index -a --stage=all >out &&
+ git checkout-index -a --stage=all >out &&
test $(wc -l <out) = 1 &&
test $(grep path5 out | cut "-d " -f2) = path5 &&
grep path5 out | cut "-d " -f1 | (read s1 s2 s3 &&
'checkout --temp symlink' '
rm -f path* .merge_* out .git/index &&
ln -s b a &&
-git-update-index --add a &&
-t4=$(git-write-tree) &&
+git update-index --add a &&
+t4=$(git write-tree) &&
rm -f .git/index &&
-git-read-tree $t4 &&
-git-checkout-index --temp -a >out &&
+git read-tree $t4 &&
+git checkout-index --temp -a >out &&
test $(wc -l <out) = 1 &&
test $(cut "-d " -f2 out) = a &&
p=$(cut "-d " -f1 out) &&
# Copyright (c) 2007 Johannes Sixt
#
-test_description='git-checkout-index on filesystem w/o symlinks test.
+test_description='git checkout-index on filesystem w/o symlinks test.
-This tests that git-checkout-index creates a symbolic link as a plain
+This tests that git checkout-index creates a symbolic link as a plain
file if core.symlinks is false.'
. ./test-lib.sh
test_expect_success \
'preparation' '
-git-config core.symlinks false &&
+git config core.symlinks false &&
l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
-echo "120000 $l symlink" | git-update-index --index-info'
+echo "120000 $l symlink" | git update-index --index-info'
test_expect_success \
'the checked-out symlink must be a file' '
-git-checkout-index symlink &&
+git checkout-index symlink &&
test -f symlink'
test_expect_success \
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-update-index nonsense-path test.
+test_description='git update-index nonsense-path test.
This test creates the following structure in the cache:
path2/file2 - a file in a directory
path3/file3 - a file in a directory
-and tries to git-update-index --add the following:
+and tries to git update-index --add the following:
path0/file0 - a file in a directory
path1/file1 - a file in a directory
date >path3/file3
test_expect_success \
- 'git-update-index --add to add various paths.' \
- 'git-update-index --add -- path0 path1 path2/file2 path3/file3'
+ 'git update-index --add to add various paths.' \
+ 'git update-index --add -- path0 path1 path2/file2 path3/file3'
rm -fr path?
for p in path0/file0 path1/file1 path2 path3
do
test_expect_failure \
- "git-update-index to add conflicting path $p should fail." \
- "git-update-index --add -- $p"
+ "git update-index to add conflicting path $p should fail." \
+ "git update-index --add -- $p"
done
test_done
# Copyright (c) 2006 Junio C Hamano
#
-test_description='git-update-index --again test.
+test_description='git update-index --again test.
'
. ./test-lib.sh
test_expect_success 'update-index --add' \
'echo hello world >file1 &&
echo goodbye people >file2 &&
- git-update-index --add file1 file2 &&
- git-ls-files -s >current &&
+ git update-index --add file1 file2 &&
+ git ls-files -s >current &&
cmp current expected'
test_expect_success 'update-index --again' \
'rm -f file1 &&
echo hello everybody >file2 &&
- if git-update-index --again
+ if git update-index --again
then
echo should have refused to remove file1
exit 1
else
echo happy - failed as expected
fi &&
- git-ls-files -s >current &&
+ git ls-files -s >current &&
cmp current expected'
cat > expected <<\EOF
100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
EOF
test_expect_success 'update-index --remove --again' \
- 'git-update-index --remove --again &&
- git-ls-files -s >current &&
+ 'git update-index --remove --again &&
+ git ls-files -s >current &&
cmp current expected'
test_expect_success 'first commit' 'git-commit -m initial'
'mkdir -p dir1 &&
echo hello world >dir1/file3 &&
echo goodbye people >file2 &&
- git-update-index --add file2 dir1/file3 &&
+ git update-index --add file2 dir1/file3 &&
echo hello everybody >file2
echo happy >dir1/file3 &&
- git-update-index --again &&
- git-ls-files -s >current &&
+ git update-index --again &&
+ git ls-files -s >current &&
cmp current expected'
cat > expected <<\EOF
'echo not so happy >file2 &&
cd dir1 &&
cat ../file2 >file3 &&
- git-update-index --again &&
+ git update-index --again &&
cd .. &&
- git-ls-files -s >current &&
+ git ls-files -s >current &&
cmp current expected'
cat > expected <<\EOF
test_expect_success 'update-index --update with pathspec' \
'echo very happy >file2 &&
cat file2 >dir1/file3 &&
- git-update-index --again dir1/ &&
- git-ls-files -s >current &&
+ git update-index --again dir1/ &&
+ git ls-files -s >current &&
cmp current expected'
test_done
# Copyright (c) 2007 Johannes Sixt
#
-test_description='git-update-index on filesystem w/o symlinks test.
+test_description='git update-index on filesystem w/o symlinks test.
-This tests that git-update-index keeps the symbolic link property
+This tests that git update-index keeps the symbolic link property
even if a plain file is in the working tree if core.symlinks is false.'
. ./test-lib.sh
test_expect_success \
'preparation' '
-git-config core.symlinks false &&
+git config core.symlinks false &&
l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
-echo "120000 $l symlink" | git-update-index --index-info'
+echo "120000 $l symlink" | git update-index --index-info'
test_expect_success \
'modify the symbolic link' '
echo -n new-file > symlink &&
-git-update-index symlink'
+git update-index symlink'
test_expect_success \
'the index entry must still be a symbolic link' '
-case "`git-ls-files --stage --cached symlink`" in
+case "`git ls-files --stage --cached symlink`" in
120000" "*symlink) echo ok;;
-*) echo fail; git-ls-files --stage --cached symlink; (exit 1);;
+*) echo fail; git ls-files --stage --cached symlink; (exit 1);;
esac'
test_done
#!/bin/sh
-test_description='git-add -u with path limiting
+test_description='git add -u with path limiting
This test creates a working tree state with three files:
dir/sub (previously committed, modified)
dir/other (untracked)
-and issues a git-add -u with path limiting on "dir" to add
+and issues a git add -u with path limiting on "dir" to add
only the updates to dir/sub.'
. ./test-lib.sh
echo initial >top &&
mkdir dir &&
echo initial >dir/sub &&
-git-add dir/sub top &&
+git add dir/sub top &&
git-commit -m initial &&
echo changed >top &&
echo changed >dir/sub &&
echo other >dir/other
'
-test_expect_success 'update' 'git-add -u dir'
+test_expect_success 'update' 'git add -u dir'
test_expect_success 'update touched correct path' \
- 'test "`git-diff-files --name-status dir/sub`" = ""'
+ 'test "`git diff-files --name-status dir/sub`" = ""'
test_expect_success 'update did not touch other tracked files' \
- 'test "`git-diff-files --name-status top`" = "M top"'
+ 'test "`git diff-files --name-status top`" = "M top"'
test_expect_success 'update did not touch untracked files' \
- 'test "`git-diff-files --name-status dir/other`" = ""'
+ 'test "`git diff-files --name-status dir/other`" = ""'
test_done
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-ls-files test (--others should pick up symlinks).
+test_description='git ls-files test (--others should pick up symlinks).
-This test runs git-ls-files --others with the following on the
+This test runs git ls-files --others with the following on the
filesystem.
path0 - a file
date >path2-junk
date >path3/file3
date >path3-junk
-git-update-index --add path3-junk path3/file3
+git update-index --add path3-junk path3/file3
cat >expected1 <<EOF
expected1
sed -e 's|path2/file2|path2/|' <expected1 >expected2
test_expect_success \
- 'git-ls-files --others to show output.' \
- 'git-ls-files --others >output'
+ 'git ls-files --others to show output.' \
+ 'git ls-files --others >output'
test_expect_success \
- 'git-ls-files --others should pick up symlinks.' \
+ 'git ls-files --others should pick up symlinks.' \
'diff output expected1'
test_expect_success \
- 'git-ls-files --others --directory to show output.' \
- 'git-ls-files --others --directory >output'
+ 'git ls-files --others --directory to show output.' \
+ 'git ls-files --others --directory >output'
test_expect_success \
- 'git-ls-files --others --directory should not get confused.' \
+ 'git ls-files --others --directory should not get confused.' \
'diff output expected2'
test_done
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-ls-files --others --exclude
+test_description='git ls-files --others --exclude
-This test runs git-ls-files --others and tests --exclude patterns.
+This test runs git ls-files --others and tests --exclude patterns.
'
. ./test-lib.sh
!*.8' >one/two/.gitignore
test_expect_success \
- 'git-ls-files --others with various exclude options.' \
- 'git-ls-files --others \
+ 'git ls-files --others with various exclude options.' \
+ 'git ls-files --others \
--exclude=\*.6 \
--exclude-per-directory=.gitignore \
--exclude-from=.git/ignore \
printf '*.1\r\n/*.3\r\n!*.6\r\n' >.gitignore
test_expect_success \
- 'git-ls-files --others with \r\n line endings.' \
- 'git-ls-files --others \
+ 'git ls-files --others with \r\n line endings.' \
+ 'git ls-files --others \
--exclude=\*.6 \
--exclude-per-directory=.gitignore \
--exclude-from=.git/ignore \
e*
EOF
-git-config core.excludesFile excludes-file
+git config core.excludesFile excludes-file
-git-runstatus | grep "^# " > output
+git runstatus | grep "^# " > output
cat > expect << EOF
# .gitignore
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-ls-files test (-- to terminate the path list).
+test_description='git ls-files test (-- to terminate the path list).
-This test runs git-ls-files --others with the following on the
+This test runs git ls-files --others with the following on the
filesystem.
path0 - a file
echo frotz >./--'
test_expect_success \
- 'git-ls-files without path restriction.' \
- 'git-ls-files --others >output &&
+ 'git ls-files without path restriction.' \
+ 'git ls-files --others >output &&
git diff output - <<EOF
--
-foo
'
test_expect_success \
- 'git-ls-files with path restriction.' \
- 'git-ls-files --others path0 >output &&
+ 'git ls-files with path restriction.' \
+ 'git ls-files --others path0 >output &&
git diff output - <<EOF
path0
EOF
'
test_expect_success \
- 'git-ls-files with path restriction with --.' \
- 'git-ls-files --others -- path0 >output &&
+ 'git ls-files with path restriction with --.' \
+ 'git ls-files --others -- path0 >output &&
git diff output - <<EOF
path0
EOF
'
test_expect_success \
- 'git-ls-files with path restriction with -- --.' \
- 'git-ls-files --others -- -- >output &&
+ 'git ls-files with path restriction with -- --.' \
+ 'git ls-files --others -- -- >output &&
git diff output - <<EOF
--
EOF
'
test_expect_success \
- 'git-ls-files with no path restriction.' \
- 'git-ls-files --others -- >output &&
+ 'git ls-files with no path restriction.' \
+ 'git ls-files --others -- >output &&
git diff output - <<EOF
--
-foo
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-ls-files -k and -m flags test.
+test_description='git ls-files -k and -m flags test.
This test prepares the following in the cache:
path5 - a symlink
path6/file6 - a file in a directory
-git-ls-files -k should report that existing filesystem
+git ls-files -k should report that existing filesystem
objects except path4, path5 and path6/file6 to be killed.
Also for modification test, the cache and working tree have:
: >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"
+ '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
touch path10
test_expect_success \
- 'git-ls-files -k to show killed files.' \
- 'git-ls-files -k >.output'
+ 'git ls-files -k to show killed files.' \
+ 'git ls-files -k >.output'
cat >.expected <<EOF
path0/file0
path1/file1
EOF
test_expect_success \
- 'validate git-ls-files -k output.' \
+ 'validate git ls-files -k output.' \
'diff .output .expected'
test_expect_success \
- 'git-ls-files -m to show modified files.' \
- 'git-ls-files -m >.output'
+ 'git ls-files -m to show modified files.' \
+ 'git ls-files -m >.output'
cat >.expected <<EOF
path0
path1
EOF
test_expect_success \
- 'validate git-ls-files -m output.' \
+ 'validate git ls-files -m output.' \
'diff .output .expected'
test_done
# Copyright (c) 2006 Carl D. Worth
#
-test_description='git-ls-files test for --error-unmatch option
+test_description='git ls-files test for --error-unmatch option
-This test runs git-ls-files --error-unmatch to ensure it correctly
+This test runs git ls-files --error-unmatch to ensure it correctly
returns an error when a non-existent path is provided on the command
line.
'
. ./test-lib.sh
touch foo bar
-git-update-index --add foo bar
+git update-index --add foo bar
git-commit -m "add foo bar"
test_expect_failure \
- 'git-ls-files --error-unmatch should fail with unmatched path.' \
- 'git-ls-files --error-unmatch foo bar-does-not-match'
+ 'git ls-files --error-unmatch should fail with unmatched path.' \
+ 'git ls-files --error-unmatch foo bar-does-not-match'
test_expect_success \
- 'git-ls-files --error-unmatch should succeed eith matched paths.' \
- 'git-ls-files --error-unmatch foo bar'
+ 'git ls-files --error-unmatch should succeed eith matched paths.' \
+ 'git ls-files --error-unmatch foo bar'
test_done
1
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-ls-tree test.
+test_description='git ls-tree test.
-This test runs git-ls-tree with the following in a tree.
+This test runs git ls-tree with the following in a tree.
path0 - a file
path1 - a symlink
ln -s ../path1 path2/bazbo &&
echo Mi >path2/baz/b &&
find path? \( -type f -o -type l \) -print |
- xargs git-update-index --add &&
- tree=`git-write-tree` &&
+ xargs git update-index --add &&
+ tree=`git write-tree` &&
echo $tree'
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
test_expect_success \
'ls-tree plain' \
- 'git-ls-tree $tree >current &&
+ 'git ls-tree $tree >current &&
cat >expected <<\EOF &&
100644 blob X path0
120000 blob X path1
test_expect_success \
'ls-tree recursive' \
- 'git-ls-tree -r $tree >current &&
+ 'git ls-tree -r $tree >current &&
cat >expected <<\EOF &&
100644 blob X path0
120000 blob X path1
test_expect_success \
'ls-tree recursive with -t' \
- 'git-ls-tree -r -t $tree >current &&
+ 'git ls-tree -r -t $tree >current &&
cat >expected <<\EOF &&
100644 blob X path0
120000 blob X path1
test_expect_success \
'ls-tree recursive with -d' \
- 'git-ls-tree -r -d $tree >current &&
+ 'git ls-tree -r -d $tree >current &&
cat >expected <<\EOF &&
040000 tree X path2
040000 tree X path2/baz
test_expect_success \
'ls-tree filtered with path' \
- 'git-ls-tree $tree path >current &&
+ 'git ls-tree $tree path >current &&
cat >expected <<\EOF &&
EOF
test_output'
# they are shown in canonical order.
test_expect_success \
'ls-tree filtered with path1 path0' \
- 'git-ls-tree $tree path1 path0 >current &&
+ 'git ls-tree $tree path1 path0 >current &&
cat >expected <<\EOF &&
100644 blob X path0
120000 blob X path1
test_expect_success \
'ls-tree filtered with path0/' \
- 'git-ls-tree $tree path0/ >current &&
+ 'git ls-tree $tree path0/ >current &&
cat >expected <<\EOF &&
EOF
test_output'
# with pathspec semantics it shows only path2
test_expect_success \
'ls-tree filtered with path2' \
- 'git-ls-tree $tree path2 >current &&
+ 'git ls-tree $tree path2 >current &&
cat >expected <<\EOF &&
040000 tree X path2
EOF
# ... and path2/ shows the children.
test_expect_success \
'ls-tree filtered with path2/' \
- 'git-ls-tree $tree path2/ >current &&
+ 'git ls-tree $tree path2/ >current &&
cat >expected <<\EOF &&
040000 tree X path2/baz
120000 blob X path2/bazbo
# path2/baz
test_expect_success \
'ls-tree filtered with path2/baz' \
- 'git-ls-tree $tree path2/baz >current &&
+ 'git ls-tree $tree path2/baz >current &&
cat >expected <<\EOF &&
040000 tree X path2/baz
EOF
test_expect_success \
'ls-tree filtered with path2/bak' \
- 'git-ls-tree $tree path2/bak >current &&
+ 'git ls-tree $tree path2/bak >current &&
cat >expected <<\EOF &&
EOF
test_output'
test_expect_success \
'ls-tree -t filtered with path2/bak' \
- 'git-ls-tree -t $tree path2/bak >current &&
+ 'git ls-tree -t $tree path2/bak >current &&
cat >expected <<\EOF &&
040000 tree X path2
EOF
# Copyright (c) 2005 Robert Fitzsimons
#
-test_description='git-ls-tree directory and filenames handling.
+test_description='git ls-tree directory and filenames handling.
-This test runs git-ls-tree with the following in a tree.
+This test runs git ls-tree with the following in a tree.
1.txt - a file
2.txt - a file
echo 111 >path3/1.txt &&
echo 222 >path3/2.txt &&
find *.txt path* \( -type f -o -type l \) -print |
- xargs git-update-index --add &&
- tree=`git-write-tree` &&
+ xargs git update-index --add &&
+ tree=`git write-tree` &&
echo $tree'
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
test_expect_success \
'ls-tree plain' \
- 'git-ls-tree $tree >current &&
+ 'git ls-tree $tree >current &&
cat >expected <<\EOF &&
100644 blob X 1.txt
100644 blob X 2.txt
# Recursive does not show tree nodes anymore...
test_expect_success \
'ls-tree recursive' \
- 'git-ls-tree -r $tree >current &&
+ 'git ls-tree -r $tree >current &&
cat >expected <<\EOF &&
100644 blob X 1.txt
100644 blob X 2.txt
test_expect_success \
'ls-tree filter 1.txt' \
- 'git-ls-tree $tree 1.txt >current &&
+ 'git ls-tree $tree 1.txt >current &&
cat >expected <<\EOF &&
100644 blob X 1.txt
EOF
test_expect_success \
'ls-tree filter path1/b/c/1.txt' \
- 'git-ls-tree $tree path1/b/c/1.txt >current &&
+ 'git ls-tree $tree path1/b/c/1.txt >current &&
cat >expected <<\EOF &&
100644 blob X path1/b/c/1.txt
EOF
test_expect_success \
'ls-tree filter all 1.txt files' \
- 'git-ls-tree $tree 1.txt path0/a/b/c/1.txt path1/b/c/1.txt path2/1.txt path3/1.txt >current &&
+ 'git ls-tree $tree 1.txt path0/a/b/c/1.txt path1/b/c/1.txt path2/1.txt path3/1.txt >current &&
cat >expected <<\EOF &&
100644 blob X 1.txt
100644 blob X path0/a/b/c/1.txt
# it behaves as if path0/a/b/c, path1/b/c, path2 and path3 are specified.
test_expect_success \
'ls-tree filter directories' \
- 'git-ls-tree $tree path3 path2 path0/a/b/c path1/b/c path0/a >current &&
+ 'git ls-tree $tree path3 path2 path0/a/b/c path1/b/c path0/a >current &&
cat >expected <<\EOF &&
040000 tree X path0/a/b/c
040000 tree X path1/b/c
# having 1.txt and path3
test_expect_success \
'ls-tree filter odd names' \
- 'git-ls-tree $tree 1.txt /1.txt //1.txt path3/1.txt /path3/1.txt //path3//1.txt path3 /path3/ path3// >current &&
+ 'git ls-tree $tree 1.txt /1.txt //1.txt path3/1.txt /path3/1.txt //path3//1.txt path3 /path3/ path3// >current &&
cat >expected <<\EOF &&
100644 blob X 1.txt
100644 blob X path3/1.txt
test_expect_success \
'ls-tree filter missing files and extra slashes' \
- 'git-ls-tree $tree 1.txt/ abc.txt path3//23.txt path3/2.txt/// >current &&
+ 'git ls-tree $tree 1.txt/ abc.txt path3//23.txt path3/2.txt/// >current &&
cat >expected <<\EOF &&
EOF
test_output'
test_expect_success \
'prepare a trivial repository' \
'echo Hello > A &&
- git-update-index --add A &&
+ git update-index --add A &&
git-commit -m "Initial commit." &&
- HEAD=$(git-rev-parse --verify HEAD)'
+ HEAD=$(git rev-parse --verify HEAD)'
test_expect_failure \
'git branch --help should not have created a bogus branch' \
- 'git-branch --help </dev/null >/dev/null 2>/dev/null || :
+ 'git branch --help </dev/null >/dev/null 2>/dev/null || :
test -f .git/refs/heads/--help'
test_expect_success \
'git branch abc should create a branch' \
- 'git-branch abc && test -f .git/refs/heads/abc'
+ 'git branch abc && test -f .git/refs/heads/abc'
test_expect_success \
'git branch a/b/c should create a branch' \
- 'git-branch a/b/c && test -f .git/refs/heads/a/b/c'
+ 'git branch a/b/c && test -f .git/refs/heads/a/b/c'
cat >expect <<EOF
0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
test_expect_success \
'git branch -l d/e/f should create a branch and a log' \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
- git-branch -l d/e/f &&
+ git branch -l d/e/f &&
test -f .git/refs/heads/d/e/f &&
test -f .git/logs/refs/heads/d/e/f &&
diff expect .git/logs/refs/heads/d/e/f'
test_expect_success \
'git branch -d d/e/f should delete a branch and a log' \
- 'git-branch -d d/e/f &&
+ 'git branch -d d/e/f &&
test ! -f .git/refs/heads/d/e/f &&
test ! -f .git/logs/refs/heads/d/e/f'
test_expect_success \
'git branch j/k should work after branch j has been deleted' \
- 'git-branch j &&
- git-branch -d j &&
- git-branch j/k'
+ 'git branch j &&
+ git branch -d j &&
+ git branch j/k'
test_expect_success \
'git branch l should work after branch l/m has been deleted' \
- 'git-branch l/m &&
- git-branch -d l/m &&
- git-branch l'
+ 'git branch l/m &&
+ git branch -d l/m &&
+ git branch l'
test_expect_success \
'git branch -m m m/m should work' \
- 'git-branch -l m &&
- git-branch -m m m/m &&
+ 'git branch -l m &&
+ git branch -m m m/m &&
test -f .git/logs/refs/heads/m/m'
test_expect_success \
'git branch -m n/n n should work' \
- 'git-branch -l n/n &&
- git-branch -m n/n n
+ 'git branch -l n/n &&
+ git branch -m n/n n
test -f .git/logs/refs/heads/n'
test_expect_failure \
'git branch -m o/o o should fail when o/p exists' \
- 'git-branch o/o &&
- git-branch o/p &&
- git-branch -m o/o o'
+ 'git branch o/o &&
+ git branch o/p &&
+ git branch -m o/o o'
test_expect_failure \
'git branch -m q r/q should fail when r exists' \
- 'git-branch q &&
- git-branch r &&
- git-branch -m q r/q'
+ 'git branch q &&
+ git branch r &&
+ git branch -m q r/q'
mv .git/config .git/config-saved
test_expect_success 'git branch -m q q2 without config should succeed' '
- git-branch -m q q2 &&
- git-branch -m q2 q
+ git branch -m q q2 &&
+ git branch -m q2 q
'
mv .git/config-saved .git/config
-git-config branch.s/s.dummy Hello
+git config branch.s/s.dummy Hello
test_expect_success \
'git branch -m s/s s should work when s/t is deleted' \
- 'git-branch -l s/s &&
+ 'git branch -l s/s &&
test -f .git/logs/refs/heads/s/s &&
- git-branch -l s/t &&
+ git branch -l s/t &&
test -f .git/logs/refs/heads/s/t &&
- git-branch -d s/t &&
- git-branch -m s/s s &&
+ git branch -d s/t &&
+ git branch -m s/s s &&
test -f .git/logs/refs/heads/s'
test_expect_success 'config information was renamed, too' \
- "test $(git-config branch.s.dummy) = Hello &&
- ! git-config branch.s/s/dummy"
+ "test $(git config branch.s.dummy) = Hello &&
+ ! git config branch.s/s/dummy"
test_expect_failure \
- 'git-branch -m u v should fail when the reflog for u is a symlink' \
- 'git-branch -l u &&
+ 'git branch -m u v should fail when the reflog for u is a symlink' \
+ 'git branch -l u &&
mv .git/logs/refs/heads/u real-u &&
ln -s real-u .git/logs/refs/heads/u &&
- git-branch -m u v'
+ git branch -m u v'
test_expect_success 'test tracking setup via --track' \
- 'git-config remote.local.url . &&
- git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
- (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
- git-branch --track my1 local/master &&
- test $(git-config branch.my1.remote) = local &&
- test $(git-config branch.my1.merge) = refs/heads/master'
+ 'git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+ (git show-ref -q refs/remotes/local/master || git-fetch local) &&
+ git branch --track my1 local/master &&
+ test $(git config branch.my1.remote) = local &&
+ test $(git config branch.my1.merge) = refs/heads/master'
test_expect_success 'test tracking setup (non-wildcard, matching)' \
- 'git-config remote.local.url . &&
- git-config remote.local.fetch refs/heads/master:refs/remotes/local/master &&
- (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
- git-branch --track my4 local/master &&
- test $(git-config branch.my4.remote) = local &&
- test $(git-config branch.my4.merge) = refs/heads/master'
+ 'git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/master:refs/remotes/local/master &&
+ (git show-ref -q refs/remotes/local/master || git-fetch local) &&
+ git branch --track my4 local/master &&
+ test $(git config branch.my4.remote) = local &&
+ test $(git config branch.my4.merge) = refs/heads/master'
test_expect_success 'test tracking setup (non-wildcard, not matching)' \
- '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'
+ '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_expect_success 'test tracking setup via config' \
- 'git-config branch.autosetupmerge true &&
- git-config remote.local.url . &&
- git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
- (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
- git-branch my3 local/master &&
- test $(git-config branch.my3.remote) = local &&
- test $(git-config branch.my3.merge) = refs/heads/master'
+ 'git config branch.autosetupmerge true &&
+ git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+ (git show-ref -q refs/remotes/local/master || git-fetch local) &&
+ git branch my3 local/master &&
+ test $(git config branch.my3.remote) = local &&
+ test $(git config branch.my3.merge) = refs/heads/master'
+
+test_expect_success 'avoid ambiguous track' '
+ git config branch.autosetupmerge true &&
+ git config remote.ambi1.url = lalala &&
+ git config remote.ambi1.fetch = refs/heads/lalala:refs/heads/master &&
+ git config remote.ambi2.url = lilili &&
+ git config remote.ambi2.fetch = refs/heads/lilili:refs/heads/master &&
+ git branch all1 master &&
+ test -z "$(git config branch.all1.merge)"
+'
test_expect_success 'test overriding tracking setup via --no-track' \
- 'git-config branch.autosetupmerge true &&
- git-config remote.local.url . &&
- git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
- (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
- git-branch --no-track my2 local/master &&
- git-config branch.autosetupmerge false &&
- ! test "$(git-config branch.my2.remote)" = local &&
- ! test "$(git-config branch.my2.merge)" = refs/heads/master'
-
-test_expect_success 'test local tracking setup' \
+ 'git config branch.autosetupmerge true &&
+ git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+ (git show-ref -q refs/remotes/local/master || git-fetch local) &&
+ git branch --no-track my2 local/master &&
+ git config branch.autosetupmerge false &&
+ ! test "$(git config branch.my2.remote)" = local &&
+ ! test "$(git config branch.my2.merge)" = refs/heads/master'
+
+test_expect_success 'no tracking without .fetch entries' \
'git branch --track my6 s &&
- test $(git-config branch.my6.remote) = . &&
- test $(git-config branch.my6.merge) = refs/heads/s'
+ test -z "$(git config branch.my6.remote)" &&
+ test -z "$(git config branch.my6.merge)"'
test_expect_success 'test tracking setup via --track but deeper' \
- 'git-config remote.local.url . &&
- git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
- (git-show-ref -q refs/remotes/local/o/o || git-fetch local) &&
- git-branch --track my7 local/o/o &&
- test "$(git-config branch.my7.remote)" = local &&
- test "$(git-config branch.my7.merge)" = refs/heads/o/o'
+ 'git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+ (git show-ref -q refs/remotes/local/o/o || git-fetch local) &&
+ git branch --track my7 local/o/o &&
+ test "$(git config branch.my7.remote)" = local &&
+ test "$(git config branch.my7.merge)" = refs/heads/o/o'
+
+test_expect_success 'test deleting branch deletes branch config' \
+ 'git branch -d my7 &&
+ test -z "$(git config branch.my7.remote)" &&
+ test -z "$(git config branch.my7.merge)"'
+
+test_expect_success 'test deleting branch without config' \
+ 'git branch my7 s &&
+ test "$(git branch -d my7 2>&1)" = "Deleted branch my7."'
# Keep this test last, as it changes the current branch
cat >expect <<EOF
test_expect_success \
'prepare a trivial repository' \
'echo Hello > A &&
- git-update-index --add A &&
+ git update-index --add A &&
git-commit -m "Initial commit." &&
- HEAD=$(git-rev-parse --verify HEAD)'
+ HEAD=$(git rev-parse --verify HEAD)'
SHA1=
test_expect_success \
'see if git show-ref works as expected' \
- 'git-branch a &&
+ 'git branch a &&
SHA1=`cat .git/refs/heads/a` &&
echo "$SHA1 refs/heads/a" >expect &&
- git-show-ref a >result &&
+ git show-ref a >result &&
diff expect result'
test_expect_success \
'see if a branch still exists when packed' \
- 'git-branch b &&
- git-pack-refs --all &&
+ 'git branch b &&
+ git pack-refs --all &&
rm -f .git/refs/heads/b &&
echo "$SHA1 refs/heads/b" >expect &&
- git-show-ref b >result &&
+ git show-ref b >result &&
diff expect result'
test_expect_failure \
'git branch c/d should barf if branch c exists' \
- 'git-branch c &&
- git-pack-refs --all &&
+ 'git branch c &&
+ git pack-refs --all &&
rm .git/refs/heads/c &&
- git-branch c/d'
+ git branch c/d'
test_expect_success \
'see if a branch still exists after git pack-refs --prune' \
- 'git-branch e &&
- git-pack-refs --all --prune &&
+ 'git branch e &&
+ git pack-refs --all --prune &&
echo "$SHA1 refs/heads/e" >expect &&
- git-show-ref e >result &&
+ git show-ref e >result &&
diff expect result'
test_expect_failure \
'see if git pack-refs --prune remove ref files' \
- 'git-branch f &&
- git-pack-refs --all --prune &&
+ 'git branch f &&
+ git pack-refs --all --prune &&
ls .git/refs/heads/f'
test_expect_success \
'git branch g should work when git branch g/h has been deleted' \
- 'git-branch g/h &&
- git-pack-refs --all --prune &&
- git-branch -d g/h &&
- git-branch g &&
- git-pack-refs --all &&
- git-branch -d g'
+ 'git branch g/h &&
+ git pack-refs --all --prune &&
+ git branch -d g/h &&
+ git branch g &&
+ git pack-refs --all &&
+ git branch -d g'
test_expect_failure \
'git branch i/j/k should barf if branch i exists' \
- 'git-branch i &&
- git-pack-refs --all --prune &&
- git-branch i/j/k'
+ 'git branch i &&
+ git pack-refs --all --prune &&
+ git branch i/j/k'
test_expect_success \
'test git branch k after branch k/l/m and k/lm have been deleted' \
- 'git-branch k/l &&
- git-branch k/lm &&
- git-branch -d k/l &&
- git-branch k/l/m &&
- git-branch -d k/l/m &&
- git-branch -d k/lm &&
- git-branch k'
+ 'git branch k/l &&
+ git branch k/lm &&
+ git branch -d k/l &&
+ git branch k/l/m &&
+ git branch -d k/l/m &&
+ git branch -d k/lm &&
+ git branch k'
test_expect_success \
'test git branch n after some branch deletion and pruning' \
- 'git-branch n/o &&
- git-branch n/op &&
- git-branch -d n/o &&
- git-branch n/o/p &&
- git-branch -d n/op &&
- git-pack-refs --all --prune &&
- git-branch -d n/o/p &&
- git-branch n'
+ 'git branch n/o &&
+ git branch n/op &&
+ git branch -d n/o &&
+ git branch n/o/p &&
+ git branch -d n/op &&
+ git pack-refs --all --prune &&
+ git branch -d n/o/p &&
+ git branch n'
test_expect_success 'pack, prune and repack' '
git-tag foo &&
- git-pack-refs --all --prune &&
- git-show-ref >all-of-them &&
- git-pack-refs &&
- git-show-ref >again &&
+ git pack-refs --all --prune &&
+ git show-ref >all-of-them &&
+ git pack-refs &&
+ git show-ref >again &&
diff all-of-them again
'
echo 'just space
no-funny' >expected
-test_expect_success 'git-ls-files no-funny' \
- 'git-update-index --add "$p0" "$p2" &&
- git-ls-files >current &&
+test_expect_success 'git ls-files no-funny' \
+ 'git update-index --add "$p0" "$p2" &&
+ git ls-files >current &&
git diff expected current'
-t0=`git-write-tree`
+t0=`git write-tree`
echo "$t0" >t0
cat > expected <<\EOF
no-funny
"tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git-ls-files with-funny' \
- 'git-update-index --add "$p1" &&
- git-ls-files >current &&
+test_expect_success 'git ls-files with-funny' \
+ 'git update-index --add "$p1" &&
+ git ls-files >current &&
git diff expected current'
echo 'just space
no-funny
tabs ," (dq) and spaces' >expected
-test_expect_success 'git-ls-files -z with-funny' \
- 'git-ls-files -z | tr \\0 \\012 >current &&
+test_expect_success 'git ls-files -z with-funny' \
+ 'git ls-files -z | tr \\0 \\012 >current &&
git diff expected current'
-t1=`git-write-tree`
+t1=`git write-tree`
echo "$t1" >t1
cat > expected <<\EOF
no-funny
"tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git-ls-tree with funny' \
- 'git-ls-tree -r $t1 | sed -e "s/^[^ ]* //" >current &&
+test_expect_success 'git ls-tree with funny' \
+ 'git ls-tree -r $t1 | sed -e "s/^[^ ]* //" >current &&
git diff expected current'
cat > expected <<\EOF
A "tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git-diff-index with-funny' \
- 'git-diff-index --name-status $t0 >current &&
+test_expect_success 'git diff-index with-funny' \
+ 'git diff-index --name-status $t0 >current &&
git diff expected current'
-test_expect_success 'git-diff-tree with-funny' \
- 'git-diff-tree --name-status $t0 $t1 >current &&
+test_expect_success 'git diff-tree with-funny' \
+ 'git diff-tree --name-status $t0 $t1 >current &&
git diff expected current'
echo 'A
tabs ," (dq) and spaces' >expected
-test_expect_success 'git-diff-index -z with-funny' \
- 'git-diff-index -z --name-status $t0 | tr \\0 \\012 >current &&
+test_expect_success 'git diff-index -z with-funny' \
+ 'git diff-index -z --name-status $t0 | tr \\0 \\012 >current &&
git diff expected current'
-test_expect_success 'git-diff-tree -z with-funny' \
- 'git-diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
+test_expect_success 'git diff-tree -z with-funny' \
+ 'git diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
git diff expected current'
cat > expected <<\EOF
CNUM no-funny "tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git-diff-tree -C with-funny' \
- 'git-diff-tree -C --find-copies-harder --name-status \
+test_expect_success 'git diff-tree -C with-funny' \
+ 'git diff-tree -C --find-copies-harder --name-status \
$t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
git diff expected current'
cat > expected <<\EOF
RNUM no-funny "tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git-diff-tree delete with-funny' \
- 'git-update-index --force-remove "$p0" &&
- git-diff-index -M --name-status \
+test_expect_success 'git diff-tree delete with-funny' \
+ 'git update-index --force-remove "$p0" &&
+ git diff-index -M --name-status \
$t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
git diff expected current'
rename from no-funny
rename to "tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git-diff-tree delete with-funny' \
- 'git-diff-index -M -p $t0 |
+test_expect_success 'git diff-tree delete with-funny' \
+ 'git diff-index -M -p $t0 |
sed -e "s/index [0-9]*%/index NUM%/" >current &&
git diff expected current'
rename from no-funny
rename to "tabs\t,\" (dq) and spaces"
EOF
-test_expect_success 'git-diff-tree delete with-funny' \
- 'git-diff-index -M -p $t0 |
+test_expect_success 'git diff-tree delete with-funny' \
+ 'git diff-index -M -p $t0 |
sed -e "s/index [0-9]*%/index NUM%/" >current &&
git diff expected current'
"tabs\t,\" (dq) and spaces"
1 files changed, 0 insertions(+), 0 deletions(-)
EOF
-test_expect_success 'git-diff-tree rename with-funny applied' \
- 'git-diff-index -M -p $t0 |
- git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+test_expect_success 'git diff-tree rename with-funny applied' \
+ 'git diff-index -M -p $t0 |
+ git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
git diff expected current'
cat > expected <<\EOF
"tabs\t,\" (dq) and spaces"
2 files changed, 3 insertions(+), 3 deletions(-)
EOF
-test_expect_success 'git-diff-tree delete with-funny applied' \
- 'git-diff-index -p $t0 |
- git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+test_expect_success 'git diff-tree delete with-funny applied' \
+ 'git diff-index -p $t0 |
+ git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
git diff expected current'
-test_expect_success 'git-apply non-git diff' \
- 'git-diff-index -p $t0 |
+test_expect_success 'git apply non-git diff' \
+ 'git diff-index -p $t0 |
sed -ne "/^[-+@]/p" |
- git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+ git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
git diff expected current'
test_done
export GIT_AUTHOR_EMAIL=bogus_email_address
test_expect_success \
- 'prepare repository with topic branch, then rebase against master' \
+ 'prepare repository with topic branches' \
'echo First > A &&
- git-update-index --add A &&
+ git update-index --add A &&
git-commit -m "Add A." &&
git checkout -b my-topic-branch &&
echo Second > B &&
- git-update-index --add B &&
+ git update-index --add B &&
git-commit -m "Add B." &&
git checkout -f master &&
echo Third >> A &&
- git-update-index A &&
+ git update-index A &&
git-commit -m "Modify A." &&
+ git checkout -b side my-topic-branch &&
+ 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
+'
+
+test_expect_success 'rebase against master' '
git rebase master'
test_expect_failure \
'the rebase operation should not have destroyed author information' \
'git log | grep "Author:" | grep "<>"'
+test_expect_success 'rebase after merge master' '
+ 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_done
test_expect_success \
'prepare repository with topic branch' \
'echo First > A &&
- git-update-index --add A &&
+ git update-index --add A &&
git-commit -m "Add A." &&
git-checkout -b my-topic-branch &&
echo Second > B &&
- git-update-index --add B &&
+ git update-index --add B &&
git-commit -m "Add B." &&
echo AnotherSecond > C &&
- git-update-index --add C &&
+ git update-index --add C &&
git-commit -m "Add C." &&
git-checkout -f master &&
echo Third >> A &&
- git-update-index A &&
+ git update-index A &&
git-commit -m "Modify A."
'
test_expect_success \
'pick top patch from topic branch into master' \
- 'git-cherry-pick my-topic-branch^0 &&
+ 'git cherry-pick my-topic-branch^0 &&
git-checkout -f my-topic-branch &&
- git-branch master-merge master &&
- git-branch my-topic-branch-merge my-topic-branch
+ git branch master-merge master &&
+ git branch my-topic-branch-merge my-topic-branch
'
test_debug \
- 'git-cherry master &&
- git-format-patch -k --stdout --full-index master >/dev/null &&
+ 'git cherry master &&
+ git format-patch -k --stdout --full-index master >/dev/null &&
gitk --all & sleep 1
'
'
test_expect_success 'merge and reference trees equal' \
- 'test -z "`git-diff-tree skip-merge skip-reference`"'
+ 'test -z "`git diff-tree skip-merge skip-reference`"'
test_debug 'gitk --all & sleep 1'
test_done
-
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='git rebase interactive
+
+This test runs git rebase "interactively", by faking an edit, and verifies
+that the result still makes sense.
+'
+. ./test-lib.sh
+
+# set up two branches like this:
+#
+# A - B - C - D - E
+# \
+# F - G - H
+# \
+# I
+#
+# where B, D and G touch the same file.
+
+test_expect_success 'setup' '
+ : > file1 &&
+ git add file1 &&
+ test_tick &&
+ git commit -m A &&
+ git tag A &&
+ echo 1 > file1 &&
+ test_tick &&
+ git commit -m B file1 &&
+ : > file2 &&
+ git add file2 &&
+ test_tick &&
+ git commit -m C &&
+ echo 2 > file1 &&
+ test_tick &&
+ git commit -m D file1 &&
+ : > file3 &&
+ git add file3 &&
+ test_tick &&
+ git commit -m E &&
+ git checkout -b branch1 A &&
+ : > file4 &&
+ git add file4 &&
+ test_tick &&
+ git commit -m F &&
+ git tag F &&
+ echo 3 > file1 &&
+ test_tick &&
+ git commit -m G file1 &&
+ : > file5 &&
+ git add file5 &&
+ test_tick &&
+ git commit -m H &&
+ git checkout -b branch2 F &&
+ : > file6 &&
+ git add file6 &&
+ test_tick &&
+ git commit -m I &&
+ git tag I
+'
+
+cat > fake-editor.sh << EOF
+#!/bin/sh
+test "\$1" = .git/COMMIT_EDITMSG && {
+ test -z "\$FAKE_COMMIT_MESSAGE" || echo "\$FAKE_COMMIT_MESSAGE" > "\$1"
+ test -z "\$FAKE_COMMIT_AMEND" || echo "\$FAKE_COMMIT_AMEND" >> "\$1"
+ exit
+}
+test -z "\$EXPECT_COUNT" ||
+ test "\$EXPECT_COUNT" = \$(grep -ve "^#" -e "^$" < "\$1" | wc -l) ||
+ exit
+test -z "\$FAKE_LINES" && exit
+grep -v "^#" < "\$1" > "\$1".tmp
+rm "\$1"
+cat "\$1".tmp
+action=pick
+for line in \$FAKE_LINES; do
+ case \$line in
+ squash)
+ action="\$line";;
+ *)
+ echo sed -n "\${line}s/^pick/\$action/p"
+ sed -n "\${line}p" < "\$1".tmp
+ sed -n "\${line}s/^pick/\$action/p" < "\$1".tmp >> "\$1"
+ action=pick;;
+ esac
+done
+EOF
+
+chmod a+x fake-editor.sh
+VISUAL="$(pwd)/fake-editor.sh"
+export VISUAL
+
+test_expect_success 'no changes are a nop' '
+ git rebase -i F &&
+ test $(git rev-parse I) = $(git rev-parse HEAD)
+'
+
+test_expect_success 'test the [branch] option' '
+ git checkout -b dead-end &&
+ git rm file6 &&
+ git commit -m "stop here" &&
+ git rebase -i F branch2 &&
+ test $(git rev-parse I) = $(git rev-parse HEAD)
+'
+
+test_expect_success 'rebase on top of a non-conflicting commit' '
+ git checkout branch1 &&
+ git tag original-branch1 &&
+ git rebase -i branch2 &&
+ test file6 = $(git diff --name-only original-branch1) &&
+ test $(git rev-parse I) = $(git rev-parse HEAD~2)
+'
+
+test_expect_success 'reflog for the branch shows state before rebase' '
+ test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1)
+'
+
+test_expect_success 'exchange two commits' '
+ FAKE_LINES="2 1" git rebase -i HEAD~2 &&
+ test H = $(git cat-file commit HEAD^ | tail -n 1) &&
+ test G = $(git cat-file commit HEAD | tail -n 1)
+'
+
+cat > expect << EOF
+diff --git a/file1 b/file1
+index e69de29..00750ed 100644
+--- a/file1
++++ b/file1
+@@ -0,0 +1 @@
++3
+EOF
+
+cat > expect2 << EOF
+<<<<<<< HEAD:file1
+2
+=======
+3
+>>>>>>> b7ca976... G:file1
+EOF
+
+test_expect_success 'stop on conflicting pick' '
+ git tag new-branch1 &&
+ ! git rebase -i master &&
+ diff -u expect .git/.dotest-merge/patch &&
+ diff -u expect2 file1 &&
+ test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) &&
+ test 0 = $(grep -v "^#" < .git/.dotest-merge/todo | wc -l)
+'
+
+test_expect_success 'abort' '
+ git rebase --abort &&
+ test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
+ ! test -d .git/.dotest-merge
+'
+
+test_expect_success 'retain authorship' '
+ echo A > file7 &&
+ git add file7 &&
+ test_tick &&
+ GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
+ git tag twerp &&
+ git rebase -i --onto master HEAD^ &&
+ git show HEAD | grep "^Author: Twerp Snog"
+'
+
+test_expect_success 'squash' '
+ git reset --hard twerp &&
+ echo B > file7 &&
+ test_tick &&
+ GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 &&
+ echo "******************************" &&
+ FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 &&
+ test B = $(cat file7) &&
+ test $(git rev-parse HEAD^) = $(git rev-parse master)
+'
+
+test_expect_success 'retain authorship when squashing' '
+ git show HEAD | grep "^Author: Nitfol"
+'
+
+test_expect_success 'preserve merges with -p' '
+ git checkout -b to-be-preserved master^ &&
+ : > unrelated-file &&
+ git add unrelated-file &&
+ test_tick &&
+ git commit -m "unrelated" &&
+ git checkout -b to-be-rebased master &&
+ echo B > file1 &&
+ test_tick &&
+ git commit -m J file1 &&
+ test_tick &&
+ git merge to-be-preserved &&
+ echo C > file1 &&
+ test_tick &&
+ git commit -m K file1 &&
+ test_tick &&
+ git rebase -i -p --onto branch1 master &&
+ test $(git rev-parse HEAD^^2) = $(git rev-parse to-be-preserved) &&
+ test $(git rev-parse HEAD~3) = $(git rev-parse branch1) &&
+ test $(git show HEAD:file1) = C &&
+ test $(git show HEAD~2:file1) = B
+'
+
+test_expect_success '--continue tries to commit' '
+ test_tick &&
+ ! git rebase -i --onto new-branch1 HEAD^ &&
+ echo resolved > file1 &&
+ git add file1 &&
+ FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue &&
+ test $(git rev-parse HEAD^) = $(git rev-parse new-branch1) &&
+ git show HEAD | grep chouette
+'
+
+test_expect_success 'verbose flag is heeded, even after --continue' '
+ git reset --hard HEAD@{1} &&
+ test_tick &&
+ ! git rebase -v -i --onto new-branch1 HEAD^ &&
+ echo resolved > file1 &&
+ git add file1 &&
+ git rebase --continue > output &&
+ grep "^ file1 | 2 +-$" output
+'
+
+test_expect_success 'multi-squash only fires up editor once' '
+ base=$(git rev-parse HEAD~4) &&
+ FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \
+ git rebase -i $base &&
+ test $base = $(git rev-parse HEAD^) &&
+ test 1 = $(git show | grep ONCE | wc -l)
+'
+
+test_expect_success 'squash works as expected' '
+ for n in one two three four
+ do
+ echo $n >> file$n &&
+ git add file$n &&
+ git commit -m $n
+ done &&
+ one=$(git rev-parse HEAD~3) &&
+ FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
+ test $one = $(git rev-parse HEAD~2)
+'
+
+test_expect_success 'interrupted squash works as expected' '
+ for n in one two three four
+ do
+ echo $n >> conflict &&
+ git add conflict &&
+ git commit -m $n
+ done &&
+ one=$(git rev-parse HEAD~3) &&
+ ! FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
+ (echo one; echo two; echo four) > conflict &&
+ git add conflict &&
+ ! git rebase --continue &&
+ echo resolved > conflict &&
+ git add conflict &&
+ git rebase --continue &&
+ 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^ &&
+ 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_done
--- /dev/null
+#!/bin/sh
+
+test_description='rebase should not insist on git message convention'
+
+. ./test-lib.sh
+
+cat >F <<\EOF
+This is an example of a commit log message
+that does not conform to git commit convention.
+
+It has two paragraphs, but its first paragraph is not friendly
+to oneline summary format.
+EOF
+
+test_expect_success setup '
+
+ >file1 &&
+ >file2 &&
+ git add file1 file2 &&
+ test_tick &&
+ git commit -m "Initial commit" &&
+
+ git checkout -b side &&
+ cat F >file2 &&
+ git add file2 &&
+ test_tick &&
+ git commit -F F &&
+
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >F0 &&
+
+ git checkout master &&
+
+ echo One >file1 &&
+ test_tick &&
+ git add file1 &&
+ git commit -m "Second commit"
+'
+
+test_expect_success rebase '
+
+ git rebase master side &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >F1 &&
+
+ diff -u F0 F1 &&
+ diff -u F F0
+'
+
+test_done
# Copyright (c) 2006 Yann Dirson, based on t3400 by Amos Waterland
#
-test_description='git-cherry should detect patches integrated upstream
+test_description='git cherry should detect patches integrated upstream
This test cherry-picks one local change of two into master branch, and
-checks that git-cherry only returns the second patch in the local branch
+checks that git cherry only returns the second patch in the local branch
'
. ./test-lib.sh
test_expect_success \
'prepare repository with topic branch, and check cherry finds the 2 patches from there' \
'echo First > A &&
- git-update-index --add A &&
+ git update-index --add A &&
git-commit -m "Add A." &&
git-checkout -b my-topic-branch &&
echo Second > B &&
- git-update-index --add B &&
+ git update-index --add B &&
git-commit -m "Add B." &&
sleep 2 &&
echo AnotherSecond > C &&
- git-update-index --add C &&
+ git update-index --add C &&
git-commit -m "Add C." &&
git-checkout -f master &&
rm -f B C &&
echo Third >> A &&
- git-update-index A &&
+ git update-index A &&
git-commit -m "Modify A." &&
- expr "$(echo $(git-cherry master my-topic-branch) )" : "+ [^ ]* + .*"
+ expr "$(echo $(git cherry master my-topic-branch) )" : "+ [^ ]* + .*"
'
test_expect_success \
'check that cherry with limit returns only the top patch'\
- 'expr "$(echo $(git-cherry master my-topic-branch my-topic-branch^1) )" : "+ [^ ]*"
+ 'expr "$(echo $(git cherry master my-topic-branch my-topic-branch^1) )" : "+ [^ ]*"
'
test_expect_success \
'cherry-pick one of the 2 patches, and check cherry recognized one and only one as new' \
- 'git-cherry-pick my-topic-branch^0 &&
- echo $(git-cherry master my-topic-branch) &&
- expr "$(echo $(git-cherry master my-topic-branch) )" : "+ [^ ]* - .*"
+ 'git cherry-pick my-topic-branch^0 &&
+ echo $(git cherry master my-topic-branch) &&
+ expr "$(echo $(git cherry master my-topic-branch) )" : "+ [^ ]* - .*"
'
test_done
# Copyright (c) 2006 Carl D. Worth
#
-test_description='Test of the various options to git-rm.'
+test_description='Test of the various options to git rm.'
. ./test-lib.sh
test_expect_success \
'Initialize test directory' \
"touch -- foo bar baz 'space embedded' -q &&
- git-add -- foo bar baz 'space embedded' -q &&
+ git add -- foo bar baz 'space embedded' -q &&
git-commit -m 'add normal files' &&
test_tabs=y &&
if touch -- 'tab embedded' 'newline
embedded'
then
- git-add -- 'tab embedded' 'newline
+ git add -- 'tab embedded' 'newline
embedded' &&
git-commit -m 'add files with tabs and newlines'
else
fi"
# Later we will try removing an unremovable path to make sure
-# git-rm barfs, but if the test is run as root that cannot be
+# git rm barfs, but if the test is run as root that cannot be
# arranged.
test_expect_success \
'Determine rm behavior' \
rm -f test-file'
test_expect_success \
- 'Pre-check that foo exists and is in index before git-rm foo' \
- '[ -f foo ] && git-ls-files --error-unmatch foo'
+ 'Pre-check that foo exists and is in index before git rm foo' \
+ '[ -f foo ] && git ls-files --error-unmatch foo'
test_expect_success \
- 'Test that git-rm foo succeeds' \
- 'git-rm --cached foo'
+ 'Test that git rm foo succeeds' \
+ 'git rm --cached foo'
test_expect_success \
- 'Post-check that foo exists but is not in index after git-rm foo' \
- '[ -f foo ] && ! git-ls-files --error-unmatch foo'
+ 'Test that git rm --cached foo succeeds if the index matches the file' \
+ 'echo content > foo
+ git add foo
+ git rm --cached foo'
test_expect_success \
- 'Pre-check that bar exists and is in index before "git-rm bar"' \
- '[ -f bar ] && git-ls-files --error-unmatch bar'
+ 'Test that git rm --cached foo succeeds if the index matches the file' \
+ 'echo content > foo
+ git add foo
+ git commit -m foo
+ echo "other content" > foo
+ git rm --cached foo'
+
+test_expect_failure \
+ 'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' \
+ 'echo content > foo
+ git add foo
+ git commit -m foo
+ echo "other content" > foo
+ git add foo
+ echo "yet another content" > foo
+ git rm --cached foo'
+
+test_expect_success \
+ 'Test that git rm --cached -f foo works in case where --cached only did not' \
+ 'echo content > foo
+ git add foo
+ git commit -m foo
+ echo "other content" > foo
+ git add foo
+ echo "yet another content" > foo
+ git rm --cached -f foo'
+
+test_expect_success \
+ 'Post-check that foo exists but is not in index after git rm foo' \
+ '[ -f foo ] && ! git ls-files --error-unmatch foo'
+
+test_expect_success \
+ 'Pre-check that bar exists and is in index before "git rm bar"' \
+ '[ -f bar ] && git ls-files --error-unmatch bar'
test_expect_success \
- 'Test that "git-rm bar" succeeds' \
- 'git-rm bar'
+ 'Test that "git rm bar" succeeds' \
+ 'git rm bar'
test_expect_success \
- 'Post-check that bar does not exist and is not in index after "git-rm -f bar"' \
- '! [ -f bar ] && ! git-ls-files --error-unmatch bar'
+ 'Post-check that bar does not exist and is not in index after "git rm -f bar"' \
+ '! [ -f bar ] && ! git ls-files --error-unmatch bar'
test_expect_success \
- 'Test that "git-rm -- -q" succeeds (remove a file that looks like an option)' \
- 'git-rm -- -q'
+ 'Test that "git rm -- -q" succeeds (remove a file that looks like an option)' \
+ 'git rm -- -q'
test "$test_tabs" = y && test_expect_success \
- "Test that \"git-rm -f\" succeeds with embedded space, tab, or newline characters." \
- "git-rm -f 'space embedded' 'tab embedded' 'newline
+ "Test that \"git rm -f\" succeeds with embedded space, tab, or newline characters." \
+ "git rm -f 'space embedded' 'tab embedded' 'newline
embedded'"
if test "$test_failed_remove" = y; then
chmod a-w .
test_expect_failure \
- 'Test that "git-rm -f" fails if its rm fails' \
- 'git-rm -f baz'
+ 'Test that "git rm -f" fails if its rm fails' \
+ 'git rm -f baz'
chmod 775 .
else
test_expect_success 'skipping removal failure (perhaps running as root?)' :
fi
test_expect_success \
- 'When the rm in "git-rm -f" fails, it should not remove the file from the index' \
- 'git-ls-files --error-unmatch baz'
+ 'When the rm in "git rm -f" fails, it should not remove the file from the index' \
+ 'git ls-files --error-unmatch baz'
test_expect_success 'Remove nonexistent file with --ignore-unmatch' '
git rm --ignore-unmatch nonexistent
# Copyright (c) 2006 Carl D. Worth
#
-test_description='Test of git-add, including the -- option.'
+test_description='Test of git add, including the -- option.'
. ./test-lib.sh
test_expect_success \
- 'Test of git-add' \
- 'touch foo && git-add foo'
+ 'Test of git add' \
+ 'touch foo && git add foo'
test_expect_success \
'Post-check that foo is in the index' \
- 'git-ls-files foo | grep foo'
+ 'git ls-files foo | grep foo'
test_expect_success \
- 'Test that "git-add -- -q" works' \
- 'touch -- -q && git-add -- -q'
+ 'Test that "git add -- -q" works' \
+ 'touch -- -q && git add -- -q'
test_expect_success \
- 'git-add: Test that executable bit is not used if core.filemode=0' \
+ 'git add: Test that executable bit is not used if core.filemode=0' \
'git config core.filemode 0 &&
echo foo >xfoo1 &&
chmod 755 xfoo1 &&
- git-add xfoo1 &&
- case "`git-ls-files --stage xfoo1`" in
+ git add xfoo1 &&
+ case "`git ls-files --stage xfoo1`" in
100644" "*xfoo1) echo ok;;
- *) echo fail; git-ls-files --stage xfoo1; (exit 1);;
+ *) echo fail; git ls-files --stage xfoo1; (exit 1);;
esac'
-test_expect_success '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 &&
- case "`git-ls-files --stage xfoo1`" in
+ git add xfoo1 &&
+ case "`git ls-files --stage xfoo1`" in
120000" "*xfoo1) echo ok;;
- *) echo fail; git-ls-files --stage xfoo1; (exit 1);;
+ *) echo fail; git ls-files --stage xfoo1; (exit 1);;
esac
'
test_expect_success \
- 'git-update-index --add: Test that executable bit is not used...' \
+ 'git update-index --add: Test that executable bit is not used...' \
'git config core.filemode 0 &&
echo foo >xfoo2 &&
chmod 755 xfoo2 &&
- git-update-index --add xfoo2 &&
- case "`git-ls-files --stage xfoo2`" in
+ git update-index --add xfoo2 &&
+ case "`git ls-files --stage xfoo2`" in
100644" "*xfoo2) echo ok;;
- *) echo fail; git-ls-files --stage xfoo2; (exit 1);;
+ *) echo fail; git ls-files --stage xfoo2; (exit 1);;
esac'
-test_expect_success '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 &&
- case "`git-ls-files --stage xfoo2`" in
+ case "`git ls-files --stage xfoo2`" in
120000" "*xfoo2) echo ok;;
- *) echo fail; git-ls-files --stage xfoo2; (exit 1);;
+ *) echo fail; git ls-files --stage xfoo2; (exit 1);;
esac
'
test_expect_success \
- 'git-update-index --add: Test that executable bit is not used...' \
+ '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 &&
- case "`git-ls-files --stage xfoo3`" in
+ git update-index --add xfoo3 &&
+ case "`git ls-files --stage xfoo3`" in
120000" "*xfoo3) echo ok;;
- *) echo fail; git-ls-files --stage xfoo3; (exit 1);;
+ *) echo fail; git ls-files --stage xfoo3; (exit 1);;
esac'
test_expect_success '.gitignore test setup' '
'
test_expect_success '.gitignore is honored' '
- git-add . &&
- ! git-ls-files | grep "\\.ig"
+ git add . &&
+ ! git ls-files | grep "\\.ig"
'
test_expect_success 'error out when attempting to add ignored ones without -f' '
- ! git-add a.?? &&
- ! git-ls-files | grep "\\.ig"
+ ! git add a.?? &&
+ ! git ls-files | grep "\\.ig"
'
test_expect_success 'error out when attempting to add ignored ones without -f' '
- ! git-add d.?? &&
- ! git-ls-files | grep "\\.ig"
+ ! git add d.?? &&
+ ! git ls-files | grep "\\.ig"
'
test_expect_success 'add ignored ones with -f' '
- git-add -f a.?? &&
- git-ls-files --error-unmatch a.ig
+ git add -f a.?? &&
+ git ls-files --error-unmatch a.ig
'
test_expect_success 'add ignored ones with -f' '
- git-add -f d.??/* &&
- git-ls-files --error-unmatch d.ig/d.if d.ig/d.ig
+ git add -f d.??/* &&
+ git ls-files --error-unmatch d.ig/d.if d.ig/d.ig
'
mkdir 1 1/2 1/3
git add 1/2/a 1/3/b 1/2/c
'
+test_expect_success 'git add with filemode=0, symlinks=0, and unmerged entries' '
+ for s in 1 2 3
+ do
+ echo $s > stage$s
+ echo "100755 $(git hash-object -w stage$s) $s file"
+ echo "120000 $(printf $s | git hash-object -w -t blob --stdin) $s symlink"
+ done | git update-index --index-info &&
+ git config core.filemode 0 &&
+ git config core.symlinks 0 &&
+ echo new > file &&
+ echo new > symlink &&
+ git add file symlink &&
+ git ls-files --stage | grep "^100755 .* 0 file$" &&
+ git ls-files --stage | grep "^120000 .* 0 symlink$"
+'
+
+test_expect_success 'git add with filemode=0, symlinks=0 prefers stage 2 over stage 1' '
+ git rm --cached -f file symlink &&
+ (
+ echo "100644 $(git hash-object -w stage1) 1 file"
+ echo "100755 $(git hash-object -w stage2) 2 file"
+ echo "100644 $(printf 1 | git hash-object -w -t blob --stdin) 1 symlink"
+ echo "120000 $(printf 2 | git hash-object -w -t blob --stdin) 2 symlink"
+ ) | git update-index --index-info &&
+ git config core.filemode 0 &&
+ git config core.symlinks 0 &&
+ echo new > file &&
+ echo new > symlink &&
+ git add file symlink &&
+ git ls-files --stage | grep "^100755 .* 0 file$" &&
+ git ls-files --stage | grep "^120000 .* 0 symlink$"
+'
+
test_done
# first create a commit, so we have a valid object/type
# for the tag.
echo Hello >A
-git-update-index --add A
+git update-index --add A
git-commit -m "Initial commit"
-head=$(git-rev-parse --verify HEAD)
+head=$(git rev-parse --verify HEAD)
############################################################
# 1. length check
. ./test-lib.sh
compare_with () {
- git-show -s $1 | sed -e '1,/^$/d' -e 's/^ //' -e '$d' >current &&
+ git show -s $1 | sed -e '1,/^$/d' -e 's/^ //' -e '$d' >current &&
git diff current "$2"
}
test_expect_success setup '
: >F &&
- git-add F &&
- T=$(git-write-tree) &&
- C=$(git-commit-tree $T <../t3900/1-UTF-8.txt) &&
- git-update-ref HEAD $C &&
+ git add F &&
+ T=$(git write-tree) &&
+ C=$(git commit-tree $T <../t3900/1-UTF-8.txt) &&
+ git update-ref HEAD $C &&
git-tag C0
'
test_expect_success 'no encoding header for base case' '
- E=$(git-cat-file commit C0 | sed -ne "s/^encoding //p") &&
+ E=$(git cat-file commit C0 | sed -ne "s/^encoding //p") &&
test z = "z$E"
'
for H in ISO-8859-1 EUCJP ISO-2022-JP
do
test_expect_success "$H setup" '
- git-config i18n.commitencoding $H &&
+ git config i18n.commitencoding $H &&
git-checkout -b $H C0 &&
echo $H >F &&
git-commit -a -F ../t3900/$H.txt
for H in ISO-8859-1 EUCJP ISO-2022-JP
do
test_expect_success "check encoding header for $H" '
- E=$(git-cat-file commit '$H' | sed -ne "s/^encoding //p") &&
+ E=$(git cat-file commit '$H' | sed -ne "s/^encoding //p") &&
test "z$E" = "z'$H'"
'
done
test_expect_success 'config to remove customization' '
- git-config --unset-all i18n.commitencoding &&
- if Z=$(git-config --get-all i18n.commitencoding)
+ git config --unset-all i18n.commitencoding &&
+ if Z=$(git config --get-all i18n.commitencoding)
then
echo Oops, should have failed.
false
else
test z = "z$Z"
fi &&
- git-config i18n.commitencoding utf-8
+ git config i18n.commitencoding utf-8
'
test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' '
done
test_expect_success 'config to add customization' '
- git-config --unset-all i18n.commitencoding &&
- if Z=$(git-config --get-all i18n.commitencoding)
+ git config --unset-all i18n.commitencoding &&
+ if Z=$(git config --get-all i18n.commitencoding)
then
echo Oops, should have failed.
false
for H in ISO-8859-1 EUCJP ISO-2022-JP
do
test_expect_success "$H should be shown in itself now" '
- git-config i18n.commitencoding '$H' &&
+ git config i18n.commitencoding '$H' &&
compare_with '$H' ../t3900/'$H'.txt
'
done
test_expect_success 'config to tweak customization' '
- git-config i18n.logoutputencoding utf-8
+ git config i18n.logoutputencoding utf-8
'
test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' '
for J in EUCJP ISO-2022-JP
do
- git-config i18n.logoutputencoding $J
+ git config i18n.logoutputencoding $J
for H in EUCJP ISO-2022-JP
do
test_expect_success "$H should be shown in $J now" '
do
git format-patch --encoding=UTF-8 --stdout HEAD~$i..HEAD~$j |
grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD=20=C3=B3=C3=BA?=" &&
- git-cat-file commit HEAD~$j |
+ git cat-file commit HEAD~$j |
case "$header" in
8859)
grep "^encoding ISO-8859-1" ;;
}
test_expect_success setup '
- git-config i18n.commitencoding UTF-8 &&
+ git config i18n.commitencoding UTF-8 &&
# use UTF-8 in author and committer name to match the
# i18n.commitencoding settings
git commit -s -m "Second on side" &&
# the second one on the side branch is ISO-8859-1
- git-config i18n.commitencoding ISO-8859-1 &&
+ git config i18n.commitencoding ISO-8859-1 &&
# use author and committer name in ISO-8859-1 to match it.
. ../t3901-8859-1.txt &&
test_tick &&
git commit -s -m "Third on side" &&
# Back to default
- git-config i18n.commitencoding UTF-8
+ git config i18n.commitencoding UTF-8
'
test_expect_success 'format-patch output (ISO-8859-1)' '
- git-config i18n.logoutputencoding ISO-8859-1 &&
+ git config i18n.logoutputencoding ISO-8859-1 &&
git format-patch --stdout master..HEAD^ >out-l1 &&
git format-patch --stdout HEAD^ >out-l2 &&
test_expect_success 'rebase (U/U)' '
# We want the result of rebase in UTF-8
- git-config i18n.commitencoding UTF-8 &&
+ git config i18n.commitencoding UTF-8 &&
# The test is about logoutputencoding not affecting the
# final outcome -- it is used internally to generate the
'
test_expect_success 'rebase (U/L)' '
- git-config i18n.commitencoding UTF-8 &&
+ git config i18n.commitencoding UTF-8 &&
git config i18n.logoutputencoding ISO-8859-1 &&
. ../t3901-utf8.txt &&
test_expect_success 'rebase (L/L)' '
# In this test we want ISO-8859-1 encoded commits as the result
- git-config i18n.commitencoding ISO-8859-1 &&
+ git config i18n.commitencoding ISO-8859-1 &&
git config i18n.logoutputencoding ISO-8859-1 &&
. ../t3901-8859-1.txt &&
test_expect_success 'rebase (L/U)' '
# This is pathological -- use UTF-8 as intermediate form
# to get ISO-8859-1 results.
- git-config i18n.commitencoding ISO-8859-1 &&
+ git config i18n.commitencoding ISO-8859-1 &&
git config i18n.logoutputencoding UTF-8 &&
. ../t3901-8859-1.txt &&
test_expect_success 'cherry-pick(U/U)' '
# Both the commitencoding and logoutputencoding is set to UTF-8.
- git-config i18n.commitencoding UTF-8 &&
+ git config i18n.commitencoding UTF-8 &&
git config i18n.logoutputencoding UTF-8 &&
. ../t3901-utf8.txt &&
test_expect_success 'cherry-pick(L/L)' '
# Both the commitencoding and logoutputencoding is set to ISO-8859-1
- git-config i18n.commitencoding ISO-8859-1 &&
+ git config i18n.commitencoding ISO-8859-1 &&
git config i18n.logoutputencoding ISO-8859-1 &&
. ../t3901-8859-1.txt &&
test_expect_success 'cherry-pick(U/L)' '
# Commitencoding is set to UTF-8 but logoutputencoding is ISO-8859-1
- git-config i18n.commitencoding UTF-8 &&
+ git config i18n.commitencoding UTF-8 &&
git config i18n.logoutputencoding ISO-8859-1 &&
. ../t3901-utf8.txt &&
# Again, the commitencoding is set to ISO-8859-1 but
# logoutputencoding is set to UTF-8.
- git-config i18n.commitencoding ISO-8859-1 &&
+ git config i18n.commitencoding ISO-8859-1 &&
git config i18n.logoutputencoding UTF-8 &&
. ../t3901-8859-1.txt &&
'
test_expect_success 'rebase --merge (U/U)' '
- git-config i18n.commitencoding UTF-8 &&
+ git config i18n.commitencoding UTF-8 &&
git config i18n.logoutputencoding UTF-8 &&
. ../t3901-utf8.txt &&
'
test_expect_success 'rebase --merge (U/L)' '
- git-config i18n.commitencoding UTF-8 &&
+ git config i18n.commitencoding UTF-8 &&
git config i18n.logoutputencoding ISO-8859-1 &&
. ../t3901-utf8.txt &&
test_expect_success 'rebase --merge (L/L)' '
# In this test we want ISO-8859-1 encoded commits as the result
- git-config i18n.commitencoding ISO-8859-1 &&
+ git config i18n.commitencoding ISO-8859-1 &&
git config i18n.logoutputencoding ISO-8859-1 &&
. ../t3901-8859-1.txt &&
test_expect_success 'rebase --merge (L/U)' '
# This is pathological -- use UTF-8 as intermediate form
# to get ISO-8859-1 results.
- git-config i18n.commitencoding ISO-8859-1 &&
+ git config i18n.commitencoding ISO-8859-1 &&
git config i18n.logoutputencoding UTF-8 &&
. ../t3901-8859-1.txt &&
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='quoted output'
+
+. ./test-lib.sh
+
+FN='濱野'
+GN='純'
+HT=' '
+LF='
+'
+DQ='"'
+
+for_each_name () {
+ for name in \
+ Name "Name and a${LF}LF" "Name and an${HT}HT" "Name${DQ}" \
+ "$FN$HT$GN" "$FN$LF$GN" "$FN $GN" "$FN$GN" "$FN$DQ$GN" \
+ "With SP in it"
+ do
+ eval "$1"
+ done
+}
+
+test_expect_success setup '
+
+ for_each_name "echo initial >\"\$name\""
+ git add . &&
+ git commit -q -m Initial &&
+
+ for_each_name "echo second >\"\$name\"" &&
+ git commit -a -m Second
+
+ for_each_name "echo modified >\"\$name\""
+
+'
+
+cat >expect.quoted <<\EOF
+Name
+"Name and a\nLF"
+"Name and an\tHT"
+"Name\""
+With SP in it
+"\346\277\261\351\207\216\t\347\264\224"
+"\346\277\261\351\207\216\n\347\264\224"
+"\346\277\261\351\207\216 \347\264\224"
+"\346\277\261\351\207\216\"\347\264\224"
+"\346\277\261\351\207\216\347\264\224"
+EOF
+
+cat >expect.raw <<\EOF
+Name
+"Name and a\nLF"
+"Name and an\tHT"
+"Name\""
+With SP in it
+"濱野\t純"
+"濱野\n純"
+濱野 純
+"濱野\"純"
+濱野純
+EOF
+
+test_expect_success 'check fully quoted output from ls-files' '
+
+ git ls-files >current && diff -u expect.quoted current
+
+'
+
+test_expect_success 'check fully quoted output from diff-files' '
+
+ git diff --name-only >current &&
+ diff -u expect.quoted current
+
+'
+
+test_expect_success 'check fully quoted output from diff-index' '
+
+ git diff --name-only HEAD >current &&
+ diff -u expect.quoted current
+
+'
+
+test_expect_success 'check fully quoted output from diff-tree' '
+
+ git diff --name-only HEAD^ HEAD >current &&
+ diff -u expect.quoted current
+
+'
+
+test_expect_success 'setting core.quotepath' '
+
+ git config --bool core.quotepath false
+
+'
+
+test_expect_success 'check fully quoted output from ls-files' '
+
+ git ls-files >current && diff -u expect.raw current
+
+'
+
+test_expect_success 'check fully quoted output from diff-files' '
+
+ git diff --name-only >current &&
+ diff -u expect.raw current
+
+'
+
+test_expect_success 'check fully quoted output from diff-index' '
+
+ git diff --name-only HEAD >current &&
+ diff -u expect.raw current
+
+'
+
+test_expect_success 'check fully quoted output from diff-tree' '
+
+ git diff --name-only HEAD^ HEAD >current &&
+ diff -u expect.raw current
+
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E Schindelin
+#
+
+test_description='Test git-stash'
+
+. ./test-lib.sh
+
+test_expect_success 'stash some dirty working directory' '
+ echo 1 > file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ echo 2 > file &&
+ git add file &&
+ echo 3 > file &&
+ test_tick &&
+ git stash &&
+ git diff-files --quiet &&
+ git diff-index --cached --quiet HEAD
+'
+
+cat > expect << EOF
+diff --git a/file b/file
+index 0cfbf08..00750ed 100644
+--- a/file
++++ b/file
+@@ -1 +1 @@
+-2
++3
+EOF
+
+test_expect_success 'parents of stash' '
+ test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
+ git diff stash^2..stash > output &&
+ diff -u output expect
+'
+
+test_expect_success 'apply needs clean working directory' '
+ echo 4 > other-file &&
+ git add other-file &&
+ echo 5 > other-file
+ ! git stash apply
+'
+
+test_expect_success 'apply stashed changes' '
+ git add other-file &&
+ test_tick &&
+ git commit -m other-file &&
+ git stash apply &&
+ test 3 = $(cat file) &&
+ test 1 = $(git show :file) &&
+ test 1 = $(git show HEAD:file)
+'
+
+test_expect_success 'apply stashed changes (including index)' '
+ git reset --hard HEAD^ &&
+ echo 6 > other-file &&
+ git add other-file &&
+ test_tick &&
+ git commit -m other-file &&
+ git stash apply --index &&
+ test 3 = $(cat file) &&
+ test 2 = $(git show :file) &&
+ test 1 = $(git show HEAD:file)
+'
+
+test_expect_success 'unstashing in a subdirectory' '
+ git reset --hard HEAD &&
+ mkdir subdir &&
+ cd subdir &&
+ git stash apply
+'
+
+test_done
test_expect_success \
'update-cache --add two files with and without +x.' \
- 'git-update-index --add path0 path1'
+ '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'
+ 'git diff-files -p after editing work tree.' \
+ 'git diff-files -p >current'
# that's as far as it comes
if [ "$(git config --get core.filemode)" = false ]
EOF
test_expect_success \
- 'validate git-diff-files -p output.' \
+ 'validate git diff-files -p output.' \
'compare_diff_patch current expected'
test_done
test_expect_success \
'update-cache --add a file.' \
- 'git-update-index --add path0'
+ 'git update-index --add path0'
test_expect_success \
'write that tree.' \
- 'tree=$(git-write-tree) && echo $tree'
+ 'tree=$(git write-tree) && echo $tree'
sed -e 's/line/Line/' <path0 >path1
rm -f path0
test_expect_success \
'renamed and edited the file.' \
- 'git-update-index --add --remove path0 path1'
+ 'git update-index --add --remove path0 path1'
test_expect_success \
- 'git-diff-index -p -M after rename and editing.' \
- 'git-diff-index -p -M $tree >current'
+ 'git diff-index -p -M after rename and editing.' \
+ 'git diff-index -p -M $tree >current'
cat >expected <<\EOF
diff --git a/path0 b/path1
rename from path0
'validate the output.' \
'compare_diff_patch current expected'
+test_expect_success 'favour same basenames over different ones' '
+ cp path1 another-path &&
+ git add another-path &&
+ git commit -m 1 &&
+ git rm path1 &&
+ mkdir subdir &&
+ git mv another-path subdir/path1 &&
+ git runstatus | grep "renamed: .*path1 -> subdir/path1"'
+
+test_expect_success 'favour same basenames even with minor differences' '
+ git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
+ git runstatus | grep "renamed: .*path1 -> subdir/path1"'
+
test_done
test_expect_success \
'diff-tree of known trees.' \
- 'git-diff-tree $tree_O $tree_A >.test-a &&
+ 'git diff-tree $tree_O $tree_A >.test-a &&
cmp -s .test-a .test-plain-OA'
test_expect_success \
'diff-tree of known trees.' \
- 'git-diff-tree -r $tree_O $tree_A >.test-a &&
+ 'git diff-tree -r $tree_O $tree_A >.test-a &&
cmp -s .test-a .test-recursive-OA'
test_expect_success \
'diff-tree of known trees.' \
- 'git-diff-tree $tree_O $tree_B >.test-a &&
+ 'git diff-tree $tree_O $tree_B >.test-a &&
cmp -s .test-a .test-plain-OB'
test_expect_success \
'diff-tree of known trees.' \
- 'git-diff-tree -r $tree_O $tree_B >.test-a &&
+ 'git diff-tree -r $tree_O $tree_B >.test-a &&
cmp -s .test-a .test-recursive-OB'
test_expect_success \
'diff-tree of known trees.' \
- 'git-diff-tree $tree_A $tree_B >.test-a &&
+ 'git diff-tree $tree_A $tree_B >.test-a &&
cmp -s .test-a .test-plain-AB'
test_expect_success \
'diff-tree of known trees.' \
- 'git-diff-tree -r $tree_A $tree_B >.test-a &&
+ 'git diff-tree -r $tree_A $tree_B >.test-a &&
cmp -s .test-a .test-recursive-AB'
test_expect_success \
'diff-cache O with A in cache' \
- 'git-read-tree $tree_A &&
- git-diff-index --cached $tree_O >.test-a &&
+ 'git read-tree $tree_A &&
+ git diff-index --cached $tree_O >.test-a &&
cmp -s .test-a .test-recursive-OA'
test_expect_success \
'diff-cache O with B in cache' \
- 'git-read-tree $tree_B &&
- git-diff-index --cached $tree_O >.test-a &&
+ 'git read-tree $tree_B &&
+ git diff-index --cached $tree_O >.test-a &&
cmp -s .test-a .test-recursive-OB'
test_expect_success \
'diff-cache A with B in cache' \
- 'git-read-tree $tree_B &&
- git-diff-index --cached $tree_A >.test-a &&
+ 'git read-tree $tree_B &&
+ git diff-index --cached $tree_A >.test-a &&
cmp -s .test-a .test-recursive-AB'
test_expect_success \
'diff-files with O in cache and A checked out' \
'rm -fr Z [A-Z][A-Z] &&
- git-read-tree $tree_A &&
- git-checkout-index -f -a &&
- git-read-tree --reset $tree_O || return 1
- git-update-index --refresh >/dev/null ;# this can exit non-zero
- git-diff-files >.test-a &&
+ git read-tree $tree_A &&
+ git checkout-index -f -a &&
+ git read-tree --reset $tree_O || return 1
+ git update-index --refresh >/dev/null ;# this can exit non-zero
+ git diff-files >.test-a &&
cmp_diff_files_output .test-a .test-recursive-OA'
test_expect_success \
'diff-files with O in cache and B checked out' \
'rm -fr Z [A-Z][A-Z] &&
- git-read-tree $tree_B &&
- git-checkout-index -f -a &&
- git-read-tree --reset $tree_O || return 1
- git-update-index --refresh >/dev/null ;# this can exit non-zero
- git-diff-files >.test-a &&
+ git read-tree $tree_B &&
+ git checkout-index -f -a &&
+ git read-tree --reset $tree_O || return 1
+ git update-index --refresh >/dev/null ;# this can exit non-zero
+ git diff-files >.test-a &&
cmp_diff_files_output .test-a .test-recursive-OB'
test_expect_success \
'diff-files with A in cache and B checked out' \
'rm -fr Z [A-Z][A-Z] &&
- git-read-tree $tree_B &&
- git-checkout-index -f -a &&
- git-read-tree --reset $tree_A || return 1
- git-update-index --refresh >/dev/null ;# this can exit non-zero
- git-diff-files >.test-a &&
+ git read-tree $tree_B &&
+ git checkout-index -f -a &&
+ git read-tree --reset $tree_A || return 1
+ git update-index --refresh >/dev/null ;# this can exit non-zero
+ git diff-files >.test-a &&
cmp_diff_files_output .test-a .test-recursive-AB'
################################################################
test_expect_success \
'diff-tree O A == diff-tree -R A O' \
- 'git-diff-tree $tree_O $tree_A >.test-a &&
- git-diff-tree -R $tree_A $tree_O >.test-b &&
+ 'git diff-tree $tree_O $tree_A >.test-a &&
+ git diff-tree -R $tree_A $tree_O >.test-b &&
cmp -s .test-a .test-b'
test_expect_success \
'diff-tree -r O A == diff-tree -r -R A O' \
- 'git-diff-tree -r $tree_O $tree_A >.test-a &&
- git-diff-tree -r -R $tree_A $tree_O >.test-b &&
+ 'git diff-tree -r $tree_O $tree_A >.test-a &&
+ git diff-tree -r -R $tree_A $tree_O >.test-b &&
cmp -s .test-a .test-b'
test_expect_success \
'diff-tree B A == diff-tree -R A B' \
- 'git-diff-tree $tree_B $tree_A >.test-a &&
- git-diff-tree -R $tree_A $tree_B >.test-b &&
+ 'git diff-tree $tree_B $tree_A >.test-a &&
+ git diff-tree -R $tree_A $tree_B >.test-b &&
cmp -s .test-a .test-b'
test_expect_success \
'diff-tree -r B A == diff-tree -r -R A B' \
- 'git-diff-tree -r $tree_B $tree_A >.test-a &&
- git-diff-tree -r -R $tree_A $tree_B >.test-b &&
+ 'git diff-tree -r $tree_B $tree_A >.test-a &&
+ git diff-tree -r -R $tree_A $tree_B >.test-b &&
cmp -s .test-a .test-b'
test_done
'prepare reference tree' \
'cat ../../COPYING >COPYING &&
echo frotz >rezrov &&
- git-update-index --add COPYING rezrov &&
- tree=$(git-write-tree) &&
+ git update-index --add COPYING rezrov &&
+ tree=$(git write-tree) &&
echo $tree'
test_expect_success \
'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
rm -f COPYING &&
- git-update-index --add --remove COPYING COPYING.?'
+ git update-index --add --remove COPYING COPYING.?'
# tree has COPYING and rezrov. work tree has COPYING.1 and COPYING.2,
# both are slightly edited, and unchanged rezrov. So we say you
# copy-and-edit one, and rename-and-edit the other. We do not say
# anything about rezrov.
-GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree >current
+GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current
cat >expected <<\EOF
diff --git a/COPYING b/COPYING.1
copy from COPYING
test_expect_success \
'prepare work tree again' \
'mv COPYING.2 COPYING &&
- git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+ git update-index --add --remove COPYING COPYING.1 COPYING.2'
# tree has COPYING and rezrov. work tree has COPYING and COPYING.1,
# both are slightly edited, and unchanged rezrov. So we say you
# edited one, and copy-and-edit the other. We do not say
# anything about rezrov.
-GIT_DIFF_OPTS=--unified=0 git-diff-index -C -p $tree >current
+GIT_DIFF_OPTS=--unified=0 git diff-index -C -p $tree >current
cat >expected <<\EOF
diff --git a/COPYING b/COPYING
--- a/COPYING
test_expect_success \
'prepare work tree once again' \
'cat ../../COPYING >COPYING &&
- git-update-index --add --remove COPYING COPYING.1'
+ git update-index --add --remove COPYING COPYING.1'
# tree has COPYING and rezrov. work tree has COPYING and COPYING.1,
# but COPYING is not edited. We say you copy-and-edit COPYING.1; this
# is only possible because -C mode now reports the unmodified file to
# the diff-core. Unchanged rezrov, although being fed to
-# git-diff-index as well, should not be mentioned.
+# git diff-index as well, should not be mentioned.
GIT_DIFF_OPTS=--unified=0 \
- git-diff-index -C --find-copies-harder -p $tree >current
+ git diff-index -C --find-copies-harder -p $tree >current
cat >expected <<\EOF
diff --git a/COPYING b/COPYING.1
copy from COPYING
'prepare reference tree' \
'echo xyzzy | tr -d '\\\\'012 >yomin &&
ln -s xyzzy frotz &&
- git-update-index --add frotz yomin &&
- tree=$(git-write-tree) &&
+ git update-index --add frotz yomin &&
+ tree=$(git write-tree) &&
echo $tree'
test_expect_success \
rm -f yomin &&
ln -s xyzzy nitfol &&
ln -s xzzzy bozbar &&
- git-update-index --add --remove frotz rezrov nitfol bozbar yomin'
+ git update-index --add --remove frotz rezrov nitfol bozbar yomin'
# tree has frotz pointing at xyzzy, and yomin that contains xyzzy to
# confuse things. work tree has rezrov (xyzzy) nitfol (xyzzy) and
# rezrov and nitfol are rename/copy of frotz and bozbar should be
# a new creation.
-GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree >current
+GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current
cat >expected <<\EOF
diff --git a/bozbar b/bozbar
new file mode 120000
'prepare reference tree' \
'cat ../../COPYING >COPYING &&
echo frotz >rezrov &&
- git-update-index --add COPYING rezrov &&
- tree=$(git-write-tree) &&
+ git update-index --add COPYING rezrov &&
+ tree=$(git write-tree) &&
echo $tree'
test_expect_success \
'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
rm -f COPYING &&
- git-update-index --add --remove COPYING COPYING.?'
+ git update-index --add --remove COPYING COPYING.?'
# tree has COPYING and rezrov. work tree has COPYING.1 and COPYING.2,
# both are slightly edited, and unchanged rezrov. We say COPYING.1
# and COPYING.2 are based on COPYING, and do not say anything about
# rezrov.
-git-diff-index -M $tree >current
+git diff-index -M $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
test_expect_success \
'prepare work tree again' \
'mv COPYING.2 COPYING &&
- git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+ git update-index --add --remove COPYING COPYING.1 COPYING.2'
# tree has COPYING and rezrov. work tree has COPYING and COPYING.1,
# both are slightly edited, and unchanged rezrov. We say COPYING.1
# is based on COPYING and COPYING is still there, and do not say anything
# about rezrov.
-git-diff-index -C $tree >current
+git diff-index -C $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M COPYING
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
test_expect_success \
'prepare work tree once again' \
'cat ../../COPYING >COPYING &&
- git-update-index --add --remove COPYING COPYING.1'
+ git update-index --add --remove COPYING COPYING.1'
-git-diff-index -C --find-copies-harder $tree >current
+git diff-index -C --find-copies-harder $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
EOF
test_expect_success \
'setup' \
'echo frotz >rezrov &&
- git-update-index --add rezrov &&
- tree=`git-write-tree` &&
+ git update-index --add rezrov &&
+ tree=`git write-tree` &&
echo $tree'
if [ "$(git config --get core.filemode)" = false ]
then
say 'filemode disabled on the filesystem, using update-index --chmod=+x'
test_expect_success \
- 'git-update-index --chmod=+x' \
- 'git-update-index rezrov &&
- git-update-index --chmod=+x rezrov &&
- git-diff-index $tree >current'
+ 'git update-index --chmod=+x' \
+ 'git update-index rezrov &&
+ git update-index --chmod=+x rezrov &&
+ git diff-index $tree >current'
else
test_expect_success \
'chmod' \
'chmod +x rezrov &&
- git-update-index rezrov &&
- git-diff-index $tree >current'
+ git update-index rezrov &&
+ git diff-index $tree >current'
fi
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
'git diff expected check'
test_done
-
'prepare reference tree' \
'mkdir path0 path1 &&
cp ../../COPYING path0/COPYING &&
- git-update-index --add path0/COPYING &&
- tree=$(git-write-tree) &&
+ git update-index --add path0/COPYING &&
+ tree=$(git write-tree) &&
echo $tree'
test_expect_success \
'prepare work tree' \
'cp path0/COPYING path1/COPYING &&
- git-update-index --add --remove path0/COPYING path1/COPYING'
+ git update-index --add --remove path0/COPYING path1/COPYING'
# In the tree, there is only path0/COPYING. In the cache, path0 and
# path1 both have COPYING and the latter is a copy of path0/COPYING.
# Comparing the full tree with cache should tell us so.
-git-diff-index -C --find-copies-harder $tree >current
+git diff-index -C --find-copies-harder $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 C100 path0/COPYING path1/COPYING
# path1/COPYING suddenly appearing from nowhere, not detected as
# a copy from path0/COPYING.
-git-diff-index -C $tree path1 >current
+git diff-index -C $tree path1 >current
cat >expected <<\EOF
:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A path1/COPYING
test_expect_success \
'tweak work tree' \
'rm -f path0/COPYING &&
- git-update-index --remove path0/COPYING'
+ git update-index --remove path0/COPYING'
# In the tree, there is only path0/COPYING. In the cache, path0 does
# not have COPYING anymore and path1 has COPYING which is a copy of
# path0/COPYING. Showing the full tree with cache should tell us about
# the rename.
-git-diff-index -C $tree >current
+git diff-index -C $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100 path0/COPYING path1/COPYING
# path0/COPYING. When we say we care only about path1, we should just
# see path1/COPYING appearing from nowhere.
-git-diff-index -C $tree path1 >current
+git diff-index -C $tree path1 >current
cat >expected <<\EOF
:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A path1/COPYING
setup \
'cat ../../README >file0 &&
cat ../../COPYING >file1 &&
- git-update-index --add file0 file1 &&
- tree=$(git-write-tree) &&
+ git update-index --add file0 file1 &&
+ tree=$(git write-tree) &&
echo "$tree"'
test_expect_success \
'change file1 with copy-edit of file0 and remove file0' \
'sed -e "s/git/GIT/" file0 >file1 &&
rm -f file0 &&
- git-update-index --remove file0 file1'
+ git update-index --remove file0 file1'
test_expect_success \
'run diff with -B' \
- 'git-diff-index -B --cached "$tree" >current'
+ 'git diff-index -B --cached "$tree" >current'
cat >expected <<\EOF
:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D file0
test_expect_success \
'run diff with -B and -M' \
- 'git-diff-index -B -M "$tree" >current'
+ 'git diff-index -B -M "$tree" >current'
cat >expected <<\EOF
:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c R100 file0 file1
test_expect_success \
'swap file0 and file1' \
'rm -f file0 file1 &&
- git-read-tree -m $tree &&
- git-checkout-index -f -u -a &&
+ git read-tree -m $tree &&
+ git checkout-index -f -u -a &&
mv file0 tmp &&
mv file1 file0 &&
mv tmp file1 &&
- git-update-index file0 file1'
+ git update-index file0 file1'
test_expect_success \
'run diff with -B' \
- 'git-diff-index -B "$tree" >current'
+ 'git diff-index -B "$tree" >current'
cat >expected <<\EOF
:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100 file0
test_expect_success \
'run diff with -B and -M' \
- 'git-diff-index -B -M "$tree" >current'
+ 'git diff-index -B -M "$tree" >current'
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100 file1 file0
'make file0 into something completely different' \
'rm -f file0 &&
ln -s frotz file0 &&
- git-update-index file0 file1'
+ git update-index file0 file1'
test_expect_success \
'run diff with -B' \
- 'git-diff-index -B "$tree" >current'
+ 'git diff-index -B "$tree" >current'
cat >expected <<\EOF
:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T file0
test_expect_success \
'run diff with -B' \
- 'git-diff-index -B -M "$tree" >current'
+ 'git diff-index -B -M "$tree" >current'
# This should not mistake file0 as the copy source of new file1
# due to type differences.
test_expect_success \
'run diff with -M' \
- 'git-diff-index -M "$tree" >current'
+ 'git diff-index -M "$tree" >current'
# This should not mistake file0 as the copy source of new file1
# due to type differences.
test_expect_success \
'file1 edited to look like file0 and file0 rename-edited to file2' \
'rm -f file0 file1 &&
- git-read-tree -m $tree &&
- git-checkout-index -f -u -a &&
+ git read-tree -m $tree &&
+ git checkout-index -f -u -a &&
sed -e "s/git/GIT/" file0 >file1 &&
sed -e "s/git/GET/" file0 >file2 &&
rm -f file0
- git-update-index --add --remove file0 file1 file2'
+ git update-index --add --remove file0 file1 file2'
test_expect_success \
'run diff with -B' \
- 'git-diff-index -B "$tree" >current'
+ 'git diff-index -B "$tree" >current'
cat >expected <<\EOF
:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D file0
test_expect_success \
'run diff with -B -M' \
- 'git-diff-index -B -M "$tree" >current'
+ 'git diff-index -B -M "$tree" >current'
cat >expected <<\EOF
:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c C095 file0 file1
'prepare reference tree' \
'cat ../../COPYING >COPYING &&
echo frotz >rezrov &&
- git-update-index --add COPYING rezrov &&
- tree=$(git-write-tree) &&
+ git update-index --add COPYING rezrov &&
+ tree=$(git write-tree) &&
echo $tree'
test_expect_success \
'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
rm -f COPYING &&
- git-update-index --add --remove COPYING COPYING.?'
+ git update-index --add --remove COPYING COPYING.?'
# tree has COPYING and rezrov. work tree has COPYING.1 and COPYING.2,
# both are slightly edited, and unchanged rezrov. We say COPYING.1
# and COPYING.2 are based on COPYING, and do not say anything about
# rezrov.
-git-diff-index -z -M $tree >current
+git diff-index -z -M $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
test_expect_success \
'prepare work tree again' \
'mv COPYING.2 COPYING &&
- git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+ git update-index --add --remove COPYING COPYING.1 COPYING.2'
# tree has COPYING and rezrov. work tree has COPYING and COPYING.1,
# both are slightly edited, and unchanged rezrov. We say COPYING.1
# is based on COPYING and COPYING is still there, and do not say anything
# about rezrov.
-git-diff-index -z -C $tree >current
+git diff-index -z -C $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M
COPYING
test_expect_success \
'prepare work tree once again' \
'cat ../../COPYING >COPYING &&
- git-update-index --add --remove COPYING COPYING.1'
+ git update-index --add --remove COPYING COPYING.1'
-git-diff-index -z -C --find-copies-harder $tree >current
+git diff-index -z -C --find-copies-harder $tree >current
cat >expected <<\EOF
:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
COPYING
'echo frotz >file0 &&
mkdir path1 &&
echo rezrov >path1/file1 &&
- git-update-index --add file0 path1/file1 &&
- tree=`git-write-tree` &&
+ git update-index --add file0 path1/file1 &&
+ tree=`git write-tree` &&
echo "$tree" &&
echo nitfol >file0 &&
echo yomin >path1/file1 &&
- git-update-index file0 path1/file1'
+ git update-index file0 path1/file1'
cat >expected <<\EOF
EOF
test_expect_success \
'limit to path should show nothing' \
- 'git-diff-index --cached $tree -- path >current &&
+ 'git diff-index --cached $tree -- path >current &&
compare_diff_raw current expected'
cat >expected <<\EOF
EOF
test_expect_success \
'limit to path1 should show path1/file1' \
- 'git-diff-index --cached $tree -- path1 >current &&
+ 'git diff-index --cached $tree -- path1 >current &&
compare_diff_raw current expected'
cat >expected <<\EOF
EOF
test_expect_success \
'limit to path1/ should show path1/file1' \
- 'git-diff-index --cached $tree -- path1/ >current &&
+ 'git diff-index --cached $tree -- path1/ >current &&
compare_diff_raw current expected'
cat >expected <<\EOF
EOF
test_expect_success \
'limit to file0 should show file0' \
- 'git-diff-index --cached $tree -- file0 >current &&
+ 'git diff-index --cached $tree -- file0 >current &&
compare_diff_raw current expected'
cat >expected <<\EOF
EOF
test_expect_success \
'limit to file0/ should emit nothing.' \
- 'git-diff-index --cached $tree -- file0/ >current &&
+ 'git diff-index --cached $tree -- file0/ >current &&
compare_diff_raw current expected'
test_done
test_expect_success \
'diff new symlink' \
'ln -s xyzzy frotz &&
- git-update-index &&
- tree=$(git-write-tree) &&
- git-update-index --add frotz &&
- GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree > current &&
+ git update-index &&
+ tree=$(git write-tree) &&
+ git update-index --add frotz &&
+ GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree > current &&
compare_diff_patch current expected'
test_expect_success \
'diff unchanged symlink' \
- 'tree=$(git-write-tree) &&
- git-update-index frotz &&
- test -z "$(git-diff-index --name-only $tree)"'
+ 'tree=$(git write-tree) &&
+ git update-index frotz &&
+ test -z "$(git diff-index --name-only $tree)"'
cat > expected << EOF
diff --git a/frotz b/frotz
test_expect_success \
'diff removed symlink' \
'rm frotz &&
- git-diff-index -M -p $tree > current &&
+ git diff-index -M -p $tree > current &&
compare_diff_patch current expected'
cat > expected << EOF
'diff identical, but newly created symlink' \
'sleep 3 &&
ln -s xyzzy frotz &&
- git-diff-index -M -p $tree > current &&
+ git diff-index -M -p $tree > current &&
compare_diff_patch current expected'
cat > expected << EOF
'diff different symlink' \
'rm frotz &&
ln -s yxyyz frotz &&
- git-diff-index -M -p $tree > current &&
+ git diff-index -M -p $tree > current &&
compare_diff_patch current expected'
test_done
test_expect_success 'prepare repository' \
'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
- git-update-index --add a b c d &&
+ git update-index --add a b c d &&
echo git >a &&
cat ../test4012.png >b &&
echo git >c &&
4 files changed, 2 insertions(+), 2 deletions(-)
EOF
test_expect_success 'diff without --binary' \
- 'git-diff | git-apply --stat --summary >current &&
+ 'git diff | git apply --stat --summary >current &&
cmp current expected'
test_expect_success 'diff with --binary' \
- 'git-diff --binary | git-apply --stat --summary >current &&
+ 'git diff --binary | git apply --stat --summary >current &&
cmp current expected'
# apply needs to be able to skip the binary material correctly
# in order to report the line number of a corrupt patch.
test_expect_success 'apply detecting corrupt patch correctly' \
- 'git-diff | sed -e 's/-CIT/xCIT/' >broken &&
- if git-apply --stat --summary broken 2>detected
+ 'git diff | sed -e 's/-CIT/xCIT/' >broken &&
+ if git apply --stat --summary broken 2>detected
then
echo unhappy - should have detected an error
(exit 1)
test "$detected" = xCIT'
test_expect_success 'apply detecting corrupt patch correctly' \
- 'git-diff --binary | sed -e 's/-CIT/xCIT/' >broken &&
- if git-apply --stat --summary broken 2>detected
+ 'git diff --binary | sed -e 's/-CIT/xCIT/' >broken &&
+ if git apply --stat --summary broken 2>detected
then
echo unhappy - should have detected an error
(exit 1)
# Try removal (b), modification (d), and creation (e).
test_expect_success 'diff-index with --binary' \
'echo AIT >a && mv b e && echo CIT >c && cat e >d &&
- git-update-index --add --remove a b c d e &&
- tree0=`git-write-tree` &&
- git-diff --cached --binary >current &&
- git-apply --stat --summary current'
+ git update-index --add --remove a b c d e &&
+ tree0=`git write-tree` &&
+ git diff --cached --binary >current &&
+ git apply --stat --summary current'
test_expect_success 'apply binary patch' \
'git-reset --hard &&
- git-apply --binary --index <current &&
- tree1=`git-write-tree` &&
+ git apply --binary --index <current &&
+ tree1=`git write-tree` &&
test "$tree1" = "$tree0"'
test_done
format-patch --inline --stdout initial..master^
format-patch --inline --stdout initial..master
format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
+config format.subjectprefix DIFFERENT_PREFIX
+format-patch --inline --stdout initial..master^^
diff --abbrev initial..side
diff -r initial..side
--- /dev/null
+$ git config format.subjectprefix DIFFERENT_PREFIX
+$
--- /dev/null
+$ git format-patch --inline --stdout initial..master^^
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [DIFFERENT_PREFIX] Second
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+
+This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
} while (0);
EOF
-git-update-index --add x
+git update-index --add x
cat << EOF > x
do
+while (0);
EOF
-git-diff > out
+git diff > out
test_expect_success "Ray's example without options" 'git diff expect out'
-git-diff -w > out
+git diff -w > out
test_expect_success "Ray's example with -w" 'git diff expect out'
-git-diff -b > out
+git diff -b > out
test_expect_success "Ray's example with -b" 'git diff expect out'
tr 'Q' '\015' << EOF > x
CR at endQ
EOF
-git-update-index x
+git update-index x
cat << EOF > x
whitespace at beginning
-CR at endQ
+CR at end
EOF
-git-diff > out
+git diff > out
test_expect_success 'another test, without options' 'git diff expect out'
cat << EOF > expect
diff --git a/x b/x
index d99af23..8b32fb5 100644
EOF
-git-diff -w > out
+git diff -w > out
test_expect_success 'another test, with -w' 'git diff expect out'
tr 'Q' '\015' << EOF > expect
unchanged line
CR at endQ
EOF
-git-diff -b > out
+git diff -b > out
test_expect_success 'another test, with -b' 'git diff expect out'
test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='Test custom diff function name patterns'
+
+. ./test-lib.sh
+
+LF='
+'
+
+cat > Beer.java << EOF
+public class Beer
+{
+ int special;
+ public static void main(String args[])
+ {
+ String s=" ";
+ for(int x = 99; x > 0; x--)
+ {
+ System.out.print(x + " bottles of beer on the wall "
+ + x + " bottles of beer\n"
+ + "Take one down, pass it around, " + (x - 1)
+ + " bottles of beer on the wall.\n");
+ }
+ System.out.print("Go to the store, buy some more,\n"
+ + "99 bottles of beer on the wall.\n");
+ }
+}
+EOF
+
+sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java
+
+test_expect_success 'default behaviour' '
+ git diff Beer.java Beer-correct.java |
+ grep "^@@.*@@ public class Beer"
+'
+
+test_expect_success 'preset java pattern' '
+ echo "*.java diff=java" >.gitattributes &&
+ git diff Beer.java Beer-correct.java |
+ grep "^@@.*@@ public static void main("
+'
+
+git config diff.java.funcname '!static
+!String
+[^ ].*s.*'
+
+test_expect_success 'custom pattern' '
+ git diff Beer.java Beer-correct.java |
+ grep "^@@.*@@ int special;$"
+'
+
+test_expect_success 'last regexp must not be negated' '
+ git config diff.java.funcname "!static" &&
+ ! git diff Beer.java Beer-correct.java
+'
+
+test_done
'
+test_expect_success 'no diff with -diff' '
+ echo >.gitattributes "file -diff" &&
+ git diff | grep Binary
+'
+
+echo NULZbetweenZwords | tr Z '\0' > file
+
+test_expect_success 'force diff with "diff"' '
+ echo >.gitattributes "file diff" &&
+ git diff | grep -a second
+'
+
test_done
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-apply --stat --summary test.
+test_description='git apply --stat --summary test.
'
. ./test-lib.sh
test_expect_success \
'rename' \
- 'git-apply --stat --summary <../t4100/t-apply-1.patch >current &&
+ 'git apply --stat --summary <../t4100/t-apply-1.patch >current &&
git diff ../t4100/t-apply-1.expect current'
test_expect_success \
'copy' \
- 'git-apply --stat --summary <../t4100/t-apply-2.patch >current &&
+ 'git apply --stat --summary <../t4100/t-apply-2.patch >current &&
git diff ../t4100/t-apply-2.expect current'
test_expect_success \
'rewrite' \
- 'git-apply --stat --summary <../t4100/t-apply-3.patch >current &&
+ 'git apply --stat --summary <../t4100/t-apply-3.patch >current &&
git diff ../t4100/t-apply-3.expect current'
test_expect_success \
'mode' \
- 'git-apply --stat --summary <../t4100/t-apply-4.patch >current &&
+ 'git apply --stat --summary <../t4100/t-apply-4.patch >current &&
git diff ../t4100/t-apply-4.expect current'
test_expect_success \
'non git' \
- 'git-apply --stat --summary <../t4100/t-apply-5.patch >current &&
+ 'git apply --stat --summary <../t4100/t-apply-5.patch >current &&
git diff ../t4100/t-apply-5.expect current'
test_expect_success \
'non git' \
- 'git-apply --stat --summary <../t4100/t-apply-6.patch >current &&
+ 'git apply --stat --summary <../t4100/t-apply-6.patch >current &&
git diff ../t4100/t-apply-6.expect current'
test_expect_success \
'non git' \
- 'git-apply --stat --summary <../t4100/t-apply-7.patch >current &&
+ 'git apply --stat --summary <../t4100/t-apply-7.patch >current &&
git diff ../t4100/t-apply-7.expect current'
test_done
-
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-apply should handle files with incomplete lines.
+test_description='git apply should handle files with incomplete lines.
'
. ./test-lib.sh
cat frotz.$i >frotz
test_expect_success \
"apply diff between $i and $j" \
- "git-apply <../t4101/diff.$i-$j && diff frotz.$j frotz"
+ "git apply <../t4101/diff.$i-$j && diff frotz.$j frotz"
done
done
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-apply handling copy/rename patch.
+test_description='git apply handling copy/rename patch.
'
. ./test-lib.sh
chmod +x foo
test_expect_success setup \
- 'git-update-index --add foo'
+ 'git update-index --add foo'
test_expect_success apply \
- 'git-apply --index --stat --summary --apply test-patch'
+ 'git apply --index --stat --summary --apply test-patch'
if [ "$(git config --get core.filemode)" = false ]
then
fi
test_expect_success 'apply reverse' \
- 'git-apply -R --index --stat --summary --apply test-patch &&
+ 'git apply -R --index --stat --summary --apply test-patch &&
test "$(cat foo)" = "This is foo"'
cat >test-patch <<\EOF
EOF
test_expect_success 'apply copy' \
- 'git-apply --index --stat --summary --apply test-patch &&
+ 'git apply --index --stat --summary --apply test-patch &&
test "$(cat bar)" = "This is bar" -a "$(cat foo)" = "This is foo"'
test_done
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-apply handling binary patches
+test_description='git apply handling binary patches
'
. ./test-lib.sh
cat file1 >file2
cat file1 >file4
-git-update-index --add --remove file1 file2 file4
+git update-index --add --remove file1 file2 file4
git-commit -m 'Initial Version' 2>/dev/null
git-checkout -b binary
tr 'x' '\0' <file1 >file3
cat file3 >file4
-git-add file2
+git add file2
tr '\0' 'v' <file3 >file1
rm -f file2
-git-update-index --add --remove file1 file2 file3 file4
+git update-index --add --remove file1 file2 file3 file4
git-commit -m 'Second Version'
-git-diff-tree -p master binary >B.diff
-git-diff-tree -p -C master binary >C.diff
+git diff-tree -p master binary >B.diff
+git diff-tree -p -C master binary >C.diff
-git-diff-tree -p --binary master binary >BF.diff
-git-diff-tree -p --binary -C master binary >CF.diff
+git diff-tree -p --binary master binary >BF.diff
+git diff-tree -p --binary -C master binary >CF.diff
test_expect_success 'stat binary diff -- should not fail.' \
'git-checkout master
- git-apply --stat --summary B.diff'
+ git apply --stat --summary B.diff'
test_expect_success 'stat binary diff (copy) -- should not fail.' \
'git-checkout master
- git-apply --stat --summary C.diff'
+ git apply --stat --summary C.diff'
test_expect_failure 'check binary diff -- should fail.' \
'git-checkout master
- git-apply --check B.diff'
+ git apply --check B.diff'
test_expect_failure 'check binary diff (copy) -- should fail.' \
'git-checkout master
- git-apply --check C.diff'
+ git apply --check C.diff'
test_expect_failure 'check incomplete binary diff with replacement -- should fail.' \
'git-checkout master
- git-apply --check --allow-binary-replacement B.diff'
+ git apply --check --allow-binary-replacement B.diff'
test_expect_failure 'check incomplete binary diff with replacement (copy) -- should fail.' \
'git-checkout master
- git-apply --check --allow-binary-replacement C.diff'
+ git apply --check --allow-binary-replacement C.diff'
test_expect_success 'check binary diff with replacement.' \
'git-checkout master
- git-apply --check --allow-binary-replacement BF.diff'
+ git apply --check --allow-binary-replacement BF.diff'
test_expect_success 'check binary diff with replacement (copy).' \
'git-checkout master
- git-apply --check --allow-binary-replacement CF.diff'
+ git apply --check --allow-binary-replacement CF.diff'
# Now we start applying them.
test_expect_failure 'apply binary diff -- should fail.' \
'do_reset
- git-apply B.diff'
+ git apply B.diff'
test_expect_failure 'apply binary diff -- should fail.' \
'do_reset
- git-apply --index B.diff'
+ git apply --index B.diff'
test_expect_failure 'apply binary diff (copy) -- should fail.' \
'do_reset
- git-apply C.diff'
+ git apply C.diff'
test_expect_failure 'apply binary diff (copy) -- should fail.' \
'do_reset
- git-apply --index C.diff'
+ git apply --index C.diff'
test_expect_success 'apply binary diff without replacement.' \
'do_reset
- git-apply BF.diff'
+ git apply BF.diff'
test_expect_success 'apply binary diff without replacement (copy).' \
'do_reset
- git-apply CF.diff'
+ git apply CF.diff'
test_expect_success 'apply binary diff.' \
'do_reset
- git-apply --allow-binary-replacement --index BF.diff &&
- test -z "$(git-diff --name-status binary)"'
+ git apply --allow-binary-replacement --index BF.diff &&
+ test -z "$(git diff --name-status binary)"'
test_expect_success 'apply binary diff (copy).' \
'do_reset
- git-apply --allow-binary-replacement --index CF.diff &&
- test -z "$(git-diff --name-status binary)"'
+ git apply --allow-binary-replacement --index CF.diff &&
+ test -z "$(git diff --name-status binary)"'
test_done
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-apply boundary tests
+test_description='git apply boundary tests
'
. ./test-lib.sh
# Copyright (c) 2005 Robert Fitzsimons
#
-test_description='git-apply test patches with multiple fragments.
+test_description='git apply test patches with multiple fragments.
'
. ./test-lib.sh
EOF
-test_expect_success "S = git-apply (1)" \
- 'git-apply patch1.patch patch2.patch'
+test_expect_success "S = git apply (1)" \
+ 'git apply patch1.patch patch2.patch'
mv main.c main.c.git
test_expect_success "S = patch (1)" \
rm -f main.c main.c.git
-test_expect_success "S = git-apply (2)" \
- 'git-apply patch1.patch patch2.patch patch3.patch'
+test_expect_success "S = git apply (2)" \
+ 'git apply patch1.patch patch2.patch patch3.patch'
mv main.c main.c.git
test_expect_success "S = patch (2)" \
rm -f main.c main.c.git
-test_expect_success "S = git-apply (3)" \
- 'git-apply patch1.patch patch4.patch'
+test_expect_success "S = git apply (3)" \
+ 'git apply patch1.patch patch4.patch'
mv main.c main.c.git
test_expect_success "S = patch (3)" \
# Copyright (c) 2005 Robert Fitzsimons
#
-test_description='git-apply test for patches which require scanning forwards and backwards.
+test_description='git apply test for patches which require scanning forwards and backwards.
'
. ./test-lib.sh
+c2222
EOF
-test_expect_success "S = git-apply scan" \
- 'git-apply patch1.patch patch2.patch patch3.patch patch4.patch patch5.patch'
+test_expect_success "S = git apply scan" \
+ 'git apply patch1.patch patch2.patch patch3.patch patch4.patch patch5.patch'
mv new.txt apply.txt
test_expect_success "S = patch scan" \
'cmp apply.txt patch.txt'
test_done
-
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-apply should not get confused with rename/copy.
+test_description='git apply should not get confused with rename/copy.
'
- * arch/x86_64/include/klibc/archsetjmp.h
+ * arch/cris/include/klibc/archsetjmp.h
*/
-
+
#ifndef _KLIBC_ARCHSETJMP_H
#define _KLIBC_ARCHSETJMP_H
-
+
struct __jmp_buf {
- unsigned long __rbx;
- unsigned long __rsp;
+ unsigned long __sp;
+ unsigned long __srp;
};
-
+
typedef struct __jmp_buf jmp_buf[1];
-
+
-#endif /* _SETJMP_H */
+#endif /* _KLIBC_ARCHSETJMP_H */
diff --git a/klibc/arch/x86_64/include/klibc/archsetjmp.h b/include/arch/m32r/klibc/archsetjmp.h
- * arch/x86_64/include/klibc/archsetjmp.h
+ * arch/m32r/include/klibc/archsetjmp.h
*/
-
+
#ifndef _KLIBC_ARCHSETJMP_H
#define _KLIBC_ARCHSETJMP_H
-
+
struct __jmp_buf {
- unsigned long __rbx;
- unsigned long __rsp;
unsigned long __r15;
- unsigned long __rip;
};
-
+
typedef struct __jmp_buf jmp_buf[1];
-
+
-#endif /* _SETJMP_H */
+#endif /* _KLIBC_ARCHSETJMP_H */
EOF
-find klibc -type f -print | xargs git-update-index --add --
+find klibc -type f -print | xargs git update-index --add --
-test_expect_success 'check rename/copy patch' 'git-apply --check patch'
+test_expect_success 'check rename/copy patch' 'git apply --check patch'
-test_expect_success 'apply rename/copy patch' 'git-apply --index patch'
+test_expect_success 'apply rename/copy patch' 'git apply --index patch'
test_done
# Copyright (c) 2006 Catalin Marinas
#
-test_description='git-apply trying to add an ending line.
+test_description='git apply trying to add an ending line.
'
. ./test-lib.sh
echo 'c' >>file
test_expect_success setup \
- 'git-update-index --add file'
+ 'git update-index --add file'
# test
test_expect_failure 'apply at the end' \
- 'git-apply --index test-patch'
+ 'git apply --index test-patch'
cat >test-patch <<\EOF
diff a/file b/file
echo >file 'a
b
c'
-git-update-index file
+git update-index file
test_expect_failure 'apply at the beginning' \
- 'git-apply --index test-patch'
+ 'git apply --index test-patch'
test_done
# Copyright (c) 2006 Eric Wong
#
-test_description='git-apply should not get confused with type changes.
+test_description='git apply should not get confused with type changes.
'
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-apply symlinks and partial files
+test_description='git apply symlinks and partial files
'
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-apply in reverse
+test_description='git apply in reverse
'
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-apply with rejects
+test_description='git apply with rejects
'
# Copyright (c) 2006 Junio C Hamano
#
-test_description='git-apply with new style GNU diff with empty context
+test_description='git apply with new style GNU diff with empty context
'
'
test_done
-
# Copyright (c) 2007 Junio C Hamano
#
-test_description='git-apply --whitespace=strip and configuration file.
+test_description='git apply --whitespace=strip and configuration file.
'
+++ file1+ 2007-02-21 01:07:44.000000000 -0800
@@ -1 +1 @@
-A
-+B
++B
EOF
sed -e 's|file1|sub/&|' gpatch.file >gpatch-sub.file &&
# Copyright (c) 2007 Shawn O. Pearce
#
-test_description='git-apply -p handling.'
+test_description='git apply -p handling.'
. ./test-lib.sh
#!/bin/sh
-test_description='git-apply for contextually independent diffs'
+test_description='git apply for contextually independent diffs'
. ./test-lib.sh
echo '1
'( git diff test~2 test~1; git diff test~1 test~0 )| git apply'
test_done
-
# Copyright (c) 2006 Johannes E. Schindelin
#
-test_description='git-rerere
+test_description='git rerere
'
. ./test-lib.sh
echo "* END *" >>a1
git commit -q -a -m second
-# activate rerere
-mkdir .git/rr-cache
+test_expect_success 'nothing recorded without rerere' '
+ (rm -rf .git/rr-cache; git config rerere.enabled false) &&
+ ! git merge first &&
+ ! test -d .git/rr-cache
+'
-test_expect_failure 'conflicting merge' 'git pull . first'
+# activate rerere, old style
+test_expect_success 'conflicting merge' '
+ git reset --hard &&
+ mkdir .git/rr-cache &&
+ git config --unset rerere.enabled &&
+ ! git merge first
+'
sha1=$(sed -e 's/ .*//' .git/rr-cache/MERGE_RR)
rr=.git/rr-cache/$sha1
test_expect_success 'recorded preimage' "grep ======= $rr/preimage"
+test_expect_success 'rerere.enabled works, too' '
+ rm -rf .git/rr-cache &&
+ git config rerere.enabled true &&
+ git reset --hard &&
+ ! git merge first &&
+ grep ======= $rr/preimage
+'
+
test_expect_success 'no postimage or thisimage yet' \
"test ! -f $rr/postimage -a ! -f $rr/thisimage"
test_expect_success 'recorded postimage' "test -f $rr/postimage"
-git checkout -b third master
-git show second^:a1 | sed 's/To die: t/To die! T/' > a1
-git commit -q -a -m third
-
-test_expect_failure 'another conflicting merge' 'git pull . first'
+test_expect_success 'another conflicting merge' '
+ git checkout -b third master &&
+ git show second^:a1 | sed "s/To die: t/To die! T/" > a1 &&
+ git commit -q -a -m third &&
+ ! git pull . first
+'
git show first:a1 | sed 's/To die: t/To die! T/' > expect
test_expect_success 'rerere kicked in' "! grep ======= a1"
test_expect_success 'old records rest in peace' \
"test ! -f $rr/preimage && test ! -f $rr2/preimage"
-test_done
+test_expect_success 'file2 added differently in two branches' '
+ git reset --hard &&
+ git checkout -b fourth &&
+ echo Hallo > file2 &&
+ git add file2 &&
+ git commit -m version1 &&
+ git checkout third &&
+ echo Bello > file2 &&
+ git add file2 &&
+ git commit -m version2 &&
+ ! git merge fourth &&
+ sha1=$(sed -e "s/ .*//" .git/rr-cache/MERGE_RR) &&
+ rr=.git/rr-cache/$sha1 &&
+ echo Cello > file2 &&
+ git add file2 &&
+ git commit -m resolution
+'
+test_expect_success 'resolution was recorded properly' '
+ git reset --hard HEAD~2 &&
+ git checkout -b fifth &&
+ echo Hallo > file3 &&
+ git add file3 &&
+ git commit -m version1 &&
+ git checkout third &&
+ echo Bello > file3 &&
+ git add file3 &&
+ git commit -m version2 &&
+ ! git merge fifth &&
+ git diff-files -q &&
+ test Cello = "$(cat file3)"
+'
+test_done
# Copyright (c) 2006 Johannes E. Schindelin
#
-test_description='git-shortlog
+test_description='git shortlog
'
. ./test-lib.sh
# Copyright (C) 2005 Rene Scharfe
#
-test_description='git-tar-tree and git-get-tar-commit-id test
+test_description='git tar-tree and git get-tar-commit-id test
This test covers the topics of file contents, commit date handling and
commit id embedding:
binary file (/bin/sh). Only paths shorter than 99 characters are
used.
- git-tar-tree applies the commit date to every file in the archive it
+ git tar-tree applies the commit date to every file in the archive it
creates. The test sets the commit date to a specific value and checks
if the tar archive contains that value.
- When giving git-tar-tree a commit id (in contrast to a tree id) it
+ When giving git tar-tree a commit id (in contrast to a tree id) it
embeds this commit id into the tar archive as a comment. The test
- checks the ability of git-get-tar-commit-id to figure it out from the
+ checks the ability of git get-tar-commit-id to figure it out from the
tar file.
'
test_expect_success \
'add files to repository' \
- 'find a -type f | xargs git-update-index --add &&
- find a -type l | xargs git-update-index --add &&
- treeid=`git-write-tree` &&
+ 'find a -type f | xargs git update-index --add &&
+ find a -type l | xargs git update-index --add &&
+ treeid=`git write-tree` &&
echo $treeid >treeid &&
- git-update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
- git-commit-tree $treeid </dev/null)'
+ git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
+ git commit-tree $treeid </dev/null)'
test_expect_success \
- 'git-archive' \
- 'git-archive HEAD >b.tar'
+ 'git archive' \
+ 'git archive HEAD >b.tar'
test_expect_success \
- 'git-tar-tree' \
- 'git-tar-tree HEAD >b2.tar'
+ 'git tar-tree' \
+ 'git tar-tree HEAD >b2.tar'
test_expect_success \
- 'git-archive vs. git-tar-tree' \
+ 'git archive vs. git tar-tree' \
'diff b.tar b2.tar'
test_expect_success \
diff expected.mtime b.mtime'
test_expect_success \
- 'git-get-tar-commit-id' \
- 'git-get-tar-commit-id <b.tar >b.commitid &&
- diff .git/$(git-symbolic-ref HEAD) b.commitid'
+ 'git get-tar-commit-id' \
+ 'git get-tar-commit-id <b.tar >b.commitid &&
+ diff .git/$(git symbolic-ref HEAD) b.commitid'
test_expect_success \
'extract tar archive' \
'diff -r a b/a'
test_expect_success \
- 'git-tar-tree with prefix' \
- 'git-tar-tree HEAD prefix >c.tar'
+ 'git tar-tree with prefix' \
+ 'git tar-tree HEAD prefix >c.tar'
test_expect_success \
'extract tar archive with prefix' \
'diff -r a c/prefix/a'
test_expect_success \
- 'git-archive --format=zip' \
- 'git-archive --format=zip HEAD >d.zip'
+ 'git archive --format=zip' \
+ 'git archive --format=zip HEAD >d.zip'
+
+$UNZIP -v >/dev/null 2>&1
+if [ $? -eq 127 ]; then
+ echo "Skipping ZIP tests, because unzip was not found"
+ test_done
+ exit
+fi
test_expect_success \
'extract ZIP archive' \
'diff -r a d/a'
test_expect_success \
- 'git-archive --format=zip with prefix' \
- 'git-archive --format=zip --prefix=prefix/ HEAD >e.zip'
+ 'git archive --format=zip with prefix' \
+ 'git archive --format=zip --prefix=prefix/ HEAD >e.zip'
test_expect_success \
'extract ZIP archive with prefix' \
'diff -r a e/prefix/a'
test_expect_success \
- 'git-archive --list outside of a git repo' \
- 'GIT_DIR=some/non-existing/directory git-archive --list'
+ 'git archive --list outside of a git repo' \
+ 'GIT_DIR=some/non-existing/directory git archive --list'
test_done
# Copyright (c) 2005 Junio C Hamano
#
-test_description='git-mailinfo and git-mailsplit test'
+test_description='git mailinfo and git mailsplit test'
. ./test-lib.sh
test_expect_success 'split sample box' \
- 'git-mailsplit -o. ../t5100/sample.mbox >last &&
+ 'git mailsplit -o. ../t5100/sample.mbox >last &&
last=`cat last` &&
echo total is $last &&
test `cat last` = 8'
for mail in `echo 00*`
do
test_expect_success "mailinfo $mail" \
- "git-mailinfo -u msg$mail patch$mail <$mail >info$mail &&
+ "git mailinfo -u msg$mail patch$mail <$mail >info$mail &&
echo msg &&
diff ../t5100/msg$mail msg$mail &&
echo patch &&
for i in a b c
do
dd if=/dev/zero bs=4k count=1 | tr "\\0" $i >$i &&
- git-update-index --add $i || return 1
+ git update-index --add $i || return 1
done &&
- cat c >d && echo foo >>d && git-update-index --add d &&
- tree=`git-write-tree` &&
- commit=`git-commit-tree $tree </dev/null` && {
+ cat c >d && echo foo >>d && git update-index --add d &&
+ tree=`git write-tree` &&
+ commit=`git commit-tree $tree </dev/null` && {
echo $tree &&
echo $commit &&
- git-ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
+ git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
} >obj-list && {
- git-diff-tree --root -p $commit &&
+ git diff-tree --root -p $commit &&
while read object
do
- t=`git-cat-file -t $object` &&
- git-cat-file $t $object || return 1
+ t=`git cat-file -t $object` &&
+ git cat-file $t $object || return 1
done <obj-list
} >expect'
test_expect_success \
'pack without delta' \
- 'packname_1=$(git-pack-objects --window=0 test-1 <obj-list)'
+ 'packname_1=$(git pack-objects --window=0 test-1 <obj-list)'
rm -fr .git2
mkdir .git2
'unpack without delta' \
"GIT_OBJECT_DIRECTORY=.git2/objects &&
export GIT_OBJECT_DIRECTORY &&
- git-init &&
- git-unpack-objects -n <test-1-${packname_1}.pack &&
- git-unpack-objects <test-1-${packname_1}.pack"
+ git init &&
+ git unpack-objects -n <test-1-${packname_1}.pack &&
+ git unpack-objects <test-1-${packname_1}.pack"
unset GIT_OBJECT_DIRECTORY
cd "$TRASH/.git2"
test_expect_success \
'pack with REF_DELTA' \
'pwd &&
- packname_2=$(git-pack-objects test-2 <obj-list)'
+ packname_2=$(git pack-objects test-2 <obj-list)'
rm -fr .git2
mkdir .git2
'unpack with REF_DELTA' \
'GIT_OBJECT_DIRECTORY=.git2/objects &&
export GIT_OBJECT_DIRECTORY &&
- git-init &&
- git-unpack-objects -n <test-2-${packname_2}.pack &&
- git-unpack-objects <test-2-${packname_2}.pack'
+ git init &&
+ git unpack-objects -n <test-2-${packname_2}.pack &&
+ git unpack-objects <test-2-${packname_2}.pack'
unset GIT_OBJECT_DIRECTORY
cd "$TRASH/.git2"
test_expect_success \
'pack with OFS_DELTA' \
'pwd &&
- packname_3=$(git-pack-objects --delta-base-offset test-3 <obj-list)'
+ packname_3=$(git pack-objects --delta-base-offset test-3 <obj-list)'
rm -fr .git2
mkdir .git2
'unpack with OFS_DELTA' \
'GIT_OBJECT_DIRECTORY=.git2/objects &&
export GIT_OBJECT_DIRECTORY &&
- git-init &&
- git-unpack-objects -n <test-3-${packname_3}.pack &&
- git-unpack-objects <test-3-${packname_3}.pack'
+ git init &&
+ git unpack-objects -n <test-3-${packname_3}.pack &&
+ git unpack-objects <test-3-${packname_3}.pack'
unset GIT_OBJECT_DIRECTORY
cd "$TRASH/.git2"
'use packed objects' \
'GIT_OBJECT_DIRECTORY=.git2/objects &&
export GIT_OBJECT_DIRECTORY &&
- git-init &&
+ git init &&
cp test-1-${packname_1}.pack test-1-${packname_1}.idx .git2/objects/pack && {
- git-diff-tree --root -p $commit &&
+ git diff-tree --root -p $commit &&
while read object
do
- t=`git-cat-file -t $object` &&
- git-cat-file $t $object || return 1
+ t=`git cat-file -t $object` &&
+ git cat-file $t $object || return 1
done <obj-list
} >current &&
diff expect current'
export GIT_OBJECT_DIRECTORY &&
rm -f .git2/objects/pack/test-* &&
cp test-2-${packname_2}.pack test-2-${packname_2}.idx .git2/objects/pack && {
- git-diff-tree --root -p $commit &&
+ git diff-tree --root -p $commit &&
while read object
do
- t=`git-cat-file -t $object` &&
- git-cat-file $t $object || return 1
+ t=`git cat-file -t $object` &&
+ git cat-file $t $object || return 1
done <obj-list
} >current &&
diff expect current'
export GIT_OBJECT_DIRECTORY &&
rm -f .git2/objects/pack/test-* &&
cp test-3-${packname_3}.pack test-3-${packname_3}.idx .git2/objects/pack && {
- git-diff-tree --root -p $commit &&
+ git diff-tree --root -p $commit &&
while read object
do
- t=`git-cat-file -t $object` &&
- git-cat-file $t $object || return 1
+ t=`git cat-file -t $object` &&
+ git cat-file $t $object || return 1
done <obj-list
} >current &&
diff expect current'
test_expect_success \
'verify pack' \
- 'git-verify-pack test-1-${packname_1}.idx \
+ 'git verify-pack test-1-${packname_1}.idx \
test-2-${packname_2}.idx \
test-3-${packname_3}.idx'
'corrupt a pack and see if verify catches' \
'cat test-1-${packname_1}.idx >test-3.idx &&
cat test-2-${packname_2}.pack >test-3.pack &&
- if git-verify-pack test-3.idx
+ if git verify-pack test-3.idx
then false
else :;
fi &&
: PACK_SIGNATURE &&
cat test-1-${packname_1}.pack >test-3.pack &&
dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 &&
- if git-verify-pack test-3.idx
+ if git verify-pack test-3.idx
then false
else :;
fi &&
: PACK_VERSION &&
cat test-1-${packname_1}.pack >test-3.pack &&
dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 &&
- if git-verify-pack test-3.idx
+ if git verify-pack test-3.idx
then false
else :;
fi &&
: TYPE/SIZE byte of the first packed object data &&
cat test-1-${packname_1}.pack >test-3.pack &&
dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 &&
- if git-verify-pack test-3.idx
+ if git verify-pack test-3.idx
then false
else :;
fi &&
l=`expr $l - 20` &&
cat test-1-${packname_1}.pack >test-3.pack &&
dd if=/dev/zero of=test-3.idx count=20 bs=1 conv=notrunc seek=$l &&
- if git-verify-pack test-3.pack
+ if git verify-pack test-3.pack
then false
else :;
fi &&
do
echo $i >$i &&
test-genrandom "$i" 32768 >>$i &&
- git-update-index --add $i || return 1
+ git update-index --add $i || return 1
done &&
- echo d >d && cat c >>d && git-update-index --add d &&
- tree=`git-write-tree` &&
- commit1=`git-commit-tree $tree </dev/null` &&
- git-update-ref HEAD $commit1 &&
+ echo d >d && cat c >>d && git update-index --add d &&
+ tree=`git write-tree` &&
+ commit1=`git commit-tree $tree </dev/null` &&
+ git update-ref HEAD $commit1 &&
git-repack -a -d &&
- test "`git-count-objects`" = "0 objects, 0 kilobytes" &&
+ test "`git count-objects`" = "0 objects, 0 kilobytes" &&
pack1=`ls .git/objects/pack/*.pack` &&
test -f "$pack1"'
test_expect_success \
'verify-pack -v, defaults' \
- 'git-verify-pack -v "$pack1"'
+ 'git verify-pack -v "$pack1"'
test_expect_success \
'verify-pack -v, packedGitWindowSize == 1 page' \
- 'git-config core.packedGitWindowSize 512 &&
- git-verify-pack -v "$pack1"'
+ 'git config core.packedGitWindowSize 512 &&
+ git verify-pack -v "$pack1"'
test_expect_success \
'verify-pack -v, packedGit{WindowSize,Limit} == 1 page' \
- 'git-config core.packedGitWindowSize 512 &&
- git-config core.packedGitLimit 512 &&
- git-verify-pack -v "$pack1"'
+ 'git config core.packedGitWindowSize 512 &&
+ git config core.packedGitLimit 512 &&
+ git verify-pack -v "$pack1"'
test_expect_success \
'repack -a -d, packedGit{WindowSize,Limit} == 1 page' \
- 'git-config core.packedGitWindowSize 512 &&
- git-config core.packedGitLimit 512 &&
- commit2=`git-commit-tree $tree -p $commit1 </dev/null` &&
- git-update-ref HEAD $commit2 &&
+ 'git config core.packedGitWindowSize 512 &&
+ git config core.packedGitLimit 512 &&
+ commit2=`git commit-tree $tree -p $commit1 </dev/null` &&
+ git update-ref HEAD $commit2 &&
git-repack -a -d &&
- test "`git-count-objects`" = "0 objects, 0 kilobytes" &&
+ test "`git count-objects`" = "0 objects, 0 kilobytes" &&
pack2=`ls .git/objects/pack/*.pack` &&
test -f "$pack2"
test "$pack1" \!= "$pack2"'
test_expect_success \
'verify-pack -v, defaults' \
- 'git-config --unset core.packedGitWindowSize &&
- git-config --unset core.packedGitLimit &&
- git-verify-pack -v "$pack2"'
+ 'git config --unset core.packedGitWindowSize &&
+ git config --unset core.packedGitLimit &&
+ git verify-pack -v "$pack2"'
test_done
test_expect_success \
'setup' \
'rm -rf .git
- git-init &&
+ git init &&
i=1 &&
while test $i -le 100
do
i=`printf '%03i' $i`
echo $i >file_$i &&
test-genrandom "$i" 8192 >>file_$i &&
- git-update-index --add file_$i &&
+ git update-index --add file_$i &&
i=`expr $i + 1` || return 1
done &&
{ echo 101 && test-genrandom 100 8192; } >file_101 &&
- git-update-index --add file_101 &&
- tree=`git-write-tree` &&
- commit=`git-commit-tree $tree </dev/null` && {
+ git update-index --add file_101 &&
+ tree=`git write-tree` &&
+ commit=`git commit-tree $tree </dev/null` && {
echo $tree &&
- git-ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
+ git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
} >obj-list &&
- git-update-ref HEAD $commit'
+ git update-ref HEAD $commit'
test_expect_success \
'pack-objects with index version 1' \
- 'pack1=$(git-pack-objects --index-version=1 test-1 <obj-list) &&
- git-verify-pack -v "test-1-${pack1}.pack"'
+ 'pack1=$(git pack-objects --index-version=1 test-1 <obj-list) &&
+ git verify-pack -v "test-1-${pack1}.pack"'
test_expect_success \
'pack-objects with index version 2' \
- 'pack2=$(git-pack-objects --index-version=2 test-2 <obj-list) &&
- git-verify-pack -v "test-2-${pack2}.pack"'
+ 'pack2=$(git pack-objects --index-version=2 test-2 <obj-list) &&
+ git verify-pack -v "test-2-${pack2}.pack"'
test_expect_success \
'both packs should be identical' \
test_expect_success \
'index v2: force some 64-bit offsets with pack-objects' \
- 'pack3=$(git-pack-objects --index-version=2,0x40000 test-3 <obj-list) &&
- git-verify-pack -v "test-3-${pack3}.pack"'
+ 'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list) &&
+ git verify-pack -v "test-3-${pack3}.pack"'
test_expect_failure \
'64-bit offsets: should be different from previous index v2 results' \
test_expect_success \
'[index v1] 1) stream pack to repository' \
'git-index-pack --index-version=1 --stdin < "test-1-${pack1}.pack" &&
- git-prune-packed &&
- git-count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
+ git prune-packed &&
+ git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
cmp "test-1-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"'
test_expect_success \
'[index v1] 2) create a stealth corruption in a delta base reference' \
'# this test assumes a delta smaller than 16 bytes at the end of the pack
- git-show-index <1.idx | sort -n | tail -n 1 | (
+ git show-index <1.idx | sort -n | tail -n 1 | (
read delta_offs delta_sha1 &&
- git-cat-file blob "$delta_sha1" > blob_1 &&
+ git cat-file blob "$delta_sha1" > blob_1 &&
chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($delta_offs + 1)) \
if=".git/objects/pack/pack-${pack1}.idx" skip=$((256 * 4 + 4)) \
bs=1 count=20 conv=notrunc &&
- git-cat-file blob "$delta_sha1" > blob_2 )'
+ git cat-file blob "$delta_sha1" > blob_2 )'
test_expect_failure \
'[index v1] 3) corrupted delta happily returned wrong data' \
test_expect_failure \
'[index v1] 4) confirm that the pack is actually corrupted' \
- 'git-fsck --full $commit'
+ 'git fsck --full $commit'
test_expect_success \
'[index v1] 5) pack-objects happily reuses corrupted data' \
- 'pack4=$(git-pack-objects test-4 <obj-list) &&
+ 'pack4=$(git pack-objects test-4 <obj-list) &&
test -f "test-4-${pack1}.pack"'
test_expect_failure \
'[index v1] 6) newly created pack is BAD !' \
- 'git-verify-pack -v "test-4-${pack1}.pack"'
+ 'git verify-pack -v "test-4-${pack1}.pack"'
test_expect_success \
'[index v2] 1) stream pack to repository' \
'rm -f .git/objects/pack/* &&
git-index-pack --index-version=2,0x40000 --stdin < "test-1-${pack1}.pack" &&
- git-prune-packed &&
- git-count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
+ git prune-packed &&
+ git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
cmp "test-3-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"'
test_expect_success \
'[index v2] 2) create a stealth corruption in a delta base reference' \
'# this test assumes a delta smaller than 16 bytes at the end of the pack
- git-show-index <1.idx | sort -n | tail -n 1 | (
+ git show-index <1.idx | sort -n | tail -n 1 | (
read delta_offs delta_sha1 delta_crc &&
- git-cat-file blob "$delta_sha1" > blob_3 &&
+ git cat-file blob "$delta_sha1" > blob_3 &&
chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($delta_offs + 1)) \
if=".git/objects/pack/pack-${pack1}.idx" skip=$((8 + 256 * 4)) \
bs=1 count=20 conv=notrunc &&
- git-cat-file blob "$delta_sha1" > blob_4 )'
+ git cat-file blob "$delta_sha1" > blob_4 )'
test_expect_failure \
'[index v2] 3) corrupted delta happily returned wrong data' \
test_expect_failure \
'[index v2] 4) confirm that the pack is actually corrupted' \
- 'git-fsck --full $commit'
+ 'git fsck --full $commit'
test_expect_failure \
'[index v2] 5) pack-objects refuses to reuse corrupted data' \
- 'git-pack-objects test-5 <obj-list'
+ 'git pack-objects test-5 <obj-list'
test_done
test_tick &&
mkdir mozart mozart/is &&
echo "Commit #0" >mozart/is/pink &&
- git-update-index --add mozart/is/pink &&
- tree=$(git-write-tree) &&
- commit=$(echo "Commit #0" | git-commit-tree $tree) &&
+ git update-index --add mozart/is/pink &&
+ tree=$(git write-tree) &&
+ commit=$(echo "Commit #0" | git commit-tree $tree) &&
zero=$commit &&
parent=$zero &&
i=0 &&
i=$(($i+1)) &&
test_tick &&
echo "Commit #$i" >mozart/is/pink &&
- git-update-index --add mozart/is/pink &&
- tree=$(git-write-tree) &&
- commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) &&
- git-update-ref refs/tags/commit$i $commit &&
+ git update-index --add mozart/is/pink &&
+ tree=$(git write-tree) &&
+ commit=$(echo "Commit #$i" | git commit-tree $tree -p $parent) &&
+ git update-ref refs/tags/commit$i $commit &&
parent=$commit || return 1
done &&
- git-update-ref HEAD "$commit" &&
+ git update-ref HEAD "$commit" &&
git-clone ./. victim &&
cd victim &&
- git-log &&
+ git log &&
cd .. &&
- git-update-ref HEAD "$zero" &&
+ git update-ref HEAD "$zero" &&
parent=$zero &&
i=0 &&
while test $i -le $cnt
i=$(($i+1)) &&
test_tick &&
echo "Rebase #$i" >mozart/is/pink &&
- git-update-index --add mozart/is/pink &&
- tree=$(git-write-tree) &&
- commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) &&
- git-update-ref refs/tags/rebase$i $commit &&
+ git update-index --add mozart/is/pink &&
+ tree=$(git write-tree) &&
+ commit=$(echo "Rebase #$i" | git commit-tree $tree -p $parent) &&
+ git update-ref refs/tags/rebase$i $commit &&
parent=$commit || return 1
done &&
- git-update-ref HEAD "$commit" &&
+ git update-ref HEAD "$commit" &&
echo Rebase &&
- git-log'
+ git log'
test_expect_success 'pack the source repository' '
git repack -a -d &&
'
test_expect_success \
- 'pushing rewound head should not barf but require --force' '
+ 'pushing rewound head should not barf but require --force' '
# should not fail but refuse to update.
if git-send-pack ./victim/.git/ master
then
test_expect_success \
'pushing with --force should be denied with denyNonFastforwards' '
cd victim &&
- git-config receive.denyNonFastforwards true &&
+ git config receive.denyNonFastforwards true &&
cd .. &&
- git-update-ref refs/heads/master master^ || return 1
+ git update-ref refs/heads/master master^ || return 1
git-send-pack --force ./victim/.git/ master && return 1
! git diff .git/refs/heads/master victim/.git/refs/heads/master
'
test_expect_success setup '
echo This is a test. >a &&
- git-update-index --add a &&
- tree0=$(git-write-tree) &&
- commit0=$(echo setup | git-commit-tree $tree0) &&
+ git update-index --add a &&
+ tree0=$(git write-tree) &&
+ commit0=$(echo setup | git commit-tree $tree0) &&
echo We hope it works. >a &&
- git-update-index a &&
- tree1=$(git-write-tree) &&
- commit1=$(echo modify | git-commit-tree $tree1 -p $commit0) &&
- git-update-ref refs/heads/master $commit0 &&
- git-update-ref refs/heads/tofail $commit1 &&
+ git update-index a &&
+ tree1=$(git write-tree) &&
+ commit1=$(echo modify | git commit-tree $tree1 -p $commit0) &&
+ git update-ref refs/heads/master $commit0 &&
+ git update-ref refs/heads/tofail $commit1 &&
git-clone ./. victim &&
- GIT_DIR=victim/.git git-update-ref refs/heads/tofail $commit1 &&
- git-update-ref refs/heads/master $commit1 &&
- git-update-ref refs/heads/tofail $commit0
+ GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 &&
+ git update-ref refs/heads/master $commit1 &&
+ git update-ref refs/heads/tofail $commit0
'
cat >victim/.git/hooks/pre-receive <<'EOF'
'
test_expect_success 'updated as expected' '
- test $(GIT_DIR=victim/.git git-rev-parse master) = $commit1 &&
- test $(GIT_DIR=victim/.git git-rev-parse tofail) = $commit1
+ test $(GIT_DIR=victim/.git git rev-parse master) = $commit1 &&
+ test $(GIT_DIR=victim/.git git rev-parse tofail) = $commit1
'
test_expect_success 'hooks ran' '
done
echo "$text" > test.txt
- git-update-index --add test.txt
- tree=$(git-write-tree)
+ git update-index --add test.txt
+ tree=$(git write-tree)
# make sure timestamps are in correct order
sec=$(($sec+1))
commit=$(echo "$text" | GIT_AUTHOR_DATE=$sec \
- git-commit-tree $tree $parents 2>>log2.txt)
+ git commit-tree $tree $parents 2>>log2.txt)
export $name=$commit
echo $commit > .git/refs/heads/$branch
eval ${branch}TIP=$commit
"git-fetch-pack -k -v .. $heads"
case "$heads" in *A*) echo $ATIP > .git/refs/heads/A;; esac
case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac
- git-symbolic-ref HEAD refs/heads/`echo $heads | sed -e 's/^\(.\).*$/\1/'`
+ git symbolic-ref HEAD refs/heads/`echo $heads | sed -e 's/^\(.\).*$/\1/'`
- test_expect_success "fsck" 'git-fsck --full > fsck.txt 2>&1'
+ test_expect_success "fsck" 'git fsck --full > fsck.txt 2>&1'
test_expect_success 'check downloaded results' \
'mv .git/objects/pack/pack-* . &&
p=`ls -1 pack-*.pack` &&
- git-unpack-objects <$p &&
- git-fsck --full'
+ git unpack-objects <$p &&
+ git fsck --full'
test_expect_success "new object count after $number pull" \
'idx=`echo pack-*.idx` &&
- pack_count=`git-show-index <$idx | wc -l` &&
+ pack_count=`git show-index <$idx | wc -l` &&
test $pack_count = $count'
test -z "$pack_count" && pack_count=0
if [ -z "$no_strict_count_check" ]; then
(
mkdir client &&
cd client &&
- git-init 2>> log2.txt &&
+ git init 2>> log2.txt &&
git config transfer.unpacklimit 0
)
echo $ATIP > .git/refs/heads/A
echo $BTIP > .git/refs/heads/B
-git-symbolic-ref HEAD refs/heads/B
+git symbolic-ref HEAD refs/heads/B
pull_to_client 1st "B A" $((11*3))
test_expect_success "clone shallow" "git-clone --depth 2 . shallow"
-(cd shallow; git-count-objects -v) > count.shallow
+(cd shallow; git count-objects -v) > count.shallow
test_expect_success "clone shallow object count" \
"test \"in-pack: 18\" = \"$(grep in-pack count.shallow)\""
'
test_expect_success "fsck in shallow repo" \
- "(cd shallow; git-fsck --full)"
+ "(cd shallow; git fsck --full)"
#test_done; exit
test_expect_success "pull in shallow repo" \
"(cd shallow; git pull .. B)"
-(cd shallow; git-count-objects -v) > count.shallow
+(cd shallow; git count-objects -v) > count.shallow
test_expect_success "clone shallow object count" \
"test \"count: 6\" = \"$(grep count count.shallow)\""
test_expect_success "deepening pull in shallow repo" \
"(cd shallow; git pull --depth 4 .. B)"
-(cd shallow; git-count-objects -v) > count.shallow
+(cd shallow; git count-objects -v) > count.shallow
test_expect_success "clone shallow object count" \
"test \"count: 12\" = \"$(grep count count.shallow)\""
test_expect_success "deepening fetch in shallow repo" \
"(cd shallow; git fetch --depth 4 .. A:A)"
-(cd shallow; git-count-objects -v) > count.shallow
+(cd shallow; git count-objects -v) > count.shallow
test_expect_success "clone shallow object count" \
"test \"count: 18\" = \"$(grep count count.shallow)\""
--- /dev/null
+#!/bin/sh
+
+test_description='fetching and pushing, with or without wildcard'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+mk_empty () {
+ rm -fr testrepo &&
+ mkdir testrepo &&
+ (
+ cd testrepo &&
+ git init &&
+ mv .git/hooks .git/hooks-disabled
+ )
+}
+
+mk_test () {
+ mk_empty &&
+ (
+ for ref in "$@"
+ do
+ git push testrepo $the_first_commit:refs/$ref || {
+ echo "Oops, push refs/$ref failure"
+ exit 1
+ }
+ done &&
+ cd testrepo &&
+ for ref in "$@"
+ do
+ r=$(git show-ref -s --verify refs/$ref) &&
+ test "z$r" = "z$the_first_commit" || {
+ echo "Oops, refs/$ref is wrong"
+ exit 1
+ }
+ done &&
+ git fsck --full
+ )
+}
+
+check_push_result () {
+ (
+ cd testrepo &&
+ it="$1" &&
+ shift
+ for ref in "$@"
+ do
+ r=$(git show-ref -s --verify refs/$ref) &&
+ test "z$r" = "z$it" || {
+ echo "Oops, refs/$ref is wrong"
+ exit 1
+ }
+ done &&
+ git fsck --full
+ )
+}
+
+test_expect_success setup '
+
+ : >path1 &&
+ git add path1 &&
+ test_tick &&
+ git commit -a -m repo &&
+ the_first_commit=$(git show-ref -s --verify refs/heads/master) &&
+
+ : >path2 &&
+ git add path2 &&
+ test_tick &&
+ git commit -a -m second &&
+ the_commit=$(git show-ref -s --verify refs/heads/master)
+
+'
+
+test_expect_success 'fetch without wildcard' '
+ mk_empty &&
+ (
+ cd testrepo &&
+ git fetch .. refs/heads/master:refs/remotes/origin/master &&
+
+ r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+ test "z$r" = "z$the_commit" &&
+
+ test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ )
+'
+
+test_expect_success 'fetch with wildcard' '
+ mk_empty &&
+ (
+ cd testrepo &&
+ git config remote.up.url .. &&
+ git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
+ git fetch up &&
+
+ r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+ test "z$r" = "z$the_commit" &&
+
+ test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ )
+'
+
+test_expect_success 'push without wildcard' '
+ mk_empty &&
+
+ git push testrepo refs/heads/master:refs/remotes/origin/master &&
+ (
+ cd testrepo &&
+ r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+ test "z$r" = "z$the_commit" &&
+
+ test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ )
+'
+
+test_expect_success 'push with wildcard' '
+ mk_empty &&
+
+ git push testrepo "refs/heads/*:refs/remotes/origin/*" &&
+ (
+ cd testrepo &&
+ r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+ test "z$r" = "z$the_commit" &&
+
+ test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ )
+'
+
+test_expect_success 'push with matching heads' '
+
+ mk_test heads/master &&
+ git push testrepo &&
+ check_push_result $the_commit heads/master
+
+'
+
+test_expect_success 'push with no ambiguity (1)' '
+
+ mk_test heads/master &&
+ git push testrepo master:master &&
+ check_push_result $the_commit heads/master
+
+'
+
+test_expect_success 'push with no ambiguity (2)' '
+
+ mk_test remotes/origin/master &&
+ git push testrepo master:master &&
+ check_push_result $the_commit remotes/origin/master
+
+'
+
+test_expect_success 'push with weak ambiguity (1)' '
+
+ mk_test heads/master remotes/origin/master &&
+ git push testrepo master:master &&
+ check_push_result $the_commit heads/master &&
+ check_push_result $the_first_commit remotes/origin/master
+
+'
+
+test_expect_success 'push with weak ambiguity (2)' '
+
+ mk_test heads/master remotes/origin/master remotes/another/master &&
+ git push testrepo master:master &&
+ check_push_result $the_commit heads/master &&
+ check_push_result $the_first_commit remotes/origin/master remotes/another/master
+
+'
+
+test_expect_success 'push with ambiguity (1)' '
+
+ mk_test remotes/origin/master remotes/frotz/master &&
+ if git push testrepo master:master
+ then
+ echo "Oops, should have failed"
+ false
+ else
+ check_push_result $the_first_commit remotes/origin/master remotes/frotz/master
+ fi
+'
+
+test_expect_success 'push with ambiguity (2)' '
+
+ mk_test heads/frotz tags/frotz &&
+ if git push testrepo master:frotz
+ then
+ echo "Oops, should have failed"
+ false
+ else
+ check_push_result $the_first_commit heads/frotz tags/frotz
+ fi
+
+'
+
+test_expect_success 'push with colon-less refspec (1)' '
+
+ mk_test heads/frotz tags/frotz &&
+ git branch -f frotz master &&
+ git push testrepo frotz &&
+ check_push_result $the_commit heads/frotz &&
+ check_push_result $the_first_commit tags/frotz
+
+'
+
+test_expect_success 'push with colon-less refspec (2)' '
+
+ mk_test heads/frotz tags/frotz &&
+ if git show-ref --verify -q refs/heads/frotz
+ then
+ git branch -D frotz
+ fi &&
+ git tag -f frotz &&
+ git push testrepo frotz &&
+ check_push_result $the_commit tags/frotz &&
+ check_push_result $the_first_commit heads/frotz
+
+'
+
+test_expect_success 'push with colon-less refspec (3)' '
+
+ mk_test &&
+ if git show-ref --verify -q refs/tags/frotz
+ then
+ git tag -d frotz
+ fi &&
+ git branch -f frotz master &&
+ git push testrepo frotz &&
+ check_push_result $the_commit heads/frotz &&
+ test 1 = $( cd testrepo && git show-ref | wc -l )
+'
+
+test_expect_success 'push with colon-less refspec (4)' '
+
+ mk_test &&
+ if git show-ref --verify -q refs/heads/frotz
+ then
+ git branch -D frotz
+ fi &&
+ git tag -f frotz &&
+ git push testrepo frotz &&
+ check_push_result $the_commit tags/frotz &&
+ test 1 = $( cd testrepo && git show-ref | wc -l )
+
+'
+
+test_done
cd "$base_dir"
test_done
-
_text=$1
_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()
{
- _tag=$1
+ _tag=$1
[ -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
mv sed.script.tmp sed.script
}
-# Replace unhelpful sha1 hashses with their symbolic equivalents
+# Replace unhelpful sha1 hashses with their symbolic equivalents
entag()
{
sed -f sed.script
commit_date()
{
_commit=$1
- git-cat-file commit $_commit | sed -n "s/^committer .*> \([0-9]*\) .*/\1/p"
+ git cat-file commit $_commit | sed -n "s/^committer .*> \([0-9]*\) .*/\1/p"
}
on_committer_date()
# Execute the test described by the first argument, by eval'ing
# command line specified in the 2nd argument. Check the status code
-# is zero and that the output matches the stream read from
+# is zero and that the output matches the stream read from
# stdin.
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_expect_success "$_description" "check_output $_name \"$_test\""
+ test_expect_success "$_description" "check_output $_name \"$_test\""
}
#
# Copyright (c) 2005 Jon Seymour
#
-test_description='Tests git-rev-list --bisect functionality'
+test_description='Tests git rev-list --bisect functionality'
. ./test-lib.sh
. ../t6000lib.sh # t6xxx specific functions
_max_diff=$1
_bisect_option=$2
shift 2
- _bisection=$(git-rev-list $_bisect_option "$@")
- _list_size=$(git-rev-list "$@" | wc -l)
+ _bisection=$(git rev-list $_bisect_option "$@")
+ _list_size=$(git rev-list "$@" | wc -l)
_head=$1
shift 1
- _bisection_size=$(git-rev-list $_bisection "$@" | wc -l)
+ _bisection_size=$(git rev-list $_bisection "$@" | wc -l)
[ -n "$_list_size" -a -n "$_bisection_size" ] ||
error "test_bisection_diff failed"
# Test if bisection size is close to half of list size within
# tolerance.
- #
+ #
_bisect_err=`expr $_list_size - $_bisection_size \* 2`
test "$_bisect_err" -lt 0 && _bisect_err=`expr 0 - $_bisect_err`
_bisect_err=`expr $_bisect_err / 2` ; # floor
}
date >path0
-git-update-index --add path0
-save_tag tree git-write-tree
+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: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
-git-update-ref HEAD $(tag l5)
+git update-ref HEAD $(tag l5)
# E
test_sequence()
{
- _bisect_option=$1
-
+ _bisect_option=$1
+
test_bisection_diff 0 $_bisect_option l0 ^root
test_bisection_diff 0 $_bisect_option l1 ^root
test_bisection_diff 0 $_bisect_option l2 ^root
test_bisection_diff 0 $_bisect_option u3 ^U
test_bisection_diff 0 $_bisect_option u4 ^U
test_bisection_diff 0 $_bisect_option u5 ^U
-
+
#
# the following illustrates Linus' binary bug blatt idea.
#
# the bisection point is the head - this is the bad point.
#
-test_output_expect_success "$_bisect_option l5 ^root" 'git-rev-list $_bisect_option l5 ^root' <<EOF
+test_output_expect_success "$_bisect_option l5 ^root" 'git rev-list $_bisect_option l5 ^root' <<EOF
c3
EOF
-test_output_expect_success "$_bisect_option l5 ^root ^c3" 'git-rev-list $_bisect_option l5 ^root ^c3' <<EOF
+test_output_expect_success "$_bisect_option l5 ^root ^c3" 'git rev-list $_bisect_option l5 ^root ^c3' <<EOF
b4
EOF
-test_output_expect_success "$_bisect_option l5 ^root ^c3 ^b4" 'git-rev-list $_bisect_option l5 ^c3 ^b4' <<EOF
+test_output_expect_success "$_bisect_option l5 ^root ^c3 ^b4" 'git rev-list $_bisect_option l5 ^c3 ^b4' <<EOF
l3
EOF
-test_output_expect_success "$_bisect_option l3 ^root ^c3 ^b4" 'git-rev-list $_bisect_option l3 ^root ^c3 ^b4' <<EOF
+test_output_expect_success "$_bisect_option l3 ^root ^c3 ^b4" 'git rev-list $_bisect_option l3 ^root ^c3 ^b4' <<EOF
a4
EOF
-test_output_expect_success "$_bisect_option l5 ^b3 ^a3 ^b4 ^a4" 'git-rev-list $_bisect_option l3 ^b3 ^a3 ^a4' <<EOF
+test_output_expect_success "$_bisect_option l5 ^b3 ^a3 ^b4 ^a4" 'git rev-list $_bisect_option l3 ^b3 ^a3 ^a4' <<EOF
l3
EOF
# if l3 is bad, then l4 is bad too - so advance the bad pointer by making b4 the known bad head
#
-test_output_expect_success "$_bisect_option l4 ^a2 ^a3 ^b ^a4" 'git-rev-list $_bisect_option l4 ^a2 ^a3 ^a4' <<EOF
+test_output_expect_success "$_bisect_option l4 ^a2 ^a3 ^b ^a4" 'git rev-list $_bisect_option l4 ^a2 ^a3 ^a4' <<EOF
l3
EOF
-test_output_expect_success "$_bisect_option l3 ^a2 ^a3 ^b ^a4" 'git-rev-list $_bisect_option l3 ^a2 ^a3 ^a4' <<EOF
+test_output_expect_success "$_bisect_option l3 ^a2 ^a3 ^b ^a4" 'git rev-list $_bisect_option l3 ^a2 ^a3 ^a4' <<EOF
l3
EOF
# as another example, let's consider a4 to be the bad head, in which case
#
-test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
c2
EOF
-test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2' <<EOF
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2" 'git rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2' <<EOF
c3
EOF
-test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3' <<EOF
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3" 'git rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3' <<EOF
a4
EOF
# or consider c3 to be the bad head
#
-test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
c2
EOF
-test_output_expect_success "$_bisect_option c3 ^a2 ^a3 ^b4 ^c2" 'git-rev-list $_bisect_option c3 ^a2 ^a3 ^b4 ^c2' <<EOF
+test_output_expect_success "$_bisect_option c3 ^a2 ^a3 ^b4 ^c2" 'git rev-list $_bisect_option c3 ^a2 ^a3 ^b4 ^c2' <<EOF
c3
EOF
# Copyright (c) 2005 Jon Seymour
#
-test_description='Tests git-rev-list --topo-order functionality'
+test_description='Tests git rev-list --topo-order functionality'
. ./test-lib.sh
. ../t6000lib.sh # t6xxx specific functions
}
date >path0
-git-update-index --add path0
-save_tag tree git-write-tree
+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
save_tag g3 unique_commit g5 tree -p g2
save_tag g4 unique_commit g6 tree -p g3 -p h2
-git-update-ref HEAD $(tag l5)
+git update-ref HEAD $(tag l5)
-test_output_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -d \" \"' <<EOF
+test_output_expect_success 'rev-list has correct number of entries' 'git rev-list HEAD | wc -l | tr -d \" \"' <<EOF
19
EOF
-test_output_expect_success 'simple topo order' 'git-rev-list --topo-order HEAD' <<EOF
+test_output_expect_success 'simple topo order' 'git rev-list --topo-order HEAD' <<EOF
l5
l4
l3
root
EOF
-test_output_expect_success 'two diamonds topo order (g6)' 'git-rev-list --topo-order g4' <<EOF
+test_output_expect_success 'two diamonds topo order (g6)' 'git rev-list --topo-order g4' <<EOF
g4
h2
g3
g0
EOF
-test_output_expect_success 'multiple heads' 'git-rev-list --topo-order a3 b3 c3' <<EOF
+test_output_expect_success 'multiple heads' 'git rev-list --topo-order a3 b3 c3' <<EOF
a3
a2
a1
root
EOF
-test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --topo-order a3 b3 c3 ^a1' <<EOF
+test_output_expect_success 'multiple heads, prune at a1' 'git rev-list --topo-order a3 b3 c3 ^a1' <<EOF
a3
a2
c3
b1
EOF
-test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --topo-order a3 b3 c3 ^l1' <<EOF
+test_output_expect_success 'multiple heads, prune at l1' 'git rev-list --topo-order a3 b3 c3 ^l1' <<EOF
a3
a2
a1
l2
EOF
-test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --topo-order l5 ^l1' <<EOF
+test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git rev-list --topo-order l5 ^l1' <<EOF
l5
l4
l3
l2
EOF
-test_output_expect_success 'duplicated head arguments' 'git-rev-list --topo-order l5 l5 ^l1' <<EOF
+test_output_expect_success 'duplicated head arguments' 'git rev-list --topo-order l5 l5 ^l1' <<EOF
l5
l4
l3
l2
EOF
-test_output_expect_success 'prune near topo' 'git-rev-list --topo-order a4 ^c3' <<EOF
+test_output_expect_success 'prune near topo' 'git rev-list --topo-order a4 ^c3' <<EOF
a4
b4
a3
b3
EOF
-test_output_expect_success "head has no parent" 'git-rev-list --topo-order root' <<EOF
+test_output_expect_success "head has no parent" 'git rev-list --topo-order root' <<EOF
root
EOF
-test_output_expect_success "two nodes - one head, one base" 'git-rev-list --topo-order l0' <<EOF
+test_output_expect_success "two nodes - one head, one base" 'git rev-list --topo-order l0' <<EOF
l0
root
EOF
-test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --topo-order l1' <<EOF
+test_output_expect_success "three nodes one head, one internal, one base" 'git rev-list --topo-order l1' <<EOF
l1
l0
root
EOF
-test_output_expect_success "linear prune l2 ^root" 'git-rev-list --topo-order l2 ^root' <<EOF
+test_output_expect_success "linear prune l2 ^root" 'git rev-list --topo-order l2 ^root' <<EOF
l2
l1
l0
EOF
-test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --topo-order l2 ^l0' <<EOF
+test_output_expect_success "linear prune l2 ^l0" 'git rev-list --topo-order l2 ^l0' <<EOF
l2
l1
EOF
-test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --topo-order l2 ^l1' <<EOF
+test_output_expect_success "linear prune l2 ^l1" 'git rev-list --topo-order l2 ^l1' <<EOF
l2
EOF
-test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --topo-order l5 ^a4' <<EOF
+test_output_expect_success "linear prune l5 ^a4" 'git rev-list --topo-order l5 ^a4' <<EOF
l5
l4
l3
EOF
-test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --topo-order l5 ^l3' <<EOF
+test_output_expect_success "linear prune l5 ^l3" 'git rev-list --topo-order l5 ^l3' <<EOF
l5
l4
EOF
-test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --topo-order l5 ^l4' <<EOF
+test_output_expect_success "linear prune l5 ^l4" 'git rev-list --topo-order l5 ^l4' <<EOF
l5
EOF
-test_output_expect_success "max-count 10 - topo order" 'git-rev-list --topo-order --max-count=10 l5' <<EOF
+test_output_expect_success "max-count 10 - topo order" 'git rev-list --topo-order --max-count=10 l5' <<EOF
l5
l4
l3
a2
EOF
-test_output_expect_success "max-count 10 - non topo order" 'git-rev-list --max-count=10 l5' <<EOF
+test_output_expect_success "max-count 10 - non topo order" 'git rev-list --max-count=10 l5' <<EOF
l5
l4
l3
b3
EOF
-test_output_expect_success '--max-age=c3, no --topo-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
+test_output_expect_success '--max-age=c3, no --topo-order' "git rev-list --max-age=$(commit_date c3) l5" <<EOF
l5
l4
l3
#
# this test fails on --topo-order - a fix is required
#
-#test_output_expect_success '--max-age=c3, --topo-order' "git-rev-list --topo-order --max-age=$(commit_date c3) l5" <<EOF
+#test_output_expect_success '--max-age=c3, --topo-order' "git rev-list --topo-order --max-age=$(commit_date c3) l5" <<EOF
#l5
#l4
#l3
#a2
#EOF
-test_output_expect_success 'one specified head reachable from another a4, c3, --topo-order' "list_duplicates git-rev-list --topo-order a4 c3" <<EOF
+test_output_expect_success 'one specified head reachable from another a4, c3, --topo-order' "list_duplicates git rev-list --topo-order a4 c3" <<EOF
EOF
-test_output_expect_success 'one specified head reachable from another c3, a4, --topo-order' "list_duplicates git-rev-list --topo-order c3 a4" <<EOF
+test_output_expect_success 'one specified head reachable from another c3, a4, --topo-order' "list_duplicates git rev-list --topo-order c3 a4" <<EOF
EOF
-test_output_expect_success 'one specified head reachable from another a4, c3, no --topo-order' "list_duplicates git-rev-list a4 c3" <<EOF
+test_output_expect_success 'one specified head reachable from another a4, c3, no --topo-order' "list_duplicates git rev-list a4 c3" <<EOF
EOF
-test_output_expect_success 'one specified head reachable from another c3, a4, no --topo-order' "list_duplicates git-rev-list c3 a4" <<EOF
+test_output_expect_success 'one specified head reachable from another c3, a4, no --topo-order' "list_duplicates git rev-list c3 a4" <<EOF
EOF
-test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF
+test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git rev-list m1" <<EOF
EOF
-test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF
+test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git rev-list m2" <<EOF
EOF
-test_expect_success "head ^head --topo-order" 'git-rev-list --topo-order a3 ^a3' <<EOF
+test_expect_success "head ^head --topo-order" 'git rev-list --topo-order a3 ^a3' <<EOF
EOF
-test_expect_success "head ^head no --topo-order" 'git-rev-list a3 ^a3' <<EOF
+test_expect_success "head ^head no --topo-order" 'git rev-list a3 ^a3' <<EOF
EOF
-test_output_expect_success 'simple topo order (l5r1)' 'git-rev-list --topo-order l5r1' <<EOF
+test_output_expect_success 'simple topo order (l5r1)' 'git rev-list --topo-order l5r1' <<EOF
l5r1
r1
r0
root
EOF
-test_output_expect_success 'simple topo order (r1l5)' 'git-rev-list --topo-order r1l5' <<EOF
+test_output_expect_success 'simple topo order (r1l5)' 'git rev-list --topo-order r1l5' <<EOF
r1l5
l5
l4
alt_root
EOF
-test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --topo-order" <<EOF
+test_output_expect_success "don't print things unreachable from one branch" "git rev-list a3 ^b3 --topo-order" <<EOF
a3
a2
a1
EOF
-test_output_expect_success "--topo-order a4 l3" "git-rev-list --topo-order a4 l3" <<EOF
+test_output_expect_success "--topo-order a4 l3" "git rev-list --topo-order a4 l3" <<EOF
l3
a4
c3
#!/bin/sh
-test_description='git-rev-list trivial path optimization test'
+test_description='git rev-list trivial path optimization test'
. ./test-lib.sh
'
test_expect_success path-optimization '
- commit=$(echo "Unchanged tree" | git-commit-tree "HEAD^{tree}" -p HEAD) &&
- test $(git-rev-list $commit | wc -l) = 2 &&
- test $(git-rev-list $commit -- . | wc -l) = 1
+ commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) &&
+ test $(git rev-list $commit | wc -l) = 2 &&
+ test $(git rev-list $commit -- . | wc -l) = 1
'
test_expect_success 'further setup' '
#!/bin/sh
-test_description='git-rev-list --max-count and --skip test'
+test_description='git rev-list --max-count and --skip test'
. ./test-lib.sh
'
test_expect_success 'no options' '
- test $(git-rev-list HEAD | wc -l) = 5
+ test $(git rev-list HEAD | wc -l) = 5
'
test_expect_success '--max-count' '
- test $(git-rev-list HEAD --max-count=0 | wc -l) = 0 &&
- test $(git-rev-list HEAD --max-count=3 | wc -l) = 3 &&
- test $(git-rev-list HEAD --max-count=5 | wc -l) = 5 &&
- test $(git-rev-list HEAD --max-count=10 | wc -l) = 5
+ test $(git rev-list HEAD --max-count=0 | wc -l) = 0 &&
+ test $(git rev-list HEAD --max-count=3 | wc -l) = 3 &&
+ test $(git rev-list HEAD --max-count=5 | wc -l) = 5 &&
+ test $(git rev-list HEAD --max-count=10 | wc -l) = 5
'
test_expect_success '--max-count all forms' '
- test $(git-rev-list HEAD --max-count=1 | wc -l) = 1 &&
- test $(git-rev-list HEAD -1 | wc -l) = 1 &&
- test $(git-rev-list HEAD -n1 | wc -l) = 1 &&
- test $(git-rev-list HEAD -n 1 | wc -l) = 1
+ test $(git rev-list HEAD --max-count=1 | wc -l) = 1 &&
+ test $(git rev-list HEAD -1 | wc -l) = 1 &&
+ test $(git rev-list HEAD -n1 | wc -l) = 1 &&
+ test $(git rev-list HEAD -n 1 | wc -l) = 1
'
test_expect_success '--skip' '
- test $(git-rev-list HEAD --skip=0 | wc -l) = 5 &&
- test $(git-rev-list HEAD --skip=3 | wc -l) = 2 &&
- test $(git-rev-list HEAD --skip=5 | wc -l) = 0 &&
- test $(git-rev-list HEAD --skip=10 | wc -l) = 0
+ test $(git rev-list HEAD --skip=0 | wc -l) = 5 &&
+ test $(git rev-list HEAD --skip=3 | wc -l) = 2 &&
+ test $(git rev-list HEAD --skip=5 | wc -l) = 0 &&
+ test $(git rev-list HEAD --skip=10 | wc -l) = 0
'
test_expect_success '--skip --max-count' '
- test $(git-rev-list HEAD --skip=0 --max-count=0 | wc -l) = 0 &&
- test $(git-rev-list HEAD --skip=0 --max-count=10 | wc -l) = 5 &&
- test $(git-rev-list HEAD --skip=3 --max-count=0 | wc -l) = 0 &&
- test $(git-rev-list HEAD --skip=3 --max-count=1 | wc -l) = 1 &&
- test $(git-rev-list HEAD --skip=3 --max-count=2 | wc -l) = 2 &&
- test $(git-rev-list HEAD --skip=3 --max-count=10 | wc -l) = 2 &&
- test $(git-rev-list HEAD --skip=5 --max-count=10 | wc -l) = 0 &&
- test $(git-rev-list HEAD --skip=10 --max-count=10 | wc -l) = 0
+ test $(git rev-list HEAD --skip=0 --max-count=0 | wc -l) = 0 &&
+ test $(git rev-list HEAD --skip=0 --max-count=10 | wc -l) = 5 &&
+ test $(git rev-list HEAD --skip=3 --max-count=0 | wc -l) = 0 &&
+ test $(git rev-list HEAD --skip=3 --max-count=1 | wc -l) = 1 &&
+ test $(git rev-list HEAD --skip=3 --max-count=2 | wc -l) = 2 &&
+ test $(git rev-list HEAD --skip=3 --max-count=10 | wc -l) = 2 &&
+ test $(git rev-list HEAD --skip=5 --max-count=10 | wc -l) = 0 &&
+ test $(git rev-list HEAD --skip=10 --max-count=10 | wc -l) = 0
'
test_done
#!/bin/sh
-test_description='git-rev-list --pretty=format test'
+test_description='git rev-list --pretty=format test'
. ./test-lib.sh
test_tick
test_expect_success 'setup' '
-touch foo && git-add foo && git-commit -m "added foo" &&
+touch foo && git add foo && git-commit -m "added foo" &&
echo changed >foo && git-commit -a -m "changed foo"
'
test_format() {
cat >expect.$1
test_expect_success "format $1" "
-git-rev-list --pretty=format:$2 master >output.$1 &&
-git-diff expect.$1 output.$1
+git rev-list --pretty=format:$2 master >output.$1 &&
+git diff expect.$1 output.$1
"
}
include an iso8859 character: ¡bueno!
EOF
test_expect_success 'setup complex body' '
-git-config i18n.commitencoding iso8859-1 &&
+git config i18n.commitencoding iso8859-1 &&
echo change2 >foo && git-commit -a -F commit-msg
'
--- /dev/null
+#!/bin/sh
+
+test_description='test git rev-list --cherry-pick -- file'
+
+. ./test-lib.sh
+
+# A---B
+# \
+# \
+# C
+#
+# B changes a file foo.c, adding a line of text. C changes foo.c as
+# well as bar.c, but the change in foo.c was identical to change B.
+
+test_expect_success setup '
+ echo Hallo > foo &&
+ git add foo &&
+ test_tick &&
+ git commit -m "A" &&
+ git tag A &&
+ git checkout -b branch &&
+ echo Bello > foo &&
+ echo Cello > bar &&
+ git add foo bar &&
+ test_tick &&
+ git commit -m "C" &&
+ git tag C &&
+ git checkout master &&
+ git checkout branch foo &&
+ test_tick &&
+ git commit -m "B" &&
+ git tag B
+'
+
+test_expect_success '--cherry-pick foo comes up empty' '
+ test -z "$(git rev-list --left-right --cherry-pick B...C -- foo)"
+'
+
+test_expect_success '--cherry-pick bar does not come up empty' '
+ ! test -z "$(git rev-list --left-right --cherry-pick B...C -- bar)"
+'
+
+test_done
. ./test-lib.sh
-T=$(git-write-tree)
+T=$(git write-tree)
M=1130000000
Z=+0000
GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z"
GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE
export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
- commit=$(echo $NAME | git-commit-tree $T $PARENTS)
+ commit=$(echo $NAME | git commit-tree $T $PARENTS)
echo $commit >.git/refs/tags/$NAME
echo $commit
}
H=$(doit 8 H $A $F)
test_expect_success 'compute merge-base (single)' \
- 'MB=$(git-merge-base G H) &&
- expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+ 'MB=$(git merge-base G H) &&
+ expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"'
test_expect_success 'compute merge-base (all)' \
- 'MB=$(git-merge-base --all G H) &&
- expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+ 'MB=$(git merge-base --all G H) &&
+ expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"'
test_expect_success 'compute merge-base with show-branch' \
- 'MB=$(git-show-branch --merge-base G H) &&
- expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+ 'MB=$(git show-branch --merge-base G H) &&
+ expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"'
# Setup for second test to demonstrate that relying on timestamps in a
# distributed SCM to provide a _consistent_ partial ordering of commits
PR=$(doit 4 PR $C2 $R2)
test_expect_success 'compute merge-base (single)' \
- 'MB=$(git-merge-base PL PR) &&
- expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+ 'MB=$(git merge-base PL PR) &&
+ expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"'
test_expect_success 'compute merge-base (all)' \
- 'MB=$(git-merge-base --all PL PR) &&
- expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+ 'MB=$(git merge-base --all PL PR) &&
+ expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"'
test_done
7
8
9" > file &&
-git add file &&
+git add file &&
git commit -m "Initial commit" file &&
git branch A &&
git branch B &&
cp new1.txt test.txt
test_expect_success "merge without conflict" \
- "git-merge-file test.txt orig.txt new2.txt"
+ "git merge-file test.txt orig.txt new2.txt"
cp new1.txt test2.txt
test_expect_success "merge without conflict (missing LF at EOF)" \
- "git-merge-file test2.txt orig.txt new2.txt"
+ "git merge-file test2.txt orig.txt new2.txt"
test_expect_success "merge result added missing LF" \
"git diff test.txt test2.txt"
cp test.txt backup.txt
test_expect_failure "merge with conflicts" \
- "git-merge-file test.txt orig.txt new3.txt"
+ "git merge-file test.txt orig.txt new3.txt"
cat > expect.txt << EOF
<<<<<<< test.txt
cp backup.txt test.txt
test_expect_failure "merge with conflicts, using -L" \
- "git-merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
+ "git merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
cat > expect.txt << EOF
<<<<<<< 1
sed "s/ tu / TU /" < new1.txt > new5.txt
test_expect_failure "conflict in removed tail" \
- "git-merge-file -p orig.txt new1.txt new5.txt > out"
+ "git merge-file -p orig.txt new1.txt new5.txt > out"
cat > expect << EOF
Dominus regit me,
test_expect_success "expected conflict markers" "git diff expect out"
-test_done
+test_expect_success 'binary files cannot be merged' '
+ ! git merge-file -p orig.txt ../test4012.png new1.txt 2> merge.err &&
+ grep "Cannot merge binary files" merge.err
+'
+test_done
GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 &&
git checkout -b D A &&
-git-rev-parse B > .git/MERGE_HEAD &&
+git rev-parse B > .git/MERGE_HEAD &&
echo D > a1 &&
git update-index a1 &&
GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D &&
GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 &&
git checkout -b E C &&
-git-rev-parse B > .git/MERGE_HEAD &&
+git rev-parse B > .git/MERGE_HEAD &&
echo E > a1 &&
git update-index a1 &&
GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E &&
git checkout -b G E &&
-git-rev-parse A > .git/MERGE_HEAD &&
+git rev-parse A > .git/MERGE_HEAD &&
echo G > a1 &&
git update-index a1 &&
GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G &&
git checkout -b F D &&
-git-rev-parse C > .git/MERGE_HEAD &&
+git rev-parse C > .git/MERGE_HEAD &&
echo F > a1 &&
git update-index a1 &&
GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
test_expect_success \
'setup' '
-git-config core.symlinks false &&
+git config core.symlinks false &&
> file &&
-git-add file &&
+git add file &&
git-commit -m initial &&
-git-branch b-symlink &&
-git-branch b-file &&
+git branch b-symlink &&
+git branch b-file &&
l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
-echo "120000 $l symlink" | git-update-index --index-info &&
+echo "120000 $l symlink" | git update-index --index-info &&
git-commit -m master &&
git-checkout b-symlink &&
l=$(echo -n file-different | git-hash-object -t blob -w --stdin) &&
-echo "120000 $l symlink" | git-update-index --index-info &&
+echo "120000 $l symlink" | git update-index --index-info &&
git-commit -m b-symlink &&
git-checkout b-file &&
echo plain-file > symlink &&
-git-add symlink &&
+git add symlink &&
git-commit -m b-file'
test_expect_failure \
# Copyright (c) 2005 Johannes Schindelin
#
-test_description='Test git-rev-parse with different parent options'
+test_description='Test git rev-parse with different parent options'
. ./test-lib.sh
. ../t6000lib.sh # t6xxx specific functions
date >path0
-git-update-index --add path0
-save_tag tree git-write-tree
+git update-index --add path0
+save_tag tree git write-tree
hide_error save_tag start unique_commit "start" tree
save_tag second unique_commit "second" tree -p start
hide_error save_tag start2 unique_commit "start2" tree
save_tag two_parents unique_commit "next" tree -p second -p start2
save_tag final unique_commit "final" tree -p two_parents
-test_expect_success 'start is valid' 'git-rev-parse start | grep "^[0-9a-f]\{40\}$"'
-test_expect_success 'start^0' "test $(cat .git/refs/tags/start) = $(git-rev-parse start^0)"
-test_expect_success 'start^1 not valid' "if git-rev-parse --verify start^1; then false; else :; fi"
-test_expect_success 'second^1 = second^' "test $(git-rev-parse second^1) = $(git-rev-parse second^)"
-test_expect_success 'final^1^1^1' "test $(git-rev-parse start) = $(git-rev-parse final^1^1^1)"
-test_expect_success 'final^1^1^1 = final^^^' "test $(git-rev-parse final^1^1^1) = $(git-rev-parse final^^^)"
-test_expect_success 'final^1^2' "test $(git-rev-parse start2) = $(git-rev-parse final^1^2)"
-test_expect_success 'final^1^2 != final^1^1' "test $(git-rev-parse final^1^2) != $(git-rev-parse final^1^1)"
-test_expect_success 'final^1^3 not valid' "if git-rev-parse --verify final^1^3; then false; else :; fi"
-test_expect_failure '--verify start2^1' 'git-rev-parse --verify start2^1'
-test_expect_success '--verify start2^0' 'git-rev-parse --verify start2^0'
+test_expect_success 'start is valid' 'git rev-parse start | grep "^[0-9a-f]\{40\}$"'
+test_expect_success 'start^0' "test $(cat .git/refs/tags/start) = $(git rev-parse start^0)"
+test_expect_success 'start^1 not valid' "if git rev-parse --verify start^1; then false; else :; fi"
+test_expect_success 'second^1 = second^' "test $(git rev-parse second^1) = $(git rev-parse second^)"
+test_expect_success 'final^1^1^1' "test $(git rev-parse start) = $(git rev-parse final^1^1^1)"
+test_expect_success 'final^1^1^1 = final^^^' "test $(git rev-parse final^1^1^1) = $(git rev-parse final^^^)"
+test_expect_success 'final^1^2' "test $(git rev-parse start2) = $(git rev-parse final^1^2)"
+test_expect_success 'final^1^2 != final^1^1' "test $(git rev-parse final^1^2) != $(git rev-parse final^1^1)"
+test_expect_success 'final^1^3 not valid' "if git rev-parse --verify final^1^3; then false; else :; fi"
+test_expect_failure '--verify start2^1' 'git rev-parse --verify start2^1'
+test_expect_success '--verify start2^0' 'git rev-parse --verify start2^0'
-test_done
+test_expect_success 'repack for next test' 'git repack -a -d'
+test_expect_success 'short SHA-1 works' '
+ start=`git rev-parse --verify start` &&
+ echo $start &&
+ abbrv=`echo $start | sed s/.\$//` &&
+ echo $abbrv &&
+ abbrv=`git rev-parse --verify $abbrv` &&
+ echo $abbrv &&
+ test $start = $abbrv'
+test_done
test_expect_success setup '
test_tick &&
- echo one >file && git-add file && git-commit -m initial &&
- one=$(git-rev-parse HEAD) &&
+ echo one >file && git add file && git-commit -m initial &&
+ one=$(git rev-parse HEAD) &&
test_tick &&
- echo two >file && git-add file && git-commit -m second &&
- two=$(git-rev-parse HEAD) &&
+ echo two >file && git add file && git-commit -m second &&
+ two=$(git rev-parse HEAD) &&
test_tick &&
- echo three >file && git-add file && git-commit -m third &&
+ echo three >file && git add file && git-commit -m third &&
test_tick &&
- echo A >file && git-add file && git-commit -m A &&
+ echo A >file && git add file && git-commit -m A &&
test_tick &&
git-tag -a -m A A &&
test_tick &&
- echo c >file && git-add file && git-commit -m c &&
+ echo c >file && git add file && git-commit -m c &&
test_tick &&
git-tag c &&
git reset --hard $two &&
test_tick &&
- echo B >side && git-add side && git-commit -m B &&
+ echo B >side && git add side && git-commit -m B &&
test_tick &&
git-tag -a -m B B &&
test_tick &&
git-merge -m Merged c &&
- merged=$(git-rev-parse HEAD) &&
+ merged=$(git rev-parse HEAD) &&
git reset --hard $two &&
test_tick &&
- echo D >another && git-add another && git-commit -m D &&
+ echo D >another && git add another && git-commit -m D &&
test_tick &&
git-tag -a -m D D &&
git-merge -m Merged $merged &&
test_tick &&
- echo X >file && echo X >side && git-add file side &&
+ echo X >file && echo X >side && git add file side &&
git-commit -m x
'
#!/bin/sh
-test_description='git-mv in subdirs'
+test_description='git mv in subdirs'
. ./test-lib.sh
test_expect_success \
'prepare reference tree' \
'mkdir path0 path1 &&
cp ../../COPYING path0/COPYING &&
- git-add path0/COPYING &&
+ git add path0/COPYING &&
git-commit -m add -a'
test_expect_success \
'moving the file out of subdirectory' \
- 'cd path0 && git-mv COPYING ../path1/COPYING'
+ 'cd path0 && git mv COPYING ../path1/COPYING'
# in path0 currently
test_expect_success \
test_expect_success \
'checking the commit' \
- 'git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ 'git diff-tree -r -M --name-status HEAD^ HEAD | \
grep -E "^R100.+path0/COPYING.+path1/COPYING"'
test_expect_success \
'moving the file back into subdirectory' \
- 'cd path0 && git-mv ../path1/COPYING COPYING'
+ 'cd path0 && git mv ../path1/COPYING COPYING'
# in path0 currently
test_expect_success \
test_expect_success \
'checking the commit' \
- 'git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ 'git diff-tree -r -M --name-status HEAD^ HEAD | \
grep -E "^R100.+path1/COPYING.+path0/COPYING"'
test_expect_success \
'adding another file' \
'cp ../../README path0/README &&
- git-add path0/README &&
+ git add path0/README &&
git-commit -m add2 -a'
test_expect_success \
'moving whole subdirectory' \
- 'git-mv path0 path2'
+ 'git mv path0 path2'
test_expect_success \
'commiting the change' \
test_expect_success \
'checking the commit' \
- 'git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ 'git diff-tree -r -M --name-status HEAD^ HEAD | \
grep -E "^R100.+path0/COPYING.+path2/COPYING" &&
- git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ git diff-tree -r -M --name-status HEAD^ HEAD | \
grep -E "^R100.+path0/README.+path2/README"'
test_expect_success \
'succeed when source is a prefix of destination' \
- 'git-mv path2/COPYING path2/COPYING-renamed'
+ 'git mv path2/COPYING path2/COPYING-renamed'
test_expect_success \
'moving whole subdirectory into subdirectory' \
- 'git-mv path2 path1'
+ 'git mv path2 path1'
test_expect_success \
'commiting the change' \
test_expect_success \
'checking the commit' \
- 'git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ 'git diff-tree -r -M --name-status HEAD^ HEAD | \
grep -E "^R100.+path2/COPYING.+path1/path2/COPYING" &&
- git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ git diff-tree -r -M --name-status HEAD^ HEAD | \
grep -E "^R100.+path2/README.+path1/path2/README"'
test_expect_failure \
'do not move directory over existing directory' \
- 'mkdir path0 && mkdir path0/path2 && git-mv path2 path0'
+ 'mkdir path0 && mkdir path0/path2 && git mv path2 path0'
test_expect_success \
'move into "."' \
- 'git-mv path1/path2/ .'
+ 'git mv path1/path2/ .'
test_expect_success "Michael Cassar's test case" '
rm -fr .git papers partA &&
--- /dev/null
+#!/bin/sh
+
+test_description='git-filter-branch'
+. ./test-lib.sh
+
+make_commit () {
+ lower=$(echo $1 | tr A-Z a-z)
+ echo $lower > $lower
+ git add $lower
+ test_tick
+ git commit -m $1
+ git tag $1
+}
+
+test_expect_success 'setup' '
+ make_commit A
+ make_commit B
+ git checkout -b branch B
+ make_commit D
+ make_commit E
+ git checkout master
+ make_commit C
+ git checkout branch
+ git merge C
+ git tag F
+ make_commit G
+ make_commit H
+'
+
+H=$(git rev-parse H)
+
+test_expect_success 'rewrite identically' '
+ git-filter-branch branch
+'
+test_expect_success 'result is really identical' '
+ test $H = $(git rev-parse HEAD)
+'
+
+test_expect_success 'rewrite, renaming a specific file' '
+ git-filter-branch -f --tree-filter "mv d doh || :" HEAD
+'
+
+test_expect_success 'test that the file was renamed' '
+ test d = $(git show HEAD:doh)
+'
+
+git tag oldD HEAD~4
+test_expect_success 'rewrite one branch, keeping a side branch' '
+ git branch modD oldD &&
+ git-filter-branch -f --tree-filter "mv b boh || :" D..modD
+'
+
+test_expect_success 'common ancestor is still common (unchanged)' '
+ test "$(git merge-base modD D)" = "$(git rev-parse B)"
+'
+
+test_expect_success 'filter subdirectory only' '
+ mkdir subdir &&
+ touch subdir/new &&
+ git add subdir/new &&
+ test_tick &&
+ git commit -m "subdir" &&
+ echo H > a &&
+ test_tick &&
+ git commit -m "not subdir" a &&
+ echo A > subdir/new &&
+ test_tick &&
+ git commit -m "again subdir" subdir/new &&
+ git rm a &&
+ test_tick &&
+ git commit -m "again not subdir" &&
+ git branch sub &&
+ git-filter-branch -f --subdirectory-filter subdir refs/heads/sub
+'
+
+test_expect_success 'subdirectory filter result looks okay' '
+ test 2 = $(git rev-list sub | wc -l) &&
+ git show sub:new &&
+ ! git show sub:subdir
+'
+
+test_expect_success 'setup and filter history that requires --full-history' '
+ git checkout master &&
+ mkdir subdir &&
+ echo A > subdir/new &&
+ git add subdir/new &&
+ test_tick &&
+ git commit -m "subdir on master" subdir/new &&
+ git rm a &&
+ test_tick &&
+ git commit -m "again subdir on master" &&
+ git merge branch &&
+ git branch sub-master &&
+ git-filter-branch -f --subdirectory-filter subdir sub-master
+'
+
+test_expect_success 'subdirectory filter result looks okay' '
+ test 3 = $(git rev-list -1 --parents sub-master | wc -w) &&
+ git show sub-master^:new &&
+ git show sub-master^2:new &&
+ ! git show sub:subdir
+'
+
+test_expect_success 'use index-filter to move into a subdirectory' '
+ git branch directorymoved &&
+ git-filter-branch -f --index-filter \
+ "git ls-files -s | sed \"s-\\t-&newsubdir/-\" |
+ GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \
+ git update-index --index-info &&
+ mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" directorymoved &&
+ test -z "$(git diff HEAD directorymoved:newsubdir)"'
+
+test_expect_success 'stops when msg filter fails' '
+ old=$(git rev-parse HEAD) &&
+ ! git-filter-branch -f --msg-filter false &&
+ test $old = $(git rev-parse HEAD) &&
+ rm -rf .git-rewrite
+'
+
+test_expect_success 'author information is preserved' '
+ : > i &&
+ git add i &&
+ test_tick &&
+ GIT_AUTHOR_NAME="B V Uips" git commit -m bvuips &&
+ git branch preserved-author &&
+ git-filter-branch -f --msg-filter "cat; \
+ test \$GIT_COMMIT != $(git rev-parse master) || \
+ echo Hallo" \
+ preserved-author &&
+ test 1 = $(git rev-list --author="B V Uips" preserved-author | wc -l)
+'
+
+test_expect_success "remove a certain author's commits" '
+ echo i > i &&
+ test_tick &&
+ git commit -m i i &&
+ git branch removed-author &&
+ git-filter-branch -f --commit-filter "\
+ if [ \"\$GIT_AUTHOR_NAME\" = \"B V Uips\" ];\
+ then\
+ shift;\
+ while [ -n \"\$1\" ];\
+ do\
+ shift;\
+ echo \"\$1\";\
+ shift;\
+ done;\
+ else\
+ git commit-tree \"\$@\";\
+ fi" removed-author &&
+ cnt1=$(git rev-list master | wc -l) &&
+ cnt2=$(git rev-list removed-author | wc -l) &&
+ test $cnt1 -eq $(($cnt2 + 1)) &&
+ test 0 = $(git rev-list --author="B V Uips" removed-author | wc -l)
+'
+
+test_expect_success 'barf on invalid name' '
+ ! git filter-branch -f master xy-problem &&
+ ! git filter-branch -f HEAD^
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Carlos Rica
+#
+
+test_description='git-tag
+
+Basic tests for operations with tags.'
+
+. ./test-lib.sh
+
+# creating and listing lightweight tags:
+
+tag_exists () {
+ git show-ref --quiet --verify refs/tags/"$1"
+}
+
+# todo: git tag -l now returns always zero, when fixed, change this test
+test_expect_success 'listing all tags in an empty tree should succeed' \
+ 'git tag -l'
+
+test_expect_success 'listing all tags in an empty tree should output nothing' \
+ 'test `git-tag -l | wc -l` -eq 0'
+
+test_expect_failure 'looking for a tag in an empty tree should fail' \
+ 'tag_exists mytag'
+
+test_expect_success 'creating a tag in an empty tree should fail' '
+ ! git-tag mynotag &&
+ ! tag_exists mynotag
+'
+
+test_expect_success 'creating a tag for HEAD in an empty tree should fail' '
+ ! git-tag mytaghead HEAD &&
+ ! tag_exists mytaghead
+'
+
+test_expect_success 'creating a tag for an unknown revision should fail' '
+ ! git-tag mytagnorev aaaaaaaaaaa &&
+ ! tag_exists mytagnorev
+'
+
+# commit used in the tests, test_tick is also called here to freeze the date:
+test_expect_success 'creating a tag using default HEAD should succeed' '
+ test_tick &&
+ echo foo >foo &&
+ git add foo &&
+ git commit -m Foo &&
+ git tag mytag
+'
+
+test_expect_success 'listing all tags if one exists should succeed' \
+ 'git-tag -l'
+
+test_expect_success 'listing all tags if one exists should output that tag' \
+ 'test `git-tag -l` = mytag'
+
+# pattern matching:
+
+test_expect_success 'listing a tag using a matching pattern should succeed' \
+ 'git-tag -l mytag'
+
+test_expect_success \
+ 'listing a tag using a matching pattern should output that tag' \
+ 'test `git-tag -l mytag` = mytag'
+
+# todo: git tag -l now returns always zero, when fixed, change this test
+test_expect_success \
+ 'listing tags using a non-matching pattern should suceed' \
+ 'git-tag -l xxx'
+
+test_expect_success \
+ 'listing tags using a non-matching pattern should output nothing' \
+ 'test `git-tag -l xxx | wc -l` -eq 0'
+
+# special cases for creating tags:
+
+test_expect_failure \
+ 'trying to create a tag with the name of one existing should fail' \
+ 'git tag mytag'
+
+test_expect_success \
+ 'trying to create a tag with a non-valid name should fail' '
+ test `git-tag -l | wc -l` -eq 1 &&
+ ! git tag "" &&
+ ! git tag .othertag &&
+ ! git tag "other tag" &&
+ ! git tag "othertag^" &&
+ ! git tag "other~tag" &&
+ test `git-tag -l | wc -l` -eq 1
+'
+
+test_expect_success 'creating a tag using HEAD directly should succeed' '
+ git tag myhead HEAD &&
+ tag_exists myhead
+'
+
+# deleting tags:
+
+test_expect_success 'trying to delete an unknown tag should fail' '
+ ! tag_exists unknown-tag &&
+ ! git-tag -d unknown-tag
+'
+
+cat >expect <<EOF
+myhead
+mytag
+EOF
+test_expect_success \
+ 'trying to delete tags without params should succeed and do nothing' '
+ git tag -l > actual && git diff expect actual &&
+ git-tag -d &&
+ git tag -l > actual && git diff expect actual
+'
+
+test_expect_success \
+ 'deleting two existing tags in one command should succeed' '
+ tag_exists mytag &&
+ tag_exists myhead &&
+ git-tag -d mytag myhead &&
+ ! tag_exists mytag &&
+ ! tag_exists myhead
+'
+
+test_expect_success \
+ 'creating a tag with the name of another deleted one should succeed' '
+ ! tag_exists mytag &&
+ git-tag mytag &&
+ tag_exists mytag
+'
+
+test_expect_success \
+ 'trying to delete two tags, existing and not, should fail in the 2nd' '
+ tag_exists mytag &&
+ ! tag_exists myhead &&
+ ! git-tag -d mytag anothertag &&
+ ! tag_exists mytag &&
+ ! tag_exists myhead
+'
+
+test_expect_failure 'trying to delete an already deleted tag should fail' \
+ 'git-tag -d mytag'
+
+# listing various tags with pattern matching:
+
+cat >expect <<EOF
+a1
+aa1
+cba
+t210
+t211
+v0.2.1
+v1.0
+v1.0.1
+v1.1.3
+EOF
+test_expect_success 'listing all tags should print them ordered' '
+ git tag v1.0.1 &&
+ git tag t211 &&
+ git tag aa1 &&
+ git tag v0.2.1 &&
+ git tag v1.1.3 &&
+ git tag cba &&
+ git tag a1 &&
+ git tag v1.0 &&
+ git tag t210 &&
+ git tag -l > actual &&
+ git diff expect actual
+'
+
+cat >expect <<EOF
+a1
+aa1
+cba
+EOF
+test_expect_success \
+ 'listing tags with substring as pattern must print those matching' '
+ git-tag -l a > actual &&
+ git diff expect actual
+'
+
+cat >expect <<EOF
+v0.2.1
+v1.0.1
+v1.1.3
+EOF
+test_expect_success \
+ 'listing tags with substring as pattern must print those matching' '
+ git-tag -l .1 > actual &&
+ git diff expect actual
+'
+
+cat >expect <<EOF
+t210
+t211
+EOF
+test_expect_success \
+ 'listing tags with substring as pattern must print those matching' '
+ git-tag -l t21 > actual &&
+ git diff expect actual
+'
+
+cat >expect <<EOF
+a1
+aa1
+EOF
+test_expect_success \
+ 'listing tags using a name as pattern must print those matching' '
+ git-tag -l a1 > actual &&
+ git diff expect actual
+'
+
+cat >expect <<EOF
+v1.0
+v1.0.1
+EOF
+test_expect_success \
+ 'listing tags using a name as pattern must print those matching' '
+ git-tag -l v1.0 > actual &&
+ git diff expect actual
+'
+
+cat >expect <<EOF
+v1.1.3
+EOF
+test_expect_success \
+ 'listing tags with ? in the pattern should print those matching' '
+ git-tag -l "1.1?" > actual &&
+ git diff expect actual
+'
+
+>expect
+test_expect_success \
+ 'listing tags using v.* should print nothing because none have v.' '
+ git-tag -l "v.*" > actual &&
+ git diff expect actual
+'
+
+cat >expect <<EOF
+v0.2.1
+v1.0
+v1.0.1
+v1.1.3
+EOF
+test_expect_success \
+ 'listing tags using v* should print only those having v' '
+ git-tag -l "v*" > actual &&
+ git diff expect actual
+'
+
+# creating and verifying lightweight tags:
+
+test_expect_success \
+ 'a non-annotated tag created without parameters should point to HEAD' '
+ git-tag non-annotated-tag &&
+ test $(git cat-file -t non-annotated-tag) = commit &&
+ test $(git rev-parse non-annotated-tag) = $(git rev-parse HEAD)
+'
+
+test_expect_failure 'trying to verify an unknown tag should fail' \
+ 'git-tag -v unknown-tag'
+
+test_expect_failure \
+ 'trying to verify a non-annotated and non-signed tag should fail' \
+ 'git-tag -v non-annotated-tag'
+
+# creating annotated tags:
+
+get_tag_msg () {
+ git cat-file tag "$1" | sed -e "/BEGIN PGP/q"
+}
+
+# run test_tick before committing always gives the time in that timezone
+get_tag_header () {
+cat <<EOF
+object $2
+type $3
+tag $1
+tagger C O Mitter <committer@example.com> $4 -0700
+
+EOF
+}
+
+commit=$(git rev-parse HEAD)
+time=$test_tick
+
+get_tag_header annotated-tag $commit commit $time >expect
+echo "A message" >>expect
+test_expect_success \
+ 'creating an annotated tag with -m message should succeed' '
+ git-tag -m "A message" annotated-tag &&
+ get_tag_msg annotated-tag >actual &&
+ git diff expect actual
+'
+
+cat >msgfile <<EOF
+Another message
+in a file.
+EOF
+get_tag_header file-annotated-tag $commit commit $time >expect
+cat msgfile >>expect
+test_expect_success \
+ 'creating an annotated tag with -F messagefile should succeed' '
+ git-tag -F msgfile file-annotated-tag &&
+ get_tag_msg file-annotated-tag >actual &&
+ git diff expect actual
+'
+
+# blank and empty messages:
+
+get_tag_header empty-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with an empty -m message should succeed' '
+ git-tag -m "" empty-annotated-tag &&
+ get_tag_msg empty-annotated-tag >actual &&
+ git diff expect actual
+'
+
+>emptyfile
+get_tag_header emptyfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with an empty -F messagefile should succeed' '
+ git-tag -F emptyfile emptyfile-annotated-tag &&
+ get_tag_msg emptyfile-annotated-tag >actual &&
+ git diff expect actual
+'
+
+printf '\n\n \n\t\nLeading blank lines\n' >blanksfile
+printf '\n\t \t \nRepeated blank lines\n' >>blanksfile
+printf '\n\n\nTrailing spaces \t \n' >>blanksfile
+printf '\nTrailing blank lines\n\n\t \n\n' >>blanksfile
+get_tag_header blanks-annotated-tag $commit commit $time >expect
+cat >>expect <<EOF
+Leading blank lines
+
+Repeated blank lines
+
+Trailing spaces
+
+Trailing blank lines
+EOF
+test_expect_success \
+ 'extra blanks in the message for an annotated tag should be removed' '
+ git-tag -F blanksfile blanks-annotated-tag &&
+ get_tag_msg blanks-annotated-tag >actual &&
+ git diff expect actual
+'
+
+get_tag_header blank-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with blank -m message with spaces should succeed' '
+ git-tag -m " " blank-annotated-tag &&
+ get_tag_msg blank-annotated-tag >actual &&
+ git diff expect actual
+'
+
+echo ' ' >blankfile
+echo '' >>blankfile
+echo ' ' >>blankfile
+get_tag_header blankfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with blank -F messagefile with spaces should succeed' '
+ git-tag -F blankfile blankfile-annotated-tag &&
+ get_tag_msg blankfile-annotated-tag >actual &&
+ git diff expect actual
+'
+
+printf ' ' >blanknonlfile
+get_tag_header blanknonlfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with -F file of spaces and no newline should succeed' '
+ git-tag -F blanknonlfile blanknonlfile-annotated-tag &&
+ get_tag_msg blanknonlfile-annotated-tag >actual &&
+ git diff expect actual
+'
+
+# messages with commented lines:
+
+cat >commentsfile <<EOF
+# A comment
+
+############
+The message.
+############
+One line.
+
+
+# commented lines
+# commented lines
+
+Another line.
+# comments
+
+Last line.
+EOF
+get_tag_header comments-annotated-tag $commit commit $time >expect
+cat >>expect <<EOF
+The message.
+One line.
+
+Another line.
+
+Last line.
+EOF
+test_expect_success \
+ 'creating a tag using a -F messagefile with #comments should succeed' '
+ git-tag -F commentsfile comments-annotated-tag &&
+ get_tag_msg comments-annotated-tag >actual &&
+ git diff expect actual
+'
+
+get_tag_header comment-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with a #comment in the -m message should succeed' '
+ git-tag -m "#comment" comment-annotated-tag &&
+ get_tag_msg comment-annotated-tag >actual &&
+ git diff expect actual
+'
+
+echo '#comment' >commentfile
+echo '' >>commentfile
+echo '####' >>commentfile
+get_tag_header commentfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with #comments in the -F messagefile should succeed' '
+ git-tag -F commentfile commentfile-annotated-tag &&
+ get_tag_msg commentfile-annotated-tag >actual &&
+ git diff expect actual
+'
+
+printf '#comment' >commentnonlfile
+get_tag_header commentnonlfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with a file of #comment and no newline should succeed' '
+ git-tag -F commentnonlfile commentnonlfile-annotated-tag &&
+ get_tag_msg commentnonlfile-annotated-tag >actual &&
+ git diff expect actual
+'
+
+# listing messages for annotated non-signed tags:
+
+test_expect_success \
+ 'listing the one-line message of a non-signed tag should succeed' '
+ git-tag -m "A msg" tag-one-line &&
+
+ echo "tag-one-line" >expect &&
+ git-tag -l | grep "^tag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l | grep "^tag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l tag-one-line >actual &&
+ git diff expect actual &&
+
+ echo "tag-one-line A msg" >expect &&
+ git-tag -n xxx -l | grep "^tag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n "" -l | grep "^tag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n 1 -l | grep "^tag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n -l | grep "^tag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n 1 -l tag-one-line >actual &&
+ git diff expect actual &&
+ git-tag -n 2 -l tag-one-line >actual &&
+ git diff expect actual &&
+ git-tag -n 999 -l tag-one-line >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'listing the zero-lines message of a non-signed tag should succeed' '
+ git-tag -m "" tag-zero-lines &&
+
+ echo "tag-zero-lines" >expect &&
+ git-tag -l | grep "^tag-zero-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l | grep "^tag-zero-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l tag-zero-lines >actual &&
+ git diff expect actual &&
+
+ echo "tag-zero-lines " >expect &&
+ git-tag -n 1 -l | grep "^tag-zero-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n -l | grep "^tag-zero-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 1 -l tag-zero-lines >actual &&
+ git diff expect actual &&
+ git-tag -n 2 -l tag-zero-lines >actual &&
+ git diff expect actual &&
+ git-tag -n 999 -l tag-zero-lines >actual &&
+ git diff expect actual
+'
+
+echo 'tag line one' >annotagmsg
+echo 'tag line two' >>annotagmsg
+echo 'tag line three' >>annotagmsg
+test_expect_success \
+ 'listing many message lines of a non-signed tag should succeed' '
+ git-tag -F annotagmsg tag-lines &&
+
+ echo "tag-lines" >expect &&
+ git-tag -l | grep "^tag-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l | grep "^tag-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l tag-lines >actual &&
+ git diff expect actual &&
+
+ echo "tag-lines tag line one" >expect &&
+ git-tag -n 1 -l | grep "^tag-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n -l | grep "^tag-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 1 -l tag-lines >actual &&
+ git diff expect actual &&
+
+ echo " tag line two" >>expect &&
+ git-tag -n 2 -l | grep "^ *tag.line" >actual &&
+ git diff expect actual &&
+ git-tag -n 2 -l tag-lines >actual &&
+ git diff expect actual &&
+
+ echo " tag line three" >>expect &&
+ git-tag -n 3 -l | grep "^ *tag.line" >actual &&
+ git diff expect actual &&
+ git-tag -n 3 -l tag-lines >actual &&
+ git diff expect actual &&
+ git-tag -n 4 -l | grep "^ *tag.line" >actual &&
+ git diff expect actual &&
+ git-tag -n 4 -l tag-lines >actual &&
+ git diff expect actual &&
+ git-tag -n 99 -l | grep "^ *tag.line" >actual &&
+ git diff expect actual &&
+ git-tag -n 99 -l tag-lines >actual &&
+ git diff expect actual
+'
+
+# trying to verify annotated non-signed tags:
+
+test_expect_success \
+ 'trying to verify an annotated non-signed tag should fail' '
+ tag_exists annotated-tag &&
+ ! git-tag -v annotated-tag
+'
+
+test_expect_success \
+ 'trying to verify a file-annotated non-signed tag should fail' '
+ tag_exists file-annotated-tag &&
+ ! git-tag -v file-annotated-tag
+'
+
+# creating and verifying signed tags:
+
+gpg --version >/dev/null
+if [ $? -eq 127 ]; then
+ echo "Skipping signed tags tests, because gpg was not found"
+ test_done
+ exit
+fi
+
+# As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
+# the gpg version 1.0.6 didn't parse trust packets correctly, so for
+# that version, creation of signed tags using the generated key fails.
+case "$(gpg --version)" in
+'gpg (GnuPG) 1.0.6'*)
+ echo "Skipping signed tag tests, because a bug in 1.0.6 version"
+ test_done
+ exit
+ ;;
+esac
+
+# key generation info: gpg --homedir t/t7004 --gen-key
+# Type DSA and Elgamal, size 2048 bits, no expiration date.
+# Name and email: C O Mitter <committer@example.com>
+# No password given, to enable non-interactive operation.
+
+cp -R ../t7004 ./gpghome
+chmod 0700 gpghome
+export GNUPGHOME="$(pwd)/gpghome"
+
+get_tag_header signed-tag $commit commit $time >expect
+echo 'A signed tag message' >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success 'creating a signed tag with -m message should succeed' '
+ git-tag -s -m "A signed tag message" signed-tag &&
+ get_tag_msg signed-tag >actual &&
+ git diff expect actual
+'
+
+test_expect_success 'verifying a signed tag should succeed' \
+ 'git-tag -v signed-tag'
+
+test_expect_success 'verifying a forged tag should fail' '
+ forged=$(git cat-file tag signed-tag |
+ sed -e "s/signed-tag/forged-tag/" |
+ git mktag) &&
+ git tag forged-tag $forged &&
+ ! git-tag -v forged-tag
+'
+
+# blank and empty messages for signed tags:
+
+get_tag_header empty-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with an empty -m message should succeed' '
+ git-tag -s -m "" empty-signed-tag &&
+ get_tag_msg empty-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v empty-signed-tag
+'
+
+>sigemptyfile
+get_tag_header emptyfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with an empty -F messagefile should succeed' '
+ git-tag -s -F sigemptyfile emptyfile-signed-tag &&
+ get_tag_msg emptyfile-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v emptyfile-signed-tag
+'
+
+printf '\n\n \n\t\nLeading blank lines\n' > sigblanksfile
+printf '\n\t \t \nRepeated blank lines\n' >>sigblanksfile
+printf '\n\n\nTrailing spaces \t \n' >>sigblanksfile
+printf '\nTrailing blank lines\n\n\t \n\n' >>sigblanksfile
+get_tag_header blanks-signed-tag $commit commit $time >expect
+cat >>expect <<EOF
+Leading blank lines
+
+Repeated blank lines
+
+Trailing spaces
+
+Trailing blank lines
+EOF
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'extra blanks in the message for a signed tag should be removed' '
+ git-tag -s -F sigblanksfile blanks-signed-tag &&
+ get_tag_msg blanks-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v blanks-signed-tag
+'
+
+get_tag_header blank-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with a blank -m message should succeed' '
+ git-tag -s -m " " blank-signed-tag &&
+ get_tag_msg blank-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v blank-signed-tag
+'
+
+echo ' ' >sigblankfile
+echo '' >>sigblankfile
+echo ' ' >>sigblankfile
+get_tag_header blankfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with blank -F file with spaces should succeed' '
+ git-tag -s -F sigblankfile blankfile-signed-tag &&
+ get_tag_msg blankfile-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v blankfile-signed-tag
+'
+
+printf ' ' >sigblanknonlfile
+get_tag_header blanknonlfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with spaces and no newline should succeed' '
+ git-tag -s -F sigblanknonlfile blanknonlfile-signed-tag &&
+ get_tag_msg blanknonlfile-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v signed-tag
+'
+
+# messages with commented lines for signed tags:
+
+cat >sigcommentsfile <<EOF
+# A comment
+
+############
+The message.
+############
+One line.
+
+
+# commented lines
+# commented lines
+
+Another line.
+# comments
+
+Last line.
+EOF
+get_tag_header comments-signed-tag $commit commit $time >expect
+cat >>expect <<EOF
+The message.
+One line.
+
+Another line.
+
+Last line.
+EOF
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with a -F file with #comments should succeed' '
+ git-tag -s -F sigcommentsfile comments-signed-tag &&
+ get_tag_msg comments-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v comments-signed-tag
+'
+
+get_tag_header comment-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with #commented -m message should succeed' '
+ git-tag -s -m "#comment" comment-signed-tag &&
+ get_tag_msg comment-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v comment-signed-tag
+'
+
+echo '#comment' >sigcommentfile
+echo '' >>sigcommentfile
+echo '####' >>sigcommentfile
+get_tag_header commentfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with #commented -F messagefile should succeed' '
+ git-tag -s -F sigcommentfile commentfile-signed-tag &&
+ get_tag_msg commentfile-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v commentfile-signed-tag
+'
+
+printf '#comment' >sigcommentnonlfile
+get_tag_header commentnonlfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with a #comment and no newline should succeed' '
+ git-tag -s -F sigcommentnonlfile commentnonlfile-signed-tag &&
+ get_tag_msg commentnonlfile-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v commentnonlfile-signed-tag
+'
+
+# listing messages for signed tags:
+
+test_expect_success \
+ 'listing the one-line message of a signed tag should succeed' '
+ git-tag -s -m "A message line signed" stag-one-line &&
+
+ echo "stag-one-line" >expect &&
+ git-tag -l | grep "^stag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l | grep "^stag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l stag-one-line >actual &&
+ git diff expect actual &&
+
+ echo "stag-one-line A message line signed" >expect &&
+ git-tag -n xxx -l | grep "^stag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n "" -l | grep "^stag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n 1 -l | grep "^stag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n -l | grep "^stag-one-line" >actual &&
+ git diff expect actual &&
+ git-tag -n 1 -l stag-one-line >actual &&
+ git diff expect actual &&
+ git-tag -n 2 -l stag-one-line >actual &&
+ git diff expect actual &&
+ git-tag -n 999 -l stag-one-line >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'listing the zero-lines message of a signed tag should succeed' '
+ git-tag -s -m "" stag-zero-lines &&
+
+ echo "stag-zero-lines" >expect &&
+ git-tag -l | grep "^stag-zero-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l | grep "^stag-zero-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l stag-zero-lines >actual &&
+ git diff expect actual &&
+
+ echo "stag-zero-lines " >expect &&
+ git-tag -n 1 -l | grep "^stag-zero-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n -l | grep "^stag-zero-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 1 -l stag-zero-lines >actual &&
+ git diff expect actual &&
+ git-tag -n 2 -l stag-zero-lines >actual &&
+ git diff expect actual &&
+ git-tag -n 999 -l stag-zero-lines >actual &&
+ git diff expect actual
+'
+
+echo 'stag line one' >sigtagmsg
+echo 'stag line two' >>sigtagmsg
+echo 'stag line three' >>sigtagmsg
+test_expect_success \
+ 'listing many message lines of a signed tag should succeed' '
+ git-tag -s -F sigtagmsg stag-lines &&
+
+ echo "stag-lines" >expect &&
+ git-tag -l | grep "^stag-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l | grep "^stag-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 0 -l stag-lines >actual &&
+ git diff expect actual &&
+
+ echo "stag-lines stag line one" >expect &&
+ git-tag -n 1 -l | grep "^stag-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n -l | grep "^stag-lines" >actual &&
+ git diff expect actual &&
+ git-tag -n 1 -l stag-lines >actual &&
+ git diff expect actual &&
+
+ echo " stag line two" >>expect &&
+ git-tag -n 2 -l | grep "^ *stag.line" >actual &&
+ git diff expect actual &&
+ git-tag -n 2 -l stag-lines >actual &&
+ git diff expect actual &&
+
+ echo " stag line three" >>expect &&
+ git-tag -n 3 -l | grep "^ *stag.line" >actual &&
+ git diff expect actual &&
+ git-tag -n 3 -l stag-lines >actual &&
+ git diff expect actual &&
+ git-tag -n 4 -l | grep "^ *stag.line" >actual &&
+ git diff expect actual &&
+ git-tag -n 4 -l stag-lines >actual &&
+ git diff expect actual &&
+ git-tag -n 99 -l | grep "^ *stag.line" >actual &&
+ git diff expect actual &&
+ git-tag -n 99 -l stag-lines >actual &&
+ git diff expect actual
+'
+
+# tags pointing to objects different from commits:
+
+tree=$(git rev-parse HEAD^{tree})
+blob=$(git rev-parse HEAD:foo)
+tag=$(git rev-parse signed-tag)
+
+get_tag_header tree-signed-tag $tree tree $time >expect
+echo "A message for a tree" >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag pointing to a tree should succeed' '
+ git-tag -s -m "A message for a tree" tree-signed-tag HEAD^{tree} &&
+ get_tag_msg tree-signed-tag >actual &&
+ git diff expect actual
+'
+
+get_tag_header blob-signed-tag $blob blob $time >expect
+echo "A message for a blob" >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag pointing to a blob should succeed' '
+ git-tag -s -m "A message for a blob" blob-signed-tag HEAD:foo &&
+ get_tag_msg blob-signed-tag >actual &&
+ git diff expect actual
+'
+
+get_tag_header tag-signed-tag $tag tag $time >expect
+echo "A message for another tag" >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag pointing to another tag should succeed' '
+ git-tag -s -m "A message for another tag" tag-signed-tag signed-tag &&
+ get_tag_msg tag-signed-tag >actual &&
+ git diff expect actual
+'
+
+# try to verify without gpg:
+
+rm -rf gpghome
+test_expect_failure \
+ 'verify signed tag fails when public key is not present' \
+ 'git-tag -v signed-tag'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='GIT_EDITOR, core.editor, and stuff'
+
+. ./test-lib.sh
+
+for i in GIT_EDITOR core_editor EDITOR VISUAL vi
+do
+ cat >e-$i.sh <<-EOF
+ echo "Edited by $i" >"\$1"
+ EOF
+ chmod +x e-$i.sh
+done
+unset vi
+mv e-vi.sh vi
+PATH=".:$PATH"
+unset EDITOR VISUAL GIT_EDITOR
+
+test_expect_success setup '
+
+ msg="Hand edited" &&
+ echo "$msg" >expect &&
+ git add vi &&
+ test_tick &&
+ git commit -m "$msg" &&
+ git show -s --pretty=oneline |
+ sed -e "s/^[0-9a-f]* //" >actual &&
+ diff actual expect
+
+'
+
+TERM=dumb
+export TERM
+test_expect_success 'dumb should error out when falling back on vi' '
+
+ if git commit --amend
+ then
+ echo "Oops?"
+ exit 1
+ else
+ : happy
+ fi
+'
+
+TERM=vt100
+export TERM
+for i in vi EDITOR VISUAL core_editor GIT_EDITOR
+do
+ echo "Edited by $i" >expect
+ unset EDITOR VISUAL GIT_EDITOR
+ git config --unset-all core.editor
+ case "$i" in
+ core_editor)
+ git config core.editor ./e-core_editor.sh
+ ;;
+ [A-Z]*)
+ eval "$i=./e-$i.sh"
+ export $i
+ ;;
+ esac
+ test_expect_success "Using $i" '
+ git commit --amend &&
+ git show -s --pretty=oneline |
+ sed -e "s/^[0-9a-f]* //" >actual &&
+ diff actual expect
+ '
+done
+
+unset EDITOR VISUAL GIT_EDITOR
+git config --unset-all core.editor
+for i in vi EDITOR VISUAL core_editor GIT_EDITOR
+do
+ echo "Edited by $i" >expect
+ case "$i" in
+ core_editor)
+ git config core.editor ./e-core_editor.sh
+ ;;
+ [A-Z]*)
+ eval "$i=./e-$i.sh"
+ export $i
+ ;;
+ esac
+ test_expect_success "Using $i (override)" '
+ git commit --amend &&
+ git show -s --pretty=oneline |
+ sed -e "s/^[0-9a-f]* //" >actual &&
+ diff actual expect
+ '
+done
+
+test_done
'creating initial files' \
'mkdir path0 &&
cp ../../COPYING path0/COPYING &&
- git-add path0/COPYING &&
+ git add path0/COPYING &&
git-commit -m add -a'
test_expect_success \
cp ../../COPYING path1/COPYING &&
cp ../../COPYING COPYING &&
cp ../../COPYING path0/COPYING-TOO &&
- git-add path1/path2/COPYING &&
- git-add path1/COPYING &&
- git-add COPYING &&
- git-add path0/COPYING-TOO &&
+ git add path1/path2/COPYING &&
+ git add path1/COPYING &&
+ git add COPYING &&
+ git add path0/COPYING-TOO &&
git-commit -m change -a'
test_expect_success \
touch src/part1.c Makefile &&
echo build >.gitignore &&
echo \*.o >>.gitignore &&
- git-add . &&
+ git add . &&
git-commit -m setup &&
touch src/part2.c README &&
- git-add .
+ git add .
'
test_expect_success 'clean.requireForce' '
- git-config clean.requireForce true &&
+ git config clean.requireForce true &&
! git-clean
'
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Lars Hjemli
+#
+
+test_description='Basic porcelain support for submodules
+
+This test tries to verify basic sanity of the init, update and status
+subcommands of git-submodule.
+'
+
+. ./test-lib.sh
+
+#
+# Test setup:
+# -create a repository in directory lib
+# -add a couple of files
+# -add directory lib to 'superproject', this creates a DIRLINK entry
+# -add a couple of regular files to enable testing of submodule filtering
+# -mv lib subrepo
+# -add an entry to .gitmodules for submodule 'example'
+#
+test_expect_success 'Prepare submodule testing' '
+ : > t &&
+ git-add t &&
+ git-commit -m "initial commit" &&
+ git branch initial HEAD &&
+ mkdir lib &&
+ cd lib &&
+ git init &&
+ echo a >a &&
+ git add a &&
+ git-commit -m "submodule commit 1" &&
+ git-tag -a -m "rev-1" rev-1 &&
+ rev1=$(git rev-parse HEAD) &&
+ if test -z "$rev1"
+ then
+ echo "[OOPS] submodule git rev-parse returned nothing"
+ false
+ fi &&
+ cd .. &&
+ echo a >a &&
+ echo z >z &&
+ git add a lib z &&
+ git-commit -m "super commit 1" &&
+ mv lib .subrepo &&
+ GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/lib.git
+'
+
+test_expect_success 'status should fail for unmapped paths' '
+ if git-submodule status
+ then
+ echo "[OOPS] submodule status succeeded"
+ false
+ elif ! GIT_CONFIG=.gitmodules git config submodule.example.path lib
+ then
+ echo "[OOPS] git config failed to update .gitmodules"
+ false
+ fi
+'
+
+test_expect_success 'status should only print one line' '
+ lines=$(git-submodule status | wc -l) &&
+ test $lines = 1
+'
+
+test_expect_success 'status should initially be "missing"' '
+ git-submodule status | grep "^-$rev1"
+'
+
+test_expect_success 'init should register submodule url in .git/config' '
+ git-submodule init &&
+ url=$(git config submodule.example.url) &&
+ if test "$url" != "git://example.com/lib.git"
+ then
+ echo "[OOPS] init succeeded but submodule url is wrong"
+ false
+ elif ! git config submodule.example.url ./.subrepo
+ then
+ echo "[OOPS] init succeeded but update of url failed"
+ false
+ fi
+'
+
+test_expect_success 'update should fail when path is used by a file' '
+ echo "hello" >lib &&
+ if git-submodule update
+ then
+ echo "[OOPS] update should have failed"
+ false
+ elif test "$(cat lib)" != "hello"
+ then
+ echo "[OOPS] update failed but lib file was molested"
+ false
+ else
+ rm lib
+ fi
+'
+
+test_expect_success 'update should fail when path is used by a nonempty directory' '
+ mkdir lib &&
+ echo "hello" >lib/a &&
+ if git-submodule update
+ then
+ echo "[OOPS] update should have failed"
+ false
+ elif test "$(cat lib/a)" != "hello"
+ then
+ echo "[OOPS] update failed but lib/a was molested"
+ false
+ else
+ rm lib/a
+ fi
+'
+
+test_expect_success 'update should work when path is an empty dir' '
+ rm -rf lib &&
+ mkdir lib &&
+ git-submodule update &&
+ head=$(cd lib && git rev-parse HEAD) &&
+ if test -z "$head"
+ then
+ echo "[OOPS] Failed to obtain submodule head"
+ false
+ elif test "$head" != "$rev1"
+ then
+ echo "[OOPS] Submodule head is $head but should have been $rev1"
+ false
+ fi
+'
+
+test_expect_success 'status should be "up-to-date" after update' '
+ git-submodule status | grep "^ $rev1"
+'
+
+test_expect_success 'status should be "modified" after submodule commit' '
+ cd lib &&
+ echo b >b &&
+ git add b &&
+ git-commit -m "submodule commit 2" &&
+ rev2=$(git rev-parse HEAD) &&
+ cd .. &&
+ if test -z "$rev2"
+ then
+ echo "[OOPS] submodule git rev-parse returned nothing"
+ false
+ fi &&
+ git-submodule status | grep "^+$rev2"
+'
+
+test_expect_success 'the --cached sha1 should be rev1' '
+ git-submodule --cached status | grep "^+$rev1"
+'
+
+test_expect_success 'update should checkout rev1' '
+ git-submodule update &&
+ head=$(cd lib && git rev-parse HEAD) &&
+ if test -z "$head"
+ then
+ echo "[OOPS] submodule git rev-parse returned nothing"
+ false
+ elif test "$head" != "$rev1"
+ then
+ echo "[OOPS] init did not checkout correct head"
+ false
+ fi
+'
+
+test_expect_success 'status should be "up-to-date" after update' '
+ git-submodule status | grep "^ $rev1"
+'
+
+test_expect_success 'checkout superproject with subproject already present' '
+ git-checkout initial &&
+ git-checkout master
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Steven Grimm
+#
+
+test_description='git-commit
+
+Tests for selected commit options.'
+
+. ./test-lib.sh
+
+commit_msg_is () {
+ test "`git log --pretty=format:%s%b -1`" = "$1"
+}
+
+# A sanity check to see if commit is working at all.
+test_expect_success 'a basic commit in an empty tree should succeed' '
+ echo content > foo &&
+ git add foo &&
+ git commit -m "initial commit"
+'
+
+test_expect_success 'nonexistent template file should return error' '
+ echo changes >> foo &&
+ git add foo &&
+ ! git commit --template "$PWD"/notexist
+'
+
+test_expect_success 'nonexistent template file in config should return error' '
+ git config commit.template "$PWD"/notexist &&
+ ! git commit &&
+ git config --unset commit.template
+'
+
+# From now on we'll use a template file that exists.
+TEMPLATE="$PWD"/template
+
+test_expect_success 'unedited template should not commit' '
+ echo "template line" > "$TEMPLATE" &&
+ ! git commit --template "$TEMPLATE"
+'
+
+test_expect_success 'unedited template with comments should not commit' '
+ echo "# comment in template" >> "$TEMPLATE" &&
+ ! git commit --template "$TEMPLATE"
+'
+
+test_expect_success 'a Signed-off-by line by itself should not commit' '
+ ! GIT_EDITOR=../t7500/add-signed-off git commit --template "$TEMPLATE"
+'
+
+test_expect_success 'adding comments to a template should not commit' '
+ ! GIT_EDITOR=../t7500/add-comments git commit --template "$TEMPLATE"
+'
+
+test_expect_success 'adding real content to a template should commit' '
+ GIT_EDITOR=../t7500/add-content git commit --template "$TEMPLATE" &&
+ commit_msg_is "template linecommit message"
+'
+
+test_expect_success '-t option should be short for --template' '
+ echo "short template" > "$TEMPLATE" &&
+ echo "new content" >> foo &&
+ git add foo &&
+ GIT_EDITOR=../t7500/add-content git commit -t "$TEMPLATE" &&
+ commit_msg_is "short templatecommit message"
+'
+
+test_expect_success 'config-specified template should commit' '
+ echo "new template" > "$TEMPLATE" &&
+ git config commit.template "$TEMPLATE" &&
+ echo "more content" >> foo &&
+ git add foo &&
+ GIT_EDITOR=../t7500/add-content git commit &&
+ git config --unset commit.template &&
+ commit_msg_is "new templatecommit message"
+'
+
+test_expect_success 'explicit commit message should override template' '
+ echo "still more content" >> foo &&
+ git add foo &&
+ GIT_EDITOR=../t7500/add-content git commit --template "$TEMPLATE" \
+ -m "command line msg" &&
+ commit_msg_is "command line msg<unknown>"
+'
+
+test_expect_success 'commit message from file should override template' '
+ echo "content galore" >> foo &&
+ git add foo &&
+ echo "standard input msg" |
+ GIT_EDITOR=../t7500/add-content git commit \
+ --template "$TEMPLATE" --file - &&
+ commit_msg_is "standard input msg<unknown>"
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+echo "# this is a new comment" >> "$1"
+echo "# and so is this" >> "$1"
+exit 0
--- /dev/null
+#!/bin/sh
+echo "commit message" >> "$1"
+exit 0
--- /dev/null
+#!/bin/sh
+echo "Signed-off-by: foo <bar@frotz>" >> "$1"
+exit 0
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+#
+
+# FIXME: Test the various index usages, -i and -o, test reflog,
+# signoff, hooks
+
+test_description='git-commit'
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success \
+ "initial status" \
+ "echo 'bongo bongo' >file &&
+ git-add file && \
+ git-status | grep 'Initial commit'"
+
+test_expect_failure \
+ "fail initial amend" \
+ "git-commit --amend"
+
+test_expect_success \
+ "initial commit" \
+ "git-commit -m initial"
+
+test_expect_failure \
+ "invalid options 1" \
+ "git-commit -m foo -m bar -F file"
+
+test_expect_failure \
+ "invalid options 2" \
+ "git-commit -C HEAD -m illegal"
+
+test_expect_failure \
+ "using invalid commit with -C" \
+ "git-commit -C bogus"
+
+test_expect_failure \
+ "testing nothing to commit" \
+ "git-commit -m initial"
+
+test_expect_success \
+ "next commit" \
+ "echo 'bongo bongo bongo' >file \
+ git-commit -m next -a"
+
+test_expect_failure \
+ "commit message from non-existing file" \
+ "echo 'more bongo: bongo bongo bongo bongo' >file && \
+ git-commit -F gah -a"
+
+# Empty except stray tabs and spaces on a few lines.
+sed -e 's/@$//' >msg <<EOF
+ @
+
+ @
+Signed-off-by: hula
+EOF
+test_expect_failure \
+ "empty commit message" \
+ "git-commit -F msg -a"
+
+test_expect_success \
+ "commit message from file" \
+ "echo 'this is the commit message, coming from a file' >msg && \
+ git-commit -F msg -a"
+
+cat >editor <<\EOF
+#!/bin/sh
+sed -i -e "s/a file/an amend commit/g" $1
+EOF
+chmod 755 editor
+
+test_expect_success \
+ "amend commit" \
+ "VISUAL=./editor git-commit --amend"
+
+test_expect_failure \
+ "passing -m and -F" \
+ "echo 'enough with the bongos' >file && \
+ git-commit -F msg -m amending ."
+
+test_expect_success \
+ "using message from other commit" \
+ "git-commit -C HEAD^ ."
+
+cat >editor <<\EOF
+#!/bin/sh
+sed -i -e "s/amend/older/g" $1
+EOF
+chmod 755 editor
+
+test_expect_success \
+ "editing message from other commit" \
+ "echo 'hula hula' >file && \
+ VISUAL=./editor git-commit -c HEAD^ -a"
+
+test_expect_success \
+ "message from stdin" \
+ "echo 'silly new contents' >file && \
+ echo commit message from stdin | git-commit -F - -a"
+
+test_expect_success \
+ "overriding author from command line" \
+ "echo 'gak' >file && \
+ git-commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a"
+
+test_expect_success \
+ "interactive add" \
+ "echo 7 | git-commit --interactive | grep 'What now'"
+
+test_expect_success \
+ "showing committed revisions" \
+ "git-rev-list HEAD >current"
+
+# We could just check the head sha1, but checking each commit makes it
+# easier to isolate bugs.
+
+cat >expected <<\EOF
+72c0dc9855b0c9dadcbfd5a31cab072e0cb774ca
+9b88fc14ce6b32e3d9ee021531a54f18a5cf38a2
+3536bbb352c3a1ef9a420f5b4242d48578b92aa7
+d381ac431806e53f3dd7ac2f1ae0534f36d738b9
+4fd44095ad6334f3ef72e4c5ec8ddf108174b54a
+402702b49136e7587daa9280e91e4bb7cb2179f7
+EOF
+
+test_expect_success \
+ 'validate git-rev-list output.' \
+ 'diff current expected'
+
+test_done
#!/bin/sh
-test_description='git-annotate'
+test_description='git annotate'
. ./test-lib.sh
PROG='git annotate'
#!/bin/sh
-test_description='git-blame'
+test_description='git blame'
. ./test-lib.sh
PROG='git blame -c'
rm -f '$GIT_DIR'/index &&
git checkout -f -b mybranch3 remotes/git-svn &&
rm bar/zzz &&
- git-update-index --remove bar/zzz &&
+ git update-index --remove bar/zzz &&
mkdir bar/zzz &&
echo yyy > bar/zzz/yyy &&
- git-update-index --add bar/zzz/yyy &&
+ git update-index --add bar/zzz/yyy &&
git commit -m '$name' &&
git-svn set-tree --find-copies-harder --rmdir \
remotes/git-svn..mybranch3" || true
export GIT_SVN_ID
test_expect_success "$name" \
"git-svn init $svnrepo && git-svn fetch &&
- git-rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
- git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
+ git rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
+ git rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
git diff a b"
name='check imported tree checksums expected tree checksums'
test_expect_success "$name" "git diff a expected"
test_expect_failure 'exit if remote refs are ambigious' "
- git-config --add svn-remote.svn.fetch \
+ git config --add svn-remote.svn.fetch \
bar:refs/remotes/git-svn &&
git-svn migrate
"
test_expect_failure 'exit if init-ing a would clobber a URL' "
svnadmin create ${PWD}/svnrepo2 &&
svn mkdir -m 'mkdir bar' ${svnrepo}2/bar &&
- git-config --unset svn-remote.svn.fetch \
+ git config --unset svn-remote.svn.fetch \
'^bar:refs/remotes/git-svn$' &&
git-svn init ${svnrepo}2/bar
"
test_expect_success 'init and fetch a moved directory' "
git-svn init --minimize-url -i thunk $svnrepo/thunk &&
git-svn fetch -i thunk &&
- test \"\`git-rev-parse --verify refs/remotes/thunk@2\`\" \
- = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" &&
- test \"\`git-cat-file blob refs/remotes/thunk:readme |\
+ test \"\`git rev-parse --verify refs/remotes/thunk@2\`\" \
+ = \"\`git rev-parse --verify refs/remotes/thunk~1\`\" &&
+ test \"\`git cat-file blob refs/remotes/thunk:readme |\
sed -n -e '3p'\`\" = goodbye &&
- test -z \"\`git-config --get svn-remote.svn.fetch \
+ test -z \"\`git config --get svn-remote.svn.fetch \
'^trunk:refs/remotes/thunk@2$'\`\"
"
test_expect_success 'init and fetch from one svn-remote' "
- git-config svn-remote.svn.url $svnrepo &&
- git-config --add svn-remote.svn.fetch \
+ git config svn-remote.svn.url $svnrepo &&
+ git config --add svn-remote.svn.fetch \
trunk:refs/remotes/svn/trunk &&
- git-config --add svn-remote.svn.fetch \
+ git config --add svn-remote.svn.fetch \
thunk:refs/remotes/svn/thunk &&
git-svn fetch -i svn/thunk &&
- test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \
- = \"\`git-rev-parse --verify refs/remotes/svn/thunk~1\`\" &&
- test \"\`git-cat-file blob refs/remotes/svn/thunk:readme |\
+ test \"\`git rev-parse --verify refs/remotes/svn/trunk\`\" \
+ = \"\`git rev-parse --verify refs/remotes/svn/thunk~1\`\" &&
+ test \"\`git cat-file blob refs/remotes/svn/thunk:readme |\
sed -n -e '3p'\`\" = goodbye
"
test_expect_success 'follow deleted parent' "
svn cp -m 'resurrecting trunk as junk' \
-r2 $svnrepo/trunk $svnrepo/junk &&
- git-config --add svn-remote.svn.fetch \
+ git config --add svn-remote.svn.fetch \
junk:refs/remotes/svn/junk &&
git-svn fetch -i svn/thunk &&
git-svn fetch -i svn/junk &&
git-svn init --minimize-url -i larger \
$svnrepo/another-larger/trunk/thunk/bump/thud &&
git-svn fetch -i larger &&
- git-rev-parse --verify refs/remotes/larger &&
- git-rev-parse --verify \
+ git rev-parse --verify refs/remotes/larger &&
+ git rev-parse --verify \
refs/remotes/larger-parent/trunk/thunk/bump/thud &&
- test \"\`git-merge-base \
+ test \"\`git merge-base \
refs/remotes/larger-parent/trunk/thunk/bump/thud \
refs/remotes/larger\`\" = \
- \"\`git-rev-parse refs/remotes/larger\`\"
+ \"\`git rev-parse refs/remotes/larger\`\"
true
"
mv $GIT_DIR/svn/* $GIT_DIR/ &&
mv $GIT_DIR/svn/.metadata $GIT_DIR/ &&
rmdir $GIT_DIR/svn &&
- git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn &&
- git-update-ref refs/heads/svn-HEAD refs/remotes/git-svn &&
- git-update-ref -d refs/remotes/git-svn refs/remotes/git-svn
+ git update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn &&
+ git update-ref refs/heads/svn-HEAD refs/remotes/git-svn &&
+ git update-ref -d refs/remotes/git-svn refs/remotes/git-svn
"
head=`git rev-parse --verify refs/heads/git-svn-HEAD^0`
echo $svnrepo > $GIT_DIR/svn/info/url &&
git-svn migrate &&
! test -d $GIT_DIR/git-svn &&
- git-rev-parse --verify refs/remotes/git-svn^0 &&
- git-rev-parse --verify refs/remotes/svn^0 &&
+ git rev-parse --verify refs/remotes/git-svn^0 &&
+ git rev-parse --verify refs/remotes/svn^0 &&
test \`git config --get svn-remote.svn.url\` = '$svnrepo' &&
test \`git config --get svn-remote.svn.fetch\` = \
':refs/remotes/git-svn'
test_expect_success 'initialize a multi-repository repo' "
git-svn init $svnrepo -T trunk -t tags -b branches &&
- git-config --get-all svn-remote.svn.fetch > fetch.out &&
+ git config --get-all svn-remote.svn.fetch > fetch.out &&
grep '^trunk:refs/remotes/trunk$' fetch.out &&
- test -n \"\`git-config --get svn-remote.svn.branches \
+ test -n \"\`git config --get svn-remote.svn.branches \
'^branches/\*:refs/remotes/\*$'\`\" &&
- test -n \"\`git-config --get svn-remote.svn.tags \
+ test -n \"\`git config --get svn-remote.svn.tags \
'^tags/\*:refs/remotes/tags/\*$'\`\" &&
git config --unset svn-remote.svn.branches \
'^branches/\*:refs/remotes/\*$' &&
git config --unset svn-remote.svn.tags \
'^tags/\*:refs/remotes/tags/\*$' &&
- git-config --add svn-remote.svn.fetch 'branches/a:refs/remotes/a' &&
- git-config --add svn-remote.svn.fetch 'branches/b:refs/remotes/b' &&
+ git config --add svn-remote.svn.fetch 'branches/a:refs/remotes/a' &&
+ git config --add svn-remote.svn.fetch 'branches/b:refs/remotes/b' &&
for i in tags/0.1 tags/0.2 tags/0.3; do
- git-config --add svn-remote.svn.fetch \
+ git config --add svn-remote.svn.fetch \
\$i:refs/remotes/\$i || exit 1; done
"
echo $svnrepo\$path > $GIT_DIR/svn/\$ref/info/url ) || exit 1;
done &&
git-svn migrate --minimize &&
- test -z \"\`git-config -l |grep -v '^svn-remote\.git-svn\.'\`\" &&
- git-config --get-all svn-remote.svn.fetch > fetch.out &&
+ test -z \"\`git config -l |grep -v '^svn-remote\.git-svn\.'\`\" &&
+ git config --get-all svn-remote.svn.fetch > fetch.out &&
grep '^trunk:refs/remotes/trunk$' fetch.out &&
grep '^branches/a:refs/remotes/a$' fetch.out &&
grep '^branches/b:refs/remotes/b$' fetch.out &&
"
test_done
-
git-svn init --minimize-url -R argh -i dir $svnrepo/mirror/argh &&
git-svn init --minimize-url -R argh -i e \
$svnrepo/mirror/argh/a/b/c/d/e &&
- git-config svn.useSvmProps true &&
+ git config svn.useSvmProps true &&
git-svn fetch --all
"
bar_url=http://mayonaise/svnrepo/bar
test_expect_success 'verify metadata for /bar' "
- git-cat-file commit refs/remotes/bar | \
+ git cat-file commit refs/remotes/bar | \
grep '^git-svn-id: $bar_url@12 $uuid$' &&
- git-cat-file commit refs/remotes/bar~1 | \
+ git cat-file commit refs/remotes/bar~1 | \
grep '^git-svn-id: $bar_url@11 $uuid$' &&
- git-cat-file commit refs/remotes/bar~2 | \
+ git cat-file commit refs/remotes/bar~2 | \
grep '^git-svn-id: $bar_url@10 $uuid$' &&
- git-cat-file commit refs/remotes/bar~3 | \
+ git cat-file commit refs/remotes/bar~3 | \
grep '^git-svn-id: $bar_url@9 $uuid$' &&
- git-cat-file commit refs/remotes/bar~4 | \
+ git cat-file commit refs/remotes/bar~4 | \
grep '^git-svn-id: $bar_url@6 $uuid$' &&
- git-cat-file commit refs/remotes/bar~5 | \
+ git cat-file commit refs/remotes/bar~5 | \
grep '^git-svn-id: $bar_url@1 $uuid$'
"
e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e
test_expect_success 'verify metadata for /dir/a/b/c/d/e' "
- git-cat-file commit refs/remotes/e | \
+ git cat-file commit refs/remotes/e | \
grep '^git-svn-id: $e_url@1 $uuid$'
"
dir_url=http://mayonaise/svnrepo/dir
test_expect_success 'verify metadata for /dir' "
- git-cat-file commit refs/remotes/dir | \
+ git cat-file commit refs/remotes/dir | \
grep '^git-svn-id: $dir_url@2 $uuid$' &&
- git-cat-file commit refs/remotes/dir~1 | \
+ git cat-file commit refs/remotes/dir~1 | \
grep '^git-svn-id: $dir_url@1 $uuid$'
"
git-svn init --minimize-url -R arr -i bar $svnrepo/bar &&
git-svn init --minimize-url -R argh -i dir $svnrepo/dir &&
git-svn init --minimize-url -R argh -i e $svnrepo/dir/a/b/c/d/e &&
- git-config svn.useSvnsyncProps true &&
+ git config svn.useSvnsyncProps true &&
git-svn fetch --all
"
bar_url=http://mayonaise/svnrepo/bar
test_expect_success 'verify metadata for /bar' "
- git-cat-file commit refs/remotes/bar | \
+ git cat-file commit refs/remotes/bar | \
grep '^git-svn-id: $bar_url@12 $uuid$' &&
- git-cat-file commit refs/remotes/bar~1 | \
+ git cat-file commit refs/remotes/bar~1 | \
grep '^git-svn-id: $bar_url@11 $uuid$' &&
- git-cat-file commit refs/remotes/bar~2 | \
+ git cat-file commit refs/remotes/bar~2 | \
grep '^git-svn-id: $bar_url@10 $uuid$' &&
- git-cat-file commit refs/remotes/bar~3 | \
+ git cat-file commit refs/remotes/bar~3 | \
grep '^git-svn-id: $bar_url@9 $uuid$' &&
- git-cat-file commit refs/remotes/bar~4 | \
+ git cat-file commit refs/remotes/bar~4 | \
grep '^git-svn-id: $bar_url@6 $uuid$' &&
- git-cat-file commit refs/remotes/bar~5 | \
+ git cat-file commit refs/remotes/bar~5 | \
grep '^git-svn-id: $bar_url@1 $uuid$'
"
e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e
test_expect_success 'verify metadata for /dir/a/b/c/d/e' "
- git-cat-file commit refs/remotes/e | \
+ git cat-file commit refs/remotes/e | \
grep '^git-svn-id: $e_url@1 $uuid$'
"
dir_url=http://mayonaise/svnrepo/dir
test_expect_success 'verify metadata for /dir' "
- git-cat-file commit refs/remotes/dir | \
+ git cat-file commit refs/remotes/dir | \
grep '^git-svn-id: $dir_url@2 $uuid$' &&
- git-cat-file commit refs/remotes/dir~1 | \
+ git cat-file commit refs/remotes/dir~1 | \
grep '^git-svn-id: $dir_url@1 $uuid$'
"
Content-length: 4
cba
-
-
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+#
+
+# Don't run this test by default unless the user really wants it
+# I don't like the idea of taking a port and possibly leaving a
+# daemon running on a users system if the test fails.
+# Not all git users will need to interact with SVN.
+test -z "$SVNSERVE_PORT" && exit 0
+
+test_description='git-svn dcommit new files over svn:// test'
+
+. ./lib-git-svn.sh
+
+start_svnserve () {
+ svnserve --listen-port $SVNSERVE_PORT \
+ --root $rawsvnrepo \
+ --listen-once \
+ --listen-host 127.0.0.1 &
+}
+
+test_expect_success 'start tracking an empty repo' "
+ svn mkdir -m 'empty dir' $svnrepo/empty-dir &&
+ echo anon-access = write >> $rawsvnrepo/conf/svnserve.conf &&
+ start_svnserve &&
+ git svn init svn://127.0.0.1:$SVNSERVE_PORT &&
+ git svn fetch
+ "
+
+test_expect_success 'create files in new directory with dcommit' "
+ mkdir git-new-dir &&
+ echo hello > git-new-dir/world &&
+ git update-index --add git-new-dir/world &&
+ git commit -m hello &&
+ start_svnserve &&
+ git svn dcommit
+ "
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+# Based on a script by Joakim Tjernlund <joakim.tjernlund@transmode.se>
+
+test_description='git-svn dcommit handles merges'
+
+. ./lib-git-svn.sh
+
+big_text_block () {
+cat << EOF
+#
+# (C) Copyright 2000 - 2005
+# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+#
+# See file CREDITS for list of people who contributed to this
+# project.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+# MA 02111-1307 USA
+#
+EOF
+}
+
+test_expect_success 'setup svn repository' "
+ svn co $svnrepo mysvnwork &&
+ mkdir -p mysvnwork/trunk &&
+ cd mysvnwork &&
+ big_text_block >> trunk/README &&
+ svn add trunk &&
+ svn ci -m 'first commit' trunk &&
+ cd ..
+ "
+
+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 merge &&
+ echo new file > new_file &&
+ git add new_file &&
+ git commit -a -m 'New file' &&
+ echo hello >> README &&
+ git commit -a -m 'hello' &&
+ echo add some stuff >> new_file &&
+ git commit -a -m 'add some stuff' &&
+ git checkout svn &&
+ mv -f README tmp &&
+ echo friend > README &&
+ cat tmp >> README &&
+ git commit -a -m 'friend' &&
+ git pull . merge
+ "
+
+test_debug 'gitk --all & sleep 1'
+
+test_expect_success 'verify pre-merge ancestry' "
+ test x\`git rev-parse --verify refs/heads/svn^2\` = \
+ x\`git rev-parse --verify refs/heads/merge\` &&
+ git cat-file commit refs/heads/svn^ | grep '^friend$'
+ "
+
+test_expect_success 'git svn dcommit merges' "
+ git svn dcommit
+ "
+
+test_debug 'gitk --all & sleep 1'
+
+test_expect_success 'verify post-merge ancestry' "
+ test x\`git rev-parse --verify refs/heads/svn\` = \
+ x\`git rev-parse --verify refs/remotes/trunk \` &&
+ test x\`git rev-parse --verify refs/heads/svn^2\` = \
+ x\`git rev-parse --verify refs/heads/merge\` &&
+ git cat-file commit refs/heads/svn^ | grep '^friend$'
+ "
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+
+
+test_description='git-svn dcommit can commit renames of files with ugly names'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load repository with strange names' "
+ svnadmin load -q $rawsvnrepo < ../t9115/funky-names.dump &&
+ start_httpd
+ "
+
+test_expect_success 'init and fetch repository' "
+ git svn init $svnrepo &&
+ git svn fetch &&
+ git reset --hard git-svn
+ "
+
+test_expect_success 'create file in existing ugly and empty dir' '
+ mkdir "#{bad_directory_name}" &&
+ echo hi > "#{bad_directory_name}/ foo" &&
+ git update-index --add "#{bad_directory_name}/ foo" &&
+ git commit -m "new file in ugly parent" &&
+ git svn dcommit
+ '
+
+test_expect_success 'rename ugly file' '
+ git mv "#{bad_directory_name}/ foo" "file name with feces" &&
+ git commit -m "rename ugly file" &&
+ git svn dcommit
+ '
+
+test_expect_success 'rename pretty file' '
+ echo :x > pretty &&
+ git update-index --add pretty &&
+ git commit -m "pretty :x" &&
+ git svn dcommit &&
+ mkdir regular_dir_name &&
+ git mv pretty regular_dir_name/pretty &&
+ git commit -m "moved pretty file" &&
+ git svn dcommit
+ '
+
+test_expect_success 'rename pretty file into ugly one' '
+ git mv regular_dir_name/pretty "#{bad_directory_name}/ booboo" &&
+ git commit -m booboo &&
+ git svn dcommit
+ '
+
+stop_httpd
+
+test_done
--- /dev/null
+SVN-fs-dump-format-version: 2
+
+UUID: 819c44fe-2bcc-4066-88e4-985e2bc0b418
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2007-07-12T07:54:26.062914Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 152
+Content-length: 152
+
+K 7
+svn:log
+V 44
+what will those wacky people think of next?
+
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2007-07-12T08:00:05.011573Z
+PROPS-END
+
+Node-path: leading space
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: leading space file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: e4fa20c67542cdc21271e08d329397ab
+Content-length: 15
+
+PROPS-END
+ugly
+
+
+Node-path: #{bad_directory_name}
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: #{cool_name}
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 18
+Text-content-md5: 87dac40ca337dfa3dcc8911388c3ddda
+Content-length: 28
+
+PROPS-END
+strange name here
+
+
+Node-path: dir name with spaces
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: file name with spaces
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: c1f10cfd640618484a2a475c11410fd3
+Content-length: 17
+
+PROPS-END
+spaces
+
+
+Node-path: regular_dir_name
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
git commit -q -a -m "Initial" 2>/dev/null ||
exit 1
+check_entries () {
+ # $1 == directory, $2 == expected
+ grep '^/' "$1/CVS/Entries" | sort | cut -d/ -f2,3,5 >actual
+ if test -z "$2"
+ then
+ >expected
+ else
+ printf '%s\n' "$2" | tr '|' '\012' >expected
+ fi
+ diff -u expected actual
+}
+
test_expect_success \
'New file' \
'mkdir A B C D E F &&
id=$(git rev-list --max-count=1 HEAD) &&
(cd "$CVSWORK" &&
git cvsexportcommit -c $id &&
- test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.1/" &&
- test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "newfile2.txt/1.1/" &&
- test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "newfile3.png/1.1/-kb" &&
- test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "newfile4.png/1.1/-kb" &&
+ check_entries A "newfile1.txt/1.1/" &&
+ check_entries B "newfile2.txt/1.1/" &&
+ check_entries C "newfile3.png/1.1/-kb" &&
+ check_entries D "newfile4.png/1.1/-kb" &&
diff A/newfile1.txt ../A/newfile1.txt &&
diff B/newfile2.txt ../B/newfile2.txt &&
diff C/newfile3.png ../C/newfile3.png &&
id=$(git rev-list --max-count=1 HEAD) &&
(cd "$CVSWORK" &&
git cvsexportcommit -c $id &&
- test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.2/" &&
- test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
- test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
- test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "newfile4.png/1.2/-kb" &&
- test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" &&
- test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" &&
+ check_entries A "newfile1.txt/1.2/" &&
+ check_entries B "" &&
+ check_entries C "" &&
+ check_entries D "newfile4.png/1.2/-kb" &&
+ check_entries E "newfile5.txt/1.1/" &&
+ check_entries F "newfile6.png/1.1/-kb" &&
diff A/newfile1.txt ../A/newfile1.txt &&
diff D/newfile4.png ../D/newfile4.png &&
diff E/newfile5.txt ../E/newfile5.txt &&
id=$(git rev-list --max-count=1 HEAD) &&
(cd "$CVSWORK" &&
git cvsexportcommit -c $id &&
- test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.2/" &&
- test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
- test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
- test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
- test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" &&
- test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" &&
+ check_entries A "newfile1.txt/1.2/" &&
+ check_entries B "" &&
+ check_entries C "" &&
+ check_entries D "" &&
+ check_entries E "newfile5.txt/1.1/" &&
+ check_entries F "newfile6.png/1.1/-kb" &&
diff A/newfile1.txt ../A/newfile1.txt &&
diff E/newfile5.txt ../E/newfile5.txt &&
diff F/newfile6.png ../F/newfile6.png
id=$(git rev-list --max-count=1 HEAD) &&
(cd "$CVSWORK" &&
git cvsexportcommit -c $id &&
- test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
- test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
- test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
- test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
- test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" &&
- test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" &&
+ check_entries A "" &&
+ check_entries B "" &&
+ check_entries C "" &&
+ check_entries D "" &&
+ check_entries E "newfile5.txt/1.1/" &&
+ check_entries F "newfile6.png/1.1/-kb" &&
diff E/newfile5.txt ../E/newfile5.txt &&
diff F/newfile6.png ../F/newfile6.png
)'
id=$(git rev-list --max-count=1 HEAD) &&
(cd "$CVSWORK" &&
git-cvsexportcommit -c $id &&
- test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.1/-kb with spaces.txt/1.1/"
+ check_entries "G g" "with spaces.png/1.1/-kb|with spaces.txt/1.1/"
)'
test_expect_success \
id=$(git rev-list --max-count=1 HEAD) &&
(cd "$CVSWORK" &&
git-cvsexportcommit -c $id
- test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.2/-kb with spaces.txt/1.2/"
+ check_entries "G g" "with spaces.png/1.2/-kb|with spaces.txt/1.2/"
)'
# Some filesystems mangle pathnames with UTF-8 characters --
id=$(git rev-list --max-count=1 HEAD) &&
(cd "$CVSWORK" &&
git-cvsexportcommit -v -c $id &&
- test "$(echo $(sort Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/CVS/Entries|cut -d/ -f2,3,5))" = "gårdetsågårdet.png/1.1/-kb gårdetsågårdet.txt/1.1/"
+ check_entries \
+ "Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö" \
+ "gårdetsågårdet.png/1.1/-kb|gårdetsågårdet.txt/1.1/"
)'
fi
test_expect_success \
'A: create pack from stdin' \
'git-fast-import --export-marks=marks.out <input &&
- git-whatchanged master'
+ git whatchanged master'
test_expect_success \
'A: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+ 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
cat >expect <<EOF
author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
EOF
test_expect_success \
'A: verify commit' \
- 'git-cat-file commit master | sed 1d >actual &&
+ 'git cat-file commit master | sed 1d >actual &&
git diff expect actual'
cat >expect <<EOF
EOF
test_expect_success \
'A: verify tree' \
- 'git-cat-file -p master^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
+ 'git cat-file -p master^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
git diff expect actual'
echo "$file2_data" >expect
test_expect_success \
'A: verify file2' \
- 'git-cat-file blob master:file2 >actual && git diff expect actual'
+ 'git cat-file blob master:file2 >actual && git diff expect actual'
echo "$file3_data" >expect
test_expect_success \
'A: verify file3' \
- 'git-cat-file blob master:file3 >actual && git diff expect actual'
+ 'git cat-file blob master:file3 >actual && git diff expect actual'
printf "$file4_data" >expect
test_expect_success \
'A: verify file4' \
- 'git-cat-file blob master:file4 >actual && git diff expect actual'
+ 'git cat-file blob master:file4 >actual && git diff expect actual'
cat >expect <<EOF
-:2 `git-rev-parse --verify master:file2`
-:3 `git-rev-parse --verify master:file3`
-:4 `git-rev-parse --verify master:file4`
-:5 `git-rev-parse --verify master^0`
+:2 `git rev-parse --verify master:file2`
+:3 `git rev-parse --verify master:file3`
+:4 `git rev-parse --verify master:file4`
+:5 `git rev-parse --verify master^0`
EOF
test_expect_success \
'A: verify marks output' \
test_expect_success \
'A: verify marks import does not crash' \
'git-fast-import --import-marks=marks.out <input &&
- git-whatchanged verify--import-marks'
+ git whatchanged verify--import-marks'
test_expect_success \
'A: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+ 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
cat >expect <<EOF
:000000 100755 0000000000000000000000000000000000000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 A copy-of-file2
EOF
-git-diff-tree -M -r master verify--import-marks >actual
+git diff-tree -M -r master verify--import-marks >actual
test_expect_success \
'A: verify diff' \
'compare_diff_raw expect actual &&
- test `git-rev-parse --verify master:file2` \
- = `git-rev-parse --verify verify--import-marks:copy-of-file2`'
+ test `git rev-parse --verify master:file2` \
+ = `git rev-parse --verify verify--import-marks:copy-of-file2`'
###
### series B
###
newf=`echo hi newf | git-hash-object -w --stdin`
-oldf=`git-rev-parse --verify master:file2`
+oldf=`git rev-parse --verify master:file2`
test_tick
cat >input <<INPUT_END
commit refs/heads/branch
test_expect_success \
'C: incremental import create pack from stdin' \
'git-fast-import <input &&
- git-whatchanged branch'
+ git whatchanged branch'
test_expect_success \
'C: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+ 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
test_expect_success \
'C: validate reuse existing blob' \
- 'test $newf = `git-rev-parse --verify branch:file2/newf`
- test $oldf = `git-rev-parse --verify branch:file2/oldf`'
+ 'test $newf = `git rev-parse --verify branch:file2/newf`
+ test $oldf = `git rev-parse --verify branch:file2/oldf`'
cat >expect <<EOF
-parent `git-rev-parse --verify master^0`
+parent `git rev-parse --verify master^0`
author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
EOF
test_expect_success \
'C: verify commit' \
- 'git-cat-file commit branch | sed 1d >actual &&
+ 'git cat-file commit branch | sed 1d >actual &&
git diff expect actual'
cat >expect <<EOF
:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100 file2 file2/oldf
:100644 000000 0d92e9f3374ae2947c23aa477cbc68ce598135f1 0000000000000000000000000000000000000000 D file3
EOF
-git-diff-tree -M -r master branch >actual
+git diff-tree -M -r master branch >actual
test_expect_success \
'C: validate rename result' \
'compare_diff_raw expect actual'
test_expect_success \
'D: inline data in commit' \
'git-fast-import <input &&
- git-whatchanged branch'
+ git whatchanged branch'
test_expect_success \
'D: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+ 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
cat >expect <<EOF
:000000 100755 0000000000000000000000000000000000000000 35a59026a33beac1569b1c7f66f3090ce9c09afc A newdir/exec.sh
:000000 100644 0000000000000000000000000000000000000000 046d0371e9220107917db0d0e030628de8a1de9b A newdir/interesting
EOF
-git-diff-tree -M -r branch^ branch >actual
+git diff-tree -M -r branch^ branch >actual
test_expect_success \
'D: validate new files added' \
'compare_diff_raw expect actual'
echo "$file5_data" >expect
test_expect_success \
'D: verify file5' \
- 'git-cat-file blob branch:newdir/interesting >actual &&
+ 'git cat-file blob branch:newdir/interesting >actual &&
git diff expect actual'
echo "$file6_data" >expect
test_expect_success \
'D: verify file6' \
- 'git-cat-file blob branch:newdir/exec.sh >actual &&
+ 'git cat-file blob branch:newdir/exec.sh >actual &&
git diff expect actual'
###
'git-fast-import --date-format=rfc2822 <input'
test_expect_success \
'E: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+ 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
cat >expect <<EOF
author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 1170778938 -0500
EOF
test_expect_success \
'E: verify commit' \
- 'git-cat-file commit branch | sed 1,2d >actual &&
+ 'git cat-file commit branch | sed 1,2d >actual &&
git diff expect actual'
###
### series F
###
-old_branch=`git-rev-parse --verify branch^0`
+old_branch=`git rev-parse --verify branch^0`
test_tick
cat >input <<INPUT_END
commit refs/heads/branch
echo BAD gfi did not fail
return 1
else
- if test $old_branch = `git-rev-parse --verify branch^0`
+ if test $old_branch = `git rev-parse --verify branch^0`
then
: branch unaffected and failure returned
return 0
'
test_expect_success \
'F: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+ 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
cat >expect <<EOF
-tree `git-rev-parse branch~1^{tree}`
-parent `git-rev-parse branch~1`
+tree `git rev-parse branch~1^{tree}`
+parent `git rev-parse branch~1`
author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
EOF
test_expect_success \
'F: verify other commit' \
- 'git-cat-file commit other >actual &&
+ 'git cat-file commit other >actual &&
git diff expect actual'
###
### series G
###
-old_branch=`git-rev-parse --verify branch^0`
+old_branch=`git rev-parse --verify branch^0`
test_tick
cat >input <<INPUT_END
commit refs/heads/branch
'git-fast-import --force <input'
test_expect_success \
'G: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+ 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
test_expect_success \
'G: branch changed, but logged' \
- 'test $old_branch != `git-rev-parse --verify branch^0` &&
- test $old_branch = `git-rev-parse --verify branch@{1}`'
+ 'test $old_branch != `git rev-parse --verify branch^0` &&
+ test $old_branch = `git rev-parse --verify branch@{1}`'
###
### series H
test_expect_success \
'H: deletall, add 1' \
'git-fast-import <input &&
- git-whatchanged H'
+ git whatchanged H'
test_expect_success \
'H: verify pack' \
- 'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+ 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
cat >expect <<EOF
:100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D file2/newf
:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100 newdir/interesting h/e/l/lo
:100755 000000 e74b7d465e52746be2b4bae983670711e6e66657 0000000000000000000000000000000000000000 D newdir/exec.sh
EOF
-git-diff-tree -M -r H^ H >actual
+git diff-tree -M -r H^ H >actual
test_expect_success \
'H: validate old files removed, new files added' \
'compare_diff_raw expect actual'
echo "$file5_data" >expect
test_expect_success \
'H: verify file' \
- 'git-cat-file blob H:h/e/l/lo >actual &&
+ 'git cat-file blob H:h/e/l/lo >actual &&
git diff expect actual'
###
'git-fast-import --export-pack-edges=edges.list <input'
cat >expect <<EOF
-.git/objects/pack/pack-.pack: `git-rev-parse --verify export-boundary`
+.git/objects/pack/pack-.pack: `git rev-parse --verify export-boundary`
EOF
test_expect_success \
'I: verify edge list' \
'git-fast-import <input'
test_expect_success \
'J: branch has 1 commit, empty tree' \
- 'test 1 = `git-rev-list J | wc -l` &&
+ 'test 1 = `git rev-list J | wc -l` &&
test 0 = `git ls-tree J | wc -l`'
###
'git-fast-import <input'
test_expect_success \
'K: verify K^1 = branch^1' \
- 'test `git-rev-parse --verify branch^1` \
- = `git-rev-parse --verify K^1`'
+ 'test `git rev-parse --verify branch^1` \
+ = `git rev-parse --verify K^1`'
###
### series L
test_expect_success \
'L: verify internal tree sorting' \
'git-fast-import <input &&
- git-diff --raw L^ L >output &&
+ git diff-tree --abbrev --raw L^ L >output &&
git diff expect output'
+###
+### series M
+###
+
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/M1
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+file rename
+COMMIT
+
+from refs/heads/branch^0
+R file2/newf file2/n.e.w.f
+
+INPUT_END
+
+cat >expect <<EOF
+:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 file2/newf file2/n.e.w.f
+EOF
+test_expect_success \
+ 'M: rename file in same subdirectory' \
+ 'git-fast-import <input &&
+ git diff-tree -M -r M1^ M1 >actual &&
+ compare_diff_raw expect actual'
+
+cat >input <<INPUT_END
+commit refs/heads/M2
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+file rename
+COMMIT
+
+from refs/heads/branch^0
+R file2/newf i/am/new/to/you
+
+INPUT_END
+
+cat >expect <<EOF
+:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 file2/newf i/am/new/to/you
+EOF
+test_expect_success \
+ 'M: rename file to new subdirectory' \
+ 'git-fast-import <input &&
+ git diff-tree -M -r M2^ M2 >actual &&
+ compare_diff_raw expect actual'
+
+cat >input <<INPUT_END
+commit refs/heads/M3
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+file rename
+COMMIT
+
+from refs/heads/M2^0
+R i other/sub
+
+INPUT_END
+
+cat >expect <<EOF
+:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 i/am/new/to/you other/sub/am/new/to/you
+EOF
+test_expect_success \
+ 'M: rename subdirectory to new subdirectory' \
+ 'git-fast-import <input &&
+ git diff-tree -M -r M3^ M3 >actual &&
+ compare_diff_raw expect actual'
+
+###
+### series N
+###
+
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/N1
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+file copy
+COMMIT
+
+from refs/heads/branch^0
+C file2/newf file2/n.e.w.f
+
+INPUT_END
+
+cat >expect <<EOF
+:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file2/n.e.w.f
+EOF
+test_expect_success \
+ 'N: copy file in same subdirectory' \
+ 'git-fast-import <input &&
+ git diff-tree -C --find-copies-harder -r N1^ N1 >actual &&
+ compare_diff_raw expect actual'
+
+cat >input <<INPUT_END
+commit refs/heads/N2
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+clean directory copy
+COMMIT
+
+from refs/heads/branch^0
+C file2 file3
+
+commit refs/heads/N2
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+modify directory copy
+COMMIT
+
+M 644 inline file3/file5
+data <<EOF
+$file5_data
+EOF
+
+INPUT_END
+
+cat >expect <<EOF
+:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting file3/file5
+:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf
+:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf
+EOF
+test_expect_success \
+ 'N: copy then modify subdirectory' \
+ 'git-fast-import <input &&
+ git diff-tree -C --find-copies-harder -r N2^^ N2 >actual &&
+ compare_diff_raw expect actual'
+
+cat >input <<INPUT_END
+commit refs/heads/N3
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+dirty directory copy
+COMMIT
+
+from refs/heads/branch^0
+M 644 inline file2/file5
+data <<EOF
+$file5_data
+EOF
+
+C file2 file3
+D file2/file5
+
+INPUT_END
+
+test_expect_success \
+ 'N: copy dirty subdirectory' \
+ 'git-fast-import <input &&
+ test `git-rev-parse N2^{tree}` = `git-rev-parse N3^{tree}`'
+
test_done
git commit -q -m "First Commit" &&
git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
- GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
+ GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
exit 1
# note that cvs doesn't accept absolute pathnames
'GIT_CONFIG="$git_config" cvs -Q co -d cvswork master &&
test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5))" = "empty/1.1/"'
+#------------------------
+# PSERVER AUTHENTICATION
+#------------------------
+
+cat >request-anonymous <<EOF
+BEGIN AUTH REQUEST
+$SERVERDIR
+anonymous
+
+END AUTH REQUEST
+EOF
+
+cat >request-git <<EOF
+BEGIN AUTH REQUEST
+$SERVERDIR
+git
+
+END AUTH REQUEST
+EOF
+
+cat >login-anonymous <<EOF
+BEGIN VERIFICATION REQUEST
+$SERVERDIR
+anonymous
+
+END VERIFICATION REQUEST
+EOF
+
+cat >login-git <<EOF
+BEGIN VERIFICATION REQUEST
+$SERVERDIR
+git
+
+END VERIFICATION REQUEST
+EOF
+
+test_expect_success 'pserver authentication' \
+ 'cat request-anonymous | git-cvsserver pserver >log 2>&1 &&
+ tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_success 'pserver authentication failure (non-anonymous user)' \
+ 'if cat request-git | git-cvsserver pserver >log 2>&1
+ then
+ false
+ else
+ true
+ fi &&
+ tail -n1 log | grep -q "^I HATE YOU$"'
+
+test_expect_success 'pserver authentication (login)' \
+ 'cat login-anonymous | git-cvsserver pserver >log 2>&1 &&
+ tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_success 'pserver authentication failure (login/non-anonymous user)' \
+ 'if cat login-git | git-cvsserver pserver >log 2>&1
+ then
+ false
+ else
+ true
+ fi &&
+ tail -n1 log | grep -q "^I HATE YOU$"'
+
+
+# misuse pserver authentication for testing of req_Root
+
+cat >request-relative <<EOF
+BEGIN AUTH REQUEST
+gitcvs.git
+anonymous
+
+END AUTH REQUEST
+EOF
+
+cat >request-conflict <<EOF
+BEGIN AUTH REQUEST
+$SERVERDIR
+anonymous
+
+END AUTH REQUEST
+Root $WORKDIR
+EOF
+
+test_expect_success 'req_Root failure (relative pathname)' \
+ 'if cat request-relative | git-cvsserver pserver >log 2>&1
+ then
+ echo unexpected success
+ false
+ else
+ true
+ fi &&
+ tail log | grep -q "^error 1 Root must be an absolute pathname$"'
+
+test_expect_success 'req_Root failure (conflicting roots)' \
+ 'cat request-conflict | git-cvsserver pserver >log 2>&1 &&
+ tail log | grep -q "^error 1 Conflicting roots specified$"'
+
+test_expect_success 'req_Root (strict paths)' \
+ 'cat request-anonymous | git-cvsserver --strict-paths pserver $SERVERDIR >log 2>&1 &&
+ tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_failure 'req_Root failure (strict-paths)' \
+ 'cat request-anonymous | git-cvsserver --strict-paths pserver $WORKDIR >log 2>&1'
+
+test_expect_success 'req_Root (w/o strict-paths)' \
+ 'cat request-anonymous | git-cvsserver pserver $WORKDIR/ >log 2>&1 &&
+ tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_failure 'req_Root failure (w/o strict-paths)' \
+ 'cat request-anonymous | git-cvsserver pserver $WORKDIR/gitcvs >log 2>&1'
+
+cat >request-base <<EOF
+BEGIN AUTH REQUEST
+/gitcvs.git
+anonymous
+
+END AUTH REQUEST
+Root /gitcvs.git
+EOF
+
+test_expect_success 'req_Root (base-path)' \
+ 'cat request-base | git-cvsserver --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&
+ tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_failure 'req_Root failure (base-path)' \
+ 'cat request-anonymous | git-cvsserver --strict-paths --base-path $WORKDIR pserver $SERVERDIR >log 2>&1'
+
+GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false || exit 1
+
+test_expect_success 'req_Root (export-all)' \
+ 'cat request-anonymous | git-cvsserver --export-all pserver $WORKDIR >log 2>&1 &&
+ tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_failure 'req_Root failure (export-all w/o whitelist)' \
+ 'cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 ||
+ false'
+
+test_expect_success 'req_Root (everything together)' \
+ 'cat request-base | git-cvsserver --export-all --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&
+ tail -n1 log | grep -q "^I LOVE YOU$"'
+
+GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true || exit 1
+
+#--------------
+# CONFIG TESTS
+#--------------
+
+test_expect_success 'gitcvs.enabled = false' \
+ 'GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false &&
+ if GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1
+ then
+ echo unexpected cvs success
+ false
+ else
+ true
+ fi &&
+ cat cvs.log | grep -q "GITCVS emulation disabled" &&
+ test ! -d cvswork2'
+
+rm -fr cvswork2
+test_expect_success 'gitcvs.ext.enabled = true' \
+ 'GIT_DIR="$SERVERDIR" git config --bool gitcvs.ext.enabled true &&
+ GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false &&
+ GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
+ diff -q cvswork cvswork2'
+
+rm -fr cvswork2
+test_expect_success 'gitcvs.ext.enabled = false' \
+ 'GIT_DIR="$SERVERDIR" git config --bool gitcvs.ext.enabled false &&
+ GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
+ if GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1
+ then
+ echo unexpected cvs success
+ false
+ else
+ true
+ fi &&
+ cat cvs.log | grep -q "GITCVS emulation disabled" &&
+ test ! -d cvswork2'
+
+rm -fr cvswork2
+test_expect_success 'gitcvs.dbname' \
+ 'GIT_DIR="$SERVERDIR" git config --bool gitcvs.ext.enabled true &&
+ GIT_DIR="$SERVERDIR" git config gitcvs.dbname %Ggitcvs.%a.%m.sqlite &&
+ GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
+ diff -q cvswork cvswork2 &&
+ test -f "$SERVERDIR/gitcvs.ext.master.sqlite" &&
+ cmp "$SERVERDIR/gitcvs.master.sqlite" "$SERVERDIR/gitcvs.ext.master.sqlite"'
+
+rm -fr cvswork2
+test_expect_success 'gitcvs.ext.dbname' \
+ 'GIT_DIR="$SERVERDIR" git config --bool gitcvs.ext.enabled true &&
+ GIT_DIR="$SERVERDIR" git config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
+ GIT_DIR="$SERVERDIR" git config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
+ GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
+ diff -q cvswork cvswork2 &&
+ test -f "$SERVERDIR/gitcvs1.ext.master.sqlite" &&
+ test ! -f "$SERVERDIR/gitcvs2.ext.master.sqlite" &&
+ cmp "$SERVERDIR/gitcvs.master.sqlite" "$SERVERDIR/gitcvs1.ext.master.sqlite"'
+
+
+#------------
+# CVS UPDATE
+#------------
+
+rm -fr "$SERVERDIR"
+cd "$WORKDIR" &&
+git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
+GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
+GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
+exit 1
+
test_expect_success 'cvs update (create new file)' \
'echo testfile1 >testfile1 &&
git add testfile1 &&
test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.4/" &&
diff -q testfile1 ../testfile1'
+cd "$WORKDIR"
+test_expect_success 'cvs update (merge)' \
+ 'echo Line 0 >expected &&
+ for i in 1 2 3 4 5 6 7
+ do
+ echo Line $i >>merge
+ echo Line $i >>expected
+ done &&
+ echo Line 8 >>expected &&
+ git add merge &&
+ git commit -q -m "Merge test (pre-merge)" &&
+ git push gitcvs.git >/dev/null &&
+ cd cvswork &&
+ GIT_CONFIG="$git_config" cvs -Q update &&
+ test "$(echo $(grep merge CVS/Entries|cut -d/ -f2,3,5))" = "merge/1.1/" &&
+ diff -q merge ../merge &&
+ ( echo Line 0; cat merge ) >merge.tmp &&
+ mv merge.tmp merge &&
+ cd "$WORKDIR" &&
+ echo Line 8 >>merge &&
+ git add merge &&
+ git commit -q -m "Merge test (merge)" &&
+ git push gitcvs.git >/dev/null &&
+ cd cvswork &&
+ sleep 1 && touch merge &&
+ GIT_CONFIG="$git_config" cvs -Q update &&
+ diff -q merge ../expected'
+
+cd "$WORKDIR"
+
+cat >expected.C <<EOF
+<<<<<<< merge.mine
+Line 0
+=======
+LINE 0
+>>>>>>> merge.3
+EOF
+
+for i in 1 2 3 4 5 6 7 8
+do
+ echo Line $i >>expected.C
+done
+
+test_expect_success 'cvs update (conflict merge)' \
+ '( echo LINE 0; cat merge ) >merge.tmp &&
+ mv merge.tmp merge &&
+ git add merge &&
+ git commit -q -m "Merge test (conflict)" &&
+ git push gitcvs.git >/dev/null &&
+ cd cvswork &&
+ GIT_CONFIG="$git_config" cvs -Q update &&
+ diff -q merge ../expected.C'
+
+cd "$WORKDIR"
+test_expect_success 'cvs update (-C)' \
+ 'cd cvswork &&
+ GIT_CONFIG="$git_config" cvs -Q update -C &&
+ diff -q merge ../merge'
+
+cd "$WORKDIR"
+test_expect_success 'cvs update (merge no-op)' \
+ 'echo Line 9 >>merge &&
+ cp merge cvswork/merge &&
+ git add merge &&
+ git commit -q -m "Merge test (no-op)" &&
+ git push gitcvs.git >/dev/null &&
+ cd cvswork &&
+ sleep 1 && touch merge &&
+ GIT_CONFIG="$git_config" cvs -Q update &&
+ diff -q merge ../merge'
+
test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Jakub Narebski
+#
+
+test_description='gitweb as standalone script (basic tests).
+
+This test runs gitweb (git web interface) as CGI script from
+commandline, and checks that it would not write any errors
+or warnings to log.'
+
+gitweb_init () {
+ cat >gitweb_config.perl <<EOF
+#!/usr/bin/perl
+
+# gitweb configuration for tests
+
+our \$version = "current";
+our \$GIT = "git";
+our \$projectroot = "$(pwd)";
+our \$home_link_str = "projects";
+our \$site_name = "[localhost]";
+our \$site_header = "";
+our \$site_footer = "";
+our \$home_text = "indextext.html";
+our @stylesheets = ("file:///$(pwd)/../../gitweb/gitweb.css");
+our \$logo = "file:///$(pwd)/../../gitweb/git-logo.png";
+our \$favicon = "file:///$(pwd)/../../gitweb/git-favicon.png";
+our \$projects_list = "";
+our \$export_ok = "";
+our \$strict_export = "";
+
+CGI::Carp::set_programname("gitweb/gitweb.cgi");
+EOF
+
+ cat >.git/description <<EOF
+$0 test repository
+EOF
+}
+
+gitweb_run () {
+ export GATEWAY_INTERFACE="CGI/1.1"
+ export HTTP_ACCEPT="*/*"
+ export REQUEST_METHOD="GET"
+ export QUERY_STRING=""$1""
+ export PATH_INFO=""$2""
+
+ export GITWEB_CONFIG=$(pwd)/gitweb_config.perl
+
+ # some of git commands write to STDERR on error, but this is not
+ # written to web server logs, so we are not interested in that:
+ # we are interested only in properly formatted errors/warnings
+ rm -f gitweb.log &&
+ perl -- $(pwd)/../../gitweb/gitweb.perl \
+ >/dev/null 2>gitweb.log &&
+ if grep -q -s "^[[]" gitweb.log >/dev/null; then false; else true; fi
+
+ # gitweb.log is left for debugging
+}
+
+. ./test-lib.sh
+
+perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
+ test_expect_success 'skipping gitweb tests, perl version is too old' :
+ test_done
+ exit
+}
+
+gitweb_init
+
+# ----------------------------------------------------------------------
+# no commits (empty, just initialized repository)
+
+test_expect_success \
+ 'no commits: projects_list (implicit)' \
+ 'gitweb_run'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'no commits: projects_index' \
+ 'gitweb_run "a=project_index"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'no commits: .git summary (implicit)' \
+ 'gitweb_run "p=.git"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'no commits: .git commit (implicit HEAD)' \
+ 'gitweb_run "p=.git;a=commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'no commits: .git commitdiff (implicit HEAD)' \
+ 'gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'no commits: .git tree (implicit HEAD)' \
+ 'gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'no commits: .git heads' \
+ 'gitweb_run "p=.git;a=heads"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'no commits: .git tags' \
+ 'gitweb_run "p=.git;a=tags"'
+test_debug 'cat gitweb.log'
+
+
+# ----------------------------------------------------------------------
+# initial commit
+
+test_expect_success \
+ 'Make initial commit' \
+ 'echo "Not an empty file." > file &&
+ git add file &&
+ git commit -a -m "Initial commit." &&
+ git branch b'
+
+test_expect_success \
+ 'projects_list (implicit)' \
+ 'gitweb_run'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'projects_index' \
+ 'gitweb_run "a=project_index"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git summary (implicit)' \
+ 'gitweb_run "p=.git"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git commit (implicit HEAD)' \
+ 'gitweb_run "p=.git;a=commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git commitdiff (implicit HEAD, root commit)' \
+ 'gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git commitdiff_plain (implicit HEAD, root commit)' \
+ 'gitweb_run "p=.git;a=commitdiff_plain"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git commit (HEAD)' \
+ 'gitweb_run "p=.git;a=commit;h=HEAD"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git tree (implicit HEAD)' \
+ 'gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git blob (file)' \
+ 'gitweb_run "p=.git;a=blob;f=file"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git blob_plain (file)' \
+ 'gitweb_run "p=.git;a=blob_plain;f=file"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# nonexistent objects
+
+test_expect_success \
+ '.git commit (non-existent)' \
+ 'gitweb_run "p=.git;a=commit;h=non-existent"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git commitdiff (non-existent)' \
+ 'gitweb_run "p=.git;a=commitdiff;h=non-existent"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git commitdiff (non-existent vs HEAD)' \
+ 'gitweb_run "p=.git;a=commitdiff;hp=non-existent;h=HEAD"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git tree (0000000000000000000000000000000000000000)' \
+ 'gitweb_run "p=.git;a=tree;h=0000000000000000000000000000000000000000"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git tag (0000000000000000000000000000000000000000)' \
+ 'gitweb_run "p=.git;a=tag;h=0000000000000000000000000000000000000000"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git blob (non-existent)' \
+ 'gitweb_run "p=.git;a=blob;f=non-existent"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ '.git blob_plain (non-existent)' \
+ 'gitweb_run "p=.git;a=blob_plain;f=non-existent"'
+test_debug 'cat gitweb.log'
+
+
+# ----------------------------------------------------------------------
+# commitdiff testing (implicit, one implicit tree-ish)
+
+test_expect_success \
+ 'commitdiff(0): root' \
+ 'gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(0): file added' \
+ 'echo "New file" > new_file &&
+ git add new_file &&
+ git commit -a -m "File added." &&
+ gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(0): mode change' \
+ 'chmod a+x new_file &&
+ git commit -a -m "Mode changed." &&
+ gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(0): file renamed' \
+ 'git mv new_file renamed_file &&
+ git commit -a -m "File renamed." &&
+ gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(0): file to symlink' \
+ 'rm renamed_file &&
+ ln -s file renamed_file &&
+ git commit -a -m "File to symlink." &&
+ gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(0): file deleted' \
+ 'git rm renamed_file &&
+ rm -f renamed_file &&
+ git commit -a -m "File removed." &&
+ gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(0): file copied / new file' \
+ 'cp file file2 &&
+ git add file2 &&
+ git commit -a -m "File copied." &&
+ gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(0): mode change and modified' \
+ 'echo "New line" >> file2 &&
+ chmod a+x file2 &&
+ git commit -a -m "Mode change and modification." &&
+ gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(0): renamed and modified' \
+ 'cat >file2<<EOF &&
+Dominus regit me,
+et nihil mihi deerit.
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+EOF
+ git commit -a -m "File added." &&
+ git mv file2 file3 &&
+ echo "Propter nomen suum." >> file3 &&
+ git commit -a -m "File rename and modification." &&
+ gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(0): renamed, mode change and modified' \
+ 'git mv file3 file2 &&
+ echo "Propter nomen suum." >> file2 &&
+ chmod a+x file2 &&
+ git commit -a -m "File rename, mode change and modification." &&
+ gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# commitdiff testing (taken from t4114-apply-typechange.sh)
+
+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 &&
+ git commit -m "foo symlinked to bar" &&
+ git branch foo-symlinked-to-bar &&
+ rm -f foo &&
+ echo "how far is the sun?" > foo &&
+ git update-index foo &&
+ git commit -m "foo back to file" &&
+ git branch foo-back-to-file &&
+ rm -f foo &&
+ git update-index --remove foo &&
+ mkdir foo &&
+ echo "if only I knew" > foo/baz &&
+ git update-index --add foo/baz &&
+ git commit -m "foo becomes a directory" &&
+ git branch "foo-becomes-a-directory" &&
+ echo "hello world" > foo/baz &&
+ git update-index foo/baz &&
+ git commit -m "foo/baz is the original foo" &&
+ git branch foo-baz-renamed-from-foo
+ '
+
+test_expect_success \
+ 'commitdiff(2): file renamed from foo to foo/baz' \
+ 'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-baz-renamed-from-foo"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(2): file renamed from foo/baz to foo' \
+ 'gitweb_run "p=.git;a=commitdiff;hp=foo-baz-renamed-from-foo;h=initial"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(2): directory becomes file' \
+ 'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=initial"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(2): file becomes directory' \
+ 'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-becomes-a-directory"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(2): file becomes symlink' \
+ 'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-symlinked-to-bar"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(2): symlink becomes file' \
+ 'gitweb_run "p=.git;a=commitdiff;hp=foo-symlinked-to-bar;h=foo-back-to-file"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(2): symlink becomes directory' \
+ 'gitweb_run "p=.git;a=commitdiff;hp=foo-symlinked-to-bar;h=foo-becomes-a-directory"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(2): directory becomes symlink' \
+ 'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=foo-symlinked-to-bar"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# commit, commitdiff: merge, large
+test_expect_success \
+ 'Create a merge' \
+ 'git checkout b &&
+ echo "Branch" >> b &&
+ git add b &&
+ git commit -a -m "On branch" &&
+ git checkout master &&
+ git pull . b'
+
+test_expect_success \
+ 'commit(0): merge commit' \
+ 'gitweb_run "p=.git;a=commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(0): merge commit' \
+ 'gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'Prepare large commit' \
+ 'git checkout b &&
+ echo "To be changed" > 01-change &&
+ echo "To be renamed" > 02-pure-rename-from &&
+ echo "To be deleted" > 03-delete &&
+ echo "To be renamed and changed" > 04-rename-from &&
+ echo "To have mode changed" > 05-mode-change &&
+ echo "File to symlink" > 06-file-or-symlink &&
+ echo "To be changed and have mode changed" > 07-change-mode-change &&
+ git add 0* &&
+ git commit -a -m "Prepare large commit" &&
+ echo "Changed" > 01-change &&
+ git mv 02-pure-rename-from 02-pure-rename-to &&
+ git rm 03-delete && rm -f 03-delete &&
+ echo "A new file" > 03-new &&
+ git add 03-new &&
+ git mv 04-rename-from 04-rename-to &&
+ echo "Changed" >> 04-rename-to &&
+ chmod a+x 05-mode-change &&
+ rm -f 06-file-or-symlink && ln -s 01-change 06-file-or-symlink &&
+ echo "Changed and have mode changed" > 07-change-mode-change &&
+ chmod a+x 07-change-mode-change &&
+ git commit -a -m "Large commit" &&
+ git checkout master'
+
+test_expect_success \
+ 'commit(1): large commit' \
+ 'gitweb_run "p=.git;a=commit;h=b"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'commitdiff(1): large commit' \
+ 'gitweb_run "p=.git;a=commitdiff;h=b"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# tags testing
+
+test_expect_success \
+ 'tags: list of different types of tags' \
+ 'git checkout master &&
+ git tag -a -m "Tag commit object" tag-commit HEAD &&
+ git tag -a -m "" tag-commit-nomessage HEAD &&
+ git tag -a -m "Tag tag object" tag-tag tag-commit &&
+ git tag -a -m "Tag tree object" tag-tree HEAD^{tree} &&
+ git tag -a -m "Tag blob object" tag-blob HEAD:file &&
+ git tag lightweight/tag-commit HEAD &&
+ git tag lightweight/tag-tag tag-commit &&
+ git tag lightweight/tag-tree HEAD^{tree} &&
+ git tag lightweight/tag-blob HEAD:file &&
+ gitweb_run "p=.git;a=tags"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'tag: Tag to commit object' \
+ 'gitweb_run "p=.git;a=tag;h=tag-commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'tag: on lightweight tag (invalid)' \
+ 'gitweb_run "p=.git;a=tag;h=lightweight/tag-commit"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# logs
+
+test_expect_success \
+ 'logs: log (implicit HEAD)' \
+ 'gitweb_run "p=.git;a=log"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'logs: shortlog (implicit HEAD)' \
+ 'gitweb_run "p=.git;a=shortlog"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'logs: history (implicit HEAD, file)' \
+ 'gitweb_run "p=.git;a=history;f=file"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# feed generation
+
+test_expect_success \
+ 'feeds: OPML' \
+ 'gitweb_run "a=opml"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'feed: RSS' \
+ 'gitweb_run "p=.git;a=rss"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'feed: Atom' \
+ 'gitweb_run "p=.git;a=atom"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# encoding/decoding
+
+test_expect_success \
+ 'encode(commit): utf8' \
+ '. ../t3901-utf8.txt &&
+ echo "UTF-8" >> file &&
+ git add file &&
+ git commit -F ../t3900/1-UTF-8.txt &&
+ gitweb_run "p=.git;a=commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'encode(commit): iso-8859-1' \
+ '. ../t3901-8859-1.txt &&
+ echo "ISO-8859-1" >> file &&
+ git add file &&
+ git config i18n.commitencoding ISO-8859-1 &&
+ git commit -F ../t3900/ISO-8859-1.txt &&
+ git config --unset i18n.commitencoding &&
+ gitweb_run "p=.git;a=commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'encode(log): utf-8 and iso-8859-1' \
+ 'gitweb_run "p=.git;a=log"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# extra options
+
+test_expect_success \
+ 'opt: log --no-merges' \
+ 'gitweb_run "p=.git;a=log;opt=--no-merges"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'opt: atom --no-merges' \
+ 'gitweb_run "p=.git;a=log;opt=--no-merges"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'opt: "file" history --no-merges' \
+ 'gitweb_run "p=.git;a=history;f=file;opt=--no-merges"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'opt: log --no-such-option (invalid option)' \
+ 'gitweb_run "p=.git;a=log;opt=--no-such-option"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+ 'opt: tree --no-merges (invalid option for action)' \
+ 'gitweb_run "p=.git;a=tree;opt=--no-merges"'
+test_debug 'cat gitweb.log'
+
+test_done
export LANG LC_ALL PAGER TZ
EDITOR=:
VISUAL=:
+unset GIT_EDITOR
unset AUTHOR_DATE
unset AUTHOR_EMAIL
unset AUTHOR_NAME
GIT_COMMITTER_NAME='C O Mitter'
unset GIT_DIFF_OPTS
unset GIT_DIR
+unset GIT_WORK_TREE
unset GIT_EXTERNAL_DIFF
unset GIT_INDEX_FILE
unset GIT_OBJECT_DIRECTORY
mv .git/hooks .git/hooks-disabled
cd "$owd"
}
-
+
test_done () {
trap - exit
case "$test_failure" in
INSTALL ?= install
TAR ?= tar
+RM ?= rm -f
prefix ?= $(HOME)
-template_dir ?= $(prefix)/share/git-core/templates/
+template_dir ?= $(prefix)/share/git-core/templates
# DESTDIR=
# Shell quote (do not use $(call) to accommodate ancient setups);
$(QUIET): no custom templates yet
clean:
- rm -rf blt boilerplates.made
+ $(RM) -r blt boilerplates.made
install: all
$(INSTALL) -d -m755 '$(DESTDIR_SQ)$(template_dir_SQ)'
echo >&2 Duplicate Signed-off-by lines.
exit 1
}
-
#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
-
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
:
-
--- /dev/null
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+ while (argc > 1) {
+ puts(make_absolute_path(argv[1]));
+ argc--;
+ argv++;
+ }
+ return 0;
+}
*/
#include "cache.h"
#include "diff.h"
+#include "diffcore.h"
#include "tree.h"
static char *malloc_base(const char *base, int baselen, const char *path, int pathlen)
return 0;
}
+/*
+ * Does it look like the resulting diff might be due to a rename?
+ * - single entry
+ * - not a valid previous file
+ */
+static inline int diff_might_be_rename(void)
+{
+ return diff_queued_diff.nr == 1 &&
+ !DIFF_FILE_VALID(diff_queued_diff.queue[0]->one);
+}
+
+static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+ struct diff_options diff_opts;
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct diff_filepair *choice;
+ const char *paths[1];
+ int i;
+
+ /* Remove the file creation entry from the diff queue, and remember it */
+ choice = q->queue[0];
+ q->nr = 0;
+
+ diff_setup(&diff_opts);
+ diff_opts.recursive = 1;
+ diff_opts.detect_rename = DIFF_DETECT_RENAME;
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ diff_opts.single_follow = opt->paths[0];
+ paths[0] = NULL;
+ diff_tree_setup_paths(paths, &diff_opts);
+ if (diff_setup_done(&diff_opts) < 0)
+ die("unable to set up diff options to follow renames");
+ diff_tree(t1, t2, base, &diff_opts);
+ diffcore_std(&diff_opts);
+
+ /* Go through the new set of filepairing, and see if we find a more interesting one */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+
+ /*
+ * Found a source? Not only do we use that for the new
+ * diff_queued_diff, we will also use that as the path in
+ * the future!
+ */
+ if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) {
+ /* Switch the file-pairs around */
+ q->queue[i] = choice;
+ choice = p;
+
+ /* Update the path we use from now on.. */
+ opt->paths[0] = xstrdup(p->one->path);
+ diff_tree_setup_paths(opt->paths, opt);
+ break;
+ }
+ }
+
+ /*
+ * Then, discard all the non-relevane file pairs...
+ */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ diff_free_filepair(p);
+ }
+
+ /*
+ * .. and re-instate the one we want (which might be either the
+ * original one, or the rename/copy we found)
+ */
+ q->queue[0] = choice;
+ q->nr = 1;
+}
+
int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
{
void *tree1, *tree2;
init_tree_desc(&t1, tree1, size1);
init_tree_desc(&t2, tree2, size2);
retval = diff_tree(&t1, &t2, base, opt);
+ if (opt->follow_renames && diff_might_be_rename()) {
+ init_tree_desc(&t1, tree1, size1);
+ init_tree_desc(&t2, tree2, size2);
+ try_to_follow_renames(&t1, &t2, base, opt);
+ }
free(tree1);
free(tree2);
return retval;
free(tree);
return retval;
}
-
static inline int tree_entry_len(const char *name, const unsigned char *sha1)
{
- return (char *)sha1 - (char *)name - 1;
+ return (const char *)sha1 - name - 1;
}
void update_tree_entry(struct tree_desc *);
/* Count how many entries there are.. */
init_tree_desc(&desc, item->buffer, item->size);
while (tree_entry(&desc, &entry)) {
- if (S_ISDIRLNK(entry.mode))
+ if (S_ISGITLINK(entry.mode))
continue;
n_refs++;
}
while (tree_entry(&desc, &entry)) {
struct object *obj;
- if (S_ISDIRLNK(entry.mode))
+ if (S_ISGITLINK(entry.mode))
continue;
if (S_ISDIR(entry.mode))
obj = &lookup_tree(entry.sha1)->object;
#include "cache-tree.h"
#include "unpack-trees.h"
#include "progress.h"
+#include "refs.h"
#define DBRT_DEBUG 1
struct tree_entry_list {
struct tree_entry_list *next;
- unsigned directory : 1;
- unsigned executable : 1;
- unsigned symlink : 1;
unsigned int mode;
const char *name;
const unsigned char *sha1;
entry->name = one.path;
entry->sha1 = one.sha1;
entry->mode = one.mode;
- entry->directory = S_ISDIR(one.mode) != 0;
- entry->executable = (one.mode & S_IXUSR) != 0;
- entry->symlink = S_ISLNK(one.mode) != 0;
entry->next = NULL;
*list_p = entry;
#endif
if (!first || entcmp(first, firstdir,
posns[i]->name,
- posns[i]->directory) > 0) {
+ S_ISDIR(posns[i]->mode)) > 0) {
first = posns[i]->name;
- firstdir = posns[i]->directory;
+ firstdir = S_ISDIR(posns[i]->mode);
}
}
/* No name means we're done */
continue;
}
- if (posns[i]->directory) {
+ if (S_ISDIR(posns[i]->mode)) {
struct tree *tree = lookup_tree(posns[i]->sha1);
any_dirs = 1;
parse_tree(tree);
unsigned changed = ce_match_stat(ce, &st, 1);
if (!changed)
return;
+ /*
+ * NEEDSWORK: the current default policy is to allow
+ * submodule to be out of sync wrt the supermodule
+ * index. This needs to be tightened later for
+ * submodules that are marked to be automatically
+ * checked out.
+ */
+ if (S_ISGITLINK(ntohl(ce->ce_mode)))
+ return;
errno = 0;
}
if (errno == ENOENT)
cache_tree_invalidate_path(active_cache_tree, ce->name);
}
-static int verify_clean_subdirectory(const char *path, const char *action,
+/*
+ * Check that checking out ce->sha1 in subdir ce->name is not
+ * going to overwrite any working files.
+ *
+ * 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, const char *action,
+ struct unpack_trees_options *o)
+{
+ return 0;
+}
+
+static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
struct unpack_trees_options *o)
{
/*
- * we are about to extract "path"; we would not want to lose
+ * we are about to extract "ce->name"; we would not want to lose
* anything in the existing directory there.
*/
int namelen;
struct dir_struct d;
char *pathbuf;
int cnt = 0;
+ unsigned char sha1[20];
+
+ if (S_ISGITLINK(ntohl(ce->ce_mode)) &&
+ resolve_gitlink_ref(ce->name, "HEAD", sha1) == 0) {
+ /* If we are not going to update the submodule, then
+ * we don't care.
+ */
+ if (!hashcmp(sha1, ce->sha1))
+ return 0;
+ return verify_clean_submodule(ce, action, o);
+ }
/*
* First let's make sure we do not have a local modification
* in that directory.
*/
- namelen = strlen(path);
- pos = cache_name_pos(path, namelen);
+ namelen = strlen(ce->name);
+ pos = cache_name_pos(ce->name, namelen);
if (0 <= pos)
return cnt; /* we have it as nondirectory */
pos = -pos - 1;
struct cache_entry *ce = active_cache[i];
int len = ce_namelen(ce);
if (len < namelen ||
- strncmp(path, ce->name, namelen) ||
+ strncmp(ce->name, ce->name, namelen) ||
ce->name[namelen] != '/')
break;
/*
* present file that is not ignored.
*/
pathbuf = xmalloc(namelen + 2);
- memcpy(pathbuf, path, namelen);
+ memcpy(pathbuf, ce->name, namelen);
strcpy(pathbuf+namelen, "/");
memset(&d, 0, sizeof(d));
if (o->dir)
d.exclude_per_dir = o->dir->exclude_per_dir;
- i = read_directory(&d, path, pathbuf, namelen+1, NULL);
+ i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
if (i)
die("Updating '%s' would lose untracked files in it",
- path);
+ ce->name);
free(pathbuf);
return cnt;
}
* We do not want to remove or overwrite a working tree file that
* is not tracked, unless it is ignored.
*/
-static void verify_absent(const char *path, const char *action,
+static void verify_absent(struct cache_entry *ce, const char *action,
struct unpack_trees_options *o)
{
struct stat st;
if (o->index_only || o->reset || !o->update)
return;
- if (has_symlink_leading_path(path, NULL))
+ if (has_symlink_leading_path(ce->name, NULL))
return;
- if (!lstat(path, &st)) {
+ if (!lstat(ce->name, &st)) {
int cnt;
- if (o->dir && excluded(o->dir, path))
+ if (o->dir && excluded(o->dir, ce->name))
/*
- * path is explicitly excluded, so it is Ok to
+ * ce->name is explicitly excluded, so it is Ok to
* overwrite it.
*/
return;
* files that are in "foo/" we would lose
* it.
*/
- cnt = verify_clean_subdirectory(path, action, o);
+ cnt = verify_clean_subdirectory(ce, action, o);
/*
* If this removed entries from the index,
* delete this path, which is in a subdirectory that
* is being replaced with a blob.
*/
- cnt = cache_name_pos(path, strlen(path));
+ cnt = cache_name_pos(ce->name, strlen(ce->name));
if (0 <= cnt) {
struct cache_entry *ce = active_cache[cnt];
if (!ce_stage(ce) && !ce->ce_mode)
}
die("Untracked working tree file '%s' "
- "would be %s by merge.", path, action);
+ "would be %s by merge.", ce->name, action);
}
}
}
}
else {
- verify_absent(merge->name, "overwritten", o);
+ verify_absent(merge, "overwritten", o);
invalidate_ce_path(merge);
}
if (old)
verify_uptodate(old, o);
else
- verify_absent(ce->name, "removed", o);
+ verify_absent(ce, "removed", o);
ce->ce_mode = 0;
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
invalidate_ce_path(ce);
if (o->aggressive) {
int head_deleted = !head && !df_conflict_head;
int remote_deleted = !remote && !df_conflict_remote;
- const char *path = NULL;
+ struct cache_entry *ce = NULL;
if (index)
- path = index->name;
+ ce = index;
else if (head)
- path = head->name;
+ ce = head;
else if (remote)
- path = remote->name;
+ ce = remote;
else {
for (i = 1; i < o->head_idx; i++) {
if (stages[i] && stages[i] != o->df_conflict_entry) {
- path = stages[i]->name;
+ ce = stages[i];
break;
}
}
(remote_deleted && head && head_match)) {
if (index)
return deleted_entry(index, index, o);
- else if (path && !head_deleted)
- verify_absent(path, "removed", o);
+ else if (ce && !head_deleted)
+ verify_absent(ce, "removed", o);
return 0;
}
/*
return safe_write(fd, data, sz);
}
-FILE *pack_pipe = NULL;
+static FILE *pack_pipe = NULL;
static void show_commit(struct commit *commit)
{
if (commit->object.flags & BOUNDARY)
break;
}
}
-
+
if (i != argc-1)
usage(upload_pack_usage);
dir = argv[i];
val = read_var(argv[1]);
if (!val)
usage(var_usage);
-
+
printf("%s\n", val);
-
+
return 0;
}
#include "cache.h"
+/*
+ * Some cases use stdio, but want to flush after the write
+ * to get error handling (and to get better interactive
+ * behaviour - not buffering excessively).
+ *
+ * Of course, if the flush happened within the write itself,
+ * we've already lost the error code, and cannot report it any
+ * more. So we just ignore that case instead (and hope we get
+ * the right error code on the flush).
+ *
+ * If the file handle is stdout, and stdout is a file, then skip the
+ * flush entirely since it's not needed.
+ */
+void maybe_flush_or_die(FILE *f, const char *desc)
+{
+ static int skip_stdout_flush = -1;
+ struct stat st;
+ char *cp;
+
+ if (f == stdout) {
+ if (skip_stdout_flush < 0) {
+ cp = getenv("GIT_FLUSH");
+ if (cp)
+ skip_stdout_flush = (atoi(cp) == 0);
+ else if ((fstat(fileno(stdout), &st) == 0) &&
+ S_ISREG(st.st_mode))
+ skip_stdout_flush = 1;
+ else
+ skip_stdout_flush = 0;
+ }
+ if (skip_stdout_flush && !ferror(f))
+ return;
+ }
+ if (fflush(f)) {
+ if (errno == EPIPE)
+ exit(0);
+ die("write failure on %s: %s", desc, strerror(errno));
+ }
+}
+
int read_in_full(int fd, void *buf, size_t count)
{
char *p = buf;
read_cache();
}
-void wt_status_print_initial(struct wt_status *s)
+static void wt_status_print_initial(struct wt_status *s)
{
int i;
char buf[PATH_MAX];
return !!memchr(ptr, 0, size);
}
+struct ff_regs {
+ int nr;
+ struct ff_reg {
+ regex_t re;
+ int negate;
+ } *array;
+};
+static long ff_regexp(const char *line, long len,
+ char *buffer, long buffer_size, void *priv)
+{
+ char *line_buffer = xstrndup(line, len); /* make NUL terminated */
+ struct ff_regs *regs = priv;
+ regmatch_t pmatch[2];
+ int result = 0, i;
+
+ for (i = 0; i < regs->nr; i++) {
+ struct ff_reg *reg = regs->array + i;
+ if (reg->negate ^ !!regexec(®->re,
+ line_buffer, 2, pmatch, 0)) {
+ free(line_buffer);
+ return -1;
+ }
+ }
+ i = pmatch[1].rm_so >= 0 ? 1 : 0;
+ line += pmatch[i].rm_so;
+ result = pmatch[i].rm_eo - pmatch[i].rm_so;
+ if (result > buffer_size)
+ result = buffer_size;
+ else
+ while (result > 0 && (isspace(line[result - 1]) ||
+ line[result - 1] == '\n'))
+ result--;
+ memcpy(buffer, line, result);
+ free(line_buffer);
+ return result;
+}
+
+void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value)
+{
+ int i;
+ struct ff_regs *regs;
+
+ xecfg->find_func = ff_regexp;
+ regs = xecfg->find_func_priv = xmalloc(sizeof(struct ff_regs));
+ for (i = 0, regs->nr = 1; value[i]; i++)
+ if (value[i] == '\n')
+ regs->nr++;
+ regs->array = xmalloc(regs->nr * sizeof(struct ff_reg));
+ for (i = 0; i < regs->nr; i++) {
+ struct ff_reg *reg = regs->array + i;
+ const char *ep = strchr(value, '\n'), *expression;
+ char *buffer = NULL;
+
+ reg->negate = (*value == '!');
+ if (reg->negate && i == regs->nr - 1)
+ die("Last expression must not be negated: %s", value);
+ if (*value == '!')
+ value++;
+ if (ep)
+ expression = buffer = xstrndup(value, ep - value);
+ else
+ expression = value;
+ if (regcomp(®->re, expression, 0))
+ die("Invalid regexp to look for hunk header: %s", expression);
+ if (buffer)
+ free(buffer);
+ value = ep + 1;
+ }
+}
int read_mmfile(mmfile_t *ptr, const char *filename);
int buffer_is_binary(const char *ptr, unsigned long size);
+extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line);
+
#endif
int (*outf)(void *, mmbuffer_t *, int);
} xdemitcb_t;
+typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv);
+
typedef struct s_xdemitconf {
long ctxlen;
unsigned long flags;
+ find_func_t find_func;
+ void *find_func_priv;
} xdemitconf_t;
typedef struct s_bdiffparam {
#endif /* #ifdef __cplusplus */
#endif /* #if !defined(XDIFF_H) */
-
xdemitconf_t const *xecfg);
#endif /* #if !defined(XDIFFI_H) */
-
}
-static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
+static long def_ff(const char *rec, long len, char *buf, long sz, void *priv)
+{
+ if (len > 0 &&
+ (isalpha((unsigned char)*rec) || /* identifier? */
+ *rec == '_' || /* also identifier? */
+ *rec == '$')) { /* identifiers from VMS and other esoterico */
+ if (len > sz)
+ len = sz;
+ while (0 < len && isspace((unsigned char)rec[len - 1]))
+ len--;
+ memcpy(buf, rec, len);
+ return len;
+ }
+ return -1;
+}
+
+static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll,
+ find_func_t ff, void *ff_priv) {
/*
* Be quite stupid about this for now. Find a line in the old file
const char *rec;
long len;
- *ll = 0;
while (i-- > 0) {
len = xdl_get_rec(xf, i, &rec);
- if (len > 0 &&
- (isalpha((unsigned char)*rec) || /* identifier? */
- *rec == '_' || /* also identifier? */
- *rec == '$')) { /* mysterious GNU diff's invention */
- if (len > sz)
- len = sz;
- while (0 < len && isspace((unsigned char)rec[len - 1]))
- len--;
- memcpy(buf, rec, len);
- *ll = len;
+ if ((*ll = ff(rec, len, buf, sz, ff_priv)) >= 0)
return;
- }
}
+ *ll = 0;
}
-int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
- xdemitconf_t const *xecfg) {
+static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg) {
xdfile_t *xdf = &xe->xdf1;
const char *rchg = xdf->rchg;
long ix;
xdchange_t *xch, *xche;
char funcbuf[80];
long funclen = 0;
+ find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
if (xecfg->flags & XDL_EMIT_COMMON)
return xdl_emit_common(xe, xscr, ecb, xecfg);
if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
xdl_find_func(&xe->xdf1, s1, funcbuf,
- sizeof(funcbuf), &funclen);
+ sizeof(funcbuf), &funclen,
+ ff, xecfg->find_func_priv);
}
if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
funcbuf, funclen, ecb) < 0)
return 0;
}
-
#endif /* #if !defined(XEMIT_H) */
-
#endif /* #if !defined(XINCLUDE_H) */
-
#endif /* #if !defined(XMACROS_H) */
-
#endif /* #if !defined(XPREPARE_H) */
-
#endif /* #if !defined(XTYPES_H) */
-
#endif /* #if !defined(XUTILS_H) */
-