*.exe
*.[aos]
*.py[co]
+*.o.d
*+
/config.mak
/autom4te.cache
--- /dev/null
+Git v1.7.1 Release Notes (draft)
+================================
+
+Updates since v1.7.0
+--------------------
+
+ * "git cvsimport" learned -R option to leave revision mapping between
+ CVS revisions and resulting git commits.
+
+ * "git for-each-ref" learned %(symref), %(symref:short) and %(flag)
+ tokens.
+
+ * "git grep" learned "--no-index" option, to search inside contents that
+ are not managed by git.
+
+ * "git grep" learned --color=auto/always/never.
+
+ * "git hash-object --stdin-paths" can take "--no-filters" option now.
+
+ * "git request-pull" identifies the commit the request is relative to in
+ a more readable way.
+
+ * "git svn" should work better when interacting with repositories
+ with CRLF line endings.
+
+ * "git imap-send" learned to support CRAM-MD5 authentication.
+
+Fixes since v1.7.0
+------------------
+
+All of the fixes in v1.7.0.X maintenance series are included in this
+release, unless otherwise noted.
+
+---
+exec >/var/tmp/1
+echo O=$(git describe)
+O=v1.7.0.2-181-gc6830a3
+git shortlog --no-merges ^maint $O..
executed from the top-level directory of a repository, which may
not necessarily be the current directory.
+am.keepcr::
+ If true, git-am will call git-mailsplit for patches in mbox format
+ with parameter '--keep-cr'. In this case git-mailsplit will
+ not remove `\r` from lines ending with `\r\n`. Can be overrriden
+ by giving '--no-keep-cr' from the command line.
+ See linkgit:git-am[1], linkgit:git-mailsplit[1].
+
apply.ignorewhitespace::
When set to 'change', tells 'git apply' to ignore changes in
whitespace, in the same way as the '--ignore-space-change'
The configuration variables in the 'imap' section are described
in linkgit:git-imap-send[1].
+init.templatedir::
+ Specify the directory from which templates will be copied.
+ (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
+
instaweb.browser::
Specify the program that will be used to browse your working
repository in gitweb. See linkgit:git-instaweb[1].
option and lists the commits in that commit range like the 'summary'
option of linkgit:git-submodule[1] does.
---color::
+--color[=<when>]::
Show colored diff.
+ The value must be always (the default), never, or auto.
--no-color::
Turn off colored diff, even when the configuration file
gives the default to color output.
+ Same as `--color=never`.
--color-words[=<regex>]::
Show colored word diff, i.e., color words which have changed.
SYNOPSIS
--------
[verse]
-'git am' [--signoff] [--keep] [--utf8 | --no-utf8]
+'git am' [--signoff] [--keep] [--keep-cr | --no-keep-cr] [--utf8 | --no-utf8]
[--3way] [--interactive] [--committer-date-is-author-date]
[--ignore-date] [--ignore-space-change | --ignore-whitespace]
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
--keep::
Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+--keep-cr::
+--no-keep-cr::
+ With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1])
+ with the same option, to prevent it from stripping CR at the end of
+ lines. `am.keepcr` configuration variable can be used to specify the
+ default behaviour. `--no-keep-cr` is useful to override `am.keepcr`.
+
-c::
--scissors::
Remove everything in body before a scissors line (see
SYNOPSIS
--------
[verse]
-'git branch' [--color | --no-color] [-r | -a]
+'git branch' [--color[=<when>] | --no-color] [-r | -a]
[-v [--abbrev=<length> | --no-abbrev]]
[(--merged | --no-merged | --contains) [<commit>]]
'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
-M::
Move/rename a branch even if the new branch name already exists.
---color::
+--color[=<when>]::
Color branches to highlight current, local, and remote branches.
+ The value must be always (the default), never, or auto.
--no-color::
Turn off branch colors, even when the configuration file gives the
default to color output.
+ Same as `--color=never`.
-r::
List or delete (if used with -d) the remote-tracking branches.
--template=<template_directory>::
Specify the directory from which templates will be used;
- if unset the templates are taken from the installation
- defined default, typically `/usr/share/git-core/templates`.
+ (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
--depth <depth>::
Create a 'shallow' clone with a history truncated to the
[-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>]
- [-r <remote>] [<CVS_module>]
+ [-r <remote>] [-R] [<CVS_module>]
DESCRIPTION
export changes back to CVS again later with
'git cvsexportcommit'.
+-R::
+ Generate a `$GIT_DIR/cvs-revisions` file containing a mapping from CVS
+ revision numbers to newly-created Git commit IDs. The generated file
+ will contain one line for each (filename, revision) pair imported;
+ each line will look like
++
+---------
+src/widget.c 1.1 1d862f173cdc7325b6fa6d2ae1cfd61fd1b512b7
+---------
++
+The revision data is appended to the file if it already exists, for use when
+doing incremental imports.
++
+This option may be useful if you have CVS revision numbers stored in commit
+messages, bug-tracking systems, email archives, and the like.
+
-h::
Print a short usage message and exit.
[-z | --null]
[-c | --count] [--all-match] [-q | --quiet]
[--max-depth <depth>]
- [--color | --no-color]
+ [--color[=<when>] | --no-color]
[-A <post-context>] [-B <pre-context>] [-C <context>]
[-f <file>] [-e] <pattern>
[--and|--or|--not|(|)|-e <pattern>...]
Instead of showing every matched line, show the number of
lines that match.
---color::
+--color[=<when>]::
Show colored matches.
+ The value must be always (the default), never, or auto.
--no-color::
Turn off match highlighting, even when the configuration file
gives the default to color output.
+ Same as `--color=never`.
-[ABC] <context>::
Show `context` trailing (`A` -- after), or leading (`B`
--------
[verse]
'git hash-object' [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...
-'git hash-object' [-t <type>] [-w] --stdin-paths < <list-of-paths>
+'git hash-object' [-t <type>] [-w] --stdin-paths [--no-filters] < <list-of-paths>
DESCRIPTION
-----------
option causes Thunderbird to send the patch as a plain/text,
format=fixed email. Default is `false`.
+imap.authMethod::
+ Specify authenticate method for authentication with IMAP server.
+ Current supported method is 'CRAM-MD5' only.
+
Examples
~~~~~~~~
--template=<template_directory>::
-Provide the directory from which templates will be used. The default template
-directory is `/usr/share/git-core/templates`.
-
-When specified, `<template_directory>` is used as the source of the template
-files rather than the default. The template files include some directory
-structure, some suggested "exclude patterns", and copies of non-executing
-"hook" files. The suggested patterns and hook files are all modifiable and
-extensible.
+Specify the directory from which templates will be used. (See the "TEMPLATE
+DIRECTORY" section below.)
--shared[={false|true|umask|group|all|world|everybody|0xxx}]::
setting the configuration variables. The old name is retained
for backward compatibility reasons.
+TEMPLATE DIRECTORY
+------------------
+
+The template directory contains files and directories that will be copied to
+the `$GIT_DIR` after it is created.
+
+The template directory used will (in order):
+
+ - The argument given with the `--template` option.
+
+ - The contents of the `$GIT_TEMPLATE_DIR` environment variable.
+
+ - The `init.templatedir` configuration variable.
+
+ - The default template directory: `/usr/share/git-core/templates`.
+
+The default template directory includes some directory structure, some
+suggested "exclude patterns", and copies of sample "hook" files.
+The suggested patterns and hook files are all modifiable and extensible.
EXAMPLES
--------
SYNOPSIS
--------
-'git mailsplit' [-b] [-f<nn>] [-d<prec>] -o<directory> [--] [<mbox>|<Maildir>...]
+'git mailsplit' [-b] [-f<nn>] [-d<prec>] [--keep-cr] -o<directory> [--] [<mbox>|<Maildir>...]
DESCRIPTION
-----------
Skip the first <nn> numbers, for example if -f3 is specified,
start the numbering with 0004.
+--keep-cr::
+ Do not remove `\r` from lines ending with `\r\n`.
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
--------
[verse]
'git show-branch' [-a|--all] [-r|--remotes] [--topo-order | --date-order]
- [--current] [--color | --no-color] [--sparse]
+ [--current] [--color[=<when>] | --no-color] [--sparse]
[--more=<n> | --list | --independent | --merge-base]
[--no-name | --sha1-name] [--topics]
[<rev> | <glob>]...
When no explicit <ref> parameter is given, it defaults to the
current branch (or `HEAD` if it is detached).
---color::
+--color[=<when>]::
Color the status sign (one of these: `*` `!` `+` `-`) of each commit
corresponding to the branch it's in.
+ The value must be always (the default), never, or auto.
--no-color::
Turn off colored output, even when the configuration file gives the
default to color output.
+ Same as `--color=never`.
Note that --more, --list, --independent and --merge-base options
are mutually exclusive.
`OPT__ABBREV(&int_var)`::
Add `\--abbrev[=<n>]`.
+`OPT__COLOR(&int_var, description)`::
+ Add `\--color[=<when>]` and `--no-color`.
+
`OPT__DRY_RUN(&int_var)`::
Add `-n, \--dry-run`.
arguments. Short options that happen to be digits take
precedence over it.
+`OPT_COLOR_FLAG(short, long, &int_var, description)`::
+ Introduce an option that takes an optional argument that can
+ have one of three values: "always", "never", or "auto". If the
+ argument is not given, it defaults to "always". The `--no-` form
+ works like `--long=never`; it cannot take an argument. If
+ "always", set `int_var` to 1; if "never", set `int_var` to 0; if
+ "auto", set `int_var` to 1 if stdout is a tty or a pager,
+ 0 otherwise.
+
The last element of the array must be `OPT_END()`.
# DEFAULT_EDITOR='~/bin/vi',
# DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
# DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
+#
+# Define COMPUTE_HEADER_DEPENDENCIES if your compiler supports the -MMD option
+# and you want to avoid rebuilding objects when an unrelated header file
+# changes.
+#
+# Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded
+# dependency rules.
GIT-VERSION-FILE: FORCE
@$(SHELL_PATH) ./GIT-VERSION-GEN
# Those must not be GNU-specific; they are shared with perl/ which may
# be built by a different compiler. (Note that this is an artifact now
# but it still might be nice to keep that distinction.)
-BASIC_CFLAGS =
+BASIC_CFLAGS = -I.
BASIC_LDFLAGS =
# Guard against environment variables
COMPAT_OBJS =
LIB_H =
LIB_OBJS =
+PROGRAM_OBJS =
PROGRAMS =
SCRIPT_PERL =
SCRIPT_PYTHON =
SCRIPT_SH =
-TEST_PROGRAMS =
+SCRIPT_LIB =
+TEST_PROGRAMS_NEED_X =
SCRIPT_SH += git-am.sh
SCRIPT_SH += git-bisect.sh
SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
-SCRIPT_SH += git-mergetool--lib.sh
SCRIPT_SH += git-notes.sh
-SCRIPT_SH += git-parse-remote.sh
SCRIPT_SH += git-pull.sh
SCRIPT_SH += git-quiltimport.sh
SCRIPT_SH += git-rebase--interactive.sh
SCRIPT_SH += git-rebase.sh
SCRIPT_SH += git-repack.sh
SCRIPT_SH += git-request-pull.sh
-SCRIPT_SH += git-sh-setup.sh
SCRIPT_SH += git-stash.sh
SCRIPT_SH += git-submodule.sh
SCRIPT_SH += git-web--browse.sh
+SCRIPT_LIB += git-mergetool--lib
+SCRIPT_LIB += git-parse-remote
+SCRIPT_LIB += git-sh-setup
+
SCRIPT_PERL += git-add--interactive.perl
SCRIPT_PERL += git-difftool.perl
SCRIPT_PERL += git-archimport.perl
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS += $(EXTRA_PROGRAMS)
-PROGRAMS += git-fast-import$X
-PROGRAMS += git-imap-send$X
-PROGRAMS += git-shell$X
-PROGRAMS += git-show-index$X
-PROGRAMS += git-upload-pack$X
-PROGRAMS += git-http-backend$X
+
+PROGRAM_OBJS += fast-import.o
+PROGRAM_OBJS += imap-send.o
+PROGRAM_OBJS += shell.o
+PROGRAM_OBJS += show-index.o
+PROGRAM_OBJS += upload-pack.o
+PROGRAM_OBJS += http-backend.o
+
+PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
+
+TEST_PROGRAMS_NEED_X += test-chmtime
+TEST_PROGRAMS_NEED_X += test-ctype
+TEST_PROGRAMS_NEED_X += test-date
+TEST_PROGRAMS_NEED_X += test-delta
+TEST_PROGRAMS_NEED_X += test-dump-cache-tree
+TEST_PROGRAMS_NEED_X += test-genrandom
+TEST_PROGRAMS_NEED_X += test-match-trees
+TEST_PROGRAMS_NEED_X += test-parse-options
+TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-run-command
+TEST_PROGRAMS_NEED_X += test-sha1
+TEST_PROGRAMS_NEED_X += test-sigchain
+TEST_PROGRAMS_NEED_X += test-index-version
+
+TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
# List built-in command $C whose implementation cmd_$C() is not in
-# builtin-$C.o but is linked in as part of some other command.
-BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
+# builtin/$C.o but is linked in as part of some other command.
+BUILT_INS += $(patsubst builtin/%.o,git-%$X,$(BUILTIN_OBJS))
BUILT_INS += git-cherry$X
BUILT_INS += git-cherry-pick$X
LIB_H += builtin.h
LIB_H += cache.h
LIB_H += cache-tree.h
+LIB_H += color.h
LIB_H += commit.h
LIB_H += compat/bswap.h
LIB_H += compat/cygwin.h
LIB_H += diffcore.h
LIB_H += diff.h
LIB_H += dir.h
+LIB_H += exec_cmd.h
LIB_H += fsck.h
LIB_H += git-compat-util.h
LIB_H += graph.h
LIB_H += unpack-trees.h
LIB_H += userdiff.h
LIB_H += utf8.h
-LIB_H += wt-status.h
+LIB_H += xdiff-interface.h
+LIB_H += xdiff/xdiff.h
LIB_OBJS += abspath.o
LIB_OBJS += advice.o
LIB_OBJS += wt-status.o
LIB_OBJS += xdiff-interface.o
-BUILTIN_OBJS += builtin-add.o
-BUILTIN_OBJS += builtin-annotate.o
-BUILTIN_OBJS += builtin-apply.o
-BUILTIN_OBJS += builtin-archive.o
-BUILTIN_OBJS += builtin-bisect--helper.o
-BUILTIN_OBJS += builtin-blame.o
-BUILTIN_OBJS += builtin-branch.o
-BUILTIN_OBJS += builtin-bundle.o
-BUILTIN_OBJS += builtin-cat-file.o
-BUILTIN_OBJS += builtin-check-attr.o
-BUILTIN_OBJS += builtin-check-ref-format.o
-BUILTIN_OBJS += builtin-checkout-index.o
-BUILTIN_OBJS += builtin-checkout.o
-BUILTIN_OBJS += builtin-clean.o
-BUILTIN_OBJS += builtin-clone.o
-BUILTIN_OBJS += builtin-commit-tree.o
-BUILTIN_OBJS += builtin-commit.o
-BUILTIN_OBJS += builtin-config.o
-BUILTIN_OBJS += builtin-count-objects.o
-BUILTIN_OBJS += builtin-describe.o
-BUILTIN_OBJS += builtin-diff-files.o
-BUILTIN_OBJS += builtin-diff-index.o
-BUILTIN_OBJS += builtin-diff-tree.o
-BUILTIN_OBJS += builtin-diff.o
-BUILTIN_OBJS += builtin-fast-export.o
-BUILTIN_OBJS += builtin-fetch-pack.o
-BUILTIN_OBJS += builtin-fetch.o
-BUILTIN_OBJS += builtin-fmt-merge-msg.o
-BUILTIN_OBJS += builtin-for-each-ref.o
-BUILTIN_OBJS += builtin-fsck.o
-BUILTIN_OBJS += builtin-gc.o
-BUILTIN_OBJS += builtin-grep.o
-BUILTIN_OBJS += builtin-hash-object.o
-BUILTIN_OBJS += builtin-help.o
-BUILTIN_OBJS += builtin-index-pack.o
-BUILTIN_OBJS += builtin-init-db.o
-BUILTIN_OBJS += builtin-log.o
-BUILTIN_OBJS += builtin-ls-files.o
-BUILTIN_OBJS += builtin-ls-remote.o
-BUILTIN_OBJS += builtin-ls-tree.o
-BUILTIN_OBJS += builtin-mailinfo.o
-BUILTIN_OBJS += builtin-mailsplit.o
-BUILTIN_OBJS += builtin-merge.o
-BUILTIN_OBJS += builtin-merge-base.o
-BUILTIN_OBJS += builtin-merge-file.o
-BUILTIN_OBJS += builtin-merge-index.o
-BUILTIN_OBJS += builtin-merge-ours.o
-BUILTIN_OBJS += builtin-merge-recursive.o
-BUILTIN_OBJS += builtin-merge-tree.o
-BUILTIN_OBJS += builtin-mktag.o
-BUILTIN_OBJS += builtin-mktree.o
-BUILTIN_OBJS += builtin-mv.o
-BUILTIN_OBJS += builtin-name-rev.o
-BUILTIN_OBJS += builtin-pack-objects.o
-BUILTIN_OBJS += builtin-pack-redundant.o
-BUILTIN_OBJS += builtin-pack-refs.o
-BUILTIN_OBJS += builtin-patch-id.o
-BUILTIN_OBJS += builtin-prune-packed.o
-BUILTIN_OBJS += builtin-prune.o
-BUILTIN_OBJS += builtin-push.o
-BUILTIN_OBJS += builtin-read-tree.o
-BUILTIN_OBJS += builtin-receive-pack.o
-BUILTIN_OBJS += builtin-reflog.o
-BUILTIN_OBJS += builtin-remote.o
-BUILTIN_OBJS += builtin-replace.o
-BUILTIN_OBJS += builtin-rerere.o
-BUILTIN_OBJS += builtin-reset.o
-BUILTIN_OBJS += builtin-rev-list.o
-BUILTIN_OBJS += builtin-rev-parse.o
-BUILTIN_OBJS += builtin-revert.o
-BUILTIN_OBJS += builtin-rm.o
-BUILTIN_OBJS += builtin-send-pack.o
-BUILTIN_OBJS += builtin-shortlog.o
-BUILTIN_OBJS += builtin-show-branch.o
-BUILTIN_OBJS += builtin-show-ref.o
-BUILTIN_OBJS += builtin-stripspace.o
-BUILTIN_OBJS += builtin-symbolic-ref.o
-BUILTIN_OBJS += builtin-tag.o
-BUILTIN_OBJS += builtin-tar-tree.o
-BUILTIN_OBJS += builtin-unpack-file.o
-BUILTIN_OBJS += builtin-unpack-objects.o
-BUILTIN_OBJS += builtin-update-index.o
-BUILTIN_OBJS += builtin-update-ref.o
-BUILTIN_OBJS += builtin-update-server-info.o
-BUILTIN_OBJS += builtin-upload-archive.o
-BUILTIN_OBJS += builtin-var.o
-BUILTIN_OBJS += builtin-verify-pack.o
-BUILTIN_OBJS += builtin-verify-tag.o
-BUILTIN_OBJS += builtin-write-tree.o
+BUILTIN_OBJS += builtin/add.o
+BUILTIN_OBJS += builtin/annotate.o
+BUILTIN_OBJS += builtin/apply.o
+BUILTIN_OBJS += builtin/archive.o
+BUILTIN_OBJS += builtin/bisect--helper.o
+BUILTIN_OBJS += builtin/blame.o
+BUILTIN_OBJS += builtin/branch.o
+BUILTIN_OBJS += builtin/bundle.o
+BUILTIN_OBJS += builtin/cat-file.o
+BUILTIN_OBJS += builtin/check-attr.o
+BUILTIN_OBJS += builtin/check-ref-format.o
+BUILTIN_OBJS += builtin/checkout-index.o
+BUILTIN_OBJS += builtin/checkout.o
+BUILTIN_OBJS += builtin/clean.o
+BUILTIN_OBJS += builtin/clone.o
+BUILTIN_OBJS += builtin/commit-tree.o
+BUILTIN_OBJS += builtin/commit.o
+BUILTIN_OBJS += builtin/config.o
+BUILTIN_OBJS += builtin/count-objects.o
+BUILTIN_OBJS += builtin/describe.o
+BUILTIN_OBJS += builtin/diff-files.o
+BUILTIN_OBJS += builtin/diff-index.o
+BUILTIN_OBJS += builtin/diff-tree.o
+BUILTIN_OBJS += builtin/diff.o
+BUILTIN_OBJS += builtin/fast-export.o
+BUILTIN_OBJS += builtin/fetch-pack.o
+BUILTIN_OBJS += builtin/fetch.o
+BUILTIN_OBJS += builtin/fmt-merge-msg.o
+BUILTIN_OBJS += builtin/for-each-ref.o
+BUILTIN_OBJS += builtin/fsck.o
+BUILTIN_OBJS += builtin/gc.o
+BUILTIN_OBJS += builtin/grep.o
+BUILTIN_OBJS += builtin/hash-object.o
+BUILTIN_OBJS += builtin/help.o
+BUILTIN_OBJS += builtin/index-pack.o
+BUILTIN_OBJS += builtin/init-db.o
+BUILTIN_OBJS += builtin/log.o
+BUILTIN_OBJS += builtin/ls-files.o
+BUILTIN_OBJS += builtin/ls-remote.o
+BUILTIN_OBJS += builtin/ls-tree.o
+BUILTIN_OBJS += builtin/mailinfo.o
+BUILTIN_OBJS += builtin/mailsplit.o
+BUILTIN_OBJS += builtin/merge.o
+BUILTIN_OBJS += builtin/merge-base.o
+BUILTIN_OBJS += builtin/merge-file.o
+BUILTIN_OBJS += builtin/merge-index.o
+BUILTIN_OBJS += builtin/merge-ours.o
+BUILTIN_OBJS += builtin/merge-recursive.o
+BUILTIN_OBJS += builtin/merge-tree.o
+BUILTIN_OBJS += builtin/mktag.o
+BUILTIN_OBJS += builtin/mktree.o
+BUILTIN_OBJS += builtin/mv.o
+BUILTIN_OBJS += builtin/name-rev.o
+BUILTIN_OBJS += builtin/pack-objects.o
+BUILTIN_OBJS += builtin/pack-redundant.o
+BUILTIN_OBJS += builtin/pack-refs.o
+BUILTIN_OBJS += builtin/patch-id.o
+BUILTIN_OBJS += builtin/prune-packed.o
+BUILTIN_OBJS += builtin/prune.o
+BUILTIN_OBJS += builtin/push.o
+BUILTIN_OBJS += builtin/read-tree.o
+BUILTIN_OBJS += builtin/receive-pack.o
+BUILTIN_OBJS += builtin/reflog.o
+BUILTIN_OBJS += builtin/remote.o
+BUILTIN_OBJS += builtin/replace.o
+BUILTIN_OBJS += builtin/rerere.o
+BUILTIN_OBJS += builtin/reset.o
+BUILTIN_OBJS += builtin/rev-list.o
+BUILTIN_OBJS += builtin/rev-parse.o
+BUILTIN_OBJS += builtin/revert.o
+BUILTIN_OBJS += builtin/rm.o
+BUILTIN_OBJS += builtin/send-pack.o
+BUILTIN_OBJS += builtin/shortlog.o
+BUILTIN_OBJS += builtin/show-branch.o
+BUILTIN_OBJS += builtin/show-ref.o
+BUILTIN_OBJS += builtin/stripspace.o
+BUILTIN_OBJS += builtin/symbolic-ref.o
+BUILTIN_OBJS += builtin/tag.o
+BUILTIN_OBJS += builtin/tar-tree.o
+BUILTIN_OBJS += builtin/unpack-file.o
+BUILTIN_OBJS += builtin/unpack-objects.o
+BUILTIN_OBJS += builtin/update-index.o
+BUILTIN_OBJS += builtin/update-ref.o
+BUILTIN_OBJS += builtin/update-server-info.o
+BUILTIN_OBJS += builtin/upload-archive.o
+BUILTIN_OBJS += builtin/var.o
+BUILTIN_OBJS += builtin/verify-pack.o
+BUILTIN_OBJS += builtin/verify-tag.o
+BUILTIN_OBJS += builtin/write-tree.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
EXTLIBS =
-include config.mak.autogen
-include config.mak
+ifdef CHECK_HEADER_DEPENDENCIES
+USE_COMPUTED_HEADER_DEPENDENCIES =
+endif
+
+ifdef COMPUTE_HEADER_DEPENDENCIES
+USE_COMPUTED_HEADER_DEPENDENCIES = YesPlease
+endif
+
ifdef SANE_TOOL_PATH
SANE_TOOL_PATH_SQ = $(subst ','\'',$(SANE_TOOL_PATH))
BROKEN_PATH_FIX = 's|^\# @@BROKEN_PATH_FIX@@$$|git_broken_path_fix $(SANE_TOOL_PATH_SQ)|'
REMOTE_CURL_PRIMARY = git-remote-http$X
REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X
REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)
- PROGRAMS += $(REMOTE_CURL_NAMES) git-http-fetch$X
+ PROGRAM_OBJS += http-fetch.o
+ PROGRAMS += $(REMOTE_CURL_NAMES)
curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
ifeq "$(curl_check)" "070908"
ifndef NO_EXPAT
- PROGRAMS += git-http-push$X
+ PROGRAM_OBJS += http-push.o
endif
endif
ifndef NO_EXPAT
EXTLIBS += -lz
ifndef NO_POSIX_ONLY_PROGRAMS
- PROGRAMS += git-daemon$X
+ PROGRAM_OBJS += daemon.o
endif
ifndef NO_OPENSSL
OPENSSL_LIBSSL = -lssl
ifdef BLK_SHA1
SHA1_HEADER = "block-sha1/sha1.h"
LIB_OBJS += block-sha1/sha1.o
+ LIB_H += block-sha1/sha1.h
else
ifdef PPC_SHA1
SHA1_HEADER = "ppc/sha1.h"
LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
+ LIB_H += ppc/sha1.h
else
SHA1_HEADER = <openssl/sha.h>
EXTLIBS += $(LIB_4_CRYPTO)
SHELL = $(SHELL_PATH)
-all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
+all:: shell_compatibility_test $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
ifneq (,$X)
$(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';)
endif
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
-builtin-help.o: common-cmds.h
-builtin-help.s builtin-help.o: ALL_CFLAGS += \
+builtin/help.o: common-cmds.h
+builtin/help.s builtin/help.o: ALL_CFLAGS += \
'-DGIT_HTML_PATH="$(htmldir_SQ)"' \
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
'-DGIT_INFO_PATH="$(infodir_SQ)"'
common-cmds.h: $(wildcard Documentation/git-*.txt)
$(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
+define cmd_munge_script
+$(RM) $@ $@+ && \
+sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+ -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
+ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
+ -e $(BROKEN_PATH_FIX) \
+ $@.sh >$@+
+endef
+
$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
- $(QUIET_GEN)$(RM) $@ $@+ && \
- sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
- -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
- -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
- -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
- -e $(BROKEN_PATH_FIX) \
- $@.sh >$@+ && \
+ $(QUIET_GEN)$(cmd_munge_script) && \
chmod +x $@+ && \
mv $@+ $@
+$(SCRIPT_LIB) : % : %.sh
+ $(QUIET_GEN)$(cmd_munge_script) && \
+ mv $@+ $@
+
ifndef NO_PERL
$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
: GIT-VERSION-FILE
-%.o: %.c GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+TEST_OBJS := $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
+GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
+ git.o http.o http-walker.o remote-curl.o
+XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
+ xdiff/xmerge.o xdiff/xpatience.o
+OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS)
+
+dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
+dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
+
+ifdef COMPUTE_HEADER_DEPENDENCIES
+$(dep_dirs):
+ mkdir -p $@
+
+missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs))
+dep_file = $(dir $@).depend/$(notdir $@).d
+dep_args = -MF $(dep_file) -MMD -MP
+ifdef CHECK_HEADER_DEPENDENCIES
+$(error cannot compute header dependencies outside a normal build. \
+Please unset CHECK_HEADER_DEPENDENCIES and try again)
+endif
+endif
+
+ifndef COMPUTE_HEADER_DEPENDENCIES
+ifndef CHECK_HEADER_DEPENDENCIES
+dep_dirs =
+missing_dep_dirs =
+dep_args =
+endif
+endif
+
+ifdef CHECK_HEADER_DEPENDENCIES
+ifndef PRINT_HEADER_DEPENDENCIES
+missing_deps = $(filter-out $(notdir $^), \
+ $(notdir $(shell $(MAKE) -s $@ \
+ CHECK_HEADER_DEPENDENCIES=YesPlease \
+ USE_COMPUTED_HEADER_DEPENDENCIES=YesPlease \
+ PRINT_HEADER_DEPENDENCIES=YesPlease)))
+endif
+endif
+
+ASM_SRC := $(wildcard $(OBJECTS:o=S))
+ASM_OBJ := $(ASM_SRC:S=o)
+C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS))
+
+.SUFFIXES:
+
+ifdef PRINT_HEADER_DEPENDENCIES
+$(C_OBJ): %.o: %.c FORCE
+ echo $^
+$(ASM_OBJ): %.o: %.S FORCE
+ echo $^
+
+ifndef CHECK_HEADER_DEPENDENCIES
+$(error cannot print header dependencies during a normal build. \
+Please set CHECK_HEADER_DEPENDENCIES and try again)
+endif
+endif
+
+ifndef PRINT_HEADER_DEPENDENCIES
+ifdef CHECK_HEADER_DEPENDENCIES
+$(C_OBJ): %.o: %.c $(dep_files) FORCE
+ @set -e; echo CHECK $@; \
+ missing_deps="$(missing_deps)"; \
+ if test "$$missing_deps"; \
+ then \
+ echo missing dependencies: $$missing_deps; \
+ false; \
+ fi
+$(ASM_OBJ): %.o: %.S $(dep_files) FORCE
+ @set -e; echo CHECK $@; \
+ missing_deps="$(missing_deps)"; \
+ if test "$$missing_deps"; \
+ then \
+ echo missing dependencies: $$missing_deps; \
+ false; \
+ fi
+endif
+endif
+
+ifndef CHECK_HEADER_DEPENDENCIES
+$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs)
+ $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $<
+$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs)
+ $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $<
+endif
+
%.s: %.c GIT-CFLAGS FORCE
$(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
-%.o: %.S GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+
+ifdef USE_COMPUTED_HEADER_DEPENDENCIES
+# Take advantage of gcc's on-the-fly dependency generation
+# See <http://gcc.gnu.org/gcc-3.0/features.html>.
+dep_files_present := $(wildcard $(dep_files))
+ifneq ($(dep_files_present),)
+include $(dep_files_present)
+endif
+else
+# Dependencies on header files, for platforms that do not support
+# the gcc -MMD option.
+#
+# Dependencies on automatically generated headers such as common-cmds.h
+# should _not_ be included here, since they are necessary even when
+# building an object for the first time.
+#
+# XXX. Please check occasionally that these include all dependencies
+# gcc detects!
+
+$(GIT_OBJS): $(LIB_H)
+builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o transport.o: branch.h
+builtin/bundle.o bundle.o transport.o: bundle.h
+builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
+builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
+builtin/grep.o: thread-utils.h
+builtin/send-pack.o transport.o: send-pack.h
+builtin/log.o builtin/shortlog.o: shortlog.h
+builtin/prune.o builtin/reflog.o reachable.o: reachable.h
+builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
+builtin/tar-tree.o archive-tar.o: tar.h
+builtin/pack-objects.o: thread-utils.h
+http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
+http.o http-walker.o http-push.o remote-curl.o: http.h
+
+xdiff-interface.o $(XDIFF_OBJS): \
+ xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
+ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
+endif
exec_cmd.s exec_cmd.o: ALL_CFLAGS += \
'-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
'-DBINDIR="$(bindir_relative_SQ)"' \
'-DPREFIX="$(prefix_SQ)"'
-builtin-init-db.s builtin-init-db.o: ALL_CFLAGS += \
+builtin/init-db.s builtin/init-db.o: ALL_CFLAGS += \
-DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"'
config.s config.o: ALL_CFLAGS += -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"'
http.s http.o: ALL_CFLAGS += -DGIT_USER_AGENT='"git/$(GIT_VERSION)"'
ifdef NO_EXPAT
-http-walker.o: http.h
http-walker.s http-walker.o: ALL_CFLAGS += -DNO_EXPAT
endif
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL)
-http.o http-walker.o http-push.o: http.h
-
-http.o http-walker.o: $(LIB_H)
-
git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
-$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
-$(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h)
-builtin-revert.o wt-status.o: wt-status.h
-
$(LIB_FILE): $(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/xpatience.o
-$(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
- xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
-
$(XDIFF_LIB): $(XDIFF_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS)
fi
endif
-### Testing rules
-
-TEST_PROGRAMS_NEED_X += test-chmtime
-TEST_PROGRAMS_NEED_X += test-ctype
-TEST_PROGRAMS_NEED_X += test-date
-TEST_PROGRAMS_NEED_X += test-delta
-TEST_PROGRAMS_NEED_X += test-dump-cache-tree
-TEST_PROGRAMS_NEED_X += test-genrandom
-TEST_PROGRAMS_NEED_X += test-match-trees
-TEST_PROGRAMS_NEED_X += test-parse-options
-TEST_PROGRAMS_NEED_X += test-path-utils
-TEST_PROGRAMS_NEED_X += test-run-command
-TEST_PROGRAMS_NEED_X += test-sha1
-TEST_PROGRAMS_NEED_X += test-sigchain
-TEST_PROGRAMS_NEED_X += test-index-version
-
-TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
-
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
all:: $(TEST_PROGRAMS) $(test_bindir_programs)
export NO_SVN_TESTS
+### Testing rules
+
test: all
$(MAKE) -C t/ all
test-parse-options$X: parse-options.o
-test-parse-options.o: parse-options.h
-
-.PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
+.PRECIOUS: $(TEST_OBJS)
test-%$X: test-%.o $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+ $(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
ifndef NO_PERL
clean:
$(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \
- $(LIB_FILE) $(XDIFF_LIB)
- $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
+ builtin/*.o $(LIB_FILE) $(XDIFF_LIB)
+ $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
$(RM) $(TEST_PROGRAMS)
$(RM) -r bin-wrappers
+ $(RM) -r $(dep_dirs)
$(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope*
$(RM) -r autom4te.cache
$(RM) config.log config.mak.autogen config.mak.append config.status config.cache
### Check documentation
#
check-docs::
- @(for v in $(ALL_PROGRAMS) $(BUILT_INS) git gitk; \
+ @(for v in $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk; \
do \
case "$$v" in \
git-merge-octopus | git-merge-ours | git-merge-recursive | \
git-merge-resolve | git-merge-subtree | \
git-fsck-objects | git-init-db | \
+ git-remote-* | git-stage | \
git-?*--?* ) continue ;; \
esac ; \
test -f "Documentation/$$v.txt" || \
documented,gitrepository-layout | \
documented,gittutorial | \
documented,gittutorial-2 | \
+ documented,git-bisect-lk2009 | \
+ documented,git-remote-helpers | \
+ documented,gitworkflows | \
sentinel,not,matching,is,ok ) continue ;; \
esac; \
- case " $(ALL_PROGRAMS) $(BUILT_INS) git gitk " in \
+ case " $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk " in \
*" $$cmd "*) ;; \
*) echo "removed but $$how: $$cmd" ;; \
esac; \
-Documentation/RelNotes-1.7.0.3.txt
\ No newline at end of file
+Documentation/RelNotes-1.7.1.txt
\ No newline at end of file
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);
+ if (len && buf[len-1] != '/')
+ buf[len++] = '/';
+ strcpy(buf + len, last_elem);
free(last_elem);
last_elem = NULL;
}
+++ /dev/null
-/*
- * "git add" builtin command
- *
- * Copyright (C) 2006 Linus Torvalds
- */
-#include "cache.h"
-#include "builtin.h"
-#include "dir.h"
-#include "exec_cmd.h"
-#include "cache-tree.h"
-#include "run-command.h"
-#include "parse-options.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "revision.h"
-
-static const char * const builtin_add_usage[] = {
- "git add [options] [--] <filepattern>...",
- NULL
-};
-static int patch_interactive, add_interactive, edit_interactive;
-static int take_worktree_changes;
-
-struct update_callback_data
-{
- int flags;
- int add_errors;
-};
-
-static void update_callback(struct diff_queue_struct *q,
- struct diff_options *opt, void *cbdata)
-{
- int i;
- struct update_callback_data *data = cbdata;
-
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- const char *path = p->one->path;
- switch (p->status) {
- default:
- die("unexpected diff status %c", p->status);
- case DIFF_STATUS_UNMERGED:
- /*
- * ADD_CACHE_IGNORE_REMOVAL is unset if "git
- * add -u" is calling us, In such a case, a
- * missing work tree file needs to be removed
- * if there is an unmerged entry at stage #2,
- * but such a diff record is followed by
- * another with DIFF_STATUS_DELETED (and if
- * there is no stage #2, we won't see DELETED
- * nor MODIFIED). We can simply continue
- * either way.
- */
- if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
- continue;
- /*
- * Otherwise, it is "git add path" is asking
- * to explicitly add it; we fall through. A
- * missing work tree file is an error and is
- * caught by add_file_to_index() in such a
- * case.
- */
- case DIFF_STATUS_MODIFIED:
- case DIFF_STATUS_TYPE_CHANGED:
- if (add_file_to_index(&the_index, path, data->flags)) {
- if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
- die("updating files failed");
- data->add_errors++;
- }
- break;
- case DIFF_STATUS_DELETED:
- if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
- break;
- if (!(data->flags & ADD_CACHE_PRETEND))
- remove_file_from_index(&the_index, path);
- if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
- printf("remove '%s'\n", path);
- break;
- }
- }
-}
-
-int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
-{
- struct update_callback_data data;
- struct rev_info rev;
- init_revisions(&rev, prefix);
- setup_revisions(0, NULL, &rev, NULL);
- rev.prune_data = pathspec;
- rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
- rev.diffopt.format_callback = update_callback;
- data.flags = flags;
- data.add_errors = 0;
- rev.diffopt.format_callback_data = &data;
- run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
- return !!data.add_errors;
-}
-
-static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
-{
- int num_unmatched = 0, i;
-
- /*
- * Since we are walking the index as if we were walking the directory,
- * we have to mark the matched pathspec as seen; otherwise we will
- * mistakenly think that the user gave a pathspec that did not match
- * anything.
- */
- for (i = 0; i < specs; i++)
- if (!seen[i])
- num_unmatched++;
- if (!num_unmatched)
- return;
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
- }
-}
-
-static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
-{
- char *seen;
- int i, specs;
- struct dir_entry **src, **dst;
-
- for (specs = 0; pathspec[specs]; specs++)
- /* nothing */;
- seen = xcalloc(specs, 1);
-
- src = dst = dir->entries;
- i = dir->nr;
- while (--i >= 0) {
- struct dir_entry *entry = *src++;
- if (match_pathspec(pathspec, entry->name, entry->len,
- prefix, seen))
- *dst++ = entry;
- }
- dir->nr = dst - dir->entries;
- fill_pathspec_matches(pathspec, seen, specs);
-
- for (i = 0; i < specs; i++) {
- if (!seen[i] && pathspec[i][0] && !file_exists(pathspec[i]))
- die("pathspec '%s' did not match any files",
- pathspec[i]);
- }
- free(seen);
-}
-
-static void treat_gitlinks(const char **pathspec)
-{
- int i;
-
- if (!pathspec || !*pathspec)
- return;
-
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (S_ISGITLINK(ce->ce_mode)) {
- int len = ce_namelen(ce), j;
- for (j = 0; pathspec[j]; j++) {
- int len2 = strlen(pathspec[j]);
- if (len2 <= len || pathspec[j][len] != '/' ||
- memcmp(ce->name, pathspec[j], len))
- continue;
- if (len2 == len + 1)
- /* strip trailing slash */
- pathspec[j] = xstrndup(ce->name, len);
- else
- die ("Path '%s' is in submodule '%.*s'",
- pathspec[j], len, ce->name);
- }
- }
- }
-}
-
-static void refresh(int verbose, const char **pathspec)
-{
- char *seen;
- int i, specs;
-
- for (specs = 0; pathspec[specs]; specs++)
- /* nothing */;
- seen = xcalloc(specs, 1);
- refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
- pathspec, seen, "Unstaged changes after refreshing the index:");
- for (i = 0; i < specs; i++) {
- if (!seen[i])
- die("pathspec '%s' did not match any files", pathspec[i]);
- }
- free(seen);
-}
-
-static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
-{
- const char **pathspec = get_pathspec(prefix, argv);
-
- if (pathspec) {
- const char **p;
- for (p = pathspec; *p; p++) {
- if (has_symlink_leading_path(*p, strlen(*p))) {
- int len = prefix ? strlen(prefix) : 0;
- die("'%s' is beyond a symbolic link", *p + len);
- }
- }
- }
-
- return pathspec;
-}
-
-int run_add_interactive(const char *revision, const char *patch_mode,
- const char **pathspec)
-{
- int status, ac, pc = 0;
- const char **args;
-
- if (pathspec)
- while (pathspec[pc])
- pc++;
-
- args = xcalloc(sizeof(const char *), (pc + 5));
- ac = 0;
- args[ac++] = "add--interactive";
- if (patch_mode)
- args[ac++] = patch_mode;
- if (revision)
- args[ac++] = revision;
- args[ac++] = "--";
- if (pc) {
- memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
- ac += pc;
- }
- args[ac] = NULL;
-
- status = run_command_v_opt(args, RUN_GIT_CMD);
- free(args);
- return status;
-}
-
-int interactive_add(int argc, const char **argv, const char *prefix)
-{
- const char **pathspec = NULL;
-
- if (argc) {
- pathspec = validate_pathspec(argc, argv, prefix);
- if (!pathspec)
- return -1;
- }
-
- return run_add_interactive(NULL,
- patch_interactive ? "--patch" : NULL,
- pathspec);
-}
-
-static int edit_patch(int argc, const char **argv, const char *prefix)
-{
- char *file = xstrdup(git_path("ADD_EDIT.patch"));
- const char *apply_argv[] = { "apply", "--recount", "--cached",
- file, NULL };
- struct child_process child;
- struct rev_info rev;
- int out;
- struct stat st;
-
- git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
-
- if (read_cache() < 0)
- die ("Could not read the index");
-
- init_revisions(&rev, prefix);
- rev.diffopt.context = 7;
-
- argc = setup_revisions(argc, argv, &rev, NULL);
- rev.diffopt.output_format = DIFF_FORMAT_PATCH;
- out = open(file, O_CREAT | O_WRONLY, 0644);
- if (out < 0)
- die ("Could not open '%s' for writing.", file);
- rev.diffopt.file = xfdopen(out, "w");
- rev.diffopt.close_file = 1;
- if (run_diff_files(&rev, 0))
- die ("Could not write patch");
-
- launch_editor(file, NULL, NULL);
-
- if (stat(file, &st))
- die_errno("Could not stat '%s'", file);
- if (!st.st_size)
- die("Empty patch. Aborted.");
-
- memset(&child, 0, sizeof(child));
- child.git_cmd = 1;
- child.argv = apply_argv;
- if (run_command(&child))
- die ("Could not apply '%s'", file);
-
- unlink(file);
- return 0;
-}
-
-static struct lock_file lock_file;
-
-static const char ignore_error[] =
-"The following paths are ignored by one of your .gitignore files:\n";
-
-static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
-static int ignore_add_errors, addremove, intent_to_add;
-
-static struct option builtin_add_options[] = {
- OPT__DRY_RUN(&show_only),
- OPT__VERBOSE(&verbose),
- OPT_GROUP(""),
- OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
- OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
- OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"),
- OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"),
- OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
- OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
- OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"),
- OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
- OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
- OPT_END(),
-};
-
-static int add_config(const char *var, const char *value, void *cb)
-{
- if (!strcasecmp(var, "add.ignore-errors")) {
- ignore_add_errors = git_config_bool(var, value);
- return 0;
- }
- return git_default_config(var, value, cb);
-}
-
-static int add_files(struct dir_struct *dir, int flags)
-{
- int i, exit_status = 0;
-
- if (dir->ignored_nr) {
- fprintf(stderr, ignore_error);
- 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");
- die("no files added");
- }
-
- for (i = 0; i < dir->nr; i++)
- if (add_file_to_cache(dir->entries[i]->name, flags)) {
- if (!ignore_add_errors)
- die("adding files failed");
- exit_status = 1;
- }
- return exit_status;
-}
-
-int cmd_add(int argc, const char **argv, const char *prefix)
-{
- int exit_status = 0;
- int newfd;
- const char **pathspec;
- struct dir_struct dir;
- int flags;
- int add_new_files;
- int require_pathspec;
-
- git_config(add_config, NULL);
-
- argc = parse_options(argc, argv, prefix, builtin_add_options,
- builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
- if (patch_interactive)
- add_interactive = 1;
- if (add_interactive)
- exit(interactive_add(argc - 1, argv + 1, prefix));
-
- if (edit_interactive)
- return(edit_patch(argc, argv, prefix));
- argc--;
- argv++;
-
- if (addremove && take_worktree_changes)
- die("-A and -u are mutually incompatible");
- if ((addremove || take_worktree_changes) && !argc) {
- static const char *here[2] = { ".", NULL };
- argc = 1;
- argv = here;
- }
-
- add_new_files = !take_worktree_changes && !refresh_only;
- require_pathspec = !take_worktree_changes;
-
- newfd = hold_locked_index(&lock_file, 1);
-
- flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
- (show_only ? ADD_CACHE_PRETEND : 0) |
- (intent_to_add ? ADD_CACHE_INTENT : 0) |
- (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
- (!(addremove || take_worktree_changes)
- ? ADD_CACHE_IGNORE_REMOVAL : 0));
-
- if (require_pathspec && argc == 0) {
- fprintf(stderr, "Nothing specified, nothing added.\n");
- fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
- return 0;
- }
- pathspec = validate_pathspec(argc, argv, prefix);
-
- if (read_cache() < 0)
- die("index file corrupt");
- treat_gitlinks(pathspec);
-
- if (add_new_files) {
- int baselen;
-
- /* Set up the default git porcelain excludes */
- memset(&dir, 0, sizeof(dir));
- if (!ignored_too) {
- dir.flags |= DIR_COLLECT_IGNORED;
- setup_standard_excludes(&dir);
- }
-
- /* This picks up the paths that are not tracked */
- baselen = fill_directory(&dir, pathspec);
- if (pathspec)
- prune_directory(&dir, pathspec, baselen);
- }
-
- if (refresh_only) {
- refresh(verbose, pathspec);
- goto finish;
- }
-
- exit_status |= add_files_to_cache(prefix, pathspec, flags);
-
- if (add_new_files)
- exit_status |= add_files(&dir, flags);
-
- finish:
- if (active_cache_changed) {
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
- die("Unable to write new index file");
- }
-
- return exit_status;
-}
+++ /dev/null
-/*
- * "git annotate" builtin alias
- *
- * Copyright (C) 2006 Ryan Anderson
- */
-#include "git-compat-util.h"
-#include "builtin.h"
-
-int cmd_annotate(int argc, const char **argv, const char *prefix)
-{
- const char **nargv;
- int i;
- nargv = xmalloc(sizeof(char *) * (argc + 2));
-
- nargv[0] = "annotate";
- nargv[1] = "-c";
-
- for (i = 1; i < argc; i++) {
- nargv[i+1] = argv[i];
- }
- nargv[argc + 1] = NULL;
-
- return cmd_blame(argc + 1, nargv, prefix);
-}
+++ /dev/null
-/*
- * apply.c
- *
- * Copyright (C) Linus Torvalds, 2005
- *
- * This applies patches on top of some (arbitrary) version of the SCM.
- *
- */
-#include "cache.h"
-#include "cache-tree.h"
-#include "quote.h"
-#include "blob.h"
-#include "delta.h"
-#include "builtin.h"
-#include "string-list.h"
-#include "dir.h"
-#include "parse-options.h"
-
-/*
- * --check turns on checking that the working tree matches the
- * files that are being modified, but doesn't apply the patch
- * --stat does just a diffstat, and doesn't actually apply
- * --numstat does numeric diffstat, and doesn't actually apply
- * --index-info shows the old and new index info for paths if available.
- * --index updates the cache as well.
- * --cached updates only the cache without ever touching the working tree.
- */
-static const char *prefix;
-static int prefix_length = -1;
-static int newfd = -1;
-
-static int unidiff_zero;
-static int p_value = 1;
-static int p_value_known;
-static int check_index;
-static int update_index;
-static int cached;
-static int diffstat;
-static int numstat;
-static int summary;
-static int check;
-static int apply = 1;
-static int apply_in_reverse;
-static int apply_with_reject;
-static int apply_verbosely;
-static int no_add;
-static const char *fake_ancestor;
-static int line_termination = '\n';
-static unsigned int p_context = UINT_MAX;
-static const char * const apply_usage[] = {
- "git apply [options] [<patch>...]",
- NULL
-};
-
-static enum ws_error_action {
- nowarn_ws_error,
- warn_on_ws_error,
- die_on_ws_error,
- correct_ws_error,
-} ws_error_action = warn_on_ws_error;
-static int whitespace_error;
-static int squelch_whitespace_errors = 5;
-static int applied_after_fixing_ws;
-
-static enum ws_ignore {
- ignore_ws_none,
- ignore_ws_change,
-} ws_ignore_action = ignore_ws_none;
-
-
-static const char *patch_input_file;
-static const char *root;
-static int root_len;
-static int read_stdin = 1;
-static int options;
-
-static void parse_whitespace_option(const char *option)
-{
- if (!option) {
- ws_error_action = warn_on_ws_error;
- return;
- }
- if (!strcmp(option, "warn")) {
- ws_error_action = warn_on_ws_error;
- return;
- }
- if (!strcmp(option, "nowarn")) {
- ws_error_action = nowarn_ws_error;
- return;
- }
- if (!strcmp(option, "error")) {
- ws_error_action = die_on_ws_error;
- return;
- }
- if (!strcmp(option, "error-all")) {
- ws_error_action = die_on_ws_error;
- squelch_whitespace_errors = 0;
- return;
- }
- if (!strcmp(option, "strip") || !strcmp(option, "fix")) {
- ws_error_action = correct_ws_error;
- return;
- }
- die("unrecognized whitespace option '%s'", option);
-}
-
-static void parse_ignorewhitespace_option(const char *option)
-{
- if (!option || !strcmp(option, "no") ||
- !strcmp(option, "false") || !strcmp(option, "never") ||
- !strcmp(option, "none")) {
- ws_ignore_action = ignore_ws_none;
- return;
- }
- if (!strcmp(option, "change")) {
- ws_ignore_action = ignore_ws_change;
- return;
- }
- die("unrecognized whitespace ignore option '%s'", option);
-}
-
-static void set_default_whitespace_mode(const char *whitespace_option)
-{
- if (!whitespace_option && !apply_default_whitespace)
- ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error);
-}
-
-/*
- * For "diff-stat" like behaviour, we keep track of the biggest change
- * we've seen, and the longest filename. That allows us to do simple
- * scaling.
- */
-static int max_change, max_len;
-
-/*
- * Various "current state", notably line numbers and what
- * file (and how) we're patching right now.. The "is_xxxx"
- * things are flags, where -1 means "don't know yet".
- */
-static int linenr = 1;
-
-/*
- * This represents one "hunk" from a patch, starting with
- * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The
- * patch text is pointed at by patch, and its byte length
- * is stored in size. leading and trailing are the number
- * of context lines.
- */
-struct fragment {
- unsigned long leading, trailing;
- unsigned long oldpos, oldlines;
- unsigned long newpos, newlines;
- const char *patch;
- int size;
- int rejected;
- int linenr;
- struct fragment *next;
-};
-
-/*
- * When dealing with a binary patch, we reuse "leading" field
- * to store the type of the binary hunk, either deflated "delta"
- * or deflated "literal".
- */
-#define binary_patch_method leading
-#define BINARY_DELTA_DEFLATED 1
-#define BINARY_LITERAL_DEFLATED 2
-
-/*
- * This represents a "patch" to a file, both metainfo changes
- * such as creation/deletion, filemode and content changes represented
- * as a series of fragments.
- */
-struct patch {
- char *new_name, *old_name, *def_name;
- unsigned int old_mode, new_mode;
- int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
- int rejected;
- unsigned ws_rule;
- unsigned long deflate_origlen;
- int lines_added, lines_deleted;
- int score;
- unsigned int is_toplevel_relative:1;
- unsigned int inaccurate_eof:1;
- unsigned int is_binary:1;
- unsigned int is_copy:1;
- unsigned int is_rename:1;
- unsigned int recount:1;
- struct fragment *fragments;
- char *result;
- size_t resultsize;
- char old_sha1_prefix[41];
- char new_sha1_prefix[41];
- struct patch *next;
-};
-
-/*
- * A line in a file, len-bytes long (includes the terminating LF,
- * except for an incomplete line at the end if the file ends with
- * one), and its contents hashes to 'hash'.
- */
-struct line {
- size_t len;
- unsigned hash : 24;
- unsigned flag : 8;
-#define LINE_COMMON 1
-};
-
-/*
- * This represents a "file", which is an array of "lines".
- */
-struct image {
- char *buf;
- size_t len;
- size_t nr;
- size_t alloc;
- struct line *line_allocated;
- struct line *line;
-};
-
-/*
- * Records filenames that have been touched, in order to handle
- * the case where more than one patches touch the same file.
- */
-
-static struct string_list fn_table;
-
-static uint32_t hash_line(const char *cp, size_t len)
-{
- size_t i;
- uint32_t h;
- for (i = 0, h = 0; i < len; i++) {
- if (!isspace(cp[i])) {
- h = h * 3 + (cp[i] & 0xff);
- }
- }
- return h;
-}
-
-/*
- * Compare lines s1 of length n1 and s2 of length n2, ignoring
- * whitespace difference. Returns 1 if they match, 0 otherwise
- */
-static int fuzzy_matchlines(const char *s1, size_t n1,
- const char *s2, size_t n2)
-{
- const char *last1 = s1 + n1 - 1;
- const char *last2 = s2 + n2 - 1;
- int result = 0;
-
- if (n1 < 0 || n2 < 0)
- return 0;
-
- /* ignore line endings */
- while ((*last1 == '\r') || (*last1 == '\n'))
- last1--;
- while ((*last2 == '\r') || (*last2 == '\n'))
- last2--;
-
- /* skip leading whitespace */
- while (isspace(*s1) && (s1 <= last1))
- s1++;
- while (isspace(*s2) && (s2 <= last2))
- s2++;
- /* early return if both lines are empty */
- if ((s1 > last1) && (s2 > last2))
- return 1;
- while (!result) {
- result = *s1++ - *s2++;
- /*
- * Skip whitespace inside. We check for whitespace on
- * both buffers because we don't want "a b" to match
- * "ab"
- */
- if (isspace(*s1) && isspace(*s2)) {
- while (isspace(*s1) && s1 <= last1)
- s1++;
- while (isspace(*s2) && s2 <= last2)
- s2++;
- }
- /*
- * If we reached the end on one side only,
- * lines don't match
- */
- if (
- ((s2 > last2) && (s1 <= last1)) ||
- ((s1 > last1) && (s2 <= last2)))
- return 0;
- if ((s1 > last1) && (s2 > last2))
- break;
- }
-
- return !result;
-}
-
-static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
-{
- ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
- img->line_allocated[img->nr].len = len;
- img->line_allocated[img->nr].hash = hash_line(bol, len);
- img->line_allocated[img->nr].flag = flag;
- img->nr++;
-}
-
-static void prepare_image(struct image *image, char *buf, size_t len,
- int prepare_linetable)
-{
- const char *cp, *ep;
-
- memset(image, 0, sizeof(*image));
- image->buf = buf;
- image->len = len;
-
- if (!prepare_linetable)
- return;
-
- ep = image->buf + image->len;
- cp = image->buf;
- while (cp < ep) {
- const char *next;
- for (next = cp; next < ep && *next != '\n'; next++)
- ;
- if (next < ep)
- next++;
- add_line_info(image, cp, next - cp, 0);
- cp = next;
- }
- image->line = image->line_allocated;
-}
-
-static void clear_image(struct image *image)
-{
- free(image->buf);
- image->buf = NULL;
- image->len = 0;
-}
-
-static void say_patch_name(FILE *output, const char *pre,
- struct patch *patch, const char *post)
-{
- fputs(pre, output);
- if (patch->old_name && patch->new_name &&
- strcmp(patch->old_name, patch->new_name)) {
- quote_c_style(patch->old_name, NULL, output, 0);
- fputs(" => ", output);
- quote_c_style(patch->new_name, NULL, output, 0);
- } else {
- const char *n = patch->new_name;
- if (!n)
- n = patch->old_name;
- quote_c_style(n, NULL, output, 0);
- }
- fputs(post, output);
-}
-
-#define CHUNKSIZE (8192)
-#define SLOP (16)
-
-static void read_patch_file(struct strbuf *sb, int fd)
-{
- if (strbuf_read(sb, fd, 0) < 0)
- die_errno("git apply: failed to read");
-
- /*
- * Make sure that we have some slop in the buffer
- * so that we can do speculative "memcmp" etc, and
- * see to it that it is NUL-filled.
- */
- strbuf_grow(sb, SLOP);
- memset(sb->buf + sb->len, 0, SLOP);
-}
-
-static unsigned long linelen(const char *buffer, unsigned long size)
-{
- unsigned long len = 0;
- while (size--) {
- len++;
- if (*buffer++ == '\n')
- break;
- }
- return len;
-}
-
-static int is_dev_null(const char *str)
-{
- return !memcmp("/dev/null", str, 9) && isspace(str[9]);
-}
-
-#define TERM_SPACE 1
-#define TERM_TAB 2
-
-static int name_terminate(const char *name, int namelen, int c, int terminate)
-{
- if (c == ' ' && !(terminate & TERM_SPACE))
- return 0;
- if (c == '\t' && !(terminate & TERM_TAB))
- return 0;
-
- return 1;
-}
-
-/* remove double slashes to make --index work with such filenames */
-static char *squash_slash(char *name)
-{
- int i = 0, j = 0;
-
- if (!name)
- return NULL;
-
- while (name[i]) {
- if ((name[j++] = name[i++]) == '/')
- while (name[i] == '/')
- i++;
- }
- name[j] = '\0';
- return name;
-}
-
-static char *find_name(const char *line, char *def, int p_value, int terminate)
-{
- int len;
- const char *start = NULL;
-
- if (p_value == 0)
- start = line;
-
- if (*line == '"') {
- struct strbuf name = STRBUF_INIT;
-
- /*
- * Proposed "new-style" GNU patch/diff format; see
- * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
- */
- if (!unquote_c_style(&name, line, NULL)) {
- char *cp;
-
- for (cp = name.buf; p_value; p_value--) {
- cp = strchr(cp, '/');
- if (!cp)
- break;
- cp++;
- }
- if (cp) {
- /* name can later be freed, so we need
- * to memmove, not just return cp
- */
- strbuf_remove(&name, 0, cp - name.buf);
- free(def);
- if (root)
- strbuf_insert(&name, 0, root, root_len);
- return squash_slash(strbuf_detach(&name, NULL));
- }
- }
- strbuf_release(&name);
- }
-
- for (;;) {
- char c = *line;
-
- if (isspace(c)) {
- if (c == '\n')
- break;
- if (name_terminate(start, line-start, c, terminate))
- break;
- }
- line++;
- if (c == '/' && !--p_value)
- start = line;
- }
- if (!start)
- return squash_slash(def);
- len = line - start;
- if (!len)
- return squash_slash(def);
-
- /*
- * Generally we prefer the shorter name, especially
- * if the other one is just a variation of that with
- * something else tacked on to the end (ie "file.orig"
- * or "file~").
- */
- if (def) {
- int deflen = strlen(def);
- if (deflen < len && !strncmp(start, def, deflen))
- return squash_slash(def);
- free(def);
- }
-
- if (root) {
- char *ret = xmalloc(root_len + len + 1);
- strcpy(ret, root);
- memcpy(ret + root_len, start, len);
- ret[root_len + len] = '\0';
- return squash_slash(ret);
- }
-
- return squash_slash(xmemdupz(start, len));
-}
-
-static int count_slashes(const char *cp)
-{
- int cnt = 0;
- char ch;
-
- while ((ch = *cp++))
- if (ch == '/')
- cnt++;
- return cnt;
-}
-
-/*
- * Given the string after "--- " or "+++ ", guess the appropriate
- * p_value for the given patch.
- */
-static int guess_p_value(const char *nameline)
-{
- char *name, *cp;
- int val = -1;
-
- if (is_dev_null(nameline))
- return -1;
- name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB);
- if (!name)
- return -1;
- cp = strchr(name, '/');
- if (!cp)
- val = 0;
- else if (prefix) {
- /*
- * Does it begin with "a/$our-prefix" and such? Then this is
- * very likely to apply to our directory.
- */
- if (!strncmp(name, prefix, prefix_length))
- val = count_slashes(prefix);
- else {
- cp++;
- if (!strncmp(cp, prefix, prefix_length))
- val = count_slashes(prefix) + 1;
- }
- }
- free(name);
- return val;
-}
-
-/*
- * Does the ---/+++ line has the POSIX timestamp after the last HT?
- * GNU diff puts epoch there to signal a creation/deletion event. Is
- * this such a timestamp?
- */
-static int has_epoch_timestamp(const char *nameline)
-{
- /*
- * We are only interested in epoch timestamp; any non-zero
- * fraction cannot be one, hence "(\.0+)?" in the regexp below.
- * For the same reason, the date must be either 1969-12-31 or
- * 1970-01-01, and the seconds part must be "00".
- */
- const char stamp_regexp[] =
- "^(1969-12-31|1970-01-01)"
- " "
- "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
- " "
- "([-+][0-2][0-9][0-5][0-9])\n";
- const char *timestamp = NULL, *cp;
- static regex_t *stamp;
- regmatch_t m[10];
- int zoneoffset;
- int hourminute;
- int status;
-
- for (cp = nameline; *cp != '\n'; cp++) {
- if (*cp == '\t')
- timestamp = cp + 1;
- }
- if (!timestamp)
- return 0;
- if (!stamp) {
- stamp = xmalloc(sizeof(*stamp));
- if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
- warning("Cannot prepare timestamp regexp %s",
- stamp_regexp);
- return 0;
- }
- }
-
- status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
- if (status) {
- if (status != REG_NOMATCH)
- warning("regexec returned %d for input: %s",
- status, timestamp);
- return 0;
- }
-
- zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10);
- zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
- if (timestamp[m[3].rm_so] == '-')
- zoneoffset = -zoneoffset;
-
- /*
- * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31
- * (west of GMT) or 1970-01-01 (east of GMT)
- */
- if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) ||
- (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10)))
- return 0;
-
- hourminute = (strtol(timestamp + 11, NULL, 10) * 60 +
- strtol(timestamp + 14, NULL, 10) -
- zoneoffset);
-
- return ((zoneoffset < 0 && hourminute == 1440) ||
- (0 <= zoneoffset && !hourminute));
-}
-
-/*
- * Get the name etc info from the ---/+++ lines of a traditional patch header
- *
- * FIXME! The end-of-filename heuristics are kind of screwy. For existing
- * files, we can happily check the index for a match, but for creating a
- * new file we should try to match whatever "patch" does. I have no idea.
- */
-static void parse_traditional_patch(const char *first, const char *second, struct patch *patch)
-{
- char *name;
-
- first += 4; /* skip "--- " */
- second += 4; /* skip "+++ " */
- if (!p_value_known) {
- int p, q;
- p = guess_p_value(first);
- q = guess_p_value(second);
- if (p < 0) p = q;
- if (0 <= p && p == q) {
- p_value = p;
- p_value_known = 1;
- }
- }
- if (is_dev_null(first)) {
- patch->is_new = 1;
- patch->is_delete = 0;
- name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB);
- patch->new_name = name;
- } else if (is_dev_null(second)) {
- patch->is_new = 0;
- patch->is_delete = 1;
- name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
- patch->old_name = name;
- } else {
- name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
- name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
- if (has_epoch_timestamp(first)) {
- patch->is_new = 1;
- patch->is_delete = 0;
- patch->new_name = name;
- } else if (has_epoch_timestamp(second)) {
- patch->is_new = 0;
- patch->is_delete = 1;
- patch->old_name = name;
- } else {
- patch->old_name = patch->new_name = name;
- }
- }
- if (!name)
- die("unable to find filename in patch at line %d", linenr);
-}
-
-static int gitdiff_hdrend(const char *line, struct patch *patch)
-{
- return -1;
-}
-
-/*
- * We're anal about diff header consistency, to make
- * sure that we don't end up having strange ambiguous
- * patches floating around.
- *
- * As a result, gitdiff_{old|new}name() will check
- * their names against any previous information, just
- * to make sure..
- */
-static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
-{
- if (!orig_name && !isnull)
- return find_name(line, NULL, p_value, TERM_TAB);
-
- if (orig_name) {
- int len;
- const char *name;
- char *another;
- name = orig_name;
- len = strlen(name);
- if (isnull)
- die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
- another = find_name(line, NULL, p_value, TERM_TAB);
- if (!another || memcmp(another, name, len + 1))
- die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
- free(another);
- return orig_name;
- }
- else {
- /* expect "/dev/null" */
- if (memcmp("/dev/null", line, 9) || line[9] != '\n')
- die("git apply: bad git-diff - expected /dev/null on line %d", linenr);
- return NULL;
- }
-}
-
-static int gitdiff_oldname(const char *line, struct patch *patch)
-{
- patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
- return 0;
-}
-
-static int gitdiff_newname(const char *line, struct patch *patch)
-{
- patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
- return 0;
-}
-
-static int gitdiff_oldmode(const char *line, struct patch *patch)
-{
- patch->old_mode = strtoul(line, NULL, 8);
- return 0;
-}
-
-static int gitdiff_newmode(const char *line, struct patch *patch)
-{
- patch->new_mode = strtoul(line, NULL, 8);
- return 0;
-}
-
-static int gitdiff_delete(const char *line, struct patch *patch)
-{
- patch->is_delete = 1;
- patch->old_name = patch->def_name;
- return gitdiff_oldmode(line, patch);
-}
-
-static int gitdiff_newfile(const char *line, struct patch *patch)
-{
- patch->is_new = 1;
- patch->new_name = patch->def_name;
- return gitdiff_newmode(line, patch);
-}
-
-static int gitdiff_copysrc(const char *line, struct patch *patch)
-{
- patch->is_copy = 1;
- patch->old_name = find_name(line, NULL, 0, 0);
- return 0;
-}
-
-static int gitdiff_copydst(const char *line, struct patch *patch)
-{
- patch->is_copy = 1;
- patch->new_name = find_name(line, NULL, 0, 0);
- return 0;
-}
-
-static int gitdiff_renamesrc(const char *line, struct patch *patch)
-{
- patch->is_rename = 1;
- patch->old_name = find_name(line, NULL, 0, 0);
- return 0;
-}
-
-static int gitdiff_renamedst(const char *line, struct patch *patch)
-{
- patch->is_rename = 1;
- patch->new_name = find_name(line, NULL, 0, 0);
- return 0;
-}
-
-static int gitdiff_similarity(const char *line, struct patch *patch)
-{
- if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
- patch->score = 0;
- return 0;
-}
-
-static int gitdiff_dissimilarity(const char *line, struct patch *patch)
-{
- if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
- patch->score = 0;
- return 0;
-}
-
-static int gitdiff_index(const char *line, struct patch *patch)
-{
- /*
- * index line is N hexadecimal, "..", N hexadecimal,
- * and optional space with octal mode.
- */
- const char *ptr, *eol;
- int len;
-
- ptr = strchr(line, '.');
- if (!ptr || ptr[1] != '.' || 40 < ptr - line)
- return 0;
- len = ptr - line;
- memcpy(patch->old_sha1_prefix, line, len);
- patch->old_sha1_prefix[len] = 0;
-
- line = ptr + 2;
- ptr = strchr(line, ' ');
- eol = strchr(line, '\n');
-
- if (!ptr || eol < ptr)
- ptr = eol;
- len = ptr - line;
-
- if (40 < len)
- return 0;
- memcpy(patch->new_sha1_prefix, line, len);
- patch->new_sha1_prefix[len] = 0;
- if (*ptr == ' ')
- patch->old_mode = strtoul(ptr+1, NULL, 8);
- return 0;
-}
-
-/*
- * This is normal for a diff that doesn't change anything: we'll fall through
- * into the next diff. Tell the parser to break out.
- */
-static int gitdiff_unrecognized(const char *line, struct patch *patch)
-{
- return -1;
-}
-
-static const char *stop_at_slash(const char *line, int llen)
-{
- int nslash = p_value;
- int i;
-
- for (i = 0; i < llen; i++) {
- int ch = line[i];
- if (ch == '/' && --nslash <= 0)
- return &line[i];
- }
- return NULL;
-}
-
-/*
- * This is to extract the same name that appears on "diff --git"
- * line. We do not find and return anything if it is a rename
- * patch, and it is OK because we will find the name elsewhere.
- * We need to reliably find name only when it is mode-change only,
- * creation or deletion of an empty file. In any of these cases,
- * both sides are the same name under a/ and b/ respectively.
- */
-static char *git_header_name(char *line, int llen)
-{
- const char *name;
- const char *second = NULL;
- size_t len;
-
- line += strlen("diff --git ");
- llen -= strlen("diff --git ");
-
- if (*line == '"') {
- const char *cp;
- struct strbuf first = STRBUF_INIT;
- struct strbuf sp = STRBUF_INIT;
-
- if (unquote_c_style(&first, line, &second))
- goto free_and_fail1;
-
- /* advance to the first slash */
- cp = stop_at_slash(first.buf, first.len);
- /* we do not accept absolute paths */
- if (!cp || cp == first.buf)
- goto free_and_fail1;
- strbuf_remove(&first, 0, cp + 1 - first.buf);
-
- /*
- * second points at one past closing dq of name.
- * find the second name.
- */
- while ((second < line + llen) && isspace(*second))
- second++;
-
- if (line + llen <= second)
- goto free_and_fail1;
- if (*second == '"') {
- if (unquote_c_style(&sp, second, NULL))
- goto free_and_fail1;
- cp = stop_at_slash(sp.buf, sp.len);
- if (!cp || cp == sp.buf)
- goto free_and_fail1;
- /* They must match, otherwise ignore */
- if (strcmp(cp + 1, first.buf))
- goto free_and_fail1;
- strbuf_release(&sp);
- return strbuf_detach(&first, NULL);
- }
-
- /* unquoted second */
- cp = stop_at_slash(second, line + llen - second);
- if (!cp || cp == second)
- goto free_and_fail1;
- cp++;
- if (line + llen - cp != first.len + 1 ||
- memcmp(first.buf, cp, first.len))
- goto free_and_fail1;
- return strbuf_detach(&first, NULL);
-
- free_and_fail1:
- strbuf_release(&first);
- strbuf_release(&sp);
- return NULL;
- }
-
- /* unquoted first name */
- name = stop_at_slash(line, llen);
- if (!name || name == line)
- return NULL;
- name++;
-
- /*
- * since the first name is unquoted, a dq if exists must be
- * the beginning of the second name.
- */
- for (second = name; second < line + llen; second++) {
- if (*second == '"') {
- struct strbuf sp = STRBUF_INIT;
- const char *np;
-
- if (unquote_c_style(&sp, second, NULL))
- goto free_and_fail2;
-
- np = stop_at_slash(sp.buf, sp.len);
- if (!np || np == sp.buf)
- goto free_and_fail2;
- np++;
-
- len = sp.buf + sp.len - np;
- if (len < second - name &&
- !strncmp(np, name, len) &&
- isspace(name[len])) {
- /* Good */
- strbuf_remove(&sp, 0, np - sp.buf);
- return strbuf_detach(&sp, NULL);
- }
-
- free_and_fail2:
- strbuf_release(&sp);
- return NULL;
- }
- }
-
- /*
- * Accept a name only if it shows up twice, exactly the same
- * form.
- */
- for (len = 0 ; ; len++) {
- switch (name[len]) {
- default:
- continue;
- case '\n':
- return NULL;
- case '\t': case ' ':
- second = name+len;
- for (;;) {
- char c = *second++;
- if (c == '\n')
- return NULL;
- if (c == '/')
- break;
- }
- if (second[len] == '\n' && !memcmp(name, second, len)) {
- return xmemdupz(name, len);
- }
- }
- }
-}
-
-/* Verify that we recognize the lines following a git header */
-static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch)
-{
- unsigned long offset;
-
- /* A git diff has explicit new/delete information, so we don't guess */
- patch->is_new = 0;
- patch->is_delete = 0;
-
- /*
- * Some things may not have the old name in the
- * rest of the headers anywhere (pure mode changes,
- * or removing or adding empty files), so we get
- * the default name from the header.
- */
- patch->def_name = git_header_name(line, len);
- if (patch->def_name && root) {
- char *s = xmalloc(root_len + strlen(patch->def_name) + 1);
- strcpy(s, root);
- strcpy(s + root_len, patch->def_name);
- free(patch->def_name);
- patch->def_name = s;
- }
-
- line += len;
- size -= len;
- linenr++;
- for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) {
- static const struct opentry {
- const char *str;
- int (*fn)(const char *, struct patch *);
- } optable[] = {
- { "@@ -", gitdiff_hdrend },
- { "--- ", gitdiff_oldname },
- { "+++ ", gitdiff_newname },
- { "old mode ", gitdiff_oldmode },
- { "new mode ", gitdiff_newmode },
- { "deleted file mode ", gitdiff_delete },
- { "new file mode ", gitdiff_newfile },
- { "copy from ", gitdiff_copysrc },
- { "copy to ", gitdiff_copydst },
- { "rename old ", gitdiff_renamesrc },
- { "rename new ", gitdiff_renamedst },
- { "rename from ", gitdiff_renamesrc },
- { "rename to ", gitdiff_renamedst },
- { "similarity index ", gitdiff_similarity },
- { "dissimilarity index ", gitdiff_dissimilarity },
- { "index ", gitdiff_index },
- { "", gitdiff_unrecognized },
- };
- int i;
-
- len = linelen(line, size);
- if (!len || line[len-1] != '\n')
- break;
- for (i = 0; i < ARRAY_SIZE(optable); i++) {
- const struct opentry *p = optable + i;
- int oplen = strlen(p->str);
- if (len < oplen || memcmp(p->str, line, oplen))
- continue;
- if (p->fn(line + oplen, patch) < 0)
- return offset;
- break;
- }
- }
-
- return offset;
-}
-
-static int parse_num(const char *line, unsigned long *p)
-{
- char *ptr;
-
- if (!isdigit(*line))
- return 0;
- *p = strtoul(line, &ptr, 10);
- return ptr - line;
-}
-
-static int parse_range(const char *line, int len, int offset, const char *expect,
- unsigned long *p1, unsigned long *p2)
-{
- int digits, ex;
-
- if (offset < 0 || offset >= len)
- return -1;
- line += offset;
- len -= offset;
-
- digits = parse_num(line, p1);
- if (!digits)
- return -1;
-
- offset += digits;
- line += digits;
- len -= digits;
-
- *p2 = 1;
- if (*line == ',') {
- digits = parse_num(line+1, p2);
- if (!digits)
- return -1;
-
- offset += digits+1;
- line += digits+1;
- len -= digits+1;
- }
-
- ex = strlen(expect);
- if (ex > len)
- return -1;
- if (memcmp(line, expect, ex))
- return -1;
-
- return offset + ex;
-}
-
-static void recount_diff(char *line, int size, struct fragment *fragment)
-{
- int oldlines = 0, newlines = 0, ret = 0;
-
- if (size < 1) {
- warning("recount: ignore empty hunk");
- return;
- }
-
- for (;;) {
- int len = linelen(line, size);
- size -= len;
- line += len;
-
- if (size < 1)
- break;
-
- switch (*line) {
- case ' ': case '\n':
- newlines++;
- /* fall through */
- case '-':
- oldlines++;
- continue;
- case '+':
- newlines++;
- continue;
- case '\\':
- continue;
- case '@':
- ret = size < 3 || prefixcmp(line, "@@ ");
- break;
- case 'd':
- ret = size < 5 || prefixcmp(line, "diff ");
- break;
- default:
- ret = -1;
- break;
- }
- if (ret) {
- warning("recount: unexpected line: %.*s",
- (int)linelen(line, size), line);
- return;
- }
- break;
- }
- fragment->oldlines = oldlines;
- fragment->newlines = newlines;
-}
-
-/*
- * Parse a unified diff fragment header of the
- * form "@@ -a,b +c,d @@"
- */
-static int parse_fragment_header(char *line, int len, struct fragment *fragment)
-{
- int offset;
-
- if (!len || line[len-1] != '\n')
- return -1;
-
- /* Figure out the number of lines in a fragment */
- offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines);
- offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines);
-
- return offset;
-}
-
-static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch)
-{
- unsigned long offset, len;
-
- patch->is_toplevel_relative = 0;
- patch->is_rename = patch->is_copy = 0;
- patch->is_new = patch->is_delete = -1;
- patch->old_mode = patch->new_mode = 0;
- patch->old_name = patch->new_name = NULL;
- for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) {
- unsigned long nextlen;
-
- len = linelen(line, size);
- if (!len)
- break;
-
- /* Testing this early allows us to take a few shortcuts.. */
- if (len < 6)
- continue;
-
- /*
- * Make sure we don't find any unconnected patch fragments.
- * That's a sign that we didn't find a header, and that a
- * patch has become corrupted/broken up.
- */
- if (!memcmp("@@ -", line, 4)) {
- struct fragment dummy;
- if (parse_fragment_header(line, len, &dummy) < 0)
- continue;
- die("patch fragment without header at line %d: %.*s",
- linenr, (int)len-1, line);
- }
-
- if (size < len + 6)
- break;
-
- /*
- * Git patch? It might not have a real patch, just a rename
- * or mode change, so we handle that specially
- */
- if (!memcmp("diff --git ", line, 11)) {
- int git_hdr_len = parse_git_header(line, len, size, patch);
- if (git_hdr_len <= len)
- continue;
- if (!patch->old_name && !patch->new_name) {
- if (!patch->def_name)
- die("git diff header lacks filename information when removing "
- "%d leading pathname components (line %d)" , p_value, linenr);
- patch->old_name = patch->new_name = patch->def_name;
- }
- patch->is_toplevel_relative = 1;
- *hdrsize = git_hdr_len;
- return offset;
- }
-
- /* --- followed by +++ ? */
- if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4))
- continue;
-
- /*
- * We only accept unified patches, so we want it to
- * at least have "@@ -a,b +c,d @@\n", which is 14 chars
- * minimum ("@@ -0,0 +1 @@\n" is the shortest).
- */
- nextlen = linelen(line + len, size - len);
- if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
- continue;
-
- /* Ok, we'll consider it a patch */
- parse_traditional_patch(line, line+len, patch);
- *hdrsize = len + nextlen;
- linenr += 2;
- return offset;
- }
- return -1;
-}
-
-static void record_ws_error(unsigned result, const char *line, int len, int linenr)
-{
- char *err;
-
- if (!result)
- return;
-
- whitespace_error++;
- if (squelch_whitespace_errors &&
- squelch_whitespace_errors < whitespace_error)
- return;
-
- err = whitespace_error_string(result);
- fprintf(stderr, "%s:%d: %s.\n%.*s\n",
- patch_input_file, linenr, err, len, line);
- free(err);
-}
-
-static void check_whitespace(const char *line, int len, unsigned ws_rule)
-{
- unsigned result = ws_check(line + 1, len - 1, ws_rule);
-
- record_ws_error(result, line + 1, len - 2, linenr);
-}
-
-/*
- * Parse a unified diff. Note that this really needs to parse each
- * fragment separately, since the only way to know the difference
- * between a "---" that is part of a patch, and a "---" that starts
- * the next patch is to look at the line counts..
- */
-static int parse_fragment(char *line, unsigned long size,
- struct patch *patch, struct fragment *fragment)
-{
- int added, deleted;
- int len = linelen(line, size), offset;
- unsigned long oldlines, newlines;
- unsigned long leading, trailing;
-
- offset = parse_fragment_header(line, len, fragment);
- if (offset < 0)
- return -1;
- if (offset > 0 && patch->recount)
- recount_diff(line + offset, size - offset, fragment);
- oldlines = fragment->oldlines;
- newlines = fragment->newlines;
- leading = 0;
- trailing = 0;
-
- /* Parse the thing.. */
- line += len;
- size -= len;
- linenr++;
- added = deleted = 0;
- for (offset = len;
- 0 < size;
- offset += len, size -= len, line += len, linenr++) {
- if (!oldlines && !newlines)
- break;
- len = linelen(line, size);
- if (!len || line[len-1] != '\n')
- return -1;
- switch (*line) {
- default:
- return -1;
- case '\n': /* newer GNU diff, an empty context line */
- case ' ':
- oldlines--;
- newlines--;
- if (!deleted && !added)
- leading++;
- trailing++;
- break;
- case '-':
- if (apply_in_reverse &&
- ws_error_action != nowarn_ws_error)
- check_whitespace(line, len, patch->ws_rule);
- deleted++;
- oldlines--;
- trailing = 0;
- break;
- case '+':
- if (!apply_in_reverse &&
- ws_error_action != nowarn_ws_error)
- check_whitespace(line, len, patch->ws_rule);
- added++;
- newlines--;
- trailing = 0;
- break;
-
- /*
- * We allow "\ No newline at end of file". Depending
- * on locale settings when the patch was produced we
- * don't know what this line looks like. The only
- * thing we do know is that it begins with "\ ".
- * Checking for 12 is just for sanity check -- any
- * l10n of "\ No newline..." is at least that long.
- */
- case '\\':
- if (len < 12 || memcmp(line, "\\ ", 2))
- return -1;
- break;
- }
- }
- if (oldlines || newlines)
- return -1;
- fragment->leading = leading;
- fragment->trailing = trailing;
-
- /*
- * If a fragment ends with an incomplete line, we failed to include
- * it in the above loop because we hit oldlines == newlines == 0
- * before seeing it.
- */
- if (12 < size && !memcmp(line, "\\ ", 2))
- offset += linelen(line, size);
-
- patch->lines_added += added;
- patch->lines_deleted += deleted;
-
- if (0 < patch->is_new && oldlines)
- return error("new file depends on old contents");
- if (0 < patch->is_delete && newlines)
- return error("deleted file still has contents");
- return offset;
-}
-
-static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
-{
- unsigned long offset = 0;
- unsigned long oldlines = 0, newlines = 0, context = 0;
- struct fragment **fragp = &patch->fragments;
-
- while (size > 4 && !memcmp(line, "@@ -", 4)) {
- struct fragment *fragment;
- int len;
-
- fragment = xcalloc(1, sizeof(*fragment));
- fragment->linenr = linenr;
- len = parse_fragment(line, size, patch, fragment);
- if (len <= 0)
- die("corrupt patch at line %d", linenr);
- fragment->patch = line;
- fragment->size = len;
- oldlines += fragment->oldlines;
- newlines += fragment->newlines;
- context += fragment->leading + fragment->trailing;
-
- *fragp = fragment;
- fragp = &fragment->next;
-
- offset += len;
- line += len;
- size -= len;
- }
-
- /*
- * If something was removed (i.e. we have old-lines) it cannot
- * be creation, and if something was added it cannot be
- * deletion. However, the reverse is not true; --unified=0
- * patches that only add are not necessarily creation even
- * though they do not have any old lines, and ones that only
- * delete are not necessarily deletion.
- *
- * Unfortunately, a real creation/deletion patch do _not_ have
- * any context line by definition, so we cannot safely tell it
- * apart with --unified=0 insanity. At least if the patch has
- * more than one hunk it is not creation or deletion.
- */
- if (patch->is_new < 0 &&
- (oldlines || (patch->fragments && patch->fragments->next)))
- patch->is_new = 0;
- if (patch->is_delete < 0 &&
- (newlines || (patch->fragments && patch->fragments->next)))
- patch->is_delete = 0;
-
- if (0 < patch->is_new && oldlines)
- die("new file %s depends on old contents", patch->new_name);
- if (0 < patch->is_delete && newlines)
- die("deleted file %s still has contents", patch->old_name);
- if (!patch->is_delete && !newlines && context)
- fprintf(stderr, "** warning: file %s becomes empty but "
- "is not deleted\n", patch->new_name);
-
- return offset;
-}
-
-static inline int metadata_changes(struct patch *patch)
-{
- return patch->is_rename > 0 ||
- patch->is_copy > 0 ||
- patch->is_new > 0 ||
- patch->is_delete ||
- (patch->old_mode && patch->new_mode &&
- patch->old_mode != patch->new_mode);
-}
-
-static char *inflate_it(const void *data, unsigned long size,
- unsigned long inflated_size)
-{
- z_stream stream;
- void *out;
- int st;
-
- memset(&stream, 0, sizeof(stream));
-
- stream.next_in = (unsigned char *)data;
- stream.avail_in = size;
- stream.next_out = out = xmalloc(inflated_size);
- stream.avail_out = inflated_size;
- git_inflate_init(&stream);
- st = git_inflate(&stream, Z_FINISH);
- git_inflate_end(&stream);
- if ((st != Z_STREAM_END) || stream.total_out != inflated_size) {
- free(out);
- return NULL;
- }
- return out;
-}
-
-static struct fragment *parse_binary_hunk(char **buf_p,
- unsigned long *sz_p,
- int *status_p,
- int *used_p)
-{
- /*
- * Expect a line that begins with binary patch method ("literal"
- * or "delta"), followed by the length of data before deflating.
- * a sequence of 'length-byte' followed by base-85 encoded data
- * should follow, terminated by a newline.
- *
- * Each 5-byte sequence of base-85 encodes up to 4 bytes,
- * and we would limit the patch line to 66 characters,
- * so one line can fit up to 13 groups that would decode
- * to 52 bytes max. The length byte 'A'-'Z' corresponds
- * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes.
- */
- int llen, used;
- unsigned long size = *sz_p;
- char *buffer = *buf_p;
- int patch_method;
- unsigned long origlen;
- char *data = NULL;
- int hunk_size = 0;
- struct fragment *frag;
-
- llen = linelen(buffer, size);
- used = llen;
-
- *status_p = 0;
-
- if (!prefixcmp(buffer, "delta ")) {
- patch_method = BINARY_DELTA_DEFLATED;
- origlen = strtoul(buffer + 6, NULL, 10);
- }
- else if (!prefixcmp(buffer, "literal ")) {
- patch_method = BINARY_LITERAL_DEFLATED;
- origlen = strtoul(buffer + 8, NULL, 10);
- }
- else
- return NULL;
-
- linenr++;
- buffer += llen;
- while (1) {
- int byte_length, max_byte_length, newsize;
- llen = linelen(buffer, size);
- used += llen;
- linenr++;
- if (llen == 1) {
- /* consume the blank line */
- buffer++;
- size--;
- break;
- }
- /*
- * Minimum line is "A00000\n" which is 7-byte long,
- * and the line length must be multiple of 5 plus 2.
- */
- if ((llen < 7) || (llen-2) % 5)
- goto corrupt;
- max_byte_length = (llen - 2) / 5 * 4;
- byte_length = *buffer;
- if ('A' <= byte_length && byte_length <= 'Z')
- byte_length = byte_length - 'A' + 1;
- else if ('a' <= byte_length && byte_length <= 'z')
- byte_length = byte_length - 'a' + 27;
- else
- goto corrupt;
- /* if the input length was not multiple of 4, we would
- * have filler at the end but the filler should never
- * exceed 3 bytes
- */
- if (max_byte_length < byte_length ||
- byte_length <= max_byte_length - 4)
- goto corrupt;
- newsize = hunk_size + byte_length;
- data = xrealloc(data, newsize);
- if (decode_85(data + hunk_size, buffer + 1, byte_length))
- goto corrupt;
- hunk_size = newsize;
- buffer += llen;
- size -= llen;
- }
-
- frag = xcalloc(1, sizeof(*frag));
- frag->patch = inflate_it(data, hunk_size, origlen);
- if (!frag->patch)
- goto corrupt;
- free(data);
- frag->size = origlen;
- *buf_p = buffer;
- *sz_p = size;
- *used_p = used;
- frag->binary_patch_method = patch_method;
- return frag;
-
- corrupt:
- free(data);
- *status_p = -1;
- error("corrupt binary patch at line %d: %.*s",
- linenr-1, llen-1, buffer);
- return NULL;
-}
-
-static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
-{
- /*
- * We have read "GIT binary patch\n"; what follows is a line
- * that says the patch method (currently, either "literal" or
- * "delta") and the length of data before deflating; a
- * sequence of 'length-byte' followed by base-85 encoded data
- * follows.
- *
- * When a binary patch is reversible, there is another binary
- * hunk in the same format, starting with patch method (either
- * "literal" or "delta") with the length of data, and a sequence
- * of length-byte + base-85 encoded data, terminated with another
- * empty line. This data, when applied to the postimage, produces
- * the preimage.
- */
- struct fragment *forward;
- struct fragment *reverse;
- int status;
- int used, used_1;
-
- forward = parse_binary_hunk(&buffer, &size, &status, &used);
- if (!forward && !status)
- /* there has to be one hunk (forward hunk) */
- return error("unrecognized binary patch at line %d", linenr-1);
- if (status)
- /* otherwise we already gave an error message */
- return status;
-
- reverse = parse_binary_hunk(&buffer, &size, &status, &used_1);
- if (reverse)
- used += used_1;
- else if (status) {
- /*
- * Not having reverse hunk is not an error, but having
- * a corrupt reverse hunk is.
- */
- free((void*) forward->patch);
- free(forward);
- return status;
- }
- forward->next = reverse;
- patch->fragments = forward;
- patch->is_binary = 1;
- return used;
-}
-
-static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
-{
- int hdrsize, patchsize;
- int offset = find_header(buffer, size, &hdrsize, patch);
-
- if (offset < 0)
- return offset;
-
- patch->ws_rule = whitespace_rule(patch->new_name
- ? patch->new_name
- : patch->old_name);
-
- patchsize = parse_single_patch(buffer + offset + hdrsize,
- size - offset - hdrsize, patch);
-
- if (!patchsize) {
- static const char *binhdr[] = {
- "Binary files ",
- "Files ",
- NULL,
- };
- static const char git_binary[] = "GIT binary patch\n";
- int i;
- int hd = hdrsize + offset;
- unsigned long llen = linelen(buffer + hd, size - hd);
-
- if (llen == sizeof(git_binary) - 1 &&
- !memcmp(git_binary, buffer + hd, llen)) {
- int used;
- linenr++;
- used = parse_binary(buffer + hd + llen,
- size - hd - llen, patch);
- if (used)
- patchsize = used + llen;
- else
- patchsize = 0;
- }
- else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) {
- for (i = 0; binhdr[i]; i++) {
- int len = strlen(binhdr[i]);
- if (len < size - hd &&
- !memcmp(binhdr[i], buffer + hd, len)) {
- linenr++;
- patch->is_binary = 1;
- patchsize = llen;
- break;
- }
- }
- }
-
- /* Empty patch cannot be applied if it is a text patch
- * without metadata change. A binary patch appears
- * empty to us here.
- */
- if ((apply || check) &&
- (!patch->is_binary && !metadata_changes(patch)))
- die("patch with only garbage at line %d", linenr);
- }
-
- return offset + hdrsize + patchsize;
-}
-
-#define swap(a,b) myswap((a),(b),sizeof(a))
-
-#define myswap(a, b, size) do { \
- unsigned char mytmp[size]; \
- memcpy(mytmp, &a, size); \
- memcpy(&a, &b, size); \
- memcpy(&b, mytmp, size); \
-} while (0)
-
-static void reverse_patches(struct patch *p)
-{
- for (; p; p = p->next) {
- struct fragment *frag = p->fragments;
-
- swap(p->new_name, p->old_name);
- swap(p->new_mode, p->old_mode);
- swap(p->is_new, p->is_delete);
- swap(p->lines_added, p->lines_deleted);
- swap(p->old_sha1_prefix, p->new_sha1_prefix);
-
- for (; frag; frag = frag->next) {
- swap(frag->newpos, frag->oldpos);
- swap(frag->newlines, frag->oldlines);
- }
- }
-}
-
-static const char pluses[] =
-"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]=
-"----------------------------------------------------------------------";
-
-static void show_stats(struct patch *patch)
-{
- struct strbuf qname = STRBUF_INIT;
- char *cp = patch->new_name ? patch->new_name : patch->old_name;
- int max, add, del;
-
- quote_c_style(cp, &qname, NULL, 0);
-
- /*
- * "scale" the filename
- */
- max = max_len;
- if (max > 50)
- max = 50;
-
- if (qname.len > max) {
- cp = strchr(qname.buf + qname.len + 3 - max, '/');
- if (!cp)
- cp = qname.buf + qname.len + 3 - max;
- strbuf_splice(&qname, 0, cp - qname.buf, "...", 3);
- }
-
- if (patch->is_binary) {
- printf(" %-*s | Bin\n", max, qname.buf);
- strbuf_release(&qname);
- return;
- }
-
- printf(" %-*s |", max, qname.buf);
- strbuf_release(&qname);
-
- /*
- * scale the add/delete
- */
- max = max + max_change > 70 ? 70 - max : max_change;
- add = patch->lines_added;
- del = patch->lines_deleted;
-
- if (max_change > 0) {
- int total = ((add + del) * max + max_change / 2) / max_change;
- add = (add * max + max_change / 2) / max_change;
- del = total - add;
- }
- printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted,
- add, pluses, del, minuses);
-}
-
-static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
-{
- switch (st->st_mode & S_IFMT) {
- case S_IFLNK:
- if (strbuf_readlink(buf, path, st->st_size) < 0)
- return error("unable to read symlink %s", path);
- return 0;
- case S_IFREG:
- if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
- return error("unable to open or read %s", path);
- convert_to_git(path, buf->buf, buf->len, buf, 0);
- return 0;
- default:
- return -1;
- }
-}
-
-/*
- * Update the preimage, and the common lines in postimage,
- * from buffer buf of length len. If postlen is 0 the postimage
- * is updated in place, otherwise it's updated on a new buffer
- * of length postlen
- */
-
-static void update_pre_post_images(struct image *preimage,
- struct image *postimage,
- char *buf,
- size_t len, size_t postlen)
-{
- int i, ctx;
- char *new, *old, *fixed;
- struct image fixed_preimage;
-
- /*
- * Update the preimage with whitespace fixes. Note that we
- * are not losing preimage->buf -- apply_one_fragment() will
- * free "oldlines".
- */
- prepare_image(&fixed_preimage, buf, len, 1);
- assert(fixed_preimage.nr == preimage->nr);
- for (i = 0; i < preimage->nr; i++)
- fixed_preimage.line[i].flag = preimage->line[i].flag;
- free(preimage->line_allocated);
- *preimage = fixed_preimage;
-
- /*
- * Adjust the common context lines in postimage. This can be
- * done in-place when we are just doing whitespace fixing,
- * which does not make the string grow, but needs a new buffer
- * when ignoring whitespace causes the update, since in this case
- * we could have e.g. tabs converted to multiple spaces.
- * We trust the caller to tell us if the update can be done
- * in place (postlen==0) or not.
- */
- old = postimage->buf;
- if (postlen)
- new = postimage->buf = xmalloc(postlen);
- else
- new = old;
- fixed = preimage->buf;
- for (i = ctx = 0; i < postimage->nr; i++) {
- size_t len = postimage->line[i].len;
- if (!(postimage->line[i].flag & LINE_COMMON)) {
- /* an added line -- no counterparts in preimage */
- memmove(new, old, len);
- old += len;
- new += len;
- continue;
- }
-
- /* a common context -- skip it in the original postimage */
- old += len;
-
- /* and find the corresponding one in the fixed preimage */
- while (ctx < preimage->nr &&
- !(preimage->line[ctx].flag & LINE_COMMON)) {
- fixed += preimage->line[ctx].len;
- ctx++;
- }
- if (preimage->nr <= ctx)
- die("oops");
-
- /* and copy it in, while fixing the line length */
- len = preimage->line[ctx].len;
- memcpy(new, fixed, len);
- new += len;
- fixed += len;
- postimage->line[i].len = len;
- ctx++;
- }
-
- /* Fix the length of the whole thing */
- postimage->len = new - postimage->buf;
-}
-
-static int match_fragment(struct image *img,
- struct image *preimage,
- struct image *postimage,
- unsigned long try,
- int try_lno,
- unsigned ws_rule,
- int match_beginning, int match_end)
-{
- int i;
- char *fixed_buf, *buf, *orig, *target;
-
- if (preimage->nr + try_lno > img->nr)
- return 0;
-
- if (match_beginning && try_lno)
- return 0;
-
- if (match_end && preimage->nr + try_lno != img->nr)
- return 0;
-
- /* Quick hash check */
- for (i = 0; i < preimage->nr; i++)
- if (preimage->line[i].hash != img->line[try_lno + i].hash)
- return 0;
-
- /*
- * Do we have an exact match? If we were told to match
- * at the end, size must be exactly at try+fragsize,
- * otherwise try+fragsize must be still within the preimage,
- * and either case, the old piece should match the preimage
- * exactly.
- */
- if ((match_end
- ? (try + preimage->len == img->len)
- : (try + preimage->len <= img->len)) &&
- !memcmp(img->buf + try, preimage->buf, preimage->len))
- return 1;
-
- /*
- * No exact match. If we are ignoring whitespace, run a line-by-line
- * fuzzy matching. We collect all the line length information because
- * we need it to adjust whitespace if we match.
- */
- if (ws_ignore_action == ignore_ws_change) {
- size_t imgoff = 0;
- size_t preoff = 0;
- size_t postlen = postimage->len;
- for (i = 0; i < preimage->nr; i++) {
- size_t prelen = preimage->line[i].len;
- size_t imglen = img->line[try_lno+i].len;
-
- if (!fuzzy_matchlines(img->buf + try + imgoff, imglen,
- preimage->buf + preoff, prelen))
- return 0;
- if (preimage->line[i].flag & LINE_COMMON)
- postlen += imglen - prelen;
- imgoff += imglen;
- preoff += prelen;
- }
-
- /*
- * Ok, the preimage matches with whitespace fuzz. Update it and
- * the common postimage lines to use the same whitespace as the
- * target. imgoff now holds the true length of the target that
- * matches the preimage, and we need to update the line lengths
- * of the preimage to match the target ones.
- */
- fixed_buf = xmalloc(imgoff);
- memcpy(fixed_buf, img->buf + try, imgoff);
- for (i = 0; i < preimage->nr; i++)
- preimage->line[i].len = img->line[try_lno+i].len;
-
- /*
- * Update the preimage buffer and the postimage context lines.
- */
- update_pre_post_images(preimage, postimage,
- fixed_buf, imgoff, postlen);
- return 1;
- }
-
- if (ws_error_action != correct_ws_error)
- return 0;
-
- /*
- * The hunk does not apply byte-by-byte, but the hash says
- * it might with whitespace fuzz. We haven't been asked to
- * ignore whitespace, we were asked to correct whitespace
- * errors, so let's try matching after whitespace correction.
- */
- fixed_buf = xmalloc(preimage->len + 1);
- buf = fixed_buf;
- orig = preimage->buf;
- target = img->buf + try;
- for (i = 0; i < preimage->nr; i++) {
- size_t fixlen; /* length after fixing the preimage */
- size_t oldlen = preimage->line[i].len;
- size_t tgtlen = img->line[try_lno + i].len;
- size_t tgtfixlen; /* length after fixing the target line */
- char tgtfixbuf[1024], *tgtfix;
- int match;
-
- /* Try fixing the line in the preimage */
- fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL);
-
- /* Try fixing the line in the target */
- if (sizeof(tgtfixbuf) > tgtlen)
- tgtfix = tgtfixbuf;
- else
- tgtfix = xmalloc(tgtlen);
- tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL);
-
- /*
- * If they match, either the preimage was based on
- * a version before our tree fixed whitespace breakage,
- * or we are lacking a whitespace-fix patch the tree
- * the preimage was based on already had (i.e. target
- * has whitespace breakage, the preimage doesn't).
- * In either case, we are fixing the whitespace breakages
- * so we might as well take the fix together with their
- * real change.
- */
- match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen));
-
- if (tgtfix != tgtfixbuf)
- free(tgtfix);
- if (!match)
- goto unmatch_exit;
-
- orig += oldlen;
- buf += fixlen;
- target += tgtlen;
- }
-
- /*
- * Yes, the preimage is based on an older version that still
- * has whitespace breakages unfixed, and fixing them makes the
- * hunk match. Update the context lines in the postimage.
- */
- update_pre_post_images(preimage, postimage,
- fixed_buf, buf - fixed_buf, 0);
- return 1;
-
- unmatch_exit:
- free(fixed_buf);
- return 0;
-}
-
-static int find_pos(struct image *img,
- struct image *preimage,
- struct image *postimage,
- int line,
- unsigned ws_rule,
- int match_beginning, int match_end)
-{
- int i;
- unsigned long backwards, forwards, try;
- int backwards_lno, forwards_lno, try_lno;
-
- if (preimage->nr > img->nr)
- return -1;
-
- /*
- * If match_beginning or match_end is specified, there is no
- * point starting from a wrong line that will never match and
- * wander around and wait for a match at the specified end.
- */
- if (match_beginning)
- line = 0;
- else if (match_end)
- line = img->nr - preimage->nr;
-
- if (line > img->nr)
- line = img->nr;
-
- try = 0;
- for (i = 0; i < line; i++)
- try += img->line[i].len;
-
- /*
- * There's probably some smart way to do this, but I'll leave
- * that to the smart and beautiful people. I'm simple and stupid.
- */
- backwards = try;
- backwards_lno = line;
- forwards = try;
- forwards_lno = line;
- try_lno = line;
-
- for (i = 0; ; i++) {
- if (match_fragment(img, preimage, postimage,
- try, try_lno, ws_rule,
- match_beginning, match_end))
- return try_lno;
-
- again:
- if (backwards_lno == 0 && forwards_lno == img->nr)
- break;
-
- if (i & 1) {
- if (backwards_lno == 0) {
- i++;
- goto again;
- }
- backwards_lno--;
- backwards -= img->line[backwards_lno].len;
- try = backwards;
- try_lno = backwards_lno;
- } else {
- if (forwards_lno == img->nr) {
- i++;
- goto again;
- }
- forwards += img->line[forwards_lno].len;
- forwards_lno++;
- try = forwards;
- try_lno = forwards_lno;
- }
-
- }
- return -1;
-}
-
-static void remove_first_line(struct image *img)
-{
- img->buf += img->line[0].len;
- img->len -= img->line[0].len;
- img->line++;
- img->nr--;
-}
-
-static void remove_last_line(struct image *img)
-{
- img->len -= img->line[--img->nr].len;
-}
-
-static void update_image(struct image *img,
- int applied_pos,
- struct image *preimage,
- struct image *postimage)
-{
- /*
- * remove the copy of preimage at offset in img
- * and replace it with postimage
- */
- int i, nr;
- size_t remove_count, insert_count, applied_at = 0;
- char *result;
-
- for (i = 0; i < applied_pos; i++)
- applied_at += img->line[i].len;
-
- remove_count = 0;
- for (i = 0; i < preimage->nr; i++)
- remove_count += img->line[applied_pos + i].len;
- insert_count = postimage->len;
-
- /* Adjust the contents */
- result = xmalloc(img->len + insert_count - remove_count + 1);
- memcpy(result, img->buf, applied_at);
- memcpy(result + applied_at, postimage->buf, postimage->len);
- memcpy(result + applied_at + postimage->len,
- img->buf + (applied_at + remove_count),
- img->len - (applied_at + remove_count));
- free(img->buf);
- img->buf = result;
- img->len += insert_count - remove_count;
- result[img->len] = '\0';
-
- /* Adjust the line table */
- nr = img->nr + postimage->nr - preimage->nr;
- if (preimage->nr < postimage->nr) {
- /*
- * NOTE: this knows that we never call remove_first_line()
- * on anything other than pre/post image.
- */
- img->line = xrealloc(img->line, nr * sizeof(*img->line));
- img->line_allocated = img->line;
- }
- if (preimage->nr != postimage->nr)
- memmove(img->line + applied_pos + postimage->nr,
- img->line + applied_pos + preimage->nr,
- (img->nr - (applied_pos + preimage->nr)) *
- sizeof(*img->line));
- memcpy(img->line + applied_pos,
- postimage->line,
- postimage->nr * sizeof(*img->line));
- img->nr = nr;
-}
-
-static int apply_one_fragment(struct image *img, struct fragment *frag,
- int inaccurate_eof, unsigned ws_rule)
-{
- int match_beginning, match_end;
- const char *patch = frag->patch;
- int size = frag->size;
- char *old, *new, *oldlines, *newlines;
- int new_blank_lines_at_end = 0;
- unsigned long leading, trailing;
- int pos, applied_pos;
- struct image preimage;
- struct image postimage;
-
- memset(&preimage, 0, sizeof(preimage));
- memset(&postimage, 0, sizeof(postimage));
- oldlines = xmalloc(size);
- newlines = xmalloc(size);
-
- old = oldlines;
- new = newlines;
- while (size > 0) {
- char first;
- int len = linelen(patch, size);
- int plen, added;
- int added_blank_line = 0;
- int is_blank_context = 0;
-
- if (!len)
- break;
-
- /*
- * "plen" is how much of the line we should use for
- * the actual patch data. Normally we just remove the
- * first character on the line, but if the line is
- * followed by "\ No newline", then we also remove the
- * last one (which is the newline, of course).
- */
- plen = len - 1;
- if (len < size && patch[len] == '\\')
- plen--;
- first = *patch;
- if (apply_in_reverse) {
- if (first == '-')
- first = '+';
- else if (first == '+')
- first = '-';
- }
-
- switch (first) {
- case '\n':
- /* Newer GNU diff, empty context line */
- if (plen < 0)
- /* ... followed by '\No newline'; nothing */
- break;
- *old++ = '\n';
- *new++ = '\n';
- add_line_info(&preimage, "\n", 1, LINE_COMMON);
- add_line_info(&postimage, "\n", 1, LINE_COMMON);
- is_blank_context = 1;
- break;
- case ' ':
- if (plen && (ws_rule & WS_BLANK_AT_EOF) &&
- ws_blank_line(patch + 1, plen, ws_rule))
- is_blank_context = 1;
- case '-':
- memcpy(old, patch + 1, plen);
- add_line_info(&preimage, old, plen,
- (first == ' ' ? LINE_COMMON : 0));
- old += plen;
- if (first == '-')
- break;
- /* Fall-through for ' ' */
- case '+':
- /* --no-add does not add new lines */
- if (first == '+' && no_add)
- break;
-
- if (first != '+' ||
- !whitespace_error ||
- ws_error_action != correct_ws_error) {
- memcpy(new, patch + 1, plen);
- added = plen;
- }
- else {
- added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws);
- }
- add_line_info(&postimage, new, added,
- (first == '+' ? 0 : LINE_COMMON));
- new += added;
- if (first == '+' &&
- (ws_rule & WS_BLANK_AT_EOF) &&
- ws_blank_line(patch + 1, plen, ws_rule))
- added_blank_line = 1;
- break;
- case '@': case '\\':
- /* Ignore it, we already handled it */
- break;
- default:
- if (apply_verbosely)
- error("invalid start of line: '%c'", first);
- return -1;
- }
- if (added_blank_line)
- new_blank_lines_at_end++;
- else if (is_blank_context)
- ;
- else
- new_blank_lines_at_end = 0;
- patch += len;
- size -= len;
- }
- if (inaccurate_eof &&
- old > oldlines && old[-1] == '\n' &&
- new > newlines && new[-1] == '\n') {
- old--;
- new--;
- }
-
- leading = frag->leading;
- trailing = frag->trailing;
-
- /*
- * A hunk to change lines at the beginning would begin with
- * @@ -1,L +N,M @@
- * but we need to be careful. -U0 that inserts before the second
- * line also has this pattern.
- *
- * And a hunk to add to an empty file would begin with
- * @@ -0,0 +N,M @@
- *
- * In other words, a hunk that is (frag->oldpos <= 1) with or
- * without leading context must match at the beginning.
- */
- match_beginning = (!frag->oldpos ||
- (frag->oldpos == 1 && !unidiff_zero));
-
- /*
- * A hunk without trailing lines must match at the end.
- * However, we simply cannot tell if a hunk must match end
- * from the lack of trailing lines if the patch was generated
- * with unidiff without any context.
- */
- match_end = !unidiff_zero && !trailing;
-
- pos = frag->newpos ? (frag->newpos - 1) : 0;
- preimage.buf = oldlines;
- preimage.len = old - oldlines;
- postimage.buf = newlines;
- postimage.len = new - newlines;
- preimage.line = preimage.line_allocated;
- postimage.line = postimage.line_allocated;
-
- for (;;) {
-
- applied_pos = find_pos(img, &preimage, &postimage, pos,
- ws_rule, match_beginning, match_end);
-
- if (applied_pos >= 0)
- break;
-
- /* Am I at my context limits? */
- if ((leading <= p_context) && (trailing <= p_context))
- break;
- if (match_beginning || match_end) {
- match_beginning = match_end = 0;
- continue;
- }
-
- /*
- * Reduce the number of context lines; reduce both
- * leading and trailing if they are equal otherwise
- * just reduce the larger context.
- */
- if (leading >= trailing) {
- remove_first_line(&preimage);
- remove_first_line(&postimage);
- pos--;
- leading--;
- }
- if (trailing > leading) {
- remove_last_line(&preimage);
- remove_last_line(&postimage);
- trailing--;
- }
- }
-
- if (applied_pos >= 0) {
- if (new_blank_lines_at_end &&
- preimage.nr + applied_pos == img->nr &&
- (ws_rule & WS_BLANK_AT_EOF) &&
- ws_error_action != nowarn_ws_error) {
- record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr);
- if (ws_error_action == correct_ws_error) {
- while (new_blank_lines_at_end--)
- remove_last_line(&postimage);
- }
- /*
- * We would want to prevent write_out_results()
- * from taking place in apply_patch() that follows
- * the callchain led us here, which is:
- * apply_patch->check_patch_list->check_patch->
- * apply_data->apply_fragments->apply_one_fragment
- */
- if (ws_error_action == die_on_ws_error)
- apply = 0;
- }
-
- /*
- * Warn if it was necessary to reduce the number
- * of context lines.
- */
- if ((leading != frag->leading) ||
- (trailing != frag->trailing))
- fprintf(stderr, "Context reduced to (%ld/%ld)"
- " to apply fragment at %d\n",
- leading, trailing, applied_pos+1);
- update_image(img, applied_pos, &preimage, &postimage);
- } else {
- if (apply_verbosely)
- error("while searching for:\n%.*s",
- (int)(old - oldlines), oldlines);
- }
-
- free(oldlines);
- free(newlines);
- free(preimage.line_allocated);
- free(postimage.line_allocated);
-
- return (applied_pos < 0);
-}
-
-static int apply_binary_fragment(struct image *img, struct patch *patch)
-{
- struct fragment *fragment = patch->fragments;
- unsigned long len;
- void *dst;
-
- /* Binary patch is irreversible without the optional second hunk */
- if (apply_in_reverse) {
- if (!fragment->next)
- return error("cannot reverse-apply a binary patch "
- "without the reverse hunk to '%s'",
- patch->new_name
- ? patch->new_name : patch->old_name);
- fragment = fragment->next;
- }
- switch (fragment->binary_patch_method) {
- case BINARY_DELTA_DEFLATED:
- dst = patch_delta(img->buf, img->len, fragment->patch,
- fragment->size, &len);
- if (!dst)
- return -1;
- clear_image(img);
- img->buf = dst;
- img->len = len;
- return 0;
- case BINARY_LITERAL_DEFLATED:
- clear_image(img);
- img->len = fragment->size;
- img->buf = xmalloc(img->len+1);
- memcpy(img->buf, fragment->patch, img->len);
- img->buf[img->len] = '\0';
- return 0;
- }
- return -1;
-}
-
-static int apply_binary(struct image *img, struct patch *patch)
-{
- const char *name = patch->old_name ? patch->old_name : patch->new_name;
- unsigned char sha1[20];
-
- /*
- * For safety, we require patch index line to contain
- * full 40-byte textual SHA1 for old and new, at least for now.
- */
- if (strlen(patch->old_sha1_prefix) != 40 ||
- strlen(patch->new_sha1_prefix) != 40 ||
- get_sha1_hex(patch->old_sha1_prefix, sha1) ||
- get_sha1_hex(patch->new_sha1_prefix, sha1))
- return error("cannot apply binary patch to '%s' "
- "without full index line", name);
-
- if (patch->old_name) {
- /*
- * See if the old one matches what the patch
- * applies to.
- */
- hash_sha1_file(img->buf, img->len, blob_type, sha1);
- if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
- return error("the patch applies to '%s' (%s), "
- "which does not match the "
- "current contents.",
- name, sha1_to_hex(sha1));
- }
- else {
- /* Otherwise, the old one must be empty. */
- if (img->len)
- return error("the patch applies to an empty "
- "'%s' but it is not empty", name);
- }
-
- get_sha1_hex(patch->new_sha1_prefix, sha1);
- if (is_null_sha1(sha1)) {
- clear_image(img);
- return 0; /* deletion patch */
- }
-
- if (has_sha1_file(sha1)) {
- /* We already have the postimage */
- enum object_type type;
- unsigned long size;
- char *result;
-
- result = read_sha1_file(sha1, &type, &size);
- if (!result)
- return error("the necessary postimage %s for "
- "'%s' cannot be read",
- patch->new_sha1_prefix, name);
- clear_image(img);
- img->buf = result;
- img->len = size;
- } else {
- /*
- * We have verified buf matches the preimage;
- * apply the patch data to it, which is stored
- * in the patch->fragments->{patch,size}.
- */
- if (apply_binary_fragment(img, patch))
- return error("binary patch does not apply to '%s'",
- name);
-
- /* verify that the result matches */
- hash_sha1_file(img->buf, img->len, blob_type, sha1);
- if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
- return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
- name, patch->new_sha1_prefix, sha1_to_hex(sha1));
- }
-
- return 0;
-}
-
-static int apply_fragments(struct image *img, struct patch *patch)
-{
- struct fragment *frag = patch->fragments;
- const char *name = patch->old_name ? patch->old_name : patch->new_name;
- unsigned ws_rule = patch->ws_rule;
- unsigned inaccurate_eof = patch->inaccurate_eof;
-
- if (patch->is_binary)
- return apply_binary(img, patch);
-
- while (frag) {
- if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) {
- error("patch failed: %s:%ld", name, frag->oldpos);
- if (!apply_with_reject)
- return -1;
- frag->rejected = 1;
- }
- frag = frag->next;
- }
- return 0;
-}
-
-static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
-{
- if (!ce)
- return 0;
-
- if (S_ISGITLINK(ce->ce_mode)) {
- strbuf_grow(buf, 100);
- strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
- } else {
- enum object_type type;
- unsigned long sz;
- char *result;
-
- result = read_sha1_file(ce->sha1, &type, &sz);
- if (!result)
- return -1;
- /* XXX read_sha1_file NUL-terminates */
- strbuf_attach(buf, result, sz, sz + 1);
- }
- return 0;
-}
-
-static struct patch *in_fn_table(const char *name)
-{
- struct string_list_item *item;
-
- if (name == NULL)
- return NULL;
-
- item = string_list_lookup(name, &fn_table);
- if (item != NULL)
- return (struct patch *)item->util;
-
- return NULL;
-}
-
-/*
- * item->util in the filename table records the status of the path.
- * Usually it points at a patch (whose result records the contents
- * of it after applying it), but it could be PATH_WAS_DELETED for a
- * path that a previously applied patch has already removed.
- */
- #define PATH_TO_BE_DELETED ((struct patch *) -2)
-#define PATH_WAS_DELETED ((struct patch *) -1)
-
-static int to_be_deleted(struct patch *patch)
-{
- return patch == PATH_TO_BE_DELETED;
-}
-
-static int was_deleted(struct patch *patch)
-{
- return patch == PATH_WAS_DELETED;
-}
-
-static void add_to_fn_table(struct patch *patch)
-{
- struct string_list_item *item;
-
- /*
- * Always add new_name unless patch is a deletion
- * This should cover the cases for normal diffs,
- * file creations and copies
- */
- if (patch->new_name != NULL) {
- item = string_list_insert(patch->new_name, &fn_table);
- item->util = patch;
- }
-
- /*
- * store a failure on rename/deletion cases because
- * later chunks shouldn't patch old names
- */
- if ((patch->new_name == NULL) || (patch->is_rename)) {
- item = string_list_insert(patch->old_name, &fn_table);
- item->util = PATH_WAS_DELETED;
- }
-}
-
-static void prepare_fn_table(struct patch *patch)
-{
- /*
- * store information about incoming file deletion
- */
- while (patch) {
- if ((patch->new_name == NULL) || (patch->is_rename)) {
- struct string_list_item *item;
- item = string_list_insert(patch->old_name, &fn_table);
- item->util = PATH_TO_BE_DELETED;
- }
- patch = patch->next;
- }
-}
-
-static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
-{
- struct strbuf buf = STRBUF_INIT;
- struct image image;
- size_t len;
- char *img;
- struct patch *tpatch;
-
- if (!(patch->is_copy || patch->is_rename) &&
- (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
- if (was_deleted(tpatch)) {
- return error("patch %s has been renamed/deleted",
- patch->old_name);
- }
- /* We have a patched copy in memory use that */
- strbuf_add(&buf, tpatch->result, tpatch->resultsize);
- } else if (cached) {
- if (read_file_or_gitlink(ce, &buf))
- return error("read of %s failed", patch->old_name);
- } else if (patch->old_name) {
- if (S_ISGITLINK(patch->old_mode)) {
- if (ce) {
- read_file_or_gitlink(ce, &buf);
- } else {
- /*
- * There is no way to apply subproject
- * patch without looking at the index.
- */
- patch->fragments = NULL;
- }
- } else {
- if (read_old_data(st, patch->old_name, &buf))
- return error("read of %s failed", patch->old_name);
- }
- }
-
- img = strbuf_detach(&buf, &len);
- prepare_image(&image, img, len, !patch->is_binary);
-
- if (apply_fragments(&image, patch) < 0)
- return -1; /* note with --reject this succeeds. */
- patch->result = image.buf;
- patch->resultsize = image.len;
- add_to_fn_table(patch);
- free(image.line_allocated);
-
- if (0 < patch->is_delete && patch->resultsize)
- return error("removal patch leaves file contents");
-
- return 0;
-}
-
-static int check_to_create_blob(const char *new_name, int ok_if_exists)
-{
- struct stat nst;
- if (!lstat(new_name, &nst)) {
- if (S_ISDIR(nst.st_mode) || ok_if_exists)
- return 0;
- /*
- * A leading component of new_name might be a symlink
- * that is going to be removed with this patch, but
- * still pointing at somewhere that has the path.
- * In such a case, path "new_name" does not exist as
- * far as git is concerned.
- */
- if (has_symlink_leading_path(new_name, strlen(new_name)))
- return 0;
-
- return error("%s: already exists in working directory", new_name);
- }
- else if ((errno != ENOENT) && (errno != ENOTDIR))
- return error("%s: %s", new_name, strerror(errno));
- return 0;
-}
-
-static int verify_index_match(struct cache_entry *ce, struct stat *st)
-{
- if (S_ISGITLINK(ce->ce_mode)) {
- if (!S_ISDIR(st->st_mode))
- return -1;
- return 0;
- }
- return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
-}
-
-static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
-{
- const char *old_name = patch->old_name;
- struct patch *tpatch = NULL;
- int stat_ret = 0;
- unsigned st_mode = 0;
-
- /*
- * Make sure that we do not have local modifications from the
- * index when we are looking at the index. Also make sure
- * we have the preimage file to be patched in the work tree,
- * unless --cached, which tells git to apply only in the index.
- */
- if (!old_name)
- return 0;
-
- assert(patch->is_new <= 0);
-
- if (!(patch->is_copy || patch->is_rename) &&
- (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
- if (was_deleted(tpatch))
- return error("%s: has been deleted/renamed", old_name);
- st_mode = tpatch->new_mode;
- } else if (!cached) {
- stat_ret = lstat(old_name, st);
- if (stat_ret && errno != ENOENT)
- return error("%s: %s", old_name, strerror(errno));
- }
-
- if (to_be_deleted(tpatch))
- tpatch = NULL;
-
- if (check_index && !tpatch) {
- int pos = cache_name_pos(old_name, strlen(old_name));
- if (pos < 0) {
- if (patch->is_new < 0)
- goto is_new;
- return error("%s: does not exist in index", old_name);
- }
- *ce = active_cache[pos];
- if (stat_ret < 0) {
- struct checkout costate;
- /* checkout */
- costate.base_dir = "";
- costate.base_dir_len = 0;
- costate.force = 0;
- costate.quiet = 0;
- costate.not_new = 0;
- costate.refresh_cache = 1;
- if (checkout_entry(*ce, &costate, NULL) ||
- lstat(old_name, st))
- return -1;
- }
- if (!cached && verify_index_match(*ce, st))
- return error("%s: does not match index", old_name);
- if (cached)
- st_mode = (*ce)->ce_mode;
- } else if (stat_ret < 0) {
- if (patch->is_new < 0)
- goto is_new;
- return error("%s: %s", old_name, strerror(errno));
- }
-
- if (!cached && !tpatch)
- st_mode = ce_mode_from_stat(*ce, st->st_mode);
-
- if (patch->is_new < 0)
- patch->is_new = 0;
- if (!patch->old_mode)
- patch->old_mode = st_mode;
- if ((st_mode ^ patch->old_mode) & S_IFMT)
- return error("%s: wrong type", old_name);
- if (st_mode != patch->old_mode)
- warning("%s has type %o, expected %o",
- old_name, st_mode, patch->old_mode);
- if (!patch->new_mode && !patch->is_delete)
- patch->new_mode = st_mode;
- return 0;
-
- is_new:
- patch->is_new = 1;
- patch->is_delete = 0;
- patch->old_name = NULL;
- return 0;
-}
-
-static int check_patch(struct patch *patch)
-{
- struct stat st;
- const char *old_name = patch->old_name;
- const char *new_name = patch->new_name;
- const char *name = old_name ? old_name : new_name;
- struct cache_entry *ce = NULL;
- struct patch *tpatch;
- int ok_if_exists;
- int status;
-
- patch->rejected = 1; /* we will drop this after we succeed */
-
- status = check_preimage(patch, &ce, &st);
- if (status)
- return status;
- old_name = patch->old_name;
-
- if ((tpatch = in_fn_table(new_name)) &&
- (was_deleted(tpatch) || to_be_deleted(tpatch)))
- /*
- * A type-change diff is always split into a patch to
- * delete old, immediately followed by a patch to
- * create new (see diff.c::run_diff()); in such a case
- * it is Ok that the entry to be deleted by the
- * previous patch is still in the working tree and in
- * the index.
- */
- ok_if_exists = 1;
- else
- ok_if_exists = 0;
-
- if (new_name &&
- ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
- if (check_index &&
- cache_name_pos(new_name, strlen(new_name)) >= 0 &&
- !ok_if_exists)
- return error("%s: already exists in index", new_name);
- if (!cached) {
- int err = check_to_create_blob(new_name, ok_if_exists);
- if (err)
- return err;
- }
- if (!patch->new_mode) {
- if (0 < patch->is_new)
- patch->new_mode = S_IFREG | 0644;
- else
- patch->new_mode = patch->old_mode;
- }
- }
-
- if (new_name && old_name) {
- int same = !strcmp(old_name, new_name);
- if (!patch->new_mode)
- patch->new_mode = patch->old_mode;
- if ((patch->old_mode ^ patch->new_mode) & S_IFMT)
- return error("new mode (%o) of %s does not match old mode (%o)%s%s",
- patch->new_mode, new_name, patch->old_mode,
- same ? "" : " of ", same ? "" : old_name);
- }
-
- if (apply_data(patch, &st, ce) < 0)
- return error("%s: patch does not apply", name);
- patch->rejected = 0;
- return 0;
-}
-
-static int check_patch_list(struct patch *patch)
-{
- int err = 0;
-
- prepare_fn_table(patch);
- while (patch) {
- if (apply_verbosely)
- say_patch_name(stderr,
- "Checking patch ", patch, "...\n");
- err |= check_patch(patch);
- patch = patch->next;
- }
- return err;
-}
-
-/* This function tries to read the sha1 from the current index */
-static int get_current_sha1(const char *path, unsigned char *sha1)
-{
- int pos;
-
- if (read_cache() < 0)
- return -1;
- pos = cache_name_pos(path, strlen(path));
- if (pos < 0)
- return -1;
- hashcpy(sha1, active_cache[pos]->sha1);
- return 0;
-}
-
-/* Build an index that contains the just the files needed for a 3way merge */
-static void build_fake_ancestor(struct patch *list, const char *filename)
-{
- struct patch *patch;
- struct index_state result = { NULL };
- int fd;
-
- /* Once we start supporting the reverse patch, it may be
- * worth showing the new sha1 prefix, but until then...
- */
- for (patch = list; patch; patch = patch->next) {
- const unsigned char *sha1_ptr;
- unsigned char sha1[20];
- struct cache_entry *ce;
- const char *name;
-
- name = patch->old_name ? patch->old_name : patch->new_name;
- if (0 < patch->is_new)
- continue;
- else if (get_sha1(patch->old_sha1_prefix, sha1))
- /* git diff has no index line for mode/type changes */
- if (!patch->lines_added && !patch->lines_deleted) {
- if (get_current_sha1(patch->new_name, sha1) ||
- get_current_sha1(patch->old_name, sha1))
- die("mode change for %s, which is not "
- "in current HEAD", name);
- sha1_ptr = sha1;
- } else
- die("sha1 information is lacking or useless "
- "(%s).", name);
- else
- sha1_ptr = sha1;
-
- ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
- if (!ce)
- die("make_cache_entry failed for path '%s'", name);
- if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
- die ("Could not add %s to temporary index", name);
- }
-
- fd = open(filename, O_WRONLY | O_CREAT, 0666);
- if (fd < 0 || write_index(&result, fd) || close(fd))
- die ("Could not write temporary index to %s", filename);
-
- discard_index(&result);
-}
-
-static void stat_patch_list(struct patch *patch)
-{
- int files, adds, dels;
-
- for (files = adds = dels = 0 ; patch ; patch = patch->next) {
- files++;
- adds += patch->lines_added;
- dels += patch->lines_deleted;
- show_stats(patch);
- }
-
- printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
-}
-
-static void numstat_patch_list(struct patch *patch)
-{
- for ( ; patch; patch = patch->next) {
- const char *name;
- name = patch->new_name ? patch->new_name : patch->old_name;
- if (patch->is_binary)
- printf("-\t-\t");
- else
- printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
- write_name_quoted(name, stdout, line_termination);
- }
-}
-
-static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
-{
- if (mode)
- printf(" %s mode %06o %s\n", newdelete, mode, name);
- else
- printf(" %s %s\n", newdelete, name);
-}
-
-static void show_mode_change(struct patch *p, int show_name)
-{
- if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
- if (show_name)
- printf(" mode change %06o => %06o %s\n",
- p->old_mode, p->new_mode, p->new_name);
- else
- printf(" mode change %06o => %06o\n",
- p->old_mode, p->new_mode);
- }
-}
-
-static void show_rename_copy(struct patch *p)
-{
- const char *renamecopy = p->is_rename ? "rename" : "copy";
- const char *old, *new;
-
- /* Find common prefix */
- old = p->old_name;
- new = p->new_name;
- while (1) {
- const char *slash_old, *slash_new;
- slash_old = strchr(old, '/');
- slash_new = strchr(new, '/');
- if (!slash_old ||
- !slash_new ||
- slash_old - old != slash_new - new ||
- memcmp(old, new, slash_new - new))
- break;
- old = slash_old + 1;
- new = slash_new + 1;
- }
- /* p->old_name thru old is the common prefix, and old and new
- * through the end of names are renames
- */
- if (old != p->old_name)
- printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
- (int)(old - p->old_name), p->old_name,
- old, new, p->score);
- else
- printf(" %s %s => %s (%d%%)\n", renamecopy,
- p->old_name, p->new_name, p->score);
- show_mode_change(p, 0);
-}
-
-static void summary_patch_list(struct patch *patch)
-{
- struct patch *p;
-
- for (p = patch; p; p = p->next) {
- if (p->is_new)
- show_file_mode_name("create", p->new_mode, p->new_name);
- else if (p->is_delete)
- show_file_mode_name("delete", p->old_mode, p->old_name);
- else {
- if (p->is_rename || p->is_copy)
- show_rename_copy(p);
- else {
- if (p->score) {
- printf(" rewrite %s (%d%%)\n",
- p->new_name, p->score);
- show_mode_change(p, 0);
- }
- else
- show_mode_change(p, 1);
- }
- }
- }
-}
-
-static void patch_stats(struct patch *patch)
-{
- int lines = patch->lines_added + patch->lines_deleted;
-
- if (lines > max_change)
- max_change = lines;
- if (patch->old_name) {
- int len = quote_c_style(patch->old_name, NULL, NULL, 0);
- if (!len)
- len = strlen(patch->old_name);
- if (len > max_len)
- max_len = len;
- }
- if (patch->new_name) {
- int len = quote_c_style(patch->new_name, NULL, NULL, 0);
- if (!len)
- len = strlen(patch->new_name);
- if (len > max_len)
- max_len = len;
- }
-}
-
-static void remove_file(struct patch *patch, int rmdir_empty)
-{
- if (update_index) {
- if (remove_file_from_cache(patch->old_name) < 0)
- die("unable to remove %s from index", patch->old_name);
- }
- if (!cached) {
- if (S_ISGITLINK(patch->old_mode)) {
- if (rmdir(patch->old_name))
- warning("unable to remove submodule %s",
- patch->old_name);
- } else if (!unlink_or_warn(patch->old_name) && rmdir_empty) {
- remove_path(patch->old_name);
- }
- }
-}
-
-static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size)
-{
- struct stat st;
- struct cache_entry *ce;
- int namelen = strlen(path);
- unsigned ce_size = cache_entry_size(namelen);
-
- if (!update_index)
- return;
-
- ce = xcalloc(1, ce_size);
- memcpy(ce->name, path, namelen);
- ce->ce_mode = create_ce_mode(mode);
- ce->ce_flags = namelen;
- if (S_ISGITLINK(mode)) {
- const char *s = buf;
-
- if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
- die("corrupt patch for subproject %s", path);
- } else {
- if (!cached) {
- if (lstat(path, &st) < 0)
- die_errno("unable to stat newly created file '%s'",
- path);
- fill_stat_cache_info(ce, &st);
- }
- if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
- die("unable to create backing store for newly created file %s", path);
- }
- if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
- die("unable to add cache entry for %s", path);
-}
-
-static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
-{
- int fd;
- struct strbuf nbuf = STRBUF_INIT;
-
- if (S_ISGITLINK(mode)) {
- struct stat st;
- if (!lstat(path, &st) && S_ISDIR(st.st_mode))
- return 0;
- return mkdir(path, 0777);
- }
-
- if (has_symlinks && S_ISLNK(mode))
- /* Although buf:size is counted string, it also is NUL
- * terminated.
- */
- return symlink(buf, path);
-
- fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
- if (fd < 0)
- return -1;
-
- if (convert_to_working_tree(path, buf, size, &nbuf)) {
- size = nbuf.len;
- buf = nbuf.buf;
- }
- write_or_die(fd, buf, size);
- strbuf_release(&nbuf);
-
- if (close(fd) < 0)
- die_errno("closing file '%s'", path);
- return 0;
-}
-
-/*
- * We optimistically assume that the directories exist,
- * which is true 99% of the time anyway. If they don't,
- * we create them and try again.
- */
-static void create_one_file(char *path, unsigned mode, const char *buf, unsigned long size)
-{
- if (cached)
- return;
- if (!try_create_file(path, mode, buf, size))
- return;
-
- if (errno == ENOENT) {
- if (safe_create_leading_directories(path))
- return;
- if (!try_create_file(path, mode, buf, size))
- return;
- }
-
- if (errno == EEXIST || errno == EACCES) {
- /* We may be trying to create a file where a directory
- * used to be.
- */
- struct stat st;
- if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path)))
- errno = EEXIST;
- }
-
- if (errno == EEXIST) {
- unsigned int nr = getpid();
-
- for (;;) {
- char newpath[PATH_MAX];
- mksnpath(newpath, sizeof(newpath), "%s~%u", path, nr);
- if (!try_create_file(newpath, mode, buf, size)) {
- if (!rename(newpath, path))
- return;
- unlink_or_warn(newpath);
- break;
- }
- if (errno != EEXIST)
- break;
- ++nr;
- }
- }
- die_errno("unable to write file '%s' mode %o", path, mode);
-}
-
-static void create_file(struct patch *patch)
-{
- char *path = patch->new_name;
- unsigned mode = patch->new_mode;
- unsigned long size = patch->resultsize;
- char *buf = patch->result;
-
- if (!mode)
- mode = S_IFREG | 0644;
- create_one_file(path, mode, buf, size);
- add_index_file(path, mode, buf, size);
-}
-
-/* phase zero is to remove, phase one is to create */
-static void write_out_one_result(struct patch *patch, int phase)
-{
- if (patch->is_delete > 0) {
- if (phase == 0)
- remove_file(patch, 1);
- return;
- }
- if (patch->is_new > 0 || patch->is_copy) {
- if (phase == 1)
- create_file(patch);
- return;
- }
- /*
- * Rename or modification boils down to the same
- * thing: remove the old, write the new
- */
- if (phase == 0)
- remove_file(patch, patch->is_rename);
- if (phase == 1)
- create_file(patch);
-}
-
-static int write_out_one_reject(struct patch *patch)
-{
- FILE *rej;
- char namebuf[PATH_MAX];
- struct fragment *frag;
- int cnt = 0;
-
- for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
- if (!frag->rejected)
- continue;
- cnt++;
- }
-
- if (!cnt) {
- if (apply_verbosely)
- say_patch_name(stderr,
- "Applied patch ", patch, " cleanly.\n");
- return 0;
- }
-
- /* This should not happen, because a removal patch that leaves
- * contents are marked "rejected" at the patch level.
- */
- if (!patch->new_name)
- die("internal error");
-
- /* Say this even without --verbose */
- say_patch_name(stderr, "Applying patch ", patch, " with");
- fprintf(stderr, " %d rejects...\n", cnt);
-
- cnt = strlen(patch->new_name);
- if (ARRAY_SIZE(namebuf) <= cnt + 5) {
- cnt = ARRAY_SIZE(namebuf) - 5;
- warning("truncating .rej filename to %.*s.rej",
- cnt - 1, patch->new_name);
- }
- memcpy(namebuf, patch->new_name, cnt);
- memcpy(namebuf + cnt, ".rej", 5);
-
- rej = fopen(namebuf, "w");
- if (!rej)
- return error("cannot open %s: %s", namebuf, strerror(errno));
-
- /* Normal git tools never deal with .rej, so do not pretend
- * this is a git patch by saying --git nor give extended
- * headers. While at it, maybe please "kompare" that wants
- * the trailing TAB and some garbage at the end of line ;-).
- */
- fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n",
- patch->new_name, patch->new_name);
- for (cnt = 1, frag = patch->fragments;
- frag;
- cnt++, frag = frag->next) {
- if (!frag->rejected) {
- fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
- continue;
- }
- fprintf(stderr, "Rejected hunk #%d.\n", cnt);
- fprintf(rej, "%.*s", frag->size, frag->patch);
- if (frag->patch[frag->size-1] != '\n')
- fputc('\n', rej);
- }
- fclose(rej);
- return -1;
-}
-
-static int write_out_results(struct patch *list, int skipped_patch)
-{
- int phase;
- int errs = 0;
- struct patch *l;
-
- if (!list && !skipped_patch)
- return error("No changes");
-
- for (phase = 0; phase < 2; phase++) {
- l = list;
- while (l) {
- if (l->rejected)
- errs = 1;
- else {
- write_out_one_result(l, phase);
- if (phase == 1 && write_out_one_reject(l))
- errs = 1;
- }
- l = l->next;
- }
- }
- return errs;
-}
-
-static struct lock_file lock_file;
-
-static struct string_list limit_by_name;
-static int has_include;
-static void add_name_limit(const char *name, int exclude)
-{
- struct string_list_item *it;
-
- it = string_list_append(name, &limit_by_name);
- it->util = exclude ? NULL : (void *) 1;
-}
-
-static int use_patch(struct patch *p)
-{
- const char *pathname = p->new_name ? p->new_name : p->old_name;
- int i;
-
- /* Paths outside are not touched regardless of "--include" */
- if (0 < prefix_length) {
- int pathlen = strlen(pathname);
- if (pathlen <= prefix_length ||
- memcmp(prefix, pathname, prefix_length))
- return 0;
- }
-
- /* See if it matches any of exclude/include rule */
- for (i = 0; i < limit_by_name.nr; i++) {
- struct string_list_item *it = &limit_by_name.items[i];
- if (!fnmatch(it->string, pathname, 0))
- return (it->util != NULL);
- }
-
- /*
- * If we had any include, a path that does not match any rule is
- * not used. Otherwise, we saw bunch of exclude rules (or none)
- * and such a path is used.
- */
- return !has_include;
-}
-
-
-static void prefix_one(char **name)
-{
- char *old_name = *name;
- if (!old_name)
- return;
- *name = xstrdup(prefix_filename(prefix, prefix_length, *name));
- free(old_name);
-}
-
-static void prefix_patches(struct patch *p)
-{
- if (!prefix || p->is_toplevel_relative)
- return;
- for ( ; p; p = p->next) {
- if (p->new_name == p->old_name) {
- char *prefixed = p->new_name;
- prefix_one(&prefixed);
- p->new_name = p->old_name = prefixed;
- }
- else {
- prefix_one(&p->new_name);
- prefix_one(&p->old_name);
- }
- }
-}
-
-#define INACCURATE_EOF (1<<0)
-#define RECOUNT (1<<1)
-
-static int apply_patch(int fd, const char *filename, int options)
-{
- size_t offset;
- struct strbuf buf = STRBUF_INIT;
- struct patch *list = NULL, **listp = &list;
- int skipped_patch = 0;
-
- /* FIXME - memory leak when using multiple patch files as inputs */
- memset(&fn_table, 0, sizeof(struct string_list));
- patch_input_file = filename;
- read_patch_file(&buf, fd);
- offset = 0;
- while (offset < buf.len) {
- struct patch *patch;
- int nr;
-
- patch = xcalloc(1, sizeof(*patch));
- patch->inaccurate_eof = !!(options & INACCURATE_EOF);
- patch->recount = !!(options & RECOUNT);
- nr = parse_chunk(buf.buf + offset, buf.len - offset, patch);
- if (nr < 0)
- break;
- if (apply_in_reverse)
- reverse_patches(patch);
- if (prefix)
- prefix_patches(patch);
- if (use_patch(patch)) {
- patch_stats(patch);
- *listp = patch;
- listp = &patch->next;
- }
- else {
- /* perhaps free it a bit better? */
- free(patch);
- skipped_patch++;
- }
- offset += nr;
- }
-
- if (whitespace_error && (ws_error_action == die_on_ws_error))
- apply = 0;
-
- update_index = check_index && apply;
- if (update_index && newfd < 0)
- newfd = hold_locked_index(&lock_file, 1);
-
- if (check_index) {
- if (read_cache() < 0)
- die("unable to read index file");
- }
-
- if ((check || apply) &&
- check_patch_list(list) < 0 &&
- !apply_with_reject)
- exit(1);
-
- if (apply && write_out_results(list, skipped_patch))
- exit(1);
-
- if (fake_ancestor)
- build_fake_ancestor(list, fake_ancestor);
-
- if (diffstat)
- stat_patch_list(list);
-
- if (numstat)
- numstat_patch_list(list);
-
- if (summary)
- summary_patch_list(list);
-
- strbuf_release(&buf);
- return 0;
-}
-
-static int git_apply_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "apply.whitespace"))
- return git_config_string(&apply_default_whitespace, var, value);
- else if (!strcmp(var, "apply.ignorewhitespace"))
- return git_config_string(&apply_default_ignorewhitespace, var, value);
- return git_default_config(var, value, cb);
-}
-
-static int option_parse_exclude(const struct option *opt,
- const char *arg, int unset)
-{
- add_name_limit(arg, 1);
- return 0;
-}
-
-static int option_parse_include(const struct option *opt,
- const char *arg, int unset)
-{
- add_name_limit(arg, 0);
- has_include = 1;
- return 0;
-}
-
-static int option_parse_p(const struct option *opt,
- const char *arg, int unset)
-{
- p_value = atoi(arg);
- p_value_known = 1;
- return 0;
-}
-
-static int option_parse_z(const struct option *opt,
- const char *arg, int unset)
-{
- if (unset)
- line_termination = '\n';
- else
- line_termination = 0;
- return 0;
-}
-
-static int option_parse_space_change(const struct option *opt,
- const char *arg, int unset)
-{
- if (unset)
- ws_ignore_action = ignore_ws_none;
- else
- ws_ignore_action = ignore_ws_change;
- return 0;
-}
-
-static int option_parse_whitespace(const struct option *opt,
- const char *arg, int unset)
-{
- const char **whitespace_option = opt->value;
-
- *whitespace_option = arg;
- parse_whitespace_option(arg);
- return 0;
-}
-
-static int option_parse_directory(const struct option *opt,
- const char *arg, int unset)
-{
- root_len = strlen(arg);
- if (root_len && arg[root_len - 1] != '/') {
- char *new_root;
- root = new_root = xmalloc(root_len + 2);
- strcpy(new_root, arg);
- strcpy(new_root + root_len++, "/");
- } else
- root = arg;
- return 0;
-}
-
-int cmd_apply(int argc, const char **argv, const char *unused_prefix)
-{
- int i;
- int errs = 0;
- int is_not_gitdir;
- int binary;
- int force_apply = 0;
-
- const char *whitespace_option = NULL;
-
- struct option builtin_apply_options[] = {
- { OPTION_CALLBACK, 0, "exclude", NULL, "path",
- "don't apply changes matching the given path",
- 0, option_parse_exclude },
- { OPTION_CALLBACK, 0, "include", NULL, "path",
- "apply changes matching the given path",
- 0, option_parse_include },
- { OPTION_CALLBACK, 'p', NULL, NULL, "num",
- "remove <num> leading slashes from traditional diff paths",
- 0, option_parse_p },
- OPT_BOOLEAN(0, "no-add", &no_add,
- "ignore additions made by the patch"),
- OPT_BOOLEAN(0, "stat", &diffstat,
- "instead of applying the patch, output diffstat for the input"),
- { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary,
- NULL, "old option, now no-op",
- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
- { OPTION_BOOLEAN, 0, "binary", &binary,
- NULL, "old option, now no-op",
- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
- OPT_BOOLEAN(0, "numstat", &numstat,
- "shows number of added and deleted lines in decimal notation"),
- OPT_BOOLEAN(0, "summary", &summary,
- "instead of applying the patch, output a summary for the input"),
- OPT_BOOLEAN(0, "check", &check,
- "instead of applying the patch, see if the patch is applicable"),
- OPT_BOOLEAN(0, "index", &check_index,
- "make sure the patch is applicable to the current index"),
- OPT_BOOLEAN(0, "cached", &cached,
- "apply a patch without touching the working tree"),
- OPT_BOOLEAN(0, "apply", &force_apply,
- "also apply the patch (use with --stat/--summary/--check)"),
- OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
- "build a temporary index based on embedded index information"),
- { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
- "paths are separated with NUL character",
- PARSE_OPT_NOARG, option_parse_z },
- OPT_INTEGER('C', NULL, &p_context,
- "ensure at least <n> lines of context match"),
- { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
- "detect new or modified lines that have whitespace errors",
- 0, option_parse_whitespace },
- { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
- "ignore changes in whitespace when finding context",
- PARSE_OPT_NOARG, option_parse_space_change },
- { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
- "ignore changes in whitespace when finding context",
- PARSE_OPT_NOARG, option_parse_space_change },
- OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
- "apply the patch in reverse"),
- OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
- "don't expect at least one line of context"),
- OPT_BOOLEAN(0, "reject", &apply_with_reject,
- "leave the rejected hunks in corresponding *.rej files"),
- OPT__VERBOSE(&apply_verbosely),
- OPT_BIT(0, "inaccurate-eof", &options,
- "tolerate incorrectly detected missing new-line at the end of file",
- INACCURATE_EOF),
- OPT_BIT(0, "recount", &options,
- "do not trust the line counts in the hunk headers",
- RECOUNT),
- { OPTION_CALLBACK, 0, "directory", NULL, "root",
- "prepend <root> to all filenames",
- 0, option_parse_directory },
- OPT_END()
- };
-
- prefix = setup_git_directory_gently(&is_not_gitdir);
- prefix_length = prefix ? strlen(prefix) : 0;
- git_config(git_apply_config, NULL);
- if (apply_default_whitespace)
- parse_whitespace_option(apply_default_whitespace);
- if (apply_default_ignorewhitespace)
- parse_ignorewhitespace_option(apply_default_ignorewhitespace);
-
- argc = parse_options(argc, argv, prefix, builtin_apply_options,
- apply_usage, 0);
-
- if (apply_with_reject)
- apply = apply_verbosely = 1;
- if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
- apply = 0;
- if (check_index && is_not_gitdir)
- die("--index outside a repository");
- if (cached) {
- if (is_not_gitdir)
- die("--cached outside a repository");
- check_index = 1;
- }
- for (i = 0; i < argc; i++) {
- const char *arg = argv[i];
- int fd;
-
- if (!strcmp(arg, "-")) {
- errs |= apply_patch(0, "<stdin>", options);
- read_stdin = 0;
- continue;
- } else if (0 < prefix_length)
- arg = prefix_filename(prefix, prefix_length, arg);
-
- fd = open(arg, O_RDONLY);
- if (fd < 0)
- die_errno("can't open patch '%s'", arg);
- read_stdin = 0;
- set_default_whitespace_mode(whitespace_option);
- errs |= apply_patch(fd, arg, options);
- close(fd);
- }
- set_default_whitespace_mode(whitespace_option);
- if (read_stdin)
- errs |= apply_patch(0, "<stdin>", options);
- if (whitespace_error) {
- if (squelch_whitespace_errors &&
- squelch_whitespace_errors < whitespace_error) {
- int squelched =
- whitespace_error - squelch_whitespace_errors;
- warning("squelched %d "
- "whitespace error%s",
- squelched,
- squelched == 1 ? "" : "s");
- }
- if (ws_error_action == die_on_ws_error)
- die("%d line%s add%s whitespace errors.",
- whitespace_error,
- whitespace_error == 1 ? "" : "s",
- whitespace_error == 1 ? "s" : "");
- if (applied_after_fixing_ws && apply)
- warning("%d line%s applied after"
- " fixing whitespace errors.",
- applied_after_fixing_ws,
- applied_after_fixing_ws == 1 ? "" : "s");
- else if (whitespace_error)
- warning("%d line%s add%s whitespace errors.",
- whitespace_error,
- whitespace_error == 1 ? "" : "s",
- whitespace_error == 1 ? "s" : "");
- }
-
- if (update_index) {
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
- die("Unable to write new index file");
- }
-
- return !!errs;
-}
+++ /dev/null
-/*
- * Copyright (c) 2006 Franck Bui-Huu
- * Copyright (c) 2006 Rene Scharfe
- */
-#include "cache.h"
-#include "builtin.h"
-#include "archive.h"
-#include "transport.h"
-#include "parse-options.h"
-#include "pkt-line.h"
-#include "sideband.h"
-
-static void create_output_file(const char *output_file)
-{
- int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
- if (output_fd < 0)
- die_errno("could not create archive file '%s'", output_file);
- if (output_fd != 1) {
- if (dup2(output_fd, 1) < 0)
- die_errno("could not redirect output");
- else
- close(output_fd);
- }
-}
-
-static int run_remote_archiver(int argc, const char **argv,
- const char *remote, const char *exec)
-{
- char buf[LARGE_PACKET_MAX];
- int fd[2], i, len, rv;
- struct transport *transport;
- struct remote *_remote;
-
- _remote = remote_get(remote);
- if (!_remote->url[0])
- die("git archive: Remote with no URL");
- transport = transport_get(_remote, _remote->url[0]);
- transport_connect(transport, "git-upload-archive", exec, fd);
-
- for (i = 1; i < argc; i++)
- packet_write(fd[1], "argument %s\n", argv[i]);
- packet_flush(fd[1]);
-
- len = packet_read_line(fd[0], buf, sizeof(buf));
- if (!len)
- die("git archive: expected ACK/NAK, got EOF");
- if (buf[len-1] == '\n')
- buf[--len] = 0;
- if (strcmp(buf, "ACK")) {
- if (len > 5 && !prefixcmp(buf, "NACK "))
- die("git archive: NACK %s", buf + 5);
- die("git archive: protocol error");
- }
-
- len = packet_read_line(fd[0], buf, sizeof(buf));
- if (len)
- die("git archive: expected a flush");
-
- /* Now, start reading from fd[0] and spit it out to stdout */
- rv = recv_sideband("archive", fd[0], 1);
- rv |= transport_disconnect(transport);
-
- return !!rv;
-}
-
-static const char *format_from_name(const char *filename)
-{
- const char *ext = strrchr(filename, '.');
- if (!ext)
- return NULL;
- ext++;
- if (!strcasecmp(ext, "zip"))
- return "--format=zip";
- return NULL;
-}
-
-#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \
- PARSE_OPT_KEEP_ARGV0 | \
- PARSE_OPT_KEEP_UNKNOWN | \
- PARSE_OPT_NO_INTERNAL_HELP )
-
-int cmd_archive(int argc, const char **argv, const char *prefix)
-{
- const char *exec = "git-upload-archive";
- const char *output = NULL;
- const char *remote = NULL;
- const char *format_option = NULL;
- struct option local_opts[] = {
- OPT_STRING('o', "output", &output, "file",
- "write the archive to this file"),
- OPT_STRING(0, "remote", &remote, "repo",
- "retrieve the archive from remote repository <repo>"),
- OPT_STRING(0, "exec", &exec, "cmd",
- "path to the remote git-upload-archive command"),
- OPT_END()
- };
-
- argc = parse_options(argc, argv, prefix, local_opts, NULL,
- PARSE_OPT_KEEP_ALL);
-
- if (output) {
- create_output_file(output);
- format_option = format_from_name(output);
- }
-
- /*
- * We have enough room in argv[] to muck it in place, because
- * --output must have been given on the original command line
- * if we get to this point, and parse_options() must have eaten
- * it, i.e. we can add back one element to the array.
- *
- * We add a fake --format option at the beginning, with the
- * format inferred from our output filename. This way explicit
- * --format options can override it, and the fake option is
- * inserted before any "--" that might have been given.
- */
- if (format_option) {
- memmove(argv + 2, argv + 1, sizeof(*argv) * argc);
- argv[1] = format_option;
- argv[++argc] = NULL;
- }
-
- if (remote)
- return run_remote_archiver(argc, argv, remote, exec);
-
- setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
-
- return write_archive(argc, argv, prefix, 1);
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "parse-options.h"
-#include "bisect.h"
-
-static const char * const git_bisect_helper_usage[] = {
- "git bisect--helper --next-all",
- NULL
-};
-
-int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
-{
- int next_all = 0;
- struct option options[] = {
- OPT_BOOLEAN(0, "next-all", &next_all,
- "perform 'git bisect next'"),
- OPT_END()
- };
-
- argc = parse_options(argc, argv, prefix, options,
- git_bisect_helper_usage, 0);
-
- if (!next_all)
- usage_with_options(git_bisect_helper_usage, options);
-
- /* next-all */
- return bisect_next_all(prefix);
-}
+++ /dev/null
-/*
- * Blame
- *
- * Copyright (c) 2006, Junio C Hamano
- */
-
-#include "cache.h"
-#include "builtin.h"
-#include "blob.h"
-#include "commit.h"
-#include "tag.h"
-#include "tree-walk.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "revision.h"
-#include "quote.h"
-#include "xdiff-interface.h"
-#include "cache-tree.h"
-#include "string-list.h"
-#include "mailmap.h"
-#include "parse-options.h"
-#include "utf8.h"
-
-static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
-
-static const char *blame_opt_usage[] = {
- blame_usage,
- "",
- "[rev-opts] are documented in git-rev-list(1)",
- NULL
-};
-
-static int longest_file;
-static int longest_author;
-static int max_orig_digits;
-static int max_digits;
-static int max_score_digits;
-static int show_root;
-static int reverse;
-static int blank_boundary;
-static int incremental;
-static int xdl_opts = XDF_NEED_MINIMAL;
-
-static enum date_mode blame_date_mode = DATE_ISO8601;
-static size_t blame_date_width;
-
-static struct string_list mailmap;
-
-#ifndef DEBUG
-#define DEBUG 0
-#endif
-
-/* stats */
-static int num_read_blob;
-static int num_get_patch;
-static int num_commits;
-
-#define PICKAXE_BLAME_MOVE 01
-#define PICKAXE_BLAME_COPY 02
-#define PICKAXE_BLAME_COPY_HARDER 04
-#define PICKAXE_BLAME_COPY_HARDEST 010
-
-/*
- * blame for a blame_entry with score lower than these thresholds
- * is not passed to the parent using move/copy logic.
- */
-static unsigned blame_move_score;
-static unsigned blame_copy_score;
-#define BLAME_DEFAULT_MOVE_SCORE 20
-#define BLAME_DEFAULT_COPY_SCORE 40
-
-/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */
-#define METAINFO_SHOWN (1u<<12)
-#define MORE_THAN_ONE_PATH (1u<<13)
-
-/*
- * One blob in a commit that is being suspected
- */
-struct origin {
- int refcnt;
- struct origin *previous;
- struct commit *commit;
- mmfile_t file;
- unsigned char blob_sha1[20];
- char path[FLEX_ARRAY];
-};
-
-/*
- * Given an origin, prepare mmfile_t structure to be used by the
- * diff machinery
- */
-static void fill_origin_blob(struct origin *o, mmfile_t *file)
-{
- if (!o->file.ptr) {
- enum object_type type;
- num_read_blob++;
- file->ptr = read_sha1_file(o->blob_sha1, &type,
- (unsigned long *)(&(file->size)));
- if (!file->ptr)
- die("Cannot read blob %s for path %s",
- sha1_to_hex(o->blob_sha1),
- o->path);
- o->file = *file;
- }
- else
- *file = o->file;
-}
-
-/*
- * Origin is refcounted and usually we keep the blob contents to be
- * reused.
- */
-static inline struct origin *origin_incref(struct origin *o)
-{
- if (o)
- o->refcnt++;
- return o;
-}
-
-static void origin_decref(struct origin *o)
-{
- if (o && --o->refcnt <= 0) {
- if (o->previous)
- origin_decref(o->previous);
- free(o->file.ptr);
- free(o);
- }
-}
-
-static void drop_origin_blob(struct origin *o)
-{
- if (o->file.ptr) {
- free(o->file.ptr);
- o->file.ptr = NULL;
- }
-}
-
-/*
- * Each group of lines is described by a blame_entry; it can be split
- * as we pass blame to the parents. They form a linked list in the
- * scoreboard structure, sorted by the target line number.
- */
-struct blame_entry {
- struct blame_entry *prev;
- struct blame_entry *next;
-
- /* the first line of this group in the final image;
- * internally all line numbers are 0 based.
- */
- int lno;
-
- /* how many lines this group has */
- int num_lines;
-
- /* the commit that introduced this group into the final image */
- struct origin *suspect;
-
- /* true if the suspect is truly guilty; false while we have not
- * checked if the group came from one of its parents.
- */
- char guilty;
-
- /* true if the entry has been scanned for copies in the current parent
- */
- char scanned;
-
- /* the line number of the first line of this group in the
- * suspect's file; internally all line numbers are 0 based.
- */
- int s_lno;
-
- /* how significant this entry is -- cached to avoid
- * scanning the lines over and over.
- */
- unsigned score;
-};
-
-/*
- * The current state of the blame assignment.
- */
-struct scoreboard {
- /* the final commit (i.e. where we started digging from) */
- struct commit *final;
- struct rev_info *revs;
- const char *path;
-
- /*
- * The contents in the final image.
- * Used by many functions to obtain contents of the nth line,
- * indexed with scoreboard.lineno[blame_entry.lno].
- */
- const char *final_buf;
- unsigned long final_buf_size;
-
- /* linked list of blames */
- struct blame_entry *ent;
-
- /* look-up a line in the final buffer */
- int num_lines;
- int *lineno;
-};
-
-static inline int same_suspect(struct origin *a, struct origin *b)
-{
- if (a == b)
- return 1;
- if (a->commit != b->commit)
- return 0;
- return !strcmp(a->path, b->path);
-}
-
-static void sanity_check_refcnt(struct scoreboard *);
-
-/*
- * If two blame entries that are next to each other came from
- * contiguous lines in the same origin (i.e. <commit, path> pair),
- * merge them together.
- */
-static void coalesce(struct scoreboard *sb)
-{
- struct blame_entry *ent, *next;
-
- for (ent = sb->ent; ent && (next = ent->next); ent = next) {
- if (same_suspect(ent->suspect, next->suspect) &&
- ent->guilty == next->guilty &&
- ent->s_lno + ent->num_lines == next->s_lno) {
- ent->num_lines += next->num_lines;
- ent->next = next->next;
- if (ent->next)
- ent->next->prev = ent;
- origin_decref(next->suspect);
- free(next);
- ent->score = 0;
- next = ent; /* again */
- }
- }
-
- if (DEBUG) /* sanity */
- sanity_check_refcnt(sb);
-}
-
-/*
- * Given a commit and a path in it, create a new origin structure.
- * The callers that add blame to the scoreboard should use
- * get_origin() to obtain shared, refcounted copy instead of calling
- * this function directly.
- */
-static struct origin *make_origin(struct commit *commit, const char *path)
-{
- struct origin *o;
- o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
- o->commit = commit;
- o->refcnt = 1;
- strcpy(o->path, path);
- return o;
-}
-
-/*
- * Locate an existing origin or create a new one.
- */
-static struct origin *get_origin(struct scoreboard *sb,
- struct commit *commit,
- const char *path)
-{
- struct blame_entry *e;
-
- for (e = sb->ent; e; e = e->next) {
- if (e->suspect->commit == commit &&
- !strcmp(e->suspect->path, path))
- return origin_incref(e->suspect);
- }
- return make_origin(commit, path);
-}
-
-/*
- * Fill the blob_sha1 field of an origin if it hasn't, so that later
- * call to fill_origin_blob() can use it to locate the data. blob_sha1
- * for an origin is also used to pass the blame for the entire file to
- * the parent to detect the case where a child's blob is identical to
- * that of its parent's.
- */
-static int fill_blob_sha1(struct origin *origin)
-{
- unsigned mode;
-
- if (!is_null_sha1(origin->blob_sha1))
- return 0;
- if (get_tree_entry(origin->commit->object.sha1,
- origin->path,
- origin->blob_sha1, &mode))
- goto error_out;
- if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
- goto error_out;
- return 0;
- error_out:
- hashclr(origin->blob_sha1);
- return -1;
-}
-
-/*
- * We have an origin -- check if the same path exists in the
- * parent and return an origin structure to represent it.
- */
-static struct origin *find_origin(struct scoreboard *sb,
- struct commit *parent,
- struct origin *origin)
-{
- struct origin *porigin = NULL;
- struct diff_options diff_opts;
- const char *paths[2];
-
- if (parent->util) {
- /*
- * Each commit object can cache one origin in that
- * commit. This is a freestanding copy of origin and
- * not refcounted.
- */
- struct origin *cached = parent->util;
- if (!strcmp(cached->path, origin->path)) {
- /*
- * The same path between origin and its parent
- * without renaming -- the most common case.
- */
- porigin = get_origin(sb, parent, cached->path);
-
- /*
- * If the origin was newly created (i.e. get_origin
- * would call make_origin if none is found in the
- * scoreboard), it does not know the blob_sha1,
- * so copy it. Otherwise porigin was in the
- * scoreboard and already knows blob_sha1.
- */
- if (porigin->refcnt == 1)
- hashcpy(porigin->blob_sha1, cached->blob_sha1);
- return porigin;
- }
- /* otherwise it was not very useful; free it */
- free(parent->util);
- parent->util = NULL;
- }
-
- /* See if the origin->path is different between parent
- * and origin first. Most of the time they are the
- * same and diff-tree is fairly efficient about this.
- */
- diff_setup(&diff_opts);
- DIFF_OPT_SET(&diff_opts, RECURSIVE);
- diff_opts.detect_rename = 0;
- diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- paths[0] = origin->path;
- paths[1] = NULL;
-
- diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
-
- if (is_null_sha1(origin->commit->object.sha1))
- do_diff_cache(parent->tree->object.sha1, &diff_opts);
- else
- diff_tree_sha1(parent->tree->object.sha1,
- origin->commit->tree->object.sha1,
- "", &diff_opts);
- diffcore_std(&diff_opts);
-
- if (!diff_queued_diff.nr) {
- /* The path is the same as parent */
- porigin = get_origin(sb, parent, origin->path);
- hashcpy(porigin->blob_sha1, origin->blob_sha1);
- } else {
- /*
- * Since origin->path is a pathspec, if the parent
- * commit had it as a directory, we will see a whole
- * bunch of deletion of files in the directory that we
- * do not care about.
- */
- int i;
- struct diff_filepair *p = NULL;
- for (i = 0; i < diff_queued_diff.nr; i++) {
- const char *name;
- p = diff_queued_diff.queue[i];
- name = p->one->path ? p->one->path : p->two->path;
- if (!strcmp(name, origin->path))
- break;
- }
- if (!p)
- die("internal error in blame::find_origin");
- switch (p->status) {
- default:
- die("internal error in blame::find_origin (%c)",
- p->status);
- case 'M':
- porigin = get_origin(sb, parent, origin->path);
- hashcpy(porigin->blob_sha1, p->one->sha1);
- break;
- case 'A':
- case 'T':
- /* Did not exist in parent, or type changed */
- break;
- }
- }
- diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
- if (porigin) {
- /*
- * Create a freestanding copy that is not part of
- * the refcounted origin found in the scoreboard, and
- * cache it in the commit.
- */
- struct origin *cached;
-
- cached = make_origin(porigin->commit, porigin->path);
- hashcpy(cached->blob_sha1, porigin->blob_sha1);
- parent->util = cached;
- }
- return porigin;
-}
-
-/*
- * We have an origin -- find the path that corresponds to it in its
- * parent and return an origin structure to represent it.
- */
-static struct origin *find_rename(struct scoreboard *sb,
- struct commit *parent,
- struct origin *origin)
-{
- struct origin *porigin = NULL;
- struct diff_options diff_opts;
- int i;
- const char *paths[2];
-
- diff_setup(&diff_opts);
- DIFF_OPT_SET(&diff_opts, RECURSIVE);
- diff_opts.detect_rename = DIFF_DETECT_RENAME;
- diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- diff_opts.single_follow = origin->path;
- paths[0] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
-
- if (is_null_sha1(origin->commit->object.sha1))
- do_diff_cache(parent->tree->object.sha1, &diff_opts);
- else
- diff_tree_sha1(parent->tree->object.sha1,
- origin->commit->tree->object.sha1,
- "", &diff_opts);
- diffcore_std(&diff_opts);
-
- for (i = 0; i < diff_queued_diff.nr; i++) {
- struct diff_filepair *p = diff_queued_diff.queue[i];
- if ((p->status == 'R' || p->status == 'C') &&
- !strcmp(p->two->path, origin->path)) {
- porigin = get_origin(sb, parent, p->one->path);
- hashcpy(porigin->blob_sha1, p->one->sha1);
- break;
- }
- }
- diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
- return porigin;
-}
-
-/*
- * Link in a new blame entry to the scoreboard. Entries that cover the
- * same line range have been removed from the scoreboard previously.
- */
-static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
-{
- struct blame_entry *ent, *prev = NULL;
-
- origin_incref(e->suspect);
-
- for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next)
- prev = ent;
-
- /* prev, if not NULL, is the last one that is below e */
- e->prev = prev;
- if (prev) {
- e->next = prev->next;
- prev->next = e;
- }
- else {
- e->next = sb->ent;
- sb->ent = e;
- }
- if (e->next)
- e->next->prev = e;
-}
-
-/*
- * src typically is on-stack; we want to copy the information in it to
- * a malloced blame_entry that is already on the linked list of the
- * scoreboard. The origin of dst loses a refcnt while the origin of src
- * gains one.
- */
-static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
-{
- struct blame_entry *p, *n;
-
- p = dst->prev;
- n = dst->next;
- origin_incref(src->suspect);
- origin_decref(dst->suspect);
- memcpy(dst, src, sizeof(*src));
- dst->prev = p;
- dst->next = n;
- dst->score = 0;
-}
-
-static const char *nth_line(struct scoreboard *sb, int lno)
-{
- return sb->final_buf + sb->lineno[lno];
-}
-
-/*
- * It is known that lines between tlno to same came from parent, and e
- * has an overlap with that range. it also is known that parent's
- * line plno corresponds to e's line tlno.
- *
- * <---- e ----->
- * <------>
- * <------------>
- * <------------>
- * <------------------>
- *
- * Split e into potentially three parts; before this chunk, the chunk
- * to be blamed for the parent, and after that portion.
- */
-static void split_overlap(struct blame_entry *split,
- struct blame_entry *e,
- int tlno, int plno, int same,
- struct origin *parent)
-{
- int chunk_end_lno;
- memset(split, 0, sizeof(struct blame_entry [3]));
-
- if (e->s_lno < tlno) {
- /* there is a pre-chunk part not blamed on parent */
- split[0].suspect = origin_incref(e->suspect);
- split[0].lno = e->lno;
- split[0].s_lno = e->s_lno;
- split[0].num_lines = tlno - e->s_lno;
- split[1].lno = e->lno + tlno - e->s_lno;
- split[1].s_lno = plno;
- }
- else {
- split[1].lno = e->lno;
- split[1].s_lno = plno + (e->s_lno - tlno);
- }
-
- if (same < e->s_lno + e->num_lines) {
- /* there is a post-chunk part not blamed on parent */
- split[2].suspect = origin_incref(e->suspect);
- split[2].lno = e->lno + (same - e->s_lno);
- split[2].s_lno = e->s_lno + (same - e->s_lno);
- split[2].num_lines = e->s_lno + e->num_lines - same;
- chunk_end_lno = split[2].lno;
- }
- else
- chunk_end_lno = e->lno + e->num_lines;
- split[1].num_lines = chunk_end_lno - split[1].lno;
-
- /*
- * if it turns out there is nothing to blame the parent for,
- * forget about the splitting. !split[1].suspect signals this.
- */
- if (split[1].num_lines < 1)
- return;
- split[1].suspect = origin_incref(parent);
-}
-
-/*
- * split_overlap() divided an existing blame e into up to three parts
- * in split. Adjust the linked list of blames in the scoreboard to
- * reflect the split.
- */
-static void split_blame(struct scoreboard *sb,
- struct blame_entry *split,
- struct blame_entry *e)
-{
- struct blame_entry *new_entry;
-
- if (split[0].suspect && split[2].suspect) {
- /* The first part (reuse storage for the existing entry e) */
- dup_entry(e, &split[0]);
-
- /* The last part -- me */
- new_entry = xmalloc(sizeof(*new_entry));
- memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
-
- /* ... and the middle part -- parent */
- new_entry = xmalloc(sizeof(*new_entry));
- memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
- }
- else if (!split[0].suspect && !split[2].suspect)
- /*
- * The parent covers the entire area; reuse storage for
- * e and replace it with the parent.
- */
- dup_entry(e, &split[1]);
- else if (split[0].suspect) {
- /* me and then parent */
- dup_entry(e, &split[0]);
-
- new_entry = xmalloc(sizeof(*new_entry));
- memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
- }
- else {
- /* parent and then me */
- dup_entry(e, &split[1]);
-
- new_entry = xmalloc(sizeof(*new_entry));
- memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
- }
-
- if (DEBUG) { /* sanity */
- struct blame_entry *ent;
- int lno = sb->ent->lno, corrupt = 0;
-
- for (ent = sb->ent; ent; ent = ent->next) {
- if (lno != ent->lno)
- corrupt = 1;
- if (ent->s_lno < 0)
- corrupt = 1;
- lno += ent->num_lines;
- }
- if (corrupt) {
- lno = sb->ent->lno;
- for (ent = sb->ent; ent; ent = ent->next) {
- printf("L %8d l %8d n %8d\n",
- lno, ent->lno, ent->num_lines);
- lno = ent->lno + ent->num_lines;
- }
- die("oops");
- }
- }
-}
-
-/*
- * After splitting the blame, the origins used by the
- * on-stack blame_entry should lose one refcnt each.
- */
-static void decref_split(struct blame_entry *split)
-{
- int i;
-
- for (i = 0; i < 3; i++)
- origin_decref(split[i].suspect);
-}
-
-/*
- * Helper for blame_chunk(). blame_entry e is known to overlap with
- * the patch hunk; split it and pass blame to the parent.
- */
-static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
- int tlno, int plno, int same,
- struct origin *parent)
-{
- struct blame_entry split[3];
-
- split_overlap(split, e, tlno, plno, same, parent);
- if (split[1].suspect)
- split_blame(sb, split, e);
- decref_split(split);
-}
-
-/*
- * Find the line number of the last line the target is suspected for.
- */
-static int find_last_in_target(struct scoreboard *sb, struct origin *target)
-{
- struct blame_entry *e;
- int last_in_target = -1;
-
- for (e = sb->ent; e; e = e->next) {
- if (e->guilty || !same_suspect(e->suspect, target))
- continue;
- if (last_in_target < e->s_lno + e->num_lines)
- last_in_target = e->s_lno + e->num_lines;
- }
- return last_in_target;
-}
-
-/*
- * Process one hunk from the patch between the current suspect for
- * blame_entry e and its parent. Find and split the overlap, and
- * pass blame to the overlapping part to the parent.
- */
-static void blame_chunk(struct scoreboard *sb,
- int tlno, int plno, int same,
- struct origin *target, struct origin *parent)
-{
- struct blame_entry *e;
-
- for (e = sb->ent; e; e = e->next) {
- if (e->guilty || !same_suspect(e->suspect, target))
- continue;
- if (same <= e->s_lno)
- continue;
- if (tlno < e->s_lno + e->num_lines)
- blame_overlap(sb, e, tlno, plno, same, parent);
- }
-}
-
-struct blame_chunk_cb_data {
- struct scoreboard *sb;
- struct origin *target;
- struct origin *parent;
- long plno;
- long tlno;
-};
-
-static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
-{
- struct blame_chunk_cb_data *d = data;
- blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent);
- d->plno = p_next;
- d->tlno = t_next;
-}
-
-/*
- * We are looking at the origin 'target' and aiming to pass blame
- * for the lines it is suspected to its parent. Run diff to find
- * which lines came from parent and pass blame for them.
- */
-static int pass_blame_to_parent(struct scoreboard *sb,
- struct origin *target,
- struct origin *parent)
-{
- int last_in_target;
- mmfile_t file_p, file_o;
- struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 };
- xpparam_t xpp;
- xdemitconf_t xecfg;
-
- last_in_target = find_last_in_target(sb, target);
- if (last_in_target < 0)
- return 1; /* nothing remains for this target */
-
- fill_origin_blob(parent, &file_p);
- fill_origin_blob(target, &file_o);
- num_get_patch++;
-
- memset(&xpp, 0, sizeof(xpp));
- xpp.flags = xdl_opts;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = 0;
- xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg);
- /* The rest (i.e. anything after tlno) are the same as the parent */
- blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
-
- return 0;
-}
-
-/*
- * The lines in blame_entry after splitting blames many times can become
- * very small and trivial, and at some point it becomes pointless to
- * blame the parents. E.g. "\t\t}\n\t}\n\n" appears everywhere in any
- * ordinary C program, and it is not worth to say it was copied from
- * totally unrelated file in the parent.
- *
- * Compute how trivial the lines in the blame_entry are.
- */
-static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
-{
- unsigned score;
- const char *cp, *ep;
-
- if (e->score)
- return e->score;
-
- score = 1;
- cp = nth_line(sb, e->lno);
- ep = nth_line(sb, e->lno + e->num_lines);
- while (cp < ep) {
- unsigned ch = *((unsigned char *)cp);
- if (isalnum(ch))
- score++;
- cp++;
- }
- e->score = score;
- return score;
-}
-
-/*
- * best_so_far[] and this[] are both a split of an existing blame_entry
- * that passes blame to the parent. Maintain best_so_far the best split
- * so far, by comparing this and best_so_far and copying this into
- * bst_so_far as needed.
- */
-static void copy_split_if_better(struct scoreboard *sb,
- struct blame_entry *best_so_far,
- struct blame_entry *this)
-{
- int i;
-
- if (!this[1].suspect)
- return;
- if (best_so_far[1].suspect) {
- if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1]))
- return;
- }
-
- for (i = 0; i < 3; i++)
- origin_incref(this[i].suspect);
- decref_split(best_so_far);
- memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
-}
-
-/*
- * We are looking at a part of the final image represented by
- * ent (tlno and same are offset by ent->s_lno).
- * tlno is where we are looking at in the final image.
- * up to (but not including) same match preimage.
- * plno is where we are looking at in the preimage.
- *
- * <-------------- final image ---------------------->
- * <------ent------>
- * ^tlno ^same
- * <---------preimage----->
- * ^plno
- *
- * All line numbers are 0-based.
- */
-static void handle_split(struct scoreboard *sb,
- struct blame_entry *ent,
- int tlno, int plno, int same,
- struct origin *parent,
- struct blame_entry *split)
-{
- if (ent->num_lines <= tlno)
- return;
- if (tlno < same) {
- struct blame_entry this[3];
- tlno += ent->s_lno;
- same += ent->s_lno;
- split_overlap(this, ent, tlno, plno, same, parent);
- copy_split_if_better(sb, split, this);
- decref_split(this);
- }
-}
-
-struct handle_split_cb_data {
- struct scoreboard *sb;
- struct blame_entry *ent;
- struct origin *parent;
- struct blame_entry *split;
- long plno;
- long tlno;
-};
-
-static void handle_split_cb(void *data, long same, long p_next, long t_next)
-{
- struct handle_split_cb_data *d = data;
- handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split);
- d->plno = p_next;
- d->tlno = t_next;
-}
-
-/*
- * Find the lines from parent that are the same as ent so that
- * we can pass blames to it. file_p has the blob contents for
- * the parent.
- */
-static void find_copy_in_blob(struct scoreboard *sb,
- struct blame_entry *ent,
- struct origin *parent,
- struct blame_entry *split,
- mmfile_t *file_p)
-{
- const char *cp;
- int cnt;
- mmfile_t file_o;
- struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 };
- xpparam_t xpp;
- xdemitconf_t xecfg;
-
- /*
- * Prepare mmfile that contains only the lines in ent.
- */
- cp = nth_line(sb, ent->lno);
- file_o.ptr = (char *) cp;
- cnt = ent->num_lines;
-
- while (cnt && cp < sb->final_buf + sb->final_buf_size) {
- if (*cp++ == '\n')
- cnt--;
- }
- file_o.size = cp - file_o.ptr;
-
- /*
- * file_o is a part of final image we are annotating.
- * file_p partially may match that image.
- */
- memset(&xpp, 0, sizeof(xpp));
- xpp.flags = xdl_opts;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = 1;
- memset(split, 0, sizeof(struct blame_entry [3]));
- xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
- /* remainder, if any, all match the preimage */
- handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
-}
-
-/*
- * See if lines currently target is suspected for can be attributed to
- * parent.
- */
-static int find_move_in_parent(struct scoreboard *sb,
- struct origin *target,
- struct origin *parent)
-{
- int last_in_target, made_progress;
- struct blame_entry *e, split[3];
- mmfile_t file_p;
-
- last_in_target = find_last_in_target(sb, target);
- if (last_in_target < 0)
- return 1; /* nothing remains for this target */
-
- fill_origin_blob(parent, &file_p);
- if (!file_p.ptr)
- return 0;
-
- made_progress = 1;
- while (made_progress) {
- made_progress = 0;
- for (e = sb->ent; e; e = e->next) {
- if (e->guilty || !same_suspect(e->suspect, target) ||
- ent_score(sb, e) < blame_move_score)
- continue;
- find_copy_in_blob(sb, e, parent, split, &file_p);
- if (split[1].suspect &&
- blame_move_score < ent_score(sb, &split[1])) {
- split_blame(sb, split, e);
- made_progress = 1;
- }
- decref_split(split);
- }
- }
- return 0;
-}
-
-struct blame_list {
- struct blame_entry *ent;
- struct blame_entry split[3];
-};
-
-/*
- * Count the number of entries the target is suspected for,
- * and prepare a list of entry and the best split.
- */
-static struct blame_list *setup_blame_list(struct scoreboard *sb,
- struct origin *target,
- int min_score,
- int *num_ents_p)
-{
- struct blame_entry *e;
- int num_ents, i;
- struct blame_list *blame_list = NULL;
-
- for (e = sb->ent, num_ents = 0; e; e = e->next)
- if (!e->scanned && !e->guilty &&
- same_suspect(e->suspect, target) &&
- min_score < ent_score(sb, e))
- num_ents++;
- if (num_ents) {
- blame_list = xcalloc(num_ents, sizeof(struct blame_list));
- for (e = sb->ent, i = 0; e; e = e->next)
- if (!e->scanned && !e->guilty &&
- same_suspect(e->suspect, target) &&
- min_score < ent_score(sb, e))
- blame_list[i++].ent = e;
- }
- *num_ents_p = num_ents;
- return blame_list;
-}
-
-/*
- * Reset the scanned status on all entries.
- */
-static void reset_scanned_flag(struct scoreboard *sb)
-{
- struct blame_entry *e;
- for (e = sb->ent; e; e = e->next)
- e->scanned = 0;
-}
-
-/*
- * For lines target is suspected for, see if we can find code movement
- * across file boundary from the parent commit. porigin is the path
- * in the parent we already tried.
- */
-static int find_copy_in_parent(struct scoreboard *sb,
- struct origin *target,
- struct commit *parent,
- struct origin *porigin,
- int opt)
-{
- struct diff_options diff_opts;
- const char *paths[1];
- int i, j;
- int retval;
- struct blame_list *blame_list;
- int num_ents;
-
- blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
- if (!blame_list)
- return 1; /* nothing remains for this target */
-
- diff_setup(&diff_opts);
- DIFF_OPT_SET(&diff_opts, RECURSIVE);
- diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
-
- paths[0] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
-
- /* Try "find copies harder" on new path if requested;
- * we do not want to use diffcore_rename() actually to
- * match things up; find_copies_harder is set only to
- * force diff_tree_sha1() to feed all filepairs to diff_queue,
- * and this code needs to be after diff_setup_done(), which
- * usually makes find-copies-harder imply copy detection.
- */
- if ((opt & PICKAXE_BLAME_COPY_HARDEST)
- || ((opt & PICKAXE_BLAME_COPY_HARDER)
- && (!porigin || strcmp(target->path, porigin->path))))
- DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
-
- if (is_null_sha1(target->commit->object.sha1))
- do_diff_cache(parent->tree->object.sha1, &diff_opts);
- else
- diff_tree_sha1(parent->tree->object.sha1,
- target->commit->tree->object.sha1,
- "", &diff_opts);
-
- if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
- diffcore_std(&diff_opts);
-
- retval = 0;
- while (1) {
- int made_progress = 0;
-
- for (i = 0; i < diff_queued_diff.nr; i++) {
- struct diff_filepair *p = diff_queued_diff.queue[i];
- struct origin *norigin;
- mmfile_t file_p;
- struct blame_entry this[3];
-
- if (!DIFF_FILE_VALID(p->one))
- continue; /* does not exist in parent */
- if (S_ISGITLINK(p->one->mode))
- continue; /* ignore git links */
- if (porigin && !strcmp(p->one->path, porigin->path))
- /* find_move already dealt with this path */
- continue;
-
- norigin = get_origin(sb, parent, p->one->path);
- hashcpy(norigin->blob_sha1, p->one->sha1);
- fill_origin_blob(norigin, &file_p);
- if (!file_p.ptr)
- continue;
-
- for (j = 0; j < num_ents; j++) {
- find_copy_in_blob(sb, blame_list[j].ent,
- norigin, this, &file_p);
- copy_split_if_better(sb, blame_list[j].split,
- this);
- decref_split(this);
- }
- origin_decref(norigin);
- }
-
- for (j = 0; j < num_ents; j++) {
- struct blame_entry *split = blame_list[j].split;
- if (split[1].suspect &&
- blame_copy_score < ent_score(sb, &split[1])) {
- split_blame(sb, split, blame_list[j].ent);
- made_progress = 1;
- }
- else
- blame_list[j].ent->scanned = 1;
- decref_split(split);
- }
- free(blame_list);
-
- if (!made_progress)
- break;
- blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
- if (!blame_list) {
- retval = 1;
- break;
- }
- }
- reset_scanned_flag(sb);
- diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
- return retval;
-}
-
-/*
- * The blobs of origin and porigin exactly match, so everything
- * origin is suspected for can be blamed on the parent.
- */
-static void pass_whole_blame(struct scoreboard *sb,
- struct origin *origin, struct origin *porigin)
-{
- struct blame_entry *e;
-
- if (!porigin->file.ptr && origin->file.ptr) {
- /* Steal its file */
- porigin->file = origin->file;
- origin->file.ptr = NULL;
- }
- for (e = sb->ent; e; e = e->next) {
- if (!same_suspect(e->suspect, origin))
- continue;
- origin_incref(porigin);
- origin_decref(e->suspect);
- e->suspect = porigin;
- }
-}
-
-/*
- * We pass blame from the current commit to its parents. We keep saying
- * "parent" (and "porigin"), but what we mean is to find scapegoat to
- * exonerate ourselves.
- */
-static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
-{
- if (!reverse)
- return commit->parents;
- return lookup_decoration(&revs->children, &commit->object);
-}
-
-static int num_scapegoats(struct rev_info *revs, struct commit *commit)
-{
- int cnt;
- struct commit_list *l = first_scapegoat(revs, commit);
- for (cnt = 0; l; l = l->next)
- cnt++;
- return cnt;
-}
-
-#define MAXSG 16
-
-static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
-{
- struct rev_info *revs = sb->revs;
- int i, pass, num_sg;
- struct commit *commit = origin->commit;
- struct commit_list *sg;
- struct origin *sg_buf[MAXSG];
- struct origin *porigin, **sg_origin = sg_buf;
-
- num_sg = num_scapegoats(revs, commit);
- if (!num_sg)
- goto finish;
- else if (num_sg < ARRAY_SIZE(sg_buf))
- memset(sg_buf, 0, sizeof(sg_buf));
- else
- sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
-
- /*
- * The first pass looks for unrenamed path to optimize for
- * common cases, then we look for renames in the second pass.
- */
- for (pass = 0; pass < 2; pass++) {
- struct origin *(*find)(struct scoreboard *,
- struct commit *, struct origin *);
- find = pass ? find_rename : find_origin;
-
- for (i = 0, sg = first_scapegoat(revs, commit);
- i < num_sg && sg;
- sg = sg->next, i++) {
- struct commit *p = sg->item;
- int j, same;
-
- if (sg_origin[i])
- continue;
- if (parse_commit(p))
- continue;
- porigin = find(sb, p, origin);
- if (!porigin)
- continue;
- if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) {
- pass_whole_blame(sb, origin, porigin);
- origin_decref(porigin);
- goto finish;
- }
- for (j = same = 0; j < i; j++)
- if (sg_origin[j] &&
- !hashcmp(sg_origin[j]->blob_sha1,
- porigin->blob_sha1)) {
- same = 1;
- break;
- }
- if (!same)
- sg_origin[i] = porigin;
- else
- origin_decref(porigin);
- }
- }
-
- num_commits++;
- for (i = 0, sg = first_scapegoat(revs, commit);
- i < num_sg && sg;
- sg = sg->next, i++) {
- struct origin *porigin = sg_origin[i];
- if (!porigin)
- continue;
- if (!origin->previous) {
- origin_incref(porigin);
- origin->previous = porigin;
- }
- if (pass_blame_to_parent(sb, origin, porigin))
- goto finish;
- }
-
- /*
- * Optionally find moves in parents' files.
- */
- if (opt & PICKAXE_BLAME_MOVE)
- for (i = 0, sg = first_scapegoat(revs, commit);
- i < num_sg && sg;
- sg = sg->next, i++) {
- struct origin *porigin = sg_origin[i];
- if (!porigin)
- continue;
- if (find_move_in_parent(sb, origin, porigin))
- goto finish;
- }
-
- /*
- * Optionally find copies from parents' files.
- */
- if (opt & PICKAXE_BLAME_COPY)
- for (i = 0, sg = first_scapegoat(revs, commit);
- i < num_sg && sg;
- sg = sg->next, i++) {
- struct origin *porigin = sg_origin[i];
- if (find_copy_in_parent(sb, origin, sg->item,
- porigin, opt))
- goto finish;
- }
-
- finish:
- for (i = 0; i < num_sg; i++) {
- if (sg_origin[i]) {
- drop_origin_blob(sg_origin[i]);
- origin_decref(sg_origin[i]);
- }
- }
- drop_origin_blob(origin);
- if (sg_buf != sg_origin)
- free(sg_origin);
-}
-
-/*
- * Information on commits, used for output.
- */
-struct commit_info
-{
- const char *author;
- const char *author_mail;
- unsigned long author_time;
- const char *author_tz;
-
- /* filled only when asked for details */
- const char *committer;
- const char *committer_mail;
- unsigned long committer_time;
- const char *committer_tz;
-
- const char *summary;
-};
-
-/*
- * Parse author/committer line in the commit object buffer
- */
-static void get_ac_line(const char *inbuf, const char *what,
- int person_len, char *person,
- int mail_len, char *mail,
- unsigned long *time, const char **tz)
-{
- int len, tzlen, maillen;
- char *tmp, *endp, *timepos, *mailpos;
-
- tmp = strstr(inbuf, what);
- if (!tmp)
- goto error_out;
- tmp += strlen(what);
- endp = strchr(tmp, '\n');
- if (!endp)
- len = strlen(tmp);
- else
- len = endp - tmp;
- if (person_len <= len) {
- error_out:
- /* Ugh */
- *tz = "(unknown)";
- strcpy(person, *tz);
- strcpy(mail, *tz);
- *time = 0;
- return;
- }
- memcpy(person, tmp, len);
-
- tmp = person;
- tmp += len;
- *tmp = 0;
- while (person < tmp && *tmp != ' ')
- tmp--;
- if (tmp <= person)
- goto error_out;
- *tz = tmp+1;
- tzlen = (person+len)-(tmp+1);
-
- *tmp = 0;
- while (person < tmp && *tmp != ' ')
- tmp--;
- if (tmp <= person)
- goto error_out;
- *time = strtoul(tmp, NULL, 10);
- timepos = tmp;
-
- *tmp = 0;
- while (person < tmp && *tmp != ' ')
- tmp--;
- if (tmp <= person)
- return;
- mailpos = tmp + 1;
- *tmp = 0;
- maillen = timepos - tmp;
- memcpy(mail, mailpos, maillen);
-
- if (!mailmap.nr)
- return;
-
- /*
- * mailmap expansion may make the name longer.
- * make room by pushing stuff down.
- */
- tmp = person + person_len - (tzlen + 1);
- memmove(tmp, *tz, tzlen);
- tmp[tzlen] = 0;
- *tz = tmp;
-
- /*
- * Now, convert both name and e-mail using mailmap
- */
- if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
- /* Add a trailing '>' to email, since map_user returns plain emails
- Note: It already has '<', since we replace from mail+1 */
- mailpos = memchr(mail, '\0', mail_len);
- if (mailpos && mailpos-mail < mail_len - 1) {
- *mailpos = '>';
- *(mailpos+1) = '\0';
- }
- }
-}
-
-static void get_commit_info(struct commit *commit,
- struct commit_info *ret,
- int detailed)
-{
- int len;
- char *tmp, *endp, *reencoded, *message;
- static char author_name[1024];
- static char author_mail[1024];
- static char committer_name[1024];
- static char committer_mail[1024];
- static char summary_buf[1024];
-
- /*
- * We've operated without save_commit_buffer, so
- * we now need to populate them for output.
- */
- if (!commit->buffer) {
- enum object_type type;
- unsigned long size;
- commit->buffer =
- read_sha1_file(commit->object.sha1, &type, &size);
- if (!commit->buffer)
- die("Cannot read commit %s",
- sha1_to_hex(commit->object.sha1));
- }
- reencoded = reencode_commit_message(commit, NULL);
- message = reencoded ? reencoded : commit->buffer;
- ret->author = author_name;
- ret->author_mail = author_mail;
- get_ac_line(message, "\nauthor ",
- sizeof(author_name), author_name,
- sizeof(author_mail), author_mail,
- &ret->author_time, &ret->author_tz);
-
- if (!detailed) {
- free(reencoded);
- return;
- }
-
- ret->committer = committer_name;
- ret->committer_mail = committer_mail;
- get_ac_line(message, "\ncommitter ",
- sizeof(committer_name), committer_name,
- sizeof(committer_mail), committer_mail,
- &ret->committer_time, &ret->committer_tz);
-
- ret->summary = summary_buf;
- tmp = strstr(message, "\n\n");
- if (!tmp) {
- error_out:
- sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
- free(reencoded);
- return;
- }
- tmp += 2;
- endp = strchr(tmp, '\n');
- if (!endp)
- endp = tmp + strlen(tmp);
- len = endp - tmp;
- if (len >= sizeof(summary_buf) || len == 0)
- goto error_out;
- memcpy(summary_buf, tmp, len);
- summary_buf[len] = 0;
- free(reencoded);
-}
-
-/*
- * To allow LF and other nonportable characters in pathnames,
- * they are c-style quoted as needed.
- */
-static void write_filename_info(const char *path)
-{
- printf("filename ");
- write_name_quoted(path, stdout, '\n');
-}
-
-/*
- * Porcelain/Incremental format wants to show a lot of details per
- * commit. Instead of repeating this every line, emit it only once,
- * the first time each commit appears in the output.
- */
-static int emit_one_suspect_detail(struct origin *suspect)
-{
- struct commit_info ci;
-
- if (suspect->commit->object.flags & METAINFO_SHOWN)
- return 0;
-
- suspect->commit->object.flags |= METAINFO_SHOWN;
- get_commit_info(suspect->commit, &ci, 1);
- printf("author %s\n", ci.author);
- printf("author-mail %s\n", ci.author_mail);
- printf("author-time %lu\n", ci.author_time);
- printf("author-tz %s\n", ci.author_tz);
- printf("committer %s\n", ci.committer);
- printf("committer-mail %s\n", ci.committer_mail);
- printf("committer-time %lu\n", ci.committer_time);
- printf("committer-tz %s\n", ci.committer_tz);
- printf("summary %s\n", ci.summary);
- if (suspect->commit->object.flags & UNINTERESTING)
- printf("boundary\n");
- if (suspect->previous) {
- struct origin *prev = suspect->previous;
- printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
- write_name_quoted(prev->path, stdout, '\n');
- }
- return 1;
-}
-
-/*
- * The blame_entry is found to be guilty for the range. Mark it
- * as such, and show it in incremental output.
- */
-static void found_guilty_entry(struct blame_entry *ent)
-{
- if (ent->guilty)
- return;
- ent->guilty = 1;
- if (incremental) {
- struct origin *suspect = ent->suspect;
-
- printf("%s %d %d %d\n",
- sha1_to_hex(suspect->commit->object.sha1),
- ent->s_lno + 1, ent->lno + 1, ent->num_lines);
- emit_one_suspect_detail(suspect);
- write_filename_info(suspect->path);
- maybe_flush_or_die(stdout, "stdout");
- }
-}
-
-/*
- * The main loop -- while the scoreboard has lines whose true origin
- * is still unknown, pick one blame_entry, and allow its current
- * suspect to pass blames to its parents.
- */
-static void assign_blame(struct scoreboard *sb, int opt)
-{
- struct rev_info *revs = sb->revs;
-
- while (1) {
- struct blame_entry *ent;
- struct commit *commit;
- struct origin *suspect = NULL;
-
- /* find one suspect to break down */
- for (ent = sb->ent; !suspect && ent; ent = ent->next)
- if (!ent->guilty)
- suspect = ent->suspect;
- if (!suspect)
- return; /* all done */
-
- /*
- * We will use this suspect later in the loop,
- * so hold onto it in the meantime.
- */
- origin_incref(suspect);
- commit = suspect->commit;
- if (!commit->object.parsed)
- parse_commit(commit);
- if (reverse ||
- (!(commit->object.flags & UNINTERESTING) &&
- !(revs->max_age != -1 && commit->date < revs->max_age)))
- pass_blame(sb, suspect, opt);
- else {
- commit->object.flags |= UNINTERESTING;
- if (commit->object.parsed)
- mark_parents_uninteresting(commit);
- }
- /* treat root commit as boundary */
- if (!commit->parents && !show_root)
- commit->object.flags |= UNINTERESTING;
-
- /* Take responsibility for the remaining entries */
- for (ent = sb->ent; ent; ent = ent->next)
- if (same_suspect(ent->suspect, suspect))
- found_guilty_entry(ent);
- origin_decref(suspect);
-
- if (DEBUG) /* sanity */
- sanity_check_refcnt(sb);
- }
-}
-
-static const char *format_time(unsigned long time, const char *tz_str,
- int show_raw_time)
-{
- static char time_buf[128];
- const char *time_str;
- int time_len;
- int tz;
-
- if (show_raw_time) {
- sprintf(time_buf, "%lu %s", time, tz_str);
- }
- else {
- tz = atoi(tz_str);
- time_str = show_date(time, tz, blame_date_mode);
- time_len = strlen(time_str);
- memcpy(time_buf, time_str, time_len);
- memset(time_buf + time_len, ' ', blame_date_width - time_len);
- }
- return time_buf;
-}
-
-#define OUTPUT_ANNOTATE_COMPAT 001
-#define OUTPUT_LONG_OBJECT_NAME 002
-#define OUTPUT_RAW_TIMESTAMP 004
-#define OUTPUT_PORCELAIN 010
-#define OUTPUT_SHOW_NAME 020
-#define OUTPUT_SHOW_NUMBER 040
-#define OUTPUT_SHOW_SCORE 0100
-#define OUTPUT_NO_AUTHOR 0200
-
-static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
-{
- int cnt;
- const char *cp;
- struct origin *suspect = ent->suspect;
- char hex[41];
-
- strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
- printf("%s%c%d %d %d\n",
- hex,
- ent->guilty ? ' ' : '*', // purely for debugging
- ent->s_lno + 1,
- ent->lno + 1,
- ent->num_lines);
- if (emit_one_suspect_detail(suspect) ||
- (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
- write_filename_info(suspect->path);
-
- cp = nth_line(sb, ent->lno);
- for (cnt = 0; cnt < ent->num_lines; cnt++) {
- char ch;
- if (cnt)
- printf("%s %d %d\n", hex,
- ent->s_lno + 1 + cnt,
- ent->lno + 1 + cnt);
- putchar('\t');
- do {
- ch = *cp++;
- putchar(ch);
- } while (ch != '\n' &&
- cp < sb->final_buf + sb->final_buf_size);
- }
-
- if (sb->final_buf_size && cp[-1] != '\n')
- putchar('\n');
-}
-
-static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
-{
- int cnt;
- const char *cp;
- struct origin *suspect = ent->suspect;
- struct commit_info ci;
- char hex[41];
- int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
-
- get_commit_info(suspect->commit, &ci, 1);
- strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
-
- cp = nth_line(sb, ent->lno);
- for (cnt = 0; cnt < ent->num_lines; cnt++) {
- char ch;
- int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8;
-
- if (suspect->commit->object.flags & UNINTERESTING) {
- if (blank_boundary)
- memset(hex, ' ', length);
- else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
- length--;
- putchar('^');
- }
- }
-
- printf("%.*s", length, hex);
- if (opt & OUTPUT_ANNOTATE_COMPAT)
- printf("\t(%10s\t%10s\t%d)", ci.author,
- format_time(ci.author_time, ci.author_tz,
- show_raw_time),
- ent->lno + 1 + cnt);
- else {
- if (opt & OUTPUT_SHOW_SCORE)
- printf(" %*d %02d",
- max_score_digits, ent->score,
- ent->suspect->refcnt);
- if (opt & OUTPUT_SHOW_NAME)
- printf(" %-*.*s", longest_file, longest_file,
- suspect->path);
- if (opt & OUTPUT_SHOW_NUMBER)
- printf(" %*d", max_orig_digits,
- ent->s_lno + 1 + cnt);
-
- if (!(opt & OUTPUT_NO_AUTHOR)) {
- int pad = longest_author - utf8_strwidth(ci.author);
- printf(" (%s%*s %10s",
- ci.author, pad, "",
- format_time(ci.author_time,
- ci.author_tz,
- show_raw_time));
- }
- printf(" %*d) ",
- max_digits, ent->lno + 1 + cnt);
- }
- do {
- ch = *cp++;
- putchar(ch);
- } while (ch != '\n' &&
- cp < sb->final_buf + sb->final_buf_size);
- }
-
- if (sb->final_buf_size && cp[-1] != '\n')
- putchar('\n');
-}
-
-static void output(struct scoreboard *sb, int option)
-{
- struct blame_entry *ent;
-
- if (option & OUTPUT_PORCELAIN) {
- for (ent = sb->ent; ent; ent = ent->next) {
- struct blame_entry *oth;
- struct origin *suspect = ent->suspect;
- struct commit *commit = suspect->commit;
- if (commit->object.flags & MORE_THAN_ONE_PATH)
- continue;
- for (oth = ent->next; oth; oth = oth->next) {
- if ((oth->suspect->commit != commit) ||
- !strcmp(oth->suspect->path, suspect->path))
- continue;
- commit->object.flags |= MORE_THAN_ONE_PATH;
- break;
- }
- }
- }
-
- for (ent = sb->ent; ent; ent = ent->next) {
- if (option & OUTPUT_PORCELAIN)
- emit_porcelain(sb, ent);
- else {
- emit_other(sb, ent, option);
- }
- }
-}
-
-/*
- * To allow quick access to the contents of nth line in the
- * final image, prepare an index in the scoreboard.
- */
-static int prepare_lines(struct scoreboard *sb)
-{
- const char *buf = sb->final_buf;
- unsigned long len = sb->final_buf_size;
- int num = 0, incomplete = 0, bol = 1;
-
- if (len && buf[len-1] != '\n')
- incomplete++; /* incomplete line at the end */
- while (len--) {
- if (bol) {
- sb->lineno = xrealloc(sb->lineno,
- sizeof(int *) * (num + 1));
- sb->lineno[num] = buf - sb->final_buf;
- bol = 0;
- }
- if (*buf++ == '\n') {
- num++;
- bol = 1;
- }
- }
- sb->lineno = xrealloc(sb->lineno,
- sizeof(int *) * (num + incomplete + 1));
- sb->lineno[num + incomplete] = buf - sb->final_buf;
- sb->num_lines = num + incomplete;
- return sb->num_lines;
-}
-
-/*
- * Add phony grafts for use with -S; this is primarily to
- * support git's cvsserver that wants to give a linear history
- * to its clients.
- */
-static int read_ancestry(const char *graft_file)
-{
- FILE *fp = fopen(graft_file, "r");
- char buf[1024];
- if (!fp)
- return -1;
- while (fgets(buf, sizeof(buf), fp)) {
- /* The format is just "Commit Parent1 Parent2 ...\n" */
- int len = strlen(buf);
- struct commit_graft *graft = read_graft_line(buf, len);
- if (graft)
- register_commit_graft(graft, 0);
- }
- fclose(fp);
- return 0;
-}
-
-/*
- * How many columns do we need to show line numbers in decimal?
- */
-static int lineno_width(int lines)
-{
- int i, width;
-
- for (width = 1, i = 10; i <= lines; width++)
- i *= 10;
- return width;
-}
-
-/*
- * How many columns do we need to show line numbers, authors,
- * and filenames?
- */
-static void find_alignment(struct scoreboard *sb, int *option)
-{
- int longest_src_lines = 0;
- int longest_dst_lines = 0;
- unsigned largest_score = 0;
- struct blame_entry *e;
-
- for (e = sb->ent; e; e = e->next) {
- struct origin *suspect = e->suspect;
- struct commit_info ci;
- int num;
-
- if (strcmp(suspect->path, sb->path))
- *option |= OUTPUT_SHOW_NAME;
- num = strlen(suspect->path);
- if (longest_file < num)
- longest_file = num;
- if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
- suspect->commit->object.flags |= METAINFO_SHOWN;
- get_commit_info(suspect->commit, &ci, 1);
- num = utf8_strwidth(ci.author);
- if (longest_author < num)
- longest_author = num;
- }
- num = e->s_lno + e->num_lines;
- if (longest_src_lines < num)
- longest_src_lines = num;
- num = e->lno + e->num_lines;
- if (longest_dst_lines < num)
- longest_dst_lines = num;
- if (largest_score < ent_score(sb, e))
- largest_score = ent_score(sb, e);
- }
- max_orig_digits = lineno_width(longest_src_lines);
- max_digits = lineno_width(longest_dst_lines);
- max_score_digits = lineno_width(largest_score);
-}
-
-/*
- * For debugging -- origin is refcounted, and this asserts that
- * we do not underflow.
- */
-static void sanity_check_refcnt(struct scoreboard *sb)
-{
- int baa = 0;
- struct blame_entry *ent;
-
- for (ent = sb->ent; ent; ent = ent->next) {
- /* Nobody should have zero or negative refcnt */
- if (ent->suspect->refcnt <= 0) {
- fprintf(stderr, "%s in %s has negative refcnt %d\n",
- ent->suspect->path,
- sha1_to_hex(ent->suspect->commit->object.sha1),
- ent->suspect->refcnt);
- baa = 1;
- }
- }
- if (baa) {
- int opt = 0160;
- find_alignment(sb, &opt);
- output(sb, opt);
- die("Baa %d!", baa);
- }
-}
-
-/*
- * Used for the command line parsing; check if the path exists
- * in the working tree.
- */
-static int has_string_in_work_tree(const char *path)
-{
- struct stat st;
- return !lstat(path, &st);
-}
-
-static unsigned parse_score(const char *arg)
-{
- char *end;
- unsigned long score = strtoul(arg, &end, 10);
- if (*end)
- return 0;
- return score;
-}
-
-static const char *add_prefix(const char *prefix, const char *path)
-{
- return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
-}
-
-/*
- * Parsing of (comma separated) one item in the -L option
- */
-static const char *parse_loc(const char *spec,
- struct scoreboard *sb, long lno,
- long begin, long *ret)
-{
- char *term;
- const char *line;
- long num;
- int reg_error;
- regex_t regexp;
- regmatch_t match[1];
-
- /* Allow "-L <something>,+20" to mean starting at <something>
- * for 20 lines, or "-L <something>,-5" for 5 lines ending at
- * <something>.
- */
- if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
- num = strtol(spec + 1, &term, 10);
- if (term != spec + 1) {
- if (spec[0] == '-')
- num = 0 - num;
- if (0 < num)
- *ret = begin + num - 2;
- else if (!num)
- *ret = begin;
- else
- *ret = begin + num;
- return term;
- }
- return spec;
- }
- num = strtol(spec, &term, 10);
- if (term != spec) {
- *ret = num;
- return term;
- }
- if (spec[0] != '/')
- return spec;
-
- /* it could be a regexp of form /.../ */
- for (term = (char *) spec + 1; *term && *term != '/'; term++) {
- if (*term == '\\')
- term++;
- }
- if (*term != '/')
- return spec;
-
- /* try [spec+1 .. term-1] as regexp */
- *term = 0;
- begin--; /* input is in human terms */
- line = nth_line(sb, begin);
-
- if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) &&
- !(reg_error = regexec(®exp, line, 1, match, 0))) {
- const char *cp = line + match[0].rm_so;
- const char *nline;
-
- while (begin++ < lno) {
- nline = nth_line(sb, begin);
- if (line <= cp && cp < nline)
- break;
- line = nline;
- }
- *ret = begin;
- regfree(®exp);
- *term++ = '/';
- return term;
- }
- else {
- char errbuf[1024];
- regerror(reg_error, ®exp, errbuf, 1024);
- die("-L parameter '%s': %s", spec + 1, errbuf);
- }
-}
-
-/*
- * Parsing of -L option
- */
-static void prepare_blame_range(struct scoreboard *sb,
- const char *bottomtop,
- long lno,
- long *bottom, long *top)
-{
- const char *term;
-
- term = parse_loc(bottomtop, sb, lno, 1, bottom);
- if (*term == ',') {
- term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
- if (*term)
- usage(blame_usage);
- }
- if (*term)
- usage(blame_usage);
-}
-
-static int git_blame_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "blame.showroot")) {
- show_root = git_config_bool(var, value);
- return 0;
- }
- if (!strcmp(var, "blame.blankboundary")) {
- blank_boundary = git_config_bool(var, value);
- return 0;
- }
- if (!strcmp(var, "blame.date")) {
- if (!value)
- return config_error_nonbool(var);
- blame_date_mode = parse_date_format(value);
- return 0;
- }
- return git_default_config(var, value, cb);
-}
-
-/*
- * Prepare a dummy commit that represents the work tree (or staged) item.
- * Note that annotating work tree item never works in the reverse.
- */
-static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
-{
- struct commit *commit;
- struct origin *origin;
- unsigned char head_sha1[20];
- struct strbuf buf = STRBUF_INIT;
- const char *ident;
- time_t now;
- int size, len;
- struct cache_entry *ce;
- unsigned mode;
-
- if (get_sha1("HEAD", head_sha1))
- die("No such ref: HEAD");
-
- time(&now);
- commit = xcalloc(1, sizeof(*commit));
- commit->parents = xcalloc(1, sizeof(*commit->parents));
- commit->parents->item = lookup_commit_reference(head_sha1);
- commit->object.parsed = 1;
- commit->date = now;
- commit->object.type = OBJ_COMMIT;
-
- origin = make_origin(commit, path);
-
- if (!contents_from || strcmp("-", contents_from)) {
- struct stat st;
- const char *read_from;
-
- if (contents_from) {
- if (stat(contents_from, &st) < 0)
- die_errno("Cannot stat '%s'", contents_from);
- read_from = contents_from;
- }
- else {
- if (lstat(path, &st) < 0)
- die_errno("Cannot lstat '%s'", path);
- read_from = path;
- }
- mode = canon_mode(st.st_mode);
- switch (st.st_mode & S_IFMT) {
- case S_IFREG:
- if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
- die_errno("cannot open or read '%s'", read_from);
- break;
- case S_IFLNK:
- if (strbuf_readlink(&buf, read_from, st.st_size) < 0)
- die_errno("cannot readlink '%s'", read_from);
- break;
- default:
- die("unsupported file type %s", read_from);
- }
- }
- else {
- /* Reading from stdin */
- contents_from = "standard input";
- mode = 0;
- if (strbuf_read(&buf, 0, 0) < 0)
- die_errno("failed to read from stdin");
- }
- convert_to_git(path, buf.buf, buf.len, &buf, 0);
- origin->file.ptr = buf.buf;
- origin->file.size = buf.len;
- pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
- commit->util = origin;
-
- /*
- * Read the current index, replace the path entry with
- * origin->blob_sha1 without mucking with its mode or type
- * bits; we are not going to write this index out -- we just
- * want to run "diff-index --cached".
- */
- discard_cache();
- read_cache();
-
- len = strlen(path);
- if (!mode) {
- int pos = cache_name_pos(path, len);
- if (0 <= pos)
- mode = active_cache[pos]->ce_mode;
- else
- /* Let's not bother reading from HEAD tree */
- mode = S_IFREG | 0644;
- }
- size = cache_entry_size(len);
- ce = xcalloc(1, size);
- hashcpy(ce->sha1, origin->blob_sha1);
- memcpy(ce->name, path, len);
- ce->ce_flags = create_ce_flags(len, 0);
- ce->ce_mode = create_ce_mode(mode);
- add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
-
- /*
- * We are not going to write this out, so this does not matter
- * right now, but someday we might optimize diff-index --cached
- * with cache-tree information.
- */
- cache_tree_invalidate_path(active_cache_tree, path);
-
- commit->buffer = xmalloc(400);
- ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
- snprintf(commit->buffer, 400,
- "tree 0000000000000000000000000000000000000000\n"
- "parent %s\n"
- "author %s\n"
- "committer %s\n\n"
- "Version of %s from %s\n",
- sha1_to_hex(head_sha1),
- ident, ident, path, contents_from ? contents_from : path);
- return commit;
-}
-
-static const char *prepare_final(struct scoreboard *sb)
-{
- int i;
- const char *final_commit_name = NULL;
- struct rev_info *revs = sb->revs;
-
- /*
- * There must be one and only one positive commit in the
- * revs->pending array.
- */
- for (i = 0; i < revs->pending.nr; i++) {
- struct object *obj = revs->pending.objects[i].item;
- if (obj->flags & UNINTERESTING)
- continue;
- while (obj->type == OBJ_TAG)
- obj = deref_tag(obj, NULL, 0);
- if (obj->type != OBJ_COMMIT)
- die("Non commit %s?", revs->pending.objects[i].name);
- if (sb->final)
- die("More than one commit to dig from %s and %s?",
- revs->pending.objects[i].name,
- final_commit_name);
- sb->final = (struct commit *) obj;
- final_commit_name = revs->pending.objects[i].name;
- }
- return final_commit_name;
-}
-
-static const char *prepare_initial(struct scoreboard *sb)
-{
- int i;
- const char *final_commit_name = NULL;
- struct rev_info *revs = sb->revs;
-
- /*
- * There must be one and only one negative commit, and it must be
- * the boundary.
- */
- for (i = 0; i < revs->pending.nr; i++) {
- struct object *obj = revs->pending.objects[i].item;
- if (!(obj->flags & UNINTERESTING))
- continue;
- while (obj->type == OBJ_TAG)
- obj = deref_tag(obj, NULL, 0);
- if (obj->type != OBJ_COMMIT)
- die("Non commit %s?", revs->pending.objects[i].name);
- if (sb->final)
- die("More than one commit to dig down to %s and %s?",
- revs->pending.objects[i].name,
- final_commit_name);
- sb->final = (struct commit *) obj;
- final_commit_name = revs->pending.objects[i].name;
- }
- if (!final_commit_name)
- die("No commit to dig down to?");
- return final_commit_name;
-}
-
-static int blame_copy_callback(const struct option *option, const char *arg, int unset)
-{
- int *opt = option->value;
-
- /*
- * -C enables copy from removed files;
- * -C -C enables copy from existing files, but only
- * when blaming a new file;
- * -C -C -C enables copy from existing files for
- * everybody
- */
- if (*opt & PICKAXE_BLAME_COPY_HARDER)
- *opt |= PICKAXE_BLAME_COPY_HARDEST;
- if (*opt & PICKAXE_BLAME_COPY)
- *opt |= PICKAXE_BLAME_COPY_HARDER;
- *opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
-
- if (arg)
- blame_copy_score = parse_score(arg);
- return 0;
-}
-
-static int blame_move_callback(const struct option *option, const char *arg, int unset)
-{
- int *opt = option->value;
-
- *opt |= PICKAXE_BLAME_MOVE;
-
- if (arg)
- blame_move_score = parse_score(arg);
- return 0;
-}
-
-static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
-{
- const char **bottomtop = option->value;
- if (!arg)
- return -1;
- if (*bottomtop)
- die("More than one '-L n,m' option given");
- *bottomtop = arg;
- return 0;
-}
-
-int cmd_blame(int argc, const char **argv, const char *prefix)
-{
- struct rev_info revs;
- const char *path;
- struct scoreboard sb;
- struct origin *o;
- struct blame_entry *ent;
- long dashdash_pos, bottom, top, lno;
- const char *final_commit_name = NULL;
- enum object_type type;
-
- static const char *bottomtop = NULL;
- static int output_option = 0, opt = 0;
- static int show_stats = 0;
- static const char *revs_file = NULL;
- static const char *contents_from = NULL;
- static const struct option options[] = {
- OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"),
- OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
- OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
- OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
- OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
- OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
- OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
- OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
- OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
- OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
- OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
- OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
- OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
- OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
- OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
- { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
- { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
- OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
- OPT_END()
- };
-
- struct parse_opt_ctx_t ctx;
- int cmd_is_annotate = !strcmp(argv[0], "annotate");
-
- git_config(git_blame_config, NULL);
- init_revisions(&revs, NULL);
- revs.date_mode = blame_date_mode;
-
- save_commit_buffer = 0;
- dashdash_pos = 0;
-
- parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_KEEP_ARGV0);
- for (;;) {
- switch (parse_options_step(&ctx, options, blame_opt_usage)) {
- case PARSE_OPT_HELP:
- exit(129);
- case PARSE_OPT_DONE:
- if (ctx.argv[0])
- dashdash_pos = ctx.cpidx;
- goto parse_done;
- }
-
- if (!strcmp(ctx.argv[0], "--reverse")) {
- ctx.argv[0] = "--children";
- reverse = 1;
- }
- parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
- }
-parse_done:
- argc = parse_options_end(&ctx);
-
- if (revs_file && read_ancestry(revs_file))
- die_errno("reading graft file '%s' failed", revs_file);
-
- if (cmd_is_annotate) {
- output_option |= OUTPUT_ANNOTATE_COMPAT;
- blame_date_mode = DATE_ISO8601;
- } else {
- blame_date_mode = revs.date_mode;
- }
-
- /* The maximum width used to show the dates */
- switch (blame_date_mode) {
- case DATE_RFC2822:
- blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
- break;
- case DATE_ISO8601:
- blame_date_width = sizeof("2006-10-19 16:00:04 -0700");
- break;
- case DATE_RAW:
- blame_date_width = sizeof("1161298804 -0700");
- break;
- case DATE_SHORT:
- blame_date_width = sizeof("2006-10-19");
- break;
- case DATE_RELATIVE:
- /* "normal" is used as the fallback for "relative" */
- case DATE_LOCAL:
- case DATE_NORMAL:
- blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
- break;
- }
- blame_date_width -= 1; /* strip the null */
-
- if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
- opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
- PICKAXE_BLAME_COPY_HARDER);
-
- if (!blame_move_score)
- blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
- if (!blame_copy_score)
- blame_copy_score = BLAME_DEFAULT_COPY_SCORE;
-
- /*
- * We have collected options unknown to us in argv[1..unk]
- * which are to be passed to revision machinery if we are
- * going to do the "bottom" processing.
- *
- * The remaining are:
- *
- * (1) if dashdash_pos != 0, its either
- * "blame [revisions] -- <path>" or
- * "blame -- <path> <rev>"
- *
- * (2) otherwise, its one of the two:
- * "blame [revisions] <path>"
- * "blame <path> <rev>"
- *
- * Note that we must strip out <path> from the arguments: we do not
- * want the path pruning but we may want "bottom" processing.
- */
- if (dashdash_pos) {
- switch (argc - dashdash_pos - 1) {
- case 2: /* (1b) */
- if (argc != 4)
- usage_with_options(blame_opt_usage, options);
- /* reorder for the new way: <rev> -- <path> */
- argv[1] = argv[3];
- argv[3] = argv[2];
- argv[2] = "--";
- /* FALLTHROUGH */
- case 1: /* (1a) */
- path = add_prefix(prefix, argv[--argc]);
- argv[argc] = NULL;
- break;
- default:
- usage_with_options(blame_opt_usage, options);
- }
- } else {
- if (argc < 2)
- usage_with_options(blame_opt_usage, options);
- path = add_prefix(prefix, argv[argc - 1]);
- if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */
- path = add_prefix(prefix, argv[1]);
- argv[1] = argv[2];
- }
- argv[argc - 1] = "--";
-
- setup_work_tree();
- if (!has_string_in_work_tree(path))
- die_errno("cannot stat path '%s'", path);
- }
-
- revs.disable_stdin = 1;
- setup_revisions(argc, argv, &revs, NULL);
- memset(&sb, 0, sizeof(sb));
-
- sb.revs = &revs;
- if (!reverse)
- final_commit_name = prepare_final(&sb);
- else if (contents_from)
- die("--contents and --children do not blend well.");
- else
- final_commit_name = prepare_initial(&sb);
-
- if (!sb.final) {
- /*
- * "--not A B -- path" without anything positive;
- * do not default to HEAD, but use the working tree
- * or "--contents".
- */
- setup_work_tree();
- sb.final = fake_working_tree_commit(path, contents_from);
- add_pending_object(&revs, &(sb.final->object), ":");
- }
- else if (contents_from)
- die("Cannot use --contents with final commit object name");
-
- /*
- * If we have bottom, this will mark the ancestors of the
- * bottom commits we would reach while traversing as
- * uninteresting.
- */
- if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
-
- if (is_null_sha1(sb.final->object.sha1)) {
- char *buf;
- o = sb.final->util;
- buf = xmalloc(o->file.size + 1);
- memcpy(buf, o->file.ptr, o->file.size + 1);
- sb.final_buf = buf;
- sb.final_buf_size = o->file.size;
- }
- else {
- o = get_origin(&sb, sb.final, path);
- if (fill_blob_sha1(o))
- die("no such path %s in %s", path, final_commit_name);
-
- sb.final_buf = read_sha1_file(o->blob_sha1, &type,
- &sb.final_buf_size);
- if (!sb.final_buf)
- die("Cannot read blob %s for path %s",
- sha1_to_hex(o->blob_sha1),
- path);
- }
- num_read_blob++;
- lno = prepare_lines(&sb);
-
- bottom = top = 0;
- if (bottomtop)
- prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
- if (bottom && top && top < bottom) {
- long tmp;
- tmp = top; top = bottom; bottom = tmp;
- }
- if (bottom < 1)
- bottom = 1;
- if (top < 1)
- top = lno;
- bottom--;
- if (lno < top || lno < bottom)
- die("file %s has only %lu lines", path, lno);
-
- ent = xcalloc(1, sizeof(*ent));
- ent->lno = bottom;
- ent->num_lines = top - bottom;
- ent->suspect = o;
- ent->s_lno = bottom;
-
- sb.ent = ent;
- sb.path = path;
-
- read_mailmap(&mailmap, NULL);
-
- if (!incremental)
- setup_pager();
-
- assign_blame(&sb, opt);
-
- if (incremental)
- return 0;
-
- coalesce(&sb);
-
- if (!(output_option & OUTPUT_PORCELAIN))
- find_alignment(&sb, &output_option);
-
- output(&sb, output_option);
- free((void *)sb.final_buf);
- for (ent = sb.ent; ent; ) {
- struct blame_entry *e = ent->next;
- free(ent);
- ent = e;
- }
-
- if (show_stats) {
- printf("num read blob: %d\n", num_read_blob);
- printf("num get patch: %d\n", num_get_patch);
- printf("num commits: %d\n", num_commits);
- }
- return 0;
-}
+++ /dev/null
-/*
- * Builtin "git branch"
- *
- * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
- * Based on git-branch.sh by Junio C Hamano.
- */
-
-#include "cache.h"
-#include "color.h"
-#include "refs.h"
-#include "commit.h"
-#include "builtin.h"
-#include "remote.h"
-#include "parse-options.h"
-#include "branch.h"
-#include "diff.h"
-#include "revision.h"
-
-static const char * const builtin_branch_usage[] = {
- "git branch [options] [-r | -a] [--merged | --no-merged]",
- "git branch [options] [-l] [-f] <branchname> [<start-point>]",
- "git branch [options] [-r] (-d | -D) <branchname>",
- "git branch [options] (-m | -M) [<oldbranch>] <newbranch>",
- NULL
-};
-
-#define REF_LOCAL_BRANCH 0x01
-#define REF_REMOTE_BRANCH 0x02
-
-static const char *head;
-static unsigned char head_sha1[20];
-
-static int branch_use_color = -1;
-static char branch_colors[][COLOR_MAXLEN] = {
- GIT_COLOR_RESET,
- GIT_COLOR_NORMAL, /* PLAIN */
- GIT_COLOR_RED, /* REMOTE */
- GIT_COLOR_NORMAL, /* LOCAL */
- GIT_COLOR_GREEN, /* CURRENT */
-};
-enum color_branch {
- BRANCH_COLOR_RESET = 0,
- BRANCH_COLOR_PLAIN = 1,
- BRANCH_COLOR_REMOTE = 2,
- BRANCH_COLOR_LOCAL = 3,
- BRANCH_COLOR_CURRENT = 4,
-};
-
-static enum merge_filter {
- NO_FILTER = 0,
- SHOW_NOT_MERGED,
- SHOW_MERGED,
-} merge_filter;
-static unsigned char merge_filter_ref[20];
-
-static int parse_branch_color_slot(const char *var, int ofs)
-{
- if (!strcasecmp(var+ofs, "plain"))
- return BRANCH_COLOR_PLAIN;
- if (!strcasecmp(var+ofs, "reset"))
- return BRANCH_COLOR_RESET;
- if (!strcasecmp(var+ofs, "remote"))
- return BRANCH_COLOR_REMOTE;
- if (!strcasecmp(var+ofs, "local"))
- return BRANCH_COLOR_LOCAL;
- if (!strcasecmp(var+ofs, "current"))
- return BRANCH_COLOR_CURRENT;
- return -1;
-}
-
-static int git_branch_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "color.branch")) {
- branch_use_color = git_config_colorbool(var, value, -1);
- return 0;
- }
- if (!prefixcmp(var, "color.branch.")) {
- int slot = parse_branch_color_slot(var, 13);
- if (slot < 0)
- return 0;
- if (!value)
- return config_error_nonbool(var);
- color_parse(value, var, branch_colors[slot]);
- return 0;
- }
- return git_color_default_config(var, value, cb);
-}
-
-static const char *branch_get_color(enum color_branch ix)
-{
- if (branch_use_color > 0)
- return branch_colors[ix];
- return "";
-}
-
-static int branch_merged(int kind, const char *name,
- struct commit *rev, struct commit *head_rev)
-{
- /*
- * This checks whether the merge bases of branch and HEAD (or
- * the other branch this branch builds upon) contains the
- * branch, which means that the branch has already been merged
- * safely to HEAD (or the other branch).
- */
- struct commit *reference_rev = NULL;
- const char *reference_name = NULL;
- int merged;
-
- if (kind == REF_LOCAL_BRANCH) {
- struct branch *branch = branch_get(name);
- unsigned char sha1[20];
-
- if (branch &&
- branch->merge &&
- branch->merge[0] &&
- branch->merge[0]->dst &&
- (reference_name =
- resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
- reference_rev = lookup_commit_reference(sha1);
- }
- if (!reference_rev)
- reference_rev = head_rev;
-
- merged = in_merge_bases(rev, &reference_rev, 1);
-
- /*
- * After the safety valve is fully redefined to "check with
- * upstream, if any, otherwise with HEAD", we should just
- * return the result of the in_merge_bases() above without
- * any of the following code, but during the transition period,
- * a gentle reminder is in order.
- */
- if ((head_rev != reference_rev) &&
- in_merge_bases(rev, &head_rev, 1) != merged) {
- if (merged)
- warning("deleting branch '%s' that has been merged to\n"
- " '%s', but it is not yet merged to HEAD.",
- name, reference_name);
- else
- warning("not deleting branch '%s' that is not yet merged to\n"
- " '%s', even though it is merged to HEAD.",
- name, reference_name);
- }
- return merged;
-}
-
-static int delete_branches(int argc, const char **argv, int force, int kinds)
-{
- struct commit *rev, *head_rev = NULL;
- unsigned char sha1[20];
- char *name = NULL;
- const char *fmt, *remote;
- int i;
- int ret = 0;
- struct strbuf bname = STRBUF_INIT;
-
- switch (kinds) {
- case REF_REMOTE_BRANCH:
- fmt = "refs/remotes/%s";
- remote = "remote ";
- force = 1;
- break;
- case REF_LOCAL_BRANCH:
- fmt = "refs/heads/%s";
- remote = "";
- break;
- default:
- die("cannot use -a with -d");
- }
-
- if (!force) {
- head_rev = lookup_commit_reference(head_sha1);
- if (!head_rev)
- die("Couldn't look up commit object for HEAD");
- }
- for (i = 0; i < argc; i++, strbuf_release(&bname)) {
- strbuf_branchname(&bname, argv[i]);
- if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
- error("Cannot delete the branch '%s' "
- "which you are currently on.", bname.buf);
- ret = 1;
- continue;
- }
-
- free(name);
-
- name = xstrdup(mkpath(fmt, bname.buf));
- if (!resolve_ref(name, sha1, 1, NULL)) {
- error("%sbranch '%s' not found.",
- remote, bname.buf);
- ret = 1;
- continue;
- }
-
- rev = lookup_commit_reference(sha1);
- if (!rev) {
- error("Couldn't look up commit object for '%s'", name);
- ret = 1;
- continue;
- }
-
- if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
- error("The branch '%s' is not fully merged.\n"
- "If you are sure you want to delete it, "
- "run 'git branch -D %s'.", bname.buf, bname.buf);
- ret = 1;
- continue;
- }
-
- if (delete_ref(name, sha1, 0)) {
- error("Error deleting %sbranch '%s'", remote,
- bname.buf);
- ret = 1;
- } else {
- struct strbuf buf = STRBUF_INIT;
- printf("Deleted %sbranch %s (was %s).\n", remote,
- bname.buf,
- find_unique_abbrev(sha1, DEFAULT_ABBREV));
- strbuf_addf(&buf, "branch.%s", bname.buf);
- if (git_config_rename_section(buf.buf, NULL) < 0)
- warning("Update of config-file failed");
- strbuf_release(&buf);
- }
- }
-
- free(name);
-
- return(ret);
-}
-
-struct ref_item {
- char *name;
- char *dest;
- unsigned int kind, len;
- struct commit *commit;
-};
-
-struct ref_list {
- struct rev_info revs;
- int index, alloc, maxwidth, verbose, abbrev;
- struct ref_item *list;
- struct commit_list *with_commit;
- int kinds;
-};
-
-static char *resolve_symref(const char *src, const char *prefix)
-{
- unsigned char sha1[20];
- int flag;
- const char *dst, *cp;
-
- dst = resolve_ref(src, sha1, 0, &flag);
- if (!(dst && (flag & REF_ISSYMREF)))
- return NULL;
- if (prefix && (cp = skip_prefix(dst, prefix)))
- dst = cp;
- return xstrdup(dst);
-}
-
-static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
-{
- struct ref_list *ref_list = (struct ref_list*)(cb_data);
- struct ref_item *newitem;
- struct commit *commit;
- int kind, i;
- const char *prefix, *orig_refname = refname;
-
- static struct {
- int kind;
- const char *prefix;
- int pfxlen;
- } ref_kind[] = {
- { REF_LOCAL_BRANCH, "refs/heads/", 11 },
- { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
- };
-
- /* Detect kind */
- for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
- prefix = ref_kind[i].prefix;
- if (strncmp(refname, prefix, ref_kind[i].pfxlen))
- continue;
- kind = ref_kind[i].kind;
- refname += ref_kind[i].pfxlen;
- break;
- }
- if (ARRAY_SIZE(ref_kind) <= i)
- return 0;
-
- /* Don't add types the caller doesn't want */
- if ((kind & ref_list->kinds) == 0)
- return 0;
-
- commit = NULL;
- if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
- commit = lookup_commit_reference_gently(sha1, 1);
- if (!commit)
- return error("branch '%s' does not point at a commit", refname);
-
- /* Filter with with_commit if specified */
- if (!is_descendant_of(commit, ref_list->with_commit))
- return 0;
-
- if (merge_filter != NO_FILTER)
- add_pending_object(&ref_list->revs,
- (struct object *)commit, refname);
- }
-
- /* Resize buffer */
- if (ref_list->index >= ref_list->alloc) {
- ref_list->alloc = alloc_nr(ref_list->alloc);
- ref_list->list = xrealloc(ref_list->list,
- ref_list->alloc * sizeof(struct ref_item));
- }
-
- /* Record the new item */
- newitem = &(ref_list->list[ref_list->index++]);
- newitem->name = xstrdup(refname);
- newitem->kind = kind;
- newitem->commit = commit;
- newitem->len = strlen(refname);
- newitem->dest = resolve_symref(orig_refname, prefix);
- /* adjust for "remotes/" */
- if (newitem->kind == REF_REMOTE_BRANCH &&
- ref_list->kinds != REF_REMOTE_BRANCH)
- newitem->len += 8;
- if (newitem->len > ref_list->maxwidth)
- ref_list->maxwidth = newitem->len;
-
- return 0;
-}
-
-static void free_ref_list(struct ref_list *ref_list)
-{
- int i;
-
- for (i = 0; i < ref_list->index; i++) {
- free(ref_list->list[i].name);
- free(ref_list->list[i].dest);
- }
- free(ref_list->list);
-}
-
-static int ref_cmp(const void *r1, const void *r2)
-{
- struct ref_item *c1 = (struct ref_item *)(r1);
- struct ref_item *c2 = (struct ref_item *)(r2);
-
- if (c1->kind != c2->kind)
- return c1->kind - c2->kind;
- return strcmp(c1->name, c2->name);
-}
-
-static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
- int show_upstream_ref)
-{
- int ours, theirs;
- struct branch *branch = branch_get(branch_name);
-
- if (!stat_tracking_info(branch, &ours, &theirs)) {
- if (branch && branch->merge && branch->merge[0]->dst &&
- show_upstream_ref)
- strbuf_addf(stat, "[%s] ",
- shorten_unambiguous_ref(branch->merge[0]->dst, 0));
- return;
- }
-
- strbuf_addch(stat, '[');
- if (show_upstream_ref)
- strbuf_addf(stat, "%s: ",
- shorten_unambiguous_ref(branch->merge[0]->dst, 0));
- if (!ours)
- strbuf_addf(stat, "behind %d] ", theirs);
- else if (!theirs)
- strbuf_addf(stat, "ahead %d] ", ours);
- else
- strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs);
-}
-
-static int matches_merge_filter(struct commit *commit)
-{
- int is_merged;
-
- if (merge_filter == NO_FILTER)
- return 1;
-
- is_merged = !!(commit->object.flags & UNINTERESTING);
- return (is_merged == (merge_filter == SHOW_MERGED));
-}
-
-static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
- int abbrev, int current, char *prefix)
-{
- char c;
- int color;
- struct commit *commit = item->commit;
- struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
-
- if (!matches_merge_filter(commit))
- return;
-
- switch (item->kind) {
- case REF_LOCAL_BRANCH:
- color = BRANCH_COLOR_LOCAL;
- break;
- case REF_REMOTE_BRANCH:
- color = BRANCH_COLOR_REMOTE;
- break;
- default:
- color = BRANCH_COLOR_PLAIN;
- break;
- }
-
- c = ' ';
- if (current) {
- c = '*';
- color = BRANCH_COLOR_CURRENT;
- }
-
- strbuf_addf(&name, "%s%s", prefix, item->name);
- if (verbose)
- strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
- maxwidth, name.buf,
- branch_get_color(BRANCH_COLOR_RESET));
- else
- strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
- name.buf, branch_get_color(BRANCH_COLOR_RESET));
-
- if (item->dest)
- strbuf_addf(&out, " -> %s", item->dest);
- else if (verbose) {
- struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
- const char *sub = " **** invalid ref ****";
-
- commit = item->commit;
- if (commit && !parse_commit(commit)) {
- struct pretty_print_context ctx = {0};
- pretty_print_commit(CMIT_FMT_ONELINE, commit,
- &subject, &ctx);
- sub = subject.buf;
- }
-
- if (item->kind == REF_LOCAL_BRANCH)
- fill_tracking_info(&stat, item->name, verbose > 1);
-
- strbuf_addf(&out, " %s %s%s",
- find_unique_abbrev(item->commit->object.sha1, abbrev),
- stat.buf, sub);
- strbuf_release(&stat);
- strbuf_release(&subject);
- }
- printf("%s\n", out.buf);
- strbuf_release(&name);
- strbuf_release(&out);
-}
-
-static int calc_maxwidth(struct ref_list *refs)
-{
- int i, w = 0;
- for (i = 0; i < refs->index; i++) {
- if (!matches_merge_filter(refs->list[i].commit))
- continue;
- if (refs->list[i].len > w)
- w = refs->list[i].len;
- }
- return w;
-}
-
-
-static void show_detached(struct ref_list *ref_list)
-{
- struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
-
- if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
- struct ref_item item;
- item.name = xstrdup("(no branch)");
- item.len = strlen(item.name);
- item.kind = REF_LOCAL_BRANCH;
- item.dest = NULL;
- item.commit = head_commit;
- if (item.len > ref_list->maxwidth)
- ref_list->maxwidth = item.len;
- print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, "");
- free(item.name);
- }
-}
-
-static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
-{
- int i;
- struct ref_list ref_list;
-
- memset(&ref_list, 0, sizeof(ref_list));
- ref_list.kinds = kinds;
- ref_list.verbose = verbose;
- ref_list.abbrev = abbrev;
- ref_list.with_commit = with_commit;
- if (merge_filter != NO_FILTER)
- init_revisions(&ref_list.revs, NULL);
- for_each_rawref(append_ref, &ref_list);
- if (merge_filter != NO_FILTER) {
- struct commit *filter;
- filter = lookup_commit_reference_gently(merge_filter_ref, 0);
- filter->object.flags |= UNINTERESTING;
- add_pending_object(&ref_list.revs,
- (struct object *) filter, "");
- ref_list.revs.limited = 1;
- prepare_revision_walk(&ref_list.revs);
- if (verbose)
- ref_list.maxwidth = calc_maxwidth(&ref_list);
- }
-
- qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
-
- detached = (detached && (kinds & REF_LOCAL_BRANCH));
- if (detached)
- show_detached(&ref_list);
-
- for (i = 0; i < ref_list.index; i++) {
- int current = !detached &&
- (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
- !strcmp(ref_list.list[i].name, head);
- char *prefix = (kinds != REF_REMOTE_BRANCH &&
- ref_list.list[i].kind == REF_REMOTE_BRANCH)
- ? "remotes/" : "";
- print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
- abbrev, current, prefix);
- }
-
- free_ref_list(&ref_list);
-}
-
-static void rename_branch(const char *oldname, const char *newname, int force)
-{
- struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
- unsigned char sha1[20];
- struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
- int recovery = 0;
-
- if (!oldname)
- die("cannot rename the current branch while not on any.");
-
- if (strbuf_check_branch_ref(&oldref, oldname)) {
- /*
- * Bad name --- this could be an attempt to rename a
- * ref that we used to allow to be created by accident.
- */
- if (resolve_ref(oldref.buf, sha1, 1, NULL))
- recovery = 1;
- else
- die("Invalid branch name: '%s'", oldname);
- }
-
- if (strbuf_check_branch_ref(&newref, newname))
- die("Invalid branch name: '%s'", newname);
-
- if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
- die("A branch named '%s' already exists.", newref.buf + 11);
-
- strbuf_addf(&logmsg, "Branch: renamed %s to %s",
- oldref.buf, newref.buf);
-
- if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
- die("Branch rename failed");
- strbuf_release(&logmsg);
-
- if (recovery)
- warning("Renamed a misnamed branch '%s' away", oldref.buf + 11);
-
- /* no need to pass logmsg here as HEAD didn't really move */
- if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
- die("Branch renamed to %s, but HEAD is not updated!", newname);
-
- strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
- strbuf_release(&oldref);
- strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
- strbuf_release(&newref);
- if (git_config_rename_section(oldsection.buf, newsection.buf) < 0)
- die("Branch is renamed, but update of config-file failed");
- strbuf_release(&oldsection);
- strbuf_release(&newsection);
-}
-
-static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
-{
- merge_filter = ((opt->long_name[0] == 'n')
- ? SHOW_NOT_MERGED
- : SHOW_MERGED);
- if (unset)
- merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
- if (!arg)
- arg = "HEAD";
- if (get_sha1(arg, merge_filter_ref))
- die("malformed object name %s", arg);
- return 0;
-}
-
-int cmd_branch(int argc, const char **argv, const char *prefix)
-{
- int delete = 0, rename = 0, force_create = 0;
- int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
- int reflog = 0;
- enum branch_track track;
- int kinds = REF_LOCAL_BRANCH;
- struct commit_list *with_commit = NULL;
-
- struct option options[] = {
- OPT_GROUP("Generic options"),
- OPT__VERBOSE(&verbose),
- OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))",
- BRANCH_TRACK_EXPLICIT),
- OPT_SET_INT( 0, "set-upstream", &track, "change upstream info",
- BRANCH_TRACK_OVERRIDE),
- OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"),
- OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches",
- REF_REMOTE_BRANCH),
- {
- OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
- "print only branches that contain the commit",
- PARSE_OPT_LASTARG_DEFAULT,
- parse_opt_with_commit, (intptr_t)"HEAD",
- },
- {
- OPTION_CALLBACK, 0, "with", &with_commit, "commit",
- "print only branches that contain the commit",
- PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
- parse_opt_with_commit, (intptr_t) "HEAD",
- },
- OPT__ABBREV(&abbrev),
-
- OPT_GROUP("Specific git-branch actions:"),
- OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
- REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
- OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
- OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
- OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
- OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
- OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
- OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"),
- {
- OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
- "commit", "print only not merged branches",
- PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
- opt_parse_merge_filter, (intptr_t) "HEAD",
- },
- {
- OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
- "commit", "print only merged branches",
- PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
- opt_parse_merge_filter, (intptr_t) "HEAD",
- },
- OPT_END(),
- };
-
- git_config(git_branch_config, NULL);
-
- if (branch_use_color == -1)
- branch_use_color = git_use_color_default;
-
- track = git_branch_track;
-
- head = resolve_ref("HEAD", head_sha1, 0, NULL);
- if (!head)
- die("Failed to resolve HEAD as a valid ref.");
- head = xstrdup(head);
- if (!strcmp(head, "HEAD")) {
- detached = 1;
- } else {
- if (prefixcmp(head, "refs/heads/"))
- die("HEAD not found below refs/heads!");
- head += 11;
- }
- hashcpy(merge_filter_ref, head_sha1);
-
- argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
- 0);
- if (!!delete + !!rename + !!force_create > 1)
- usage_with_options(builtin_branch_usage, options);
-
- if (delete)
- return delete_branches(argc, argv, delete > 1, kinds);
- else if (argc == 0)
- print_ref_list(kinds, detached, verbose, abbrev, with_commit);
- else if (rename && (argc == 1))
- rename_branch(head, argv[0], rename > 1);
- else if (rename && (argc == 2))
- rename_branch(argv[0], argv[1], rename > 1);
- else if (argc <= 2) {
- if (kinds != REF_LOCAL_BRANCH)
- die("-a and -r options to 'git branch' do not make sense with a branch name");
- create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
- force_create, reflog, track);
- } else
- usage_with_options(builtin_branch_usage, options);
-
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "bundle.h"
-
-/*
- * Basic handler for bundle files to connect repositories via sneakernet.
- * Invocation must include action.
- * This function can create a bundle or provide information on an existing
- * bundle supporting "fetch", "pull", and "ls-remote".
- */
-
-static const char builtin_bundle_usage[] =
- "git bundle create <file> <git-rev-list args>\n"
- " or: git bundle verify <file>\n"
- " or: git bundle list-heads <file> [refname...]\n"
- " or: git bundle unbundle <file> [refname...]";
-
-int cmd_bundle(int argc, const char **argv, const char *prefix)
-{
- struct bundle_header header;
- int nongit;
- const char *cmd, *bundle_file;
- int bundle_fd = -1;
- char buffer[PATH_MAX];
-
- if (argc < 3)
- usage(builtin_bundle_usage);
-
- cmd = argv[1];
- bundle_file = argv[2];
- argc -= 2;
- argv += 2;
-
- prefix = setup_git_directory_gently(&nongit);
- if (prefix && bundle_file[0] != '/') {
- snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file);
- bundle_file = buffer;
- }
-
- memset(&header, 0, sizeof(header));
- if (strcmp(cmd, "create") && (bundle_fd =
- read_bundle_header(bundle_file, &header)) < 0)
- return 1;
-
- if (!strcmp(cmd, "verify")) {
- close(bundle_fd);
- if (verify_bundle(&header, 1))
- return 1;
- fprintf(stderr, "%s is okay\n", bundle_file);
- return 0;
- }
- if (!strcmp(cmd, "list-heads")) {
- close(bundle_fd);
- return !!list_bundle_refs(&header, argc, argv);
- }
- if (!strcmp(cmd, "create")) {
- if (nongit)
- die("Need a repository to create a bundle.");
- return !!create_bundle(&header, bundle_file, argc, argv);
- } else if (!strcmp(cmd, "unbundle")) {
- if (nongit)
- die("Need a repository to unbundle.");
- return !!unbundle(&header, bundle_fd) ||
- list_bundle_refs(&header, argc, argv);
- } else
- usage(builtin_bundle_usage);
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "exec_cmd.h"
-#include "tag.h"
-#include "tree.h"
-#include "builtin.h"
-#include "parse-options.h"
-
-#define BATCH 1
-#define BATCH_CHECK 2
-
-static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
-{
- /* the parser in tag.c is useless here. */
- const char *endp = buf + size;
- const char *cp = buf;
-
- while (cp < endp) {
- char c = *cp++;
- if (c != '\n')
- continue;
- if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) {
- const char *tagger = cp;
-
- /* Found the tagger line. Copy out the contents
- * of the buffer so far.
- */
- write_or_die(1, buf, cp - buf);
-
- /*
- * Do something intelligent, like pretty-printing
- * the date.
- */
- while (cp < endp) {
- if (*cp++ == '\n') {
- /* tagger to cp is a line
- * that has ident and time.
- */
- const char *sp = tagger;
- char *ep;
- unsigned long date;
- long tz;
- while (sp < cp && *sp != '>')
- sp++;
- if (sp == cp) {
- /* give up */
- write_or_die(1, tagger,
- cp - tagger);
- break;
- }
- while (sp < cp &&
- !('0' <= *sp && *sp <= '9'))
- sp++;
- write_or_die(1, tagger, sp - tagger);
- date = strtoul(sp, &ep, 10);
- tz = strtol(ep, NULL, 10);
- sp = show_date(date, tz, 0);
- write_or_die(1, sp, strlen(sp));
- xwrite(1, "\n", 1);
- break;
- }
- }
- break;
- }
- if (cp < endp && *cp == '\n')
- /* end of header */
- break;
- }
- /* At this point, we have copied out the header up to the end of
- * the tagger line and cp points at one past \n. It could be the
- * next header line after the tagger line, or it could be another
- * \n that marks the end of the headers. We need to copy out the
- * remainder as is.
- */
- if (cp < endp)
- write_or_die(1, cp, endp - cp);
-}
-
-static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
-{
- unsigned char sha1[20];
- enum object_type type;
- void *buf;
- unsigned long size;
-
- if (get_sha1(obj_name, sha1))
- die("Not a valid object name %s", obj_name);
-
- buf = NULL;
- switch (opt) {
- case 't':
- type = sha1_object_info(sha1, NULL);
- if (type > 0) {
- printf("%s\n", typename(type));
- return 0;
- }
- break;
-
- case 's':
- type = sha1_object_info(sha1, &size);
- if (type > 0) {
- printf("%lu\n", size);
- return 0;
- }
- break;
-
- case 'e':
- return !has_sha1_file(sha1);
-
- case 'p':
- type = sha1_object_info(sha1, NULL);
- if (type < 0)
- die("Not a valid object name %s", obj_name);
-
- /* custom pretty-print here */
- if (type == OBJ_TREE) {
- const char *ls_args[3] = {"ls-tree", obj_name, NULL};
- return cmd_ls_tree(2, ls_args, NULL);
- }
-
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf)
- die("Cannot read object %s", obj_name);
- if (type == OBJ_TAG) {
- pprint_tag(sha1, buf, size);
- return 0;
- }
-
- /* otherwise just spit out the data */
- break;
- case 0:
- buf = read_object_with_reference(sha1, exp_type, &size, NULL);
- break;
-
- default:
- die("git cat-file: unknown option: %s", exp_type);
- }
-
- if (!buf)
- die("git cat-file %s: bad file", obj_name);
-
- write_or_die(1, buf, size);
- return 0;
-}
-
-static int batch_one_object(const char *obj_name, int print_contents)
-{
- unsigned char sha1[20];
- enum object_type type = 0;
- unsigned long size;
- void *contents = contents;
-
- if (!obj_name)
- return 1;
-
- if (get_sha1(obj_name, sha1)) {
- printf("%s missing\n", obj_name);
- fflush(stdout);
- return 0;
- }
-
- if (print_contents == BATCH)
- contents = read_sha1_file(sha1, &type, &size);
- else
- type = sha1_object_info(sha1, &size);
-
- if (type <= 0) {
- printf("%s missing\n", obj_name);
- fflush(stdout);
- return 0;
- }
-
- printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size);
- fflush(stdout);
-
- if (print_contents == BATCH) {
- write_or_die(1, contents, size);
- printf("\n");
- fflush(stdout);
- free(contents);
- }
-
- return 0;
-}
-
-static int batch_objects(int print_contents)
-{
- struct strbuf buf = STRBUF_INIT;
-
- while (strbuf_getline(&buf, stdin, '\n') != EOF) {
- int error = batch_one_object(buf.buf, print_contents);
- if (error)
- return error;
- }
-
- return 0;
-}
-
-static const char * const cat_file_usage[] = {
- "git cat-file (-t|-s|-e|-p|<type>) <object>",
- "git cat-file (--batch|--batch-check) < <list_of_objects>",
- NULL
-};
-
-int cmd_cat_file(int argc, const char **argv, const char *prefix)
-{
- int opt = 0, batch = 0;
- const char *exp_type = NULL, *obj_name = NULL;
-
- const struct option options[] = {
- OPT_GROUP("<type> can be one of: blob, tree, commit, tag"),
- OPT_SET_INT('t', NULL, &opt, "show object type", 't'),
- OPT_SET_INT('s', NULL, &opt, "show object size", 's'),
- OPT_SET_INT('e', NULL, &opt,
- "exit with zero when there's no error", 'e'),
- OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'),
- OPT_SET_INT(0, "batch", &batch,
- "show info and content of objects fed from the standard input",
- BATCH),
- OPT_SET_INT(0, "batch-check", &batch,
- "show info about objects fed from the standard input",
- BATCH_CHECK),
- OPT_END()
- };
-
- git_config(git_default_config, NULL);
-
- if (argc != 3 && argc != 2)
- usage_with_options(cat_file_usage, options);
-
- argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);
-
- if (opt) {
- if (argc == 1)
- obj_name = argv[0];
- else
- usage_with_options(cat_file_usage, options);
- }
- if (!opt && !batch) {
- if (argc == 2) {
- exp_type = argv[0];
- obj_name = argv[1];
- } else
- usage_with_options(cat_file_usage, options);
- }
- if (batch && (opt || argc)) {
- usage_with_options(cat_file_usage, options);
- }
-
- if (batch)
- return batch_objects(batch);
-
- return cat_one_file(opt, exp_type, obj_name);
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "attr.h"
-#include "quote.h"
-#include "parse-options.h"
-
-static int stdin_paths;
-static const char * const check_attr_usage[] = {
-"git check-attr attr... [--] pathname...",
-"git check-attr --stdin attr... < <list-of-paths>",
-NULL
-};
-
-static int null_term_line;
-
-static const struct option check_attr_options[] = {
- OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"),
- OPT_BOOLEAN('z', NULL, &null_term_line,
- "input paths are terminated by a null character"),
- OPT_END()
-};
-
-static void check_attr(int cnt, struct git_attr_check *check,
- const char** name, const char *file)
-{
- int j;
- if (git_checkattr(file, cnt, check))
- die("git_checkattr died");
- for (j = 0; j < cnt; j++) {
- const char *value = check[j].value;
-
- if (ATTR_TRUE(value))
- value = "set";
- else if (ATTR_FALSE(value))
- value = "unset";
- else if (ATTR_UNSET(value))
- value = "unspecified";
-
- quote_c_style(file, NULL, stdout, 0);
- printf(": %s: %s\n", name[j], value);
- }
-}
-
-static void check_attr_stdin_paths(int cnt, struct git_attr_check *check,
- const char** name)
-{
- struct strbuf buf, nbuf;
- int line_termination = null_term_line ? 0 : '\n';
-
- strbuf_init(&buf, 0);
- strbuf_init(&nbuf, 0);
- while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
- if (line_termination && buf.buf[0] == '"') {
- strbuf_reset(&nbuf);
- if (unquote_c_style(&nbuf, buf.buf, NULL))
- die("line is badly quoted");
- strbuf_swap(&buf, &nbuf);
- }
- check_attr(cnt, check, name, buf.buf);
- maybe_flush_or_die(stdout, "attribute to stdout");
- }
- strbuf_release(&buf);
- strbuf_release(&nbuf);
-}
-
-int cmd_check_attr(int argc, const char **argv, const char *prefix)
-{
- struct git_attr_check *check;
- int cnt, i, doubledash;
- const char *errstr = NULL;
-
- argc = parse_options(argc, argv, prefix, check_attr_options,
- check_attr_usage, PARSE_OPT_KEEP_DASHDASH);
- if (!argc)
- usage_with_options(check_attr_usage, check_attr_options);
-
- if (read_cache() < 0) {
- die("invalid cache");
- }
-
- doubledash = -1;
- for (i = 0; doubledash < 0 && i < argc; i++) {
- if (!strcmp(argv[i], "--"))
- doubledash = i;
- }
-
- /* If there is no double dash, we handle only one attribute */
- if (doubledash < 0) {
- cnt = 1;
- doubledash = 0;
- } else
- cnt = doubledash;
- doubledash++;
-
- if (cnt <= 0)
- errstr = "No attribute specified";
- else if (stdin_paths && doubledash < argc)
- errstr = "Can't specify files with --stdin";
- if (errstr) {
- error("%s", errstr);
- usage_with_options(check_attr_usage, check_attr_options);
- }
-
- check = xcalloc(cnt, sizeof(*check));
- for (i = 0; i < cnt; i++) {
- const char *name;
- struct git_attr *a;
- name = argv[i];
- a = git_attr(name);
- if (!a)
- return error("%s: not a valid attribute name", name);
- check[i].attr = a;
- }
-
- if (stdin_paths)
- check_attr_stdin_paths(cnt, check, argv);
- else {
- for (i = doubledash; i < argc; i++)
- check_attr(cnt, check, argv, argv[i]);
- maybe_flush_or_die(stdout, "attribute to stdout");
- }
- return 0;
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- */
-
-#include "cache.h"
-#include "refs.h"
-#include "builtin.h"
-#include "strbuf.h"
-
-static const char builtin_check_ref_format_usage[] =
-"git check-ref-format [--print] <refname>\n"
-" or: git check-ref-format --branch <branchname-shorthand>";
-
-/*
- * Replace each run of adjacent slashes in src with a single slash,
- * and write the result to dst.
- *
- * This function is similar to normalize_path_copy(), but stripped down
- * to meet check_ref_format's simpler needs.
- */
-static void collapse_slashes(char *dst, const char *src)
-{
- char ch;
- char prev = '\0';
-
- while ((ch = *src++) != '\0') {
- if (prev == '/' && ch == prev)
- continue;
-
- *dst++ = ch;
- prev = ch;
- }
- *dst = '\0';
-}
-
-int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
-{
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage(builtin_check_ref_format_usage);
-
- if (argc == 3 && !strcmp(argv[1], "--branch")) {
- struct strbuf sb = STRBUF_INIT;
-
- if (strbuf_check_branch_ref(&sb, argv[2]))
- die("'%s' is not a valid branch name", argv[2]);
- printf("%s\n", sb.buf + 11);
- exit(0);
- }
- if (argc == 3 && !strcmp(argv[1], "--print")) {
- char *refname = xmalloc(strlen(argv[2]) + 1);
-
- if (check_ref_format(argv[2]))
- exit(1);
- collapse_slashes(refname, argv[2]);
- printf("%s\n", refname);
- exit(0);
- }
- if (argc != 2)
- usage(builtin_check_ref_format_usage);
- return !!check_ref_format(argv[1]);
-}
+++ /dev/null
-/*
- * Check-out files from the "current cache directory"
- *
- * Copyright (C) 2005 Linus Torvalds
- *
- * Careful: order of argument flags does matter. For example,
- *
- * git checkout-index -a -f file.c
- *
- * Will first check out all files listed in the cache (but not
- * overwrite any old ones), and then force-checkout "file.c" a
- * second time (ie that one _will_ overwrite any old contents
- * with the same filename).
- *
- * Also, just doing "git checkout-index" does nothing. You probably
- * meant "git checkout-index -a". And if you want to force it, you
- * want "git checkout-index -f -a".
- *
- * Intuitiveness is not the goal here. Repeatability is. The
- * reason for the "no arguments means no work" thing is that
- * from scripts you are supposed to be able to do things like
- *
- * find . -name '*.h' -print0 | xargs -0 git checkout-index -f --
- *
- * or:
- *
- * find . -name '*.h' -print0 | git checkout-index -f -z --stdin
- *
- * which will force all existing *.h files to be replaced with
- * their cached copies. If an empty command line implied "all",
- * then this would force-refresh everything in the cache, which
- * was not the point.
- *
- * Oh, and the "--" is just a good idea when you know the rest
- * will be filenames. Just so that you wouldn't have a filename
- * of "-a" causing problems (not possible in the above example,
- * but get used to it in scripting!).
- */
-#include "builtin.h"
-#include "cache.h"
-#include "quote.h"
-#include "cache-tree.h"
-#include "parse-options.h"
-
-#define CHECKOUT_ALL 4
-static int line_termination = '\n';
-static int checkout_stage; /* default to checkout stage0 */
-static int to_tempfile;
-static char topath[4][PATH_MAX + 1];
-
-static struct checkout state;
-
-static void write_tempfile_record(const char *name, int prefix_length)
-{
- int i;
-
- if (CHECKOUT_ALL == checkout_stage) {
- for (i = 1; i < 4; i++) {
- if (i > 1)
- putchar(' ');
- if (topath[i][0])
- fputs(topath[i], stdout);
- else
- putchar('.');
- }
- } else
- fputs(topath[checkout_stage], stdout);
-
- putchar('\t');
- write_name_quoted(name + prefix_length, stdout, line_termination);
-
- for (i = 0; i < 4; i++) {
- topath[i][0] = 0;
- }
-}
-
-static int checkout_file(const char *name, int prefix_length)
-{
- int namelen = strlen(name);
- int pos = cache_name_pos(name, namelen);
- int has_same_name = 0;
- int did_checkout = 0;
- int errs = 0;
-
- if (pos < 0)
- pos = -pos - 1;
-
- while (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos];
- if (ce_namelen(ce) != namelen ||
- memcmp(ce->name, name, namelen))
- break;
- has_same_name = 1;
- pos++;
- if (ce_stage(ce) != checkout_stage
- && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
- continue;
- did_checkout = 1;
- if (checkout_entry(ce, &state,
- to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
- errs++;
- }
-
- if (did_checkout) {
- if (to_tempfile)
- write_tempfile_record(name, prefix_length);
- return errs > 0 ? -1 : 0;
- }
-
- if (!state.quiet) {
- fprintf(stderr, "git checkout-index: %s ", name);
- if (!has_same_name)
- fprintf(stderr, "is not in the cache");
- else if (checkout_stage)
- fprintf(stderr, "does not exist at stage %d",
- checkout_stage);
- else
- fprintf(stderr, "is unmerged");
- fputc('\n', stderr);
- }
- return -1;
-}
-
-static void checkout_all(const char *prefix, int prefix_length)
-{
- int i, errs = 0;
- struct cache_entry *last_ce = NULL;
-
- for (i = 0; i < active_nr ; i++) {
- struct cache_entry *ce = active_cache[i];
- if (ce_stage(ce) != checkout_stage
- && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
- continue;
- if (prefix && *prefix &&
- (ce_namelen(ce) <= prefix_length ||
- memcmp(prefix, ce->name, prefix_length)))
- continue;
- if (last_ce && to_tempfile) {
- if (ce_namelen(last_ce) != ce_namelen(ce)
- || memcmp(last_ce->name, ce->name, ce_namelen(ce)))
- write_tempfile_record(last_ce->name, prefix_length);
- }
- if (checkout_entry(ce, &state,
- to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
- errs++;
- last_ce = ce;
- }
- if (last_ce && to_tempfile)
- write_tempfile_record(last_ce->name, prefix_length);
- if (errs)
- /* we have already done our error reporting.
- * exit with the same code as die().
- */
- exit(128);
-}
-
-static const char * const builtin_checkout_index_usage[] = {
- "git checkout-index [options] [--] <file>...",
- NULL
-};
-
-static struct lock_file lock_file;
-
-static int option_parse_u(const struct option *opt,
- const char *arg, int unset)
-{
- int *newfd = opt->value;
-
- state.refresh_cache = 1;
- if (*newfd < 0)
- *newfd = hold_locked_index(&lock_file, 1);
- return 0;
-}
-
-static int option_parse_z(const struct option *opt,
- const char *arg, int unset)
-{
- if (unset)
- line_termination = '\n';
- else
- line_termination = 0;
- return 0;
-}
-
-static int option_parse_prefix(const struct option *opt,
- const char *arg, int unset)
-{
- state.base_dir = arg;
- state.base_dir_len = strlen(arg);
- return 0;
-}
-
-static int option_parse_stage(const struct option *opt,
- const char *arg, int unset)
-{
- if (!strcmp(arg, "all")) {
- to_tempfile = 1;
- checkout_stage = CHECKOUT_ALL;
- } else {
- int ch = arg[0];
- if ('1' <= ch && ch <= '3')
- checkout_stage = arg[0] - '0';
- else
- die("stage should be between 1 and 3 or all");
- }
- return 0;
-}
-
-int cmd_checkout_index(int argc, const char **argv, const char *prefix)
-{
- int i;
- int newfd = -1;
- int all = 0;
- int read_from_stdin = 0;
- int prefix_length;
- int force = 0, quiet = 0, not_new = 0;
- struct option builtin_checkout_index_options[] = {
- OPT_BOOLEAN('a', "all", &all,
- "checks out all files in the index"),
- OPT_BOOLEAN('f', "force", &force,
- "forces overwrite of existing files"),
- OPT__QUIET(&quiet),
- OPT_BOOLEAN('n', "no-create", ¬_new,
- "don't checkout new files"),
- { OPTION_CALLBACK, 'u', "index", &newfd, NULL,
- "update stat information in the index file",
- PARSE_OPT_NOARG, option_parse_u },
- { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
- "paths are separated with NUL character",
- PARSE_OPT_NOARG, option_parse_z },
- OPT_BOOLEAN(0, "stdin", &read_from_stdin,
- "read list of paths from the standard input"),
- OPT_BOOLEAN(0, "temp", &to_tempfile,
- "write the content to temporary files"),
- OPT_CALLBACK(0, "prefix", NULL, "string",
- "when creating files, prepend <string>",
- option_parse_prefix),
- OPT_CALLBACK(0, "stage", NULL, NULL,
- "copy out the files from named stage",
- option_parse_stage),
- OPT_END()
- };
-
- git_config(git_default_config, NULL);
- state.base_dir = "";
- prefix_length = prefix ? strlen(prefix) : 0;
-
- if (read_cache() < 0) {
- die("invalid cache");
- }
-
- argc = parse_options(argc, argv, prefix, builtin_checkout_index_options,
- builtin_checkout_index_usage, 0);
- state.force = force;
- state.quiet = quiet;
- state.not_new = not_new;
-
- if (state.base_dir_len || to_tempfile) {
- /* when --prefix is specified we do not
- * want to update cache.
- */
- if (state.refresh_cache) {
- rollback_lock_file(&lock_file);
- newfd = -1;
- }
- state.refresh_cache = 0;
- }
-
- /* Check out named files first */
- for (i = 0; i < argc; i++) {
- const char *arg = argv[i];
- const char *p;
-
- if (all)
- die("git checkout-index: don't mix '--all' and explicit filenames");
- if (read_from_stdin)
- die("git checkout-index: don't mix '--stdin' and explicit filenames");
- p = prefix_path(prefix, prefix_length, arg);
- checkout_file(p, prefix_length);
- if (p < arg || p > arg + strlen(arg))
- free((char *)p);
- }
-
- if (read_from_stdin) {
- struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
-
- if (all)
- die("git checkout-index: don't mix '--all' and '--stdin'");
-
- while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
- const char *p;
- if (line_termination && buf.buf[0] == '"') {
- strbuf_reset(&nbuf);
- if (unquote_c_style(&nbuf, buf.buf, NULL))
- die("line is badly quoted");
- strbuf_swap(&buf, &nbuf);
- }
- p = prefix_path(prefix, prefix_length, buf.buf);
- checkout_file(p, prefix_length);
- if (p < buf.buf || p > buf.buf + buf.len)
- free((char *)p);
- }
- strbuf_release(&nbuf);
- strbuf_release(&buf);
- }
-
- if (all)
- checkout_all(prefix, prefix_length);
-
- if (0 <= newfd &&
- (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file)))
- die("Unable to write new index file");
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "builtin.h"
-#include "parse-options.h"
-#include "refs.h"
-#include "commit.h"
-#include "tree.h"
-#include "tree-walk.h"
-#include "cache-tree.h"
-#include "unpack-trees.h"
-#include "dir.h"
-#include "run-command.h"
-#include "merge-recursive.h"
-#include "branch.h"
-#include "diff.h"
-#include "revision.h"
-#include "remote.h"
-#include "blob.h"
-#include "xdiff-interface.h"
-#include "ll-merge.h"
-#include "resolve-undo.h"
-
-static const char * const checkout_usage[] = {
- "git checkout [options] <branch>",
- "git checkout [options] [<branch>] -- <file>...",
- NULL,
-};
-
-struct checkout_opts {
- int quiet;
- int merge;
- int force;
- int writeout_stage;
- int writeout_error;
-
- const char *new_branch;
- int new_branch_log;
- enum branch_track track;
-};
-
-static int post_checkout_hook(struct commit *old, struct commit *new,
- int changed)
-{
- return run_hook(NULL, "post-checkout",
- sha1_to_hex(old ? old->object.sha1 : null_sha1),
- sha1_to_hex(new ? new->object.sha1 : null_sha1),
- changed ? "1" : "0", NULL);
- /* "new" can be NULL when checking out from the index before
- a commit exists. */
-
-}
-
-static int update_some(const unsigned char *sha1, const char *base, int baselen,
- const char *pathname, unsigned mode, int stage, void *context)
-{
- int len;
- struct cache_entry *ce;
-
- if (S_ISDIR(mode))
- return READ_TREE_RECURSIVE;
-
- len = baselen + strlen(pathname);
- ce = xcalloc(1, cache_entry_size(len));
- hashcpy(ce->sha1, sha1);
- memcpy(ce->name, base, baselen);
- memcpy(ce->name + baselen, pathname, len - baselen);
- ce->ce_flags = create_ce_flags(len, 0);
- ce->ce_mode = create_ce_mode(mode);
- add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
- return 0;
-}
-
-static int read_tree_some(struct tree *tree, const char **pathspec)
-{
- read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
-
- /* update the index with the given tree's info
- * for all args, expanding wildcards, and exit
- * with any non-zero return code.
- */
- return 0;
-}
-
-static int skip_same_name(struct cache_entry *ce, int pos)
-{
- while (++pos < active_nr &&
- !strcmp(active_cache[pos]->name, ce->name))
- ; /* skip */
- return pos;
-}
-
-static int check_stage(int stage, struct cache_entry *ce, int pos)
-{
- while (pos < active_nr &&
- !strcmp(active_cache[pos]->name, ce->name)) {
- if (ce_stage(active_cache[pos]) == stage)
- return 0;
- pos++;
- }
- return error("path '%s' does not have %s version",
- ce->name,
- (stage == 2) ? "our" : "their");
-}
-
-static int check_all_stages(struct cache_entry *ce, int pos)
-{
- if (ce_stage(ce) != 1 ||
- active_nr <= pos + 2 ||
- strcmp(active_cache[pos+1]->name, ce->name) ||
- ce_stage(active_cache[pos+1]) != 2 ||
- strcmp(active_cache[pos+2]->name, ce->name) ||
- ce_stage(active_cache[pos+2]) != 3)
- return error("path '%s' does not have all three versions",
- ce->name);
- return 0;
-}
-
-static int checkout_stage(int stage, struct cache_entry *ce, int pos,
- struct checkout *state)
-{
- while (pos < active_nr &&
- !strcmp(active_cache[pos]->name, ce->name)) {
- if (ce_stage(active_cache[pos]) == stage)
- return checkout_entry(active_cache[pos], state, NULL);
- pos++;
- }
- return error("path '%s' does not have %s version",
- ce->name,
- (stage == 2) ? "our" : "their");
-}
-
-/* NEEDSWORK: share with merge-recursive */
-static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
-{
- unsigned long size;
- enum object_type type;
-
- if (!hashcmp(sha1, null_sha1)) {
- mm->ptr = xstrdup("");
- mm->size = 0;
- return;
- }
-
- mm->ptr = read_sha1_file(sha1, &type, &size);
- if (!mm->ptr || type != OBJ_BLOB)
- die("unable to read blob object %s", sha1_to_hex(sha1));
- mm->size = size;
-}
-
-static int checkout_merged(int pos, struct checkout *state)
-{
- struct cache_entry *ce = active_cache[pos];
- const char *path = ce->name;
- mmfile_t ancestor, ours, theirs;
- int status;
- unsigned char sha1[20];
- mmbuffer_t result_buf;
-
- if (ce_stage(ce) != 1 ||
- active_nr <= pos + 2 ||
- strcmp(active_cache[pos+1]->name, path) ||
- ce_stage(active_cache[pos+1]) != 2 ||
- strcmp(active_cache[pos+2]->name, path) ||
- ce_stage(active_cache[pos+2]) != 3)
- return error("path '%s' does not have all 3 versions", path);
-
- fill_mm(active_cache[pos]->sha1, &ancestor);
- fill_mm(active_cache[pos+1]->sha1, &ours);
- fill_mm(active_cache[pos+2]->sha1, &theirs);
-
- status = ll_merge(&result_buf, path, &ancestor,
- &ours, "ours", &theirs, "theirs", 0);
- free(ancestor.ptr);
- free(ours.ptr);
- free(theirs.ptr);
- if (status < 0 || !result_buf.ptr) {
- free(result_buf.ptr);
- return error("path '%s': cannot merge", path);
- }
-
- /*
- * NEEDSWORK:
- * There is absolutely no reason to write this as a blob object
- * and create a phony cache entry just to leak. This hack is
- * primarily to get to the write_entry() machinery that massages
- * the contents to work-tree format and writes out which only
- * allows it for a cache entry. The code in write_entry() needs
- * to be refactored to allow us to feed a <buffer, size, mode>
- * instead of a cache entry. Such a refactoring would help
- * merge_recursive as well (it also writes the merge result to the
- * object database even when it may contain conflicts).
- */
- if (write_sha1_file(result_buf.ptr, result_buf.size,
- blob_type, sha1))
- die("Unable to add merge result for '%s'", path);
- ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
- sha1,
- path, 2, 0);
- if (!ce)
- die("make_cache_entry failed for path '%s'", path);
- status = checkout_entry(ce, state, NULL);
- return status;
-}
-
-static int checkout_paths(struct tree *source_tree, const char **pathspec,
- struct checkout_opts *opts)
-{
- int pos;
- struct checkout state;
- static char *ps_matched;
- unsigned char rev[20];
- int flag;
- struct commit *head;
- int errs = 0;
- int stage = opts->writeout_stage;
- int merge = opts->merge;
- int newfd;
- struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-
- newfd = hold_locked_index(lock_file, 1);
- if (read_cache_preload(pathspec) < 0)
- return error("corrupt index file");
-
- if (source_tree)
- read_tree_some(source_tree, pathspec);
-
- for (pos = 0; pathspec[pos]; pos++)
- ;
- ps_matched = xcalloc(1, pos);
-
- for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
- match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
- }
-
- if (report_path_error(ps_matched, pathspec, 0))
- return 1;
-
- /* "checkout -m path" to recreate conflicted state */
- if (opts->merge)
- unmerge_cache(pathspec);
-
- /* Any unmerged paths? */
- for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
- if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
- if (!ce_stage(ce))
- continue;
- if (opts->force) {
- warning("path '%s' is unmerged", ce->name);
- } else if (stage) {
- errs |= check_stage(stage, ce, pos);
- } else if (opts->merge) {
- errs |= check_all_stages(ce, pos);
- } else {
- errs = 1;
- error("path '%s' is unmerged", ce->name);
- }
- pos = skip_same_name(ce, pos) - 1;
- }
- }
- if (errs)
- return 1;
-
- /* Now we are committed to check them out */
- memset(&state, 0, sizeof(state));
- state.force = 1;
- state.refresh_cache = 1;
- for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
- if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
- if (!ce_stage(ce)) {
- errs |= checkout_entry(ce, &state, NULL);
- continue;
- }
- if (stage)
- errs |= checkout_stage(stage, ce, pos, &state);
- else if (merge)
- errs |= checkout_merged(pos, &state);
- pos = skip_same_name(ce, pos) - 1;
- }
- }
-
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
- die("unable to write new index file");
-
- resolve_ref("HEAD", rev, 0, &flag);
- head = lookup_commit_reference_gently(rev, 1);
-
- errs |= post_checkout_hook(head, head, 0);
- return errs;
-}
-
-static void show_local_changes(struct object *head)
-{
- struct rev_info rev;
- /* I think we want full paths, even if we're in a subdirectory. */
- init_revisions(&rev, NULL);
- rev.abbrev = 0;
- rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
- if (diff_setup_done(&rev.diffopt) < 0)
- die("diff_setup_done failed");
- add_pending_object(&rev, head, NULL);
- run_diff_index(&rev, 0);
-}
-
-static void describe_detached_head(char *msg, struct commit *commit)
-{
- struct strbuf sb = STRBUF_INIT;
- struct pretty_print_context ctx = {0};
- parse_commit(commit);
- pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx);
- fprintf(stderr, "%s %s... %s\n", msg,
- find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
- strbuf_release(&sb);
-}
-
-static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
-{
- struct unpack_trees_options opts;
- struct tree_desc tree_desc;
-
- memset(&opts, 0, sizeof(opts));
- opts.head_idx = -1;
- opts.update = worktree;
- opts.skip_unmerged = !worktree;
- opts.reset = 1;
- opts.merge = 1;
- opts.fn = oneway_merge;
- opts.verbose_update = !o->quiet;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
- parse_tree(tree);
- init_tree_desc(&tree_desc, tree->buffer, tree->size);
- switch (unpack_trees(1, &tree_desc, &opts)) {
- case -2:
- o->writeout_error = 1;
- /*
- * We return 0 nevertheless, as the index is all right
- * and more importantly we have made best efforts to
- * update paths in the work tree, and we cannot revert
- * them.
- */
- case 0:
- return 0;
- default:
- return 128;
- }
-}
-
-struct branch_info {
- const char *name; /* The short name used */
- const char *path; /* The full name of a real branch */
- struct commit *commit; /* The named commit */
-};
-
-static void setup_branch_path(struct branch_info *branch)
-{
- struct strbuf buf = STRBUF_INIT;
-
- strbuf_branchname(&buf, branch->name);
- if (strcmp(buf.buf, branch->name))
- branch->name = xstrdup(buf.buf);
- strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
- branch->path = strbuf_detach(&buf, NULL);
-}
-
-static int merge_working_tree(struct checkout_opts *opts,
- struct branch_info *old, struct branch_info *new)
-{
- int ret;
- struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
- int newfd = hold_locked_index(lock_file, 1);
-
- if (read_cache_preload(NULL) < 0)
- return error("corrupt index file");
-
- resolve_undo_clear();
- if (opts->force) {
- ret = reset_tree(new->commit->tree, opts, 1);
- if (ret)
- return ret;
- } else {
- struct tree_desc trees[2];
- struct tree *tree;
- struct unpack_trees_options topts;
-
- memset(&topts, 0, sizeof(topts));
- topts.head_idx = -1;
- topts.src_index = &the_index;
- topts.dst_index = &the_index;
-
- topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches.";
-
- refresh_cache(REFRESH_QUIET);
-
- if (unmerged_cache()) {
- error("you need to resolve your current index first");
- return 1;
- }
-
- /* 2-way merge to the new branch */
- topts.initial_checkout = is_cache_unborn();
- topts.update = 1;
- topts.merge = 1;
- topts.gently = opts->merge && old->commit;
- topts.verbose_update = !opts->quiet;
- topts.fn = twoway_merge;
- topts.dir = xcalloc(1, sizeof(*topts.dir));
- topts.dir->flags |= DIR_SHOW_IGNORED;
- topts.dir->exclude_per_dir = ".gitignore";
- tree = parse_tree_indirect(old->commit ?
- old->commit->object.sha1 :
- (unsigned char *)EMPTY_TREE_SHA1_BIN);
- init_tree_desc(&trees[0], tree->buffer, tree->size);
- tree = parse_tree_indirect(new->commit->object.sha1);
- init_tree_desc(&trees[1], tree->buffer, tree->size);
-
- ret = unpack_trees(2, trees, &topts);
- if (ret == -1) {
- /*
- * Unpack couldn't do a trivial merge; either
- * give up or do a real merge, depending on
- * whether the merge flag was used.
- */
- struct tree *result;
- struct tree *work;
- struct merge_options o;
- if (!opts->merge)
- return 1;
-
- /*
- * Without old->commit, the below is the same as
- * the two-tree unpack we already tried and failed.
- */
- if (!old->commit)
- return 1;
-
- /* Do more real merge */
-
- /*
- * We update the index fully, then write the
- * tree from the index, then merge the new
- * branch with the current tree, with the old
- * branch as the base. Then we reset the index
- * (but not the working tree) to the new
- * branch, leaving the working tree as the
- * merged version, but skipping unmerged
- * entries in the index.
- */
-
- add_files_to_cache(NULL, NULL, 0);
- init_merge_options(&o);
- o.verbosity = 0;
- work = write_tree_from_memory(&o);
-
- ret = reset_tree(new->commit->tree, opts, 1);
- if (ret)
- return ret;
- o.branch1 = new->name;
- o.branch2 = "local";
- merge_trees(&o, new->commit->tree, work,
- old->commit->tree, &result);
- ret = reset_tree(new->commit->tree, opts, 0);
- if (ret)
- return ret;
- }
- }
-
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
- die("unable to write new index file");
-
- if (!opts->force && !opts->quiet)
- show_local_changes(&new->commit->object);
-
- return 0;
-}
-
-static void report_tracking(struct branch_info *new)
-{
- struct strbuf sb = STRBUF_INIT;
- struct branch *branch = branch_get(new->name);
-
- if (!format_tracking_info(branch, &sb))
- return;
- fputs(sb.buf, stdout);
- strbuf_release(&sb);
-}
-
-static void detach_advice(const char *old_path, const char *new_name)
-{
- const char fmt[] =
- "Note: checking out '%s'.\n\n"
- "You are in 'detached HEAD' state. You can look around, make experimental\n"
- "changes and commit them, and you can discard any commits you make in this\n"
- "state without impacting any branches by performing another checkout.\n\n"
- "If you want to create a new branch to retain commits you create, you may\n"
- "do so (now or later) by using -b with the checkout command again. Example:\n\n"
- " git checkout -b new_branch_name\n\n";
-
- fprintf(stderr, fmt, new_name);
-}
-
-static void update_refs_for_switch(struct checkout_opts *opts,
- struct branch_info *old,
- struct branch_info *new)
-{
- struct strbuf msg = STRBUF_INIT;
- const char *old_desc;
- if (opts->new_branch) {
- create_branch(old->name, opts->new_branch, new->name, 0,
- opts->new_branch_log, opts->track);
- new->name = opts->new_branch;
- setup_branch_path(new);
- }
-
- old_desc = old->name;
- if (!old_desc && old->commit)
- old_desc = sha1_to_hex(old->commit->object.sha1);
- strbuf_addf(&msg, "checkout: moving from %s to %s",
- old_desc ? old_desc : "(invalid)", new->name);
-
- if (new->path) {
- create_symref("HEAD", new->path, msg.buf);
- if (!opts->quiet) {
- if (old->path && !strcmp(new->path, old->path))
- fprintf(stderr, "Already on '%s'\n",
- new->name);
- else
- fprintf(stderr, "Switched to%s branch '%s'\n",
- opts->new_branch ? " a new" : "",
- new->name);
- }
- } else if (strcmp(new->name, "HEAD")) {
- update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
- REF_NODEREF, DIE_ON_ERR);
- if (!opts->quiet) {
- if (old->path && advice_detached_head)
- detach_advice(old->path, new->name);
- describe_detached_head("HEAD is now at", new->commit);
- }
- }
- remove_branch_state();
- strbuf_release(&msg);
- if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
- report_tracking(new);
-}
-
-static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
-{
- int ret = 0;
- struct branch_info old;
- unsigned char rev[20];
- int flag;
- memset(&old, 0, sizeof(old));
- old.path = resolve_ref("HEAD", rev, 0, &flag);
- old.commit = lookup_commit_reference_gently(rev, 1);
- if (!(flag & REF_ISSYMREF))
- old.path = NULL;
-
- if (old.path && !prefixcmp(old.path, "refs/heads/"))
- old.name = old.path + strlen("refs/heads/");
-
- if (!new->name) {
- new->name = "HEAD";
- new->commit = old.commit;
- if (!new->commit)
- die("You are on a branch yet to be born");
- parse_commit(new->commit);
- }
-
- ret = merge_working_tree(opts, &old, new);
- if (ret)
- return ret;
-
- /*
- * If we were on a detached HEAD, but have now moved to
- * a new commit, we want to mention the old commit once more
- * to remind the user that it might be lost.
- */
- if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
- describe_detached_head("Previous HEAD position was", old.commit);
-
- update_refs_for_switch(opts, &old, new);
-
- ret = post_checkout_hook(old.commit, new->commit, 1);
- return ret || opts->writeout_error;
-}
-
-static int git_checkout_config(const char *var, const char *value, void *cb)
-{
- return git_xmerge_config(var, value, cb);
-}
-
-static int interactive_checkout(const char *revision, const char **pathspec,
- struct checkout_opts *opts)
-{
- return run_add_interactive(revision, "--patch=checkout", pathspec);
-}
-
-struct tracking_name_data {
- const char *name;
- char *remote;
- int unique;
-};
-
-static int check_tracking_name(const char *refname, const unsigned char *sha1,
- int flags, void *cb_data)
-{
- struct tracking_name_data *cb = cb_data;
- const char *slash;
-
- if (prefixcmp(refname, "refs/remotes/"))
- return 0;
- slash = strchr(refname + 13, '/');
- if (!slash || strcmp(slash + 1, cb->name))
- return 0;
- if (cb->remote) {
- cb->unique = 0;
- return 0;
- }
- cb->remote = xstrdup(refname);
- return 0;
-}
-
-static const char *unique_tracking_name(const char *name)
-{
- struct tracking_name_data cb_data = { name, NULL, 1 };
- for_each_ref(check_tracking_name, &cb_data);
- if (cb_data.unique)
- return cb_data.remote;
- free(cb_data.remote);
- return NULL;
-}
-
-int cmd_checkout(int argc, const char **argv, const char *prefix)
-{
- struct checkout_opts opts;
- unsigned char rev[20];
- const char *arg;
- struct branch_info new;
- struct tree *source_tree = NULL;
- char *conflict_style = NULL;
- int patch_mode = 0;
- int dwim_new_local_branch = 1;
- struct option options[] = {
- OPT__QUIET(&opts.quiet),
- OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
- OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
- OPT_SET_INT('t', "track", &opts.track, "track",
- BRANCH_TRACK_EXPLICIT),
- OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
- 2),
- OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
- 3),
- OPT_BOOLEAN('f', "force", &opts.force, "force"),
- OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
- OPT_STRING(0, "conflict", &conflict_style, "style",
- "conflict style (merge or diff3)"),
- OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
- { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL,
- "second guess 'git checkout no-such-branch'",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- OPT_END(),
- };
- int has_dash_dash;
-
- memset(&opts, 0, sizeof(opts));
- memset(&new, 0, sizeof(new));
-
- git_config(git_checkout_config, NULL);
-
- opts.track = BRANCH_TRACK_UNSPECIFIED;
-
- argc = parse_options(argc, argv, prefix, options, checkout_usage,
- PARSE_OPT_KEEP_DASHDASH);
-
- if (patch_mode && (opts.track > 0 || opts.new_branch
- || opts.new_branch_log || opts.merge || opts.force))
- die ("--patch is incompatible with all other options");
-
- /* --track without -b should DWIM */
- if (0 < opts.track && !opts.new_branch) {
- const char *argv0 = argv[0];
- if (!argc || !strcmp(argv0, "--"))
- die ("--track needs a branch name");
- if (!prefixcmp(argv0, "refs/"))
- argv0 += 5;
- if (!prefixcmp(argv0, "remotes/"))
- argv0 += 8;
- argv0 = strchr(argv0, '/');
- if (!argv0 || !argv0[1])
- die ("Missing branch name; try -b");
- opts.new_branch = argv0 + 1;
- }
-
- if (conflict_style) {
- opts.merge = 1; /* implied */
- git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
- }
-
- if (opts.force && opts.merge)
- die("git checkout: -f and -m are incompatible");
-
- /*
- * case 1: git checkout <ref> -- [<paths>]
- *
- * <ref> must be a valid tree, everything after the '--' must be
- * a path.
- *
- * case 2: git checkout -- [<paths>]
- *
- * everything after the '--' must be paths.
- *
- * case 3: git checkout <something> [<paths>]
- *
- * With no paths, if <something> is a commit, that is to
- * switch to the branch or detach HEAD at it. As a special case,
- * if <something> is A...B (missing A or B means HEAD but you can
- * omit at most one side), and if there is a unique merge base
- * between A and B, A...B names that merge base.
- *
- * With no paths, if <something> is _not_ a commit, no -t nor -b
- * was given, and there is a tracking branch whose name is
- * <something> in one and only one remote, then this is a short-hand
- * to fork local <something> from that remote tracking branch.
- *
- * Otherwise <something> shall not be ambiguous.
- * - If it's *only* a reference, treat it like case (1).
- * - If it's only a path, treat it like case (2).
- * - else: fail.
- *
- */
- if (argc) {
- if (!strcmp(argv[0], "--")) { /* case (2) */
- argv++;
- argc--;
- goto no_reference;
- }
-
- arg = argv[0];
- has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
-
- if (!strcmp(arg, "-"))
- arg = "@{-1}";
-
- if (get_sha1_mb(arg, rev)) {
- if (has_dash_dash) /* case (1) */
- die("invalid reference: %s", arg);
- if (!patch_mode &&
- dwim_new_local_branch &&
- opts.track == BRANCH_TRACK_UNSPECIFIED &&
- !opts.new_branch &&
- !check_filename(NULL, arg) &&
- argc == 1) {
- const char *remote = unique_tracking_name(arg);
- if (!remote || get_sha1(remote, rev))
- goto no_reference;
- opts.new_branch = arg;
- arg = remote;
- /* DWIMmed to create local branch */
- }
- else
- goto no_reference;
- }
-
- /* we can't end up being in (2) anymore, eat the argument */
- argv++;
- argc--;
-
- new.name = arg;
- if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
- setup_branch_path(&new);
-
- if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&
- resolve_ref(new.path, rev, 1, NULL))
- ;
- else
- new.path = NULL;
- parse_commit(new.commit);
- source_tree = new.commit->tree;
- } else
- source_tree = parse_tree_indirect(rev);
-
- if (!source_tree) /* case (1): want a tree */
- die("reference is not a tree: %s", arg);
- if (!has_dash_dash) {/* case (3 -> 1) */
- /*
- * Do not complain the most common case
- * git checkout branch
- * even if there happen to be a file called 'branch';
- * it would be extremely annoying.
- */
- if (argc)
- verify_non_filename(NULL, arg);
- }
- else {
- argv++;
- argc--;
- }
- }
-
-no_reference:
-
- if (opts.track == BRANCH_TRACK_UNSPECIFIED)
- opts.track = git_branch_track;
-
- if (argc) {
- const char **pathspec = get_pathspec(prefix, argv);
-
- if (!pathspec)
- die("invalid path specification");
-
- if (patch_mode)
- return interactive_checkout(new.name, pathspec, &opts);
-
- /* Checkout paths */
- if (opts.new_branch) {
- if (argc == 1) {
- die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
- } else {
- die("git checkout: updating paths is incompatible with switching branches.");
- }
- }
-
- if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
- die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
-
- return checkout_paths(source_tree, pathspec, &opts);
- }
-
- if (patch_mode)
- return interactive_checkout(new.name, NULL, &opts);
-
- if (opts.new_branch) {
- struct strbuf buf = STRBUF_INIT;
- if (strbuf_check_branch_ref(&buf, opts.new_branch))
- die("git checkout: we do not like '%s' as a branch name.",
- opts.new_branch);
- if (!get_sha1(buf.buf, rev))
- die("git checkout: branch %s already exists", opts.new_branch);
- strbuf_release(&buf);
- }
-
- if (new.name && !new.commit) {
- die("Cannot switch branch to a non-commit.");
- }
- if (opts.writeout_stage)
- die("--ours/--theirs is incompatible with switching branches.");
-
- return switch_branches(&opts, &new);
-}
+++ /dev/null
-/*
- * "git clean" builtin command
- *
- * Copyright (C) 2007 Shawn Bohrer
- *
- * Based on git-clean.sh by Pavel Roskin
- */
-
-#include "builtin.h"
-#include "cache.h"
-#include "dir.h"
-#include "parse-options.h"
-#include "quote.h"
-
-static int force = -1; /* unset */
-
-static const char *const builtin_clean_usage[] = {
- "git clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...",
- NULL
-};
-
-static int git_clean_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "clean.requireforce"))
- force = !git_config_bool(var, value);
- return git_default_config(var, value, cb);
-}
-
-int cmd_clean(int argc, const char **argv, const char *prefix)
-{
- int i;
- int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
- int ignored_only = 0, baselen = 0, config_set = 0, errors = 0;
- int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
- struct strbuf directory = STRBUF_INIT;
- struct dir_struct dir;
- static const char **pathspec;
- struct strbuf buf = STRBUF_INIT;
- const char *qname;
- char *seen = NULL;
- struct option options[] = {
- OPT__QUIET(&quiet),
- OPT__DRY_RUN(&show_only),
- OPT_BOOLEAN('f', "force", &force, "force"),
- OPT_BOOLEAN('d', NULL, &remove_directories,
- "remove whole directories"),
- OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
- OPT_BOOLEAN('X', NULL, &ignored_only,
- "remove only ignored files"),
- OPT_END()
- };
-
- git_config(git_clean_config, NULL);
- if (force < 0)
- force = 0;
- else
- config_set = 1;
-
- argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
- 0);
-
- memset(&dir, 0, sizeof(dir));
- if (ignored_only)
- dir.flags |= DIR_SHOW_IGNORED;
-
- if (ignored && ignored_only)
- die("-x and -X cannot be used together");
-
- if (!show_only && !force)
- die("clean.requireForce %s to true and neither -n nor -f given; "
- "refusing to clean", config_set ? "set" : "defaults");
-
- if (force > 1)
- rm_flags = 0;
-
- dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
-
- if (read_cache() < 0)
- die("index file corrupt");
-
- if (!ignored)
- setup_standard_excludes(&dir);
-
- pathspec = get_pathspec(prefix, argv);
-
- fill_directory(&dir, pathspec);
-
- if (pathspec)
- seen = xmalloc(argc > 0 ? argc : 1);
-
- for (i = 0; i < dir.nr; i++) {
- struct dir_entry *ent = dir.entries[i];
- int len, pos;
- int matches = 0;
- struct cache_entry *ce;
- struct stat st;
-
- /*
- * Remove the '/' at the end that directory
- * walking adds for directory entries.
- */
- len = ent->len;
- if (len && ent->name[len-1] == '/')
- len--;
- pos = cache_name_pos(ent->name, len);
- if (0 <= pos)
- continue; /* exact match */
- pos = -pos - 1;
- if (pos < active_nr) {
- ce = active_cache[pos];
- if (ce_namelen(ce) == len &&
- !memcmp(ce->name, ent->name, len))
- continue; /* Yup, this one exists unmerged */
- }
-
- /*
- * we might have removed this as part of earlier
- * recursive directory removal, so lstat() here could
- * fail with ENOENT.
- */
- if (lstat(ent->name, &st))
- continue;
-
- if (pathspec) {
- memset(seen, 0, argc > 0 ? argc : 1);
- matches = match_pathspec(pathspec, ent->name, len,
- baselen, seen);
- }
-
- if (S_ISDIR(st.st_mode)) {
- strbuf_addstr(&directory, ent->name);
- qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
- if (show_only && (remove_directories ||
- (matches == MATCHED_EXACTLY))) {
- printf("Would remove %s\n", qname);
- } else if (remove_directories ||
- (matches == MATCHED_EXACTLY)) {
- if (!quiet)
- printf("Removing %s\n", qname);
- if (remove_dir_recursively(&directory,
- rm_flags) != 0) {
- warning("failed to remove '%s'", qname);
- errors++;
- }
- } else if (show_only) {
- printf("Would not remove %s\n", qname);
- } else {
- printf("Not removing %s\n", qname);
- }
- strbuf_reset(&directory);
- } else {
- if (pathspec && !matches)
- continue;
- qname = quote_path_relative(ent->name, -1, &buf, prefix);
- if (show_only) {
- printf("Would remove %s\n", qname);
- continue;
- } else if (!quiet) {
- printf("Removing %s\n", qname);
- }
- if (unlink(ent->name) != 0) {
- warning("failed to remove '%s'", qname);
- errors++;
- }
- }
- }
- free(seen);
-
- strbuf_release(&directory);
- return (errors != 0);
-}
+++ /dev/null
-/*
- * Builtin "git clone"
- *
- * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
- * 2008 Daniel Barkalow <barkalow@iabervon.org>
- * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
- *
- * Clone a repository into a different directory that does not yet exist.
- */
-
-#include "cache.h"
-#include "parse-options.h"
-#include "fetch-pack.h"
-#include "refs.h"
-#include "tree.h"
-#include "tree-walk.h"
-#include "unpack-trees.h"
-#include "transport.h"
-#include "strbuf.h"
-#include "dir.h"
-#include "pack-refs.h"
-#include "sigchain.h"
-#include "branch.h"
-#include "remote.h"
-#include "run-command.h"
-
-/*
- * Overall FIXMEs:
- * - respect DB_ENVIRONMENT for .git/objects.
- *
- * Implementation notes:
- * - dropping use-separate-remote and no-separate-remote compatibility
- *
- */
-static const char * const builtin_clone_usage[] = {
- "git clone [options] [--] <repo> [<dir>]",
- NULL
-};
-
-static int option_quiet, option_no_checkout, option_bare, option_mirror;
-static int option_local, option_no_hardlinks, option_shared, option_recursive;
-static char *option_template, *option_reference, *option_depth;
-static char *option_origin = NULL;
-static char *option_branch = NULL;
-static char *option_upload_pack = "git-upload-pack";
-static int option_verbose;
-static int option_progress;
-
-static struct option builtin_clone_options[] = {
- OPT__QUIET(&option_quiet),
- OPT__VERBOSE(&option_verbose),
- OPT_BOOLEAN(0, "progress", &option_progress,
- "force progress reporting"),
- OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
- "don't create a checkout"),
- OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
- { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL,
- "create a bare repository",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- OPT_BOOLEAN(0, "mirror", &option_mirror,
- "create a mirror repository (implies bare)"),
- OPT_BOOLEAN('l', "local", &option_local,
- "to clone from a local repository"),
- OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
- "don't use local hardlinks, always copy"),
- OPT_BOOLEAN('s', "shared", &option_shared,
- "setup as shared repository"),
- OPT_BOOLEAN(0, "recursive", &option_recursive,
- "initialize submodules in the clone"),
- OPT_STRING(0, "template", &option_template, "path",
- "path the template repository"),
- OPT_STRING(0, "reference", &option_reference, "repo",
- "reference repository"),
- OPT_STRING('o', "origin", &option_origin, "branch",
- "use <branch> instead of 'origin' to track upstream"),
- OPT_STRING('b', "branch", &option_branch, "branch",
- "checkout <branch> instead of the remote's HEAD"),
- OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
- "path to git-upload-pack on the remote"),
- OPT_STRING(0, "depth", &option_depth, "depth",
- "create a shallow clone of that depth"),
-
- OPT_END()
-};
-
-static const char *argv_submodule[] = {
- "submodule", "update", "--init", "--recursive", NULL
-};
-
-static char *get_repo_path(const char *repo, int *is_bundle)
-{
- static char *suffix[] = { "/.git", ".git", "" };
- static char *bundle_suffix[] = { ".bundle", "" };
- struct stat st;
- int i;
-
- for (i = 0; i < ARRAY_SIZE(suffix); i++) {
- const char *path;
- path = mkpath("%s%s", repo, suffix[i]);
- if (is_directory(path)) {
- *is_bundle = 0;
- return xstrdup(make_nonrelative_path(path));
- }
- }
-
- for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {
- const char *path;
- path = mkpath("%s%s", repo, bundle_suffix[i]);
- if (!stat(path, &st) && S_ISREG(st.st_mode)) {
- *is_bundle = 1;
- return xstrdup(make_nonrelative_path(path));
- }
- }
-
- return NULL;
-}
-
-static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
-{
- const char *end = repo + strlen(repo), *start;
- char *dir;
-
- /*
- * Strip trailing spaces, slashes and /.git
- */
- while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
- end--;
- if (end - repo > 5 && is_dir_sep(end[-5]) &&
- !strncmp(end - 4, ".git", 4)) {
- end -= 5;
- while (repo < end && is_dir_sep(end[-1]))
- end--;
- }
-
- /*
- * Find last component, but be prepared that repo could have
- * the form "remote.example.com:foo.git", i.e. no slash
- * in the directory part.
- */
- start = end;
- while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':')
- start--;
-
- /*
- * Strip .{bundle,git}.
- */
- if (is_bundle) {
- if (end - start > 7 && !strncmp(end - 7, ".bundle", 7))
- end -= 7;
- } else {
- if (end - start > 4 && !strncmp(end - 4, ".git", 4))
- end -= 4;
- }
-
- if (is_bare) {
- struct strbuf result = STRBUF_INIT;
- strbuf_addf(&result, "%.*s.git", (int)(end - start), start);
- dir = strbuf_detach(&result, NULL);
- } else
- dir = xstrndup(start, end - start);
- /*
- * Replace sequences of 'control' characters and whitespace
- * with one ascii space, remove leading and trailing spaces.
- */
- if (*dir) {
- char *out = dir;
- int prev_space = 1 /* strip leading whitespace */;
- for (end = dir; *end; ++end) {
- char ch = *end;
- if ((unsigned char)ch < '\x20')
- ch = '\x20';
- if (isspace(ch)) {
- if (prev_space)
- continue;
- prev_space = 1;
- } else
- prev_space = 0;
- *out++ = ch;
- }
- *out = '\0';
- if (out > dir && prev_space)
- out[-1] = '\0';
- }
- return dir;
-}
-
-static void strip_trailing_slashes(char *dir)
-{
- char *end = dir + strlen(dir);
-
- while (dir < end - 1 && is_dir_sep(end[-1]))
- end--;
- *end = '\0';
-}
-
-static void setup_reference(const char *repo)
-{
- const char *ref_git;
- char *ref_git_copy;
-
- struct remote *remote;
- struct transport *transport;
- const struct ref *extra;
-
- ref_git = make_absolute_path(option_reference);
-
- if (is_directory(mkpath("%s/.git/objects", ref_git)))
- ref_git = mkpath("%s/.git", ref_git);
- else if (!is_directory(mkpath("%s/objects", ref_git)))
- die("reference repository '%s' is not a local directory.",
- option_reference);
-
- ref_git_copy = xstrdup(ref_git);
-
- add_to_alternates_file(ref_git_copy);
-
- remote = remote_get(ref_git_copy);
- transport = transport_get(remote, ref_git_copy);
- for (extra = transport_get_remote_refs(transport); extra;
- extra = extra->next)
- add_extra_ref(extra->name, extra->old_sha1, 0);
-
- transport_disconnect(transport);
-
- free(ref_git_copy);
-}
-
-static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
-{
- struct dirent *de;
- struct stat buf;
- int src_len, dest_len;
- DIR *dir;
-
- dir = opendir(src->buf);
- if (!dir)
- die_errno("failed to open '%s'", src->buf);
-
- if (mkdir(dest->buf, 0777)) {
- if (errno != EEXIST)
- die_errno("failed to create directory '%s'", dest->buf);
- else if (stat(dest->buf, &buf))
- die_errno("failed to stat '%s'", dest->buf);
- else if (!S_ISDIR(buf.st_mode))
- die("%s exists and is not a directory", dest->buf);
- }
-
- strbuf_addch(src, '/');
- src_len = src->len;
- strbuf_addch(dest, '/');
- dest_len = dest->len;
-
- while ((de = readdir(dir)) != NULL) {
- strbuf_setlen(src, src_len);
- strbuf_addstr(src, de->d_name);
- strbuf_setlen(dest, dest_len);
- strbuf_addstr(dest, de->d_name);
- if (stat(src->buf, &buf)) {
- warning ("failed to stat %s\n", src->buf);
- continue;
- }
- if (S_ISDIR(buf.st_mode)) {
- if (de->d_name[0] != '.')
- copy_or_link_directory(src, dest);
- continue;
- }
-
- if (unlink(dest->buf) && errno != ENOENT)
- die_errno("failed to unlink '%s'", dest->buf);
- if (!option_no_hardlinks) {
- if (!link(src->buf, dest->buf))
- continue;
- if (option_local)
- die_errno("failed to create link '%s'", dest->buf);
- option_no_hardlinks = 1;
- }
- if (copy_file_with_time(dest->buf, src->buf, 0666))
- die_errno("failed to copy file to '%s'", dest->buf);
- }
- closedir(dir);
-}
-
-static const struct ref *clone_local(const char *src_repo,
- const char *dest_repo)
-{
- const struct ref *ret;
- struct strbuf src = STRBUF_INIT;
- struct strbuf dest = STRBUF_INIT;
- struct remote *remote;
- struct transport *transport;
-
- if (option_shared)
- add_to_alternates_file(src_repo);
- else {
- strbuf_addf(&src, "%s/objects", src_repo);
- strbuf_addf(&dest, "%s/objects", dest_repo);
- copy_or_link_directory(&src, &dest);
- strbuf_release(&src);
- strbuf_release(&dest);
- }
-
- remote = remote_get(src_repo);
- transport = transport_get(remote, src_repo);
- ret = transport_get_remote_refs(transport);
- transport_disconnect(transport);
- return ret;
-}
-
-static const char *junk_work_tree;
-static const char *junk_git_dir;
-static pid_t junk_pid;
-
-static void remove_junk(void)
-{
- struct strbuf sb = STRBUF_INIT;
- if (getpid() != junk_pid)
- return;
- if (junk_git_dir) {
- strbuf_addstr(&sb, junk_git_dir);
- remove_dir_recursively(&sb, 0);
- strbuf_reset(&sb);
- }
- if (junk_work_tree) {
- strbuf_addstr(&sb, junk_work_tree);
- remove_dir_recursively(&sb, 0);
- strbuf_reset(&sb);
- }
-}
-
-static void remove_junk_on_signal(int signo)
-{
- remove_junk();
- sigchain_pop(signo);
- raise(signo);
-}
-
-static struct ref *wanted_peer_refs(const struct ref *refs,
- struct refspec *refspec)
-{
- struct ref *local_refs = NULL;
- struct ref **tail = &local_refs;
-
- get_fetch_map(refs, refspec, &tail, 0);
- if (!option_mirror)
- get_fetch_map(refs, tag_refspec, &tail, 0);
-
- return local_refs;
-}
-
-static void write_remote_refs(const struct ref *local_refs)
-{
- const struct ref *r;
-
- for (r = local_refs; r; r = r->next)
- add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
-
- pack_refs(PACK_REFS_ALL);
- clear_extra_refs();
-}
-
-int cmd_clone(int argc, const char **argv, const char *prefix)
-{
- int is_bundle = 0;
- struct stat buf;
- const char *repo_name, *repo, *work_tree, *git_dir;
- char *path, *dir;
- int dest_exists;
- const struct ref *refs, *remote_head;
- const struct ref *remote_head_points_at;
- const struct ref *our_head_points_at;
- struct ref *mapped_refs;
- struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
- struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
- struct transport *transport = NULL;
- char *src_ref_prefix = "refs/heads/";
- int err = 0;
-
- struct refspec *refspec;
- const char *fetch_pattern;
-
- junk_pid = getpid();
-
- argc = parse_options(argc, argv, prefix, builtin_clone_options,
- builtin_clone_usage, 0);
-
- if (argc > 2)
- usage_msg_opt("Too many arguments.",
- builtin_clone_usage, builtin_clone_options);
-
- if (argc == 0)
- usage_msg_opt("You must specify a repository to clone.",
- builtin_clone_usage, builtin_clone_options);
-
- if (option_mirror)
- option_bare = 1;
-
- if (option_bare) {
- if (option_origin)
- die("--bare and --origin %s options are incompatible.",
- option_origin);
- option_no_checkout = 1;
- }
-
- if (!option_origin)
- option_origin = "origin";
-
- repo_name = argv[0];
-
- path = get_repo_path(repo_name, &is_bundle);
- if (path)
- repo = xstrdup(make_nonrelative_path(repo_name));
- else if (!strchr(repo_name, ':'))
- repo = xstrdup(make_absolute_path(repo_name));
- else
- repo = repo_name;
-
- if (argc == 2)
- dir = xstrdup(argv[1]);
- else
- dir = guess_dir_name(repo_name, is_bundle, option_bare);
- strip_trailing_slashes(dir);
-
- dest_exists = !stat(dir, &buf);
- if (dest_exists && !is_empty_dir(dir))
- die("destination path '%s' already exists and is not "
- "an empty directory.", dir);
-
- strbuf_addf(&reflog_msg, "clone: from %s", repo);
-
- if (option_bare)
- work_tree = NULL;
- else {
- work_tree = getenv("GIT_WORK_TREE");
- if (work_tree && !stat(work_tree, &buf))
- die("working tree '%s' already exists.", work_tree);
- }
-
- if (option_bare || work_tree)
- git_dir = xstrdup(dir);
- else {
- work_tree = dir;
- git_dir = xstrdup(mkpath("%s/.git", dir));
- }
-
- if (!option_bare) {
- junk_work_tree = work_tree;
- if (safe_create_leading_directories_const(work_tree) < 0)
- die_errno("could not create leading directories of '%s'",
- work_tree);
- if (!dest_exists && mkdir(work_tree, 0755))
- die_errno("could not create work tree dir '%s'.",
- work_tree);
- set_git_work_tree(work_tree);
- }
- junk_git_dir = git_dir;
- atexit(remove_junk);
- sigchain_push_common(remove_junk_on_signal);
-
- setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1);
-
- if (safe_create_leading_directories_const(git_dir) < 0)
- die("could not create leading directories of '%s'", git_dir);
- set_git_dir(make_absolute_path(git_dir));
-
- init_db(option_template, option_quiet ? INIT_DB_QUIET : 0);
-
- /*
- * At this point, the config exists, so we do not need the
- * environment variable. We actually need to unset it, too, to
- * re-enable parsing of the global configs.
- */
- unsetenv(CONFIG_ENVIRONMENT);
-
- if (option_reference)
- setup_reference(git_dir);
-
- git_config(git_default_config, NULL);
-
- if (option_bare) {
- if (option_mirror)
- src_ref_prefix = "refs/";
- strbuf_addstr(&branch_top, src_ref_prefix);
-
- git_config_set("core.bare", "true");
- } else {
- strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
- }
-
- strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
-
- if (option_mirror || !option_bare) {
- /* Configure the remote */
- strbuf_addf(&key, "remote.%s.fetch", option_origin);
- git_config_set_multivar(key.buf, value.buf, "^$", 0);
- strbuf_reset(&key);
-
- if (option_mirror) {
- strbuf_addf(&key, "remote.%s.mirror", option_origin);
- git_config_set(key.buf, "true");
- strbuf_reset(&key);
- }
-
- strbuf_addf(&key, "remote.%s.url", option_origin);
- git_config_set(key.buf, repo);
- strbuf_reset(&key);
- }
-
- fetch_pattern = value.buf;
- refspec = parse_fetch_refspec(1, &fetch_pattern);
-
- strbuf_reset(&value);
-
- if (path && !is_bundle) {
- refs = clone_local(path, git_dir);
- mapped_refs = wanted_peer_refs(refs, refspec);
- } else {
- struct remote *remote = remote_get(argv[0]);
- transport = transport_get(remote, remote->url[0]);
-
- if (!transport->get_refs_list || !transport->fetch)
- die("Don't know how to clone %s", transport->url);
-
- transport_set_option(transport, TRANS_OPT_KEEP, "yes");
-
- if (option_depth)
- transport_set_option(transport, TRANS_OPT_DEPTH,
- option_depth);
-
- if (option_quiet)
- transport->verbose = -1;
- else if (option_verbose)
- transport->verbose = 1;
-
- if (option_progress)
- transport->progress = 1;
-
- if (option_upload_pack)
- transport_set_option(transport, TRANS_OPT_UPLOADPACK,
- option_upload_pack);
-
- refs = transport_get_remote_refs(transport);
- if (refs) {
- mapped_refs = wanted_peer_refs(refs, refspec);
- transport_fetch_refs(transport, mapped_refs);
- }
- }
-
- if (refs) {
- clear_extra_refs();
-
- write_remote_refs(mapped_refs);
-
- remote_head = find_ref_by_name(refs, "HEAD");
- remote_head_points_at =
- guess_remote_head(remote_head, mapped_refs, 0);
-
- if (option_branch) {
- struct strbuf head = STRBUF_INIT;
- strbuf_addstr(&head, src_ref_prefix);
- strbuf_addstr(&head, option_branch);
- our_head_points_at =
- find_ref_by_name(mapped_refs, head.buf);
- strbuf_release(&head);
-
- if (!our_head_points_at) {
- warning("Remote branch %s not found in "
- "upstream %s, using HEAD instead",
- option_branch, option_origin);
- our_head_points_at = remote_head_points_at;
- }
- }
- else
- our_head_points_at = remote_head_points_at;
- }
- else {
- warning("You appear to have cloned an empty repository.");
- our_head_points_at = NULL;
- remote_head_points_at = NULL;
- remote_head = NULL;
- option_no_checkout = 1;
- if (!option_bare)
- install_branch_config(0, "master", option_origin,
- "refs/heads/master");
- }
-
- if (remote_head_points_at && !option_bare) {
- struct strbuf head_ref = STRBUF_INIT;
- strbuf_addstr(&head_ref, branch_top.buf);
- strbuf_addstr(&head_ref, "HEAD");
- create_symref(head_ref.buf,
- remote_head_points_at->peer_ref->name,
- reflog_msg.buf);
- }
-
- if (our_head_points_at) {
- /* Local default branch link */
- create_symref("HEAD", our_head_points_at->name, NULL);
- if (!option_bare) {
- const char *head = skip_prefix(our_head_points_at->name,
- "refs/heads/");
- update_ref(reflog_msg.buf, "HEAD",
- our_head_points_at->old_sha1,
- NULL, 0, DIE_ON_ERR);
- install_branch_config(0, head, option_origin,
- our_head_points_at->name);
- }
- } else if (remote_head) {
- /* Source had detached HEAD pointing somewhere. */
- if (!option_bare) {
- update_ref(reflog_msg.buf, "HEAD",
- remote_head->old_sha1,
- NULL, REF_NODEREF, DIE_ON_ERR);
- our_head_points_at = remote_head;
- }
- } else {
- /* Nothing to checkout out */
- if (!option_no_checkout)
- warning("remote HEAD refers to nonexistent ref, "
- "unable to checkout.\n");
- option_no_checkout = 1;
- }
-
- if (transport) {
- transport_unlock_pack(transport);
- transport_disconnect(transport);
- }
-
- if (!option_no_checkout) {
- struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
- struct unpack_trees_options opts;
- struct tree *tree;
- struct tree_desc t;
- int fd;
-
- /* We need to be in the new work tree for the checkout */
- setup_work_tree();
-
- fd = hold_locked_index(lock_file, 1);
-
- memset(&opts, 0, sizeof opts);
- opts.update = 1;
- opts.merge = 1;
- opts.fn = oneway_merge;
- opts.verbose_update = !option_quiet;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
-
- tree = parse_tree_indirect(our_head_points_at->old_sha1);
- parse_tree(tree);
- init_tree_desc(&t, tree->buffer, tree->size);
- unpack_trees(1, &t, &opts);
-
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
- die("unable to write new index file");
-
- err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
- sha1_to_hex(our_head_points_at->old_sha1), "1",
- NULL);
-
- if (!err && option_recursive)
- err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
- }
-
- strbuf_release(&reflog_msg);
- strbuf_release(&branch_top);
- strbuf_release(&key);
- strbuf_release(&value);
- junk_pid = 0;
- return err;
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "commit.h"
-#include "tree.h"
-#include "builtin.h"
-#include "utf8.h"
-
-/*
- * FIXME! Share the code with "write-tree.c"
- */
-static void check_valid(unsigned char *sha1, enum object_type expect)
-{
- enum object_type type = sha1_object_info(sha1, NULL);
- if (type < 0)
- die("%s is not a valid object", sha1_to_hex(sha1));
- if (type != expect)
- die("%s is not a valid '%s' object", sha1_to_hex(sha1),
- typename(expect));
-}
-
-static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog";
-
-static void new_parent(struct commit *parent, struct commit_list **parents_p)
-{
- unsigned char *sha1 = parent->object.sha1;
- struct commit_list *parents;
- for (parents = *parents_p; parents; parents = parents->next) {
- if (parents->item == parent) {
- error("duplicate parent %s ignored", sha1_to_hex(sha1));
- return;
- }
- parents_p = &parents->next;
- }
- commit_list_insert(parent, parents_p);
-}
-
-static const char commit_utf8_warn[] =
-"Warning: commit message does not conform to UTF-8.\n"
-"You may want to amend it after fixing the message, or set the config\n"
-"variable i18n.commitencoding to the encoding your project uses.\n";
-
-int commit_tree(const char *msg, unsigned char *tree,
- struct commit_list *parents, unsigned char *ret,
- const char *author)
-{
- int result;
- int encoding_is_utf8;
- struct strbuf buffer;
-
- check_valid(tree, OBJ_TREE);
-
- /* Not having i18n.commitencoding is the same as having utf-8 */
- encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
-
- strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
- strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
-
- /*
- * NOTE! This ordering means that the same exact tree merged with a
- * different order of parents will be a _different_ changeset even
- * if everything else stays the same.
- */
- while (parents) {
- struct commit_list *next = parents->next;
- strbuf_addf(&buffer, "parent %s\n",
- sha1_to_hex(parents->item->object.sha1));
- free(parents);
- parents = next;
- }
-
- /* Person/date information */
- if (!author)
- author = git_author_info(IDENT_ERROR_ON_NO_NAME);
- strbuf_addf(&buffer, "author %s\n", author);
- strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
- if (!encoding_is_utf8)
- strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
- strbuf_addch(&buffer, '\n');
-
- /* And add the comment */
- strbuf_addstr(&buffer, msg);
-
- /* And check the encoding */
- if (encoding_is_utf8 && !is_utf8(buffer.buf))
- fprintf(stderr, commit_utf8_warn);
-
- result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
- strbuf_release(&buffer);
- return result;
-}
-
-int cmd_commit_tree(int argc, const char **argv, const char *prefix)
-{
- int i;
- struct commit_list *parents = NULL;
- unsigned char tree_sha1[20];
- unsigned char commit_sha1[20];
- struct strbuf buffer = STRBUF_INIT;
-
- git_config(git_default_config, NULL);
-
- if (argc < 2 || !strcmp(argv[1], "-h"))
- usage(commit_tree_usage);
- if (get_sha1(argv[1], tree_sha1))
- die("Not a valid object name %s", argv[1]);
-
- for (i = 2; i < argc; i += 2) {
- unsigned char sha1[20];
- const char *a, *b;
- a = argv[i]; b = argv[i+1];
- if (!b || strcmp(a, "-p"))
- usage(commit_tree_usage);
-
- if (get_sha1(b, sha1))
- die("Not a valid object name %s", b);
- check_valid(sha1, OBJ_COMMIT);
- new_parent(lookup_commit(sha1), &parents);
- }
-
- if (strbuf_read(&buffer, 0, 0) < 0)
- die_errno("git commit-tree: failed to read");
-
- if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
- printf("%s\n", sha1_to_hex(commit_sha1));
- return 0;
- }
- else
- return 1;
-}
+++ /dev/null
-/*
- * Builtin "git commit"
- *
- * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
- * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
- */
-
-#include "cache.h"
-#include "cache-tree.h"
-#include "color.h"
-#include "dir.h"
-#include "builtin.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "commit.h"
-#include "revision.h"
-#include "wt-status.h"
-#include "run-command.h"
-#include "refs.h"
-#include "log-tree.h"
-#include "strbuf.h"
-#include "utf8.h"
-#include "parse-options.h"
-#include "string-list.h"
-#include "rerere.h"
-#include "unpack-trees.h"
-#include "quote.h"
-
-static const char * const builtin_commit_usage[] = {
- "git commit [options] [--] <filepattern>...",
- NULL
-};
-
-static const char * const builtin_status_usage[] = {
- "git status [options] [--] <filepattern>...",
- NULL
-};
-
-static const char implicit_ident_advice[] =
-"Your name and email address were configured automatically based\n"
-"on your username and hostname. Please check that they are accurate.\n"
-"You can suppress this message by setting them explicitly:\n"
-"\n"
-" git config --global user.name \"Your Name\"\n"
-" git config --global user.email you@example.com\n"
-"\n"
-"If the identity used for this commit is wrong, you can fix it with:\n"
-"\n"
-" git commit --amend --author='Your Name <you@example.com>'\n";
-
-static unsigned char head_sha1[20];
-
-static char *use_message_buffer;
-static const char commit_editmsg[] = "COMMIT_EDITMSG";
-static struct lock_file index_lock; /* real index */
-static struct lock_file false_lock; /* used only for partial commits */
-static enum {
- COMMIT_AS_IS = 1,
- COMMIT_NORMAL,
- COMMIT_PARTIAL,
-} commit_style;
-
-static const char *logfile, *force_author;
-static const char *template_file;
-static char *edit_message, *use_message;
-static char *author_name, *author_email, *author_date;
-static int all, edit_flag, also, interactive, only, amend, signoff;
-static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
-static char *untracked_files_arg, *force_date;
-/*
- * The default commit message cleanup mode will remove the lines
- * beginning with # (shell comments) and leading and trailing
- * whitespaces (empty lines or containing only whitespaces)
- * if editor is used, and only the whitespaces if the message
- * is specified explicitly.
- */
-static enum {
- CLEANUP_SPACE,
- CLEANUP_NONE,
- CLEANUP_ALL,
-} cleanup_mode;
-static char *cleanup_arg;
-
-static int use_editor = 1, initial_commit, in_merge, include_status = 1;
-static const char *only_include_assumed;
-static struct strbuf message;
-
-static int null_termination;
-static enum {
- STATUS_FORMAT_LONG,
- STATUS_FORMAT_SHORT,
- STATUS_FORMAT_PORCELAIN,
-} status_format = STATUS_FORMAT_LONG;
-
-static int opt_parse_m(const struct option *opt, const char *arg, int unset)
-{
- struct strbuf *buf = opt->value;
- if (unset)
- strbuf_setlen(buf, 0);
- else {
- strbuf_addstr(buf, arg);
- strbuf_addstr(buf, "\n\n");
- }
- return 0;
-}
-
-static struct option builtin_commit_options[] = {
- OPT__QUIET(&quiet),
- OPT__VERBOSE(&verbose),
-
- OPT_GROUP("Commit message options"),
- OPT_FILENAME('F', "file", &logfile, "read log from file"),
- OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
- OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
- OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
- OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
- OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
- OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
- OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
- OPT_FILENAME('t', "template", &template_file, "use specified template file"),
- OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
- OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
- OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
- /* end commit message options */
-
- OPT_GROUP("Commit contents options"),
- OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
- OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"),
- OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
- OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
- OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
- OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
- OPT_SET_INT(0, "short", &status_format, "show status concisely",
- STATUS_FORMAT_SHORT),
- OPT_SET_INT(0, "porcelain", &status_format,
- "show porcelain output format", STATUS_FORMAT_PORCELAIN),
- OPT_BOOLEAN('z', "null", &null_termination,
- "terminate entries with NUL"),
- OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
- { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
- OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
- /* end commit contents options */
-
- OPT_END()
-};
-
-static void rollback_index_files(void)
-{
- switch (commit_style) {
- case COMMIT_AS_IS:
- break; /* nothing to do */
- case COMMIT_NORMAL:
- rollback_lock_file(&index_lock);
- break;
- case COMMIT_PARTIAL:
- rollback_lock_file(&index_lock);
- rollback_lock_file(&false_lock);
- break;
- }
-}
-
-static int commit_index_files(void)
-{
- int err = 0;
-
- switch (commit_style) {
- case COMMIT_AS_IS:
- break; /* nothing to do */
- case COMMIT_NORMAL:
- err = commit_lock_file(&index_lock);
- break;
- case COMMIT_PARTIAL:
- err = commit_lock_file(&index_lock);
- rollback_lock_file(&false_lock);
- break;
- }
-
- return err;
-}
-
-/*
- * Take a union of paths in the index and the named tree (typically, "HEAD"),
- * and return the paths that match the given pattern in list.
- */
-static int list_paths(struct string_list *list, const char *with_tree,
- const char *prefix, const char **pattern)
-{
- int i;
- char *m;
-
- for (i = 0; pattern[i]; i++)
- ;
- m = xcalloc(1, i);
-
- if (with_tree)
- overlay_tree_on_cache(with_tree, prefix);
-
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- struct string_list_item *item;
-
- if (ce->ce_flags & CE_UPDATE)
- continue;
- if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
- continue;
- item = string_list_insert(ce->name, list);
- if (ce_skip_worktree(ce))
- item->util = item; /* better a valid pointer than a fake one */
- }
-
- return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
-}
-
-static void add_remove_files(struct string_list *list)
-{
- int i;
- for (i = 0; i < list->nr; i++) {
- struct stat st;
- struct string_list_item *p = &(list->items[i]);
-
- /* p->util is skip-worktree */
- if (p->util)
- continue;
-
- if (!lstat(p->string, &st)) {
- if (add_to_cache(p->string, &st, 0))
- die("updating files failed");
- } else
- remove_file_from_cache(p->string);
- }
-}
-
-static void create_base_index(void)
-{
- struct tree *tree;
- struct unpack_trees_options opts;
- struct tree_desc t;
-
- if (initial_commit) {
- discard_cache();
- return;
- }
-
- memset(&opts, 0, sizeof(opts));
- opts.head_idx = 1;
- opts.index_only = 1;
- opts.merge = 1;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
-
- opts.fn = oneway_merge;
- tree = parse_tree_indirect(head_sha1);
- if (!tree)
- die("failed to unpack HEAD tree object");
- parse_tree(tree);
- init_tree_desc(&t, tree->buffer, tree->size);
- if (unpack_trees(1, &t, &opts))
- exit(128); /* We've already reported the error, finish dying */
-}
-
-static void refresh_cache_or_die(int refresh_flags)
-{
- /*
- * refresh_flags contains REFRESH_QUIET, so the only errors
- * are for unmerged entries.
- */
- if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN))
- die_resolve_conflict("commit");
-}
-
-static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
-{
- int fd;
- struct string_list partial;
- const char **pathspec = NULL;
- int refresh_flags = REFRESH_QUIET;
-
- if (is_status)
- refresh_flags |= REFRESH_UNMERGED;
- if (interactive) {
- if (interactive_add(argc, argv, prefix) != 0)
- die("interactive add failed");
- if (read_cache_preload(NULL) < 0)
- die("index file corrupt");
- commit_style = COMMIT_AS_IS;
- return get_index_file();
- }
-
- if (*argv)
- pathspec = get_pathspec(prefix, argv);
-
- if (read_cache_preload(pathspec) < 0)
- die("index file corrupt");
-
- /*
- * Non partial, non as-is commit.
- *
- * (1) get the real index;
- * (2) update the_index as necessary;
- * (3) write the_index out to the real index (still locked);
- * (4) return the name of the locked index file.
- *
- * The caller should run hooks on the locked real index, and
- * (A) if all goes well, commit the real index;
- * (B) on failure, rollback the real index.
- */
- if (all || (also && pathspec && *pathspec)) {
- int fd = hold_locked_index(&index_lock, 1);
- add_files_to_cache(also ? prefix : NULL, pathspec, 0);
- refresh_cache_or_die(refresh_flags);
- if (write_cache(fd, active_cache, active_nr) ||
- close_lock_file(&index_lock))
- die("unable to write new_index file");
- commit_style = COMMIT_NORMAL;
- return index_lock.filename;
- }
-
- /*
- * As-is commit.
- *
- * (1) return the name of the real index file.
- *
- * The caller should run hooks on the real index, and run
- * hooks on the real index, and create commit from the_index.
- * We still need to refresh the index here.
- */
- if (!pathspec || !*pathspec) {
- fd = hold_locked_index(&index_lock, 1);
- refresh_cache_or_die(refresh_flags);
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(&index_lock))
- die("unable to write new_index file");
- commit_style = COMMIT_AS_IS;
- return get_index_file();
- }
-
- /*
- * A partial commit.
- *
- * (0) find the set of affected paths;
- * (1) get lock on the real index file;
- * (2) update the_index with the given paths;
- * (3) write the_index out to the real index (still locked);
- * (4) get lock on the false index file;
- * (5) reset the_index from HEAD;
- * (6) update the_index the same way as (2);
- * (7) write the_index out to the false index file;
- * (8) return the name of the false index file (still locked);
- *
- * The caller should run hooks on the locked false index, and
- * create commit from it. Then
- * (A) if all goes well, commit the real index;
- * (B) on failure, rollback the real index;
- * In either case, rollback the false index.
- */
- commit_style = COMMIT_PARTIAL;
-
- if (in_merge)
- die("cannot do a partial commit during a merge.");
-
- memset(&partial, 0, sizeof(partial));
- partial.strdup_strings = 1;
- if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
- exit(1);
-
- discard_cache();
- if (read_cache() < 0)
- die("cannot read the index");
-
- fd = hold_locked_index(&index_lock, 1);
- add_remove_files(&partial);
- refresh_cache(REFRESH_QUIET);
- if (write_cache(fd, active_cache, active_nr) ||
- close_lock_file(&index_lock))
- die("unable to write new_index file");
-
- fd = hold_lock_file_for_update(&false_lock,
- git_path("next-index-%"PRIuMAX,
- (uintmax_t) getpid()),
- LOCK_DIE_ON_ERROR);
-
- create_base_index();
- add_remove_files(&partial);
- refresh_cache(REFRESH_QUIET);
-
- if (write_cache(fd, active_cache, active_nr) ||
- close_lock_file(&false_lock))
- die("unable to write temporary index file");
-
- discard_cache();
- read_cache_from(false_lock.filename);
-
- return false_lock.filename;
-}
-
-static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
- struct wt_status *s)
-{
- unsigned char sha1[20];
-
- if (s->relative_paths)
- s->prefix = prefix;
-
- if (amend) {
- s->amend = 1;
- s->reference = "HEAD^1";
- }
- s->verbose = verbose;
- s->index_file = index_file;
- s->fp = fp;
- s->nowarn = nowarn;
- s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
-
- wt_status_collect(s);
-
- switch (status_format) {
- case STATUS_FORMAT_SHORT:
- wt_shortstatus_print(s, null_termination);
- break;
- case STATUS_FORMAT_PORCELAIN:
- wt_porcelain_print(s, null_termination);
- break;
- case STATUS_FORMAT_LONG:
- wt_status_print(s);
- break;
- }
-
- return s->commitable;
-}
-
-static int is_a_merge(const unsigned char *sha1)
-{
- struct commit *commit = lookup_commit(sha1);
- if (!commit || parse_commit(commit))
- die("could not parse HEAD commit");
- return !!(commit->parents && commit->parents->next);
-}
-
-static const char sign_off_header[] = "Signed-off-by: ";
-
-static void determine_author_info(void)
-{
- char *name, *email, *date;
-
- name = getenv("GIT_AUTHOR_NAME");
- email = getenv("GIT_AUTHOR_EMAIL");
- date = getenv("GIT_AUTHOR_DATE");
-
- if (use_message && !renew_authorship) {
- const char *a, *lb, *rb, *eol;
-
- a = strstr(use_message_buffer, "\nauthor ");
- if (!a)
- die("invalid commit: %s", use_message);
-
- lb = strstr(a + 8, " <");
- rb = strstr(a + 8, "> ");
- eol = strchr(a + 8, '\n');
- if (!lb || !rb || !eol)
- die("invalid commit: %s", use_message);
-
- name = xstrndup(a + 8, lb - (a + 8));
- email = xstrndup(lb + 2, rb - (lb + 2));
- date = xstrndup(rb + 2, eol - (rb + 2));
- }
-
- if (force_author) {
- const char *lb = strstr(force_author, " <");
- const char *rb = strchr(force_author, '>');
-
- if (!lb || !rb)
- die("malformed --author parameter");
- name = xstrndup(force_author, lb - force_author);
- email = xstrndup(lb + 2, rb - (lb + 2));
- }
-
- if (force_date)
- date = force_date;
-
- author_name = name;
- author_email = email;
- author_date = date;
-}
-
-static int ends_rfc2822_footer(struct strbuf *sb)
-{
- int ch;
- int hit = 0;
- int i, j, k;
- int len = sb->len;
- int first = 1;
- const char *buf = sb->buf;
-
- for (i = len - 1; i > 0; i--) {
- if (hit && buf[i] == '\n')
- break;
- hit = (buf[i] == '\n');
- }
-
- while (i < len - 1 && buf[i] == '\n')
- i++;
-
- for (; i < len; i = k) {
- for (k = i; k < len && buf[k] != '\n'; k++)
- ; /* do nothing */
- k++;
-
- if ((buf[k] == ' ' || buf[k] == '\t') && !first)
- continue;
-
- first = 0;
-
- for (j = 0; i + j < len; j++) {
- ch = buf[i + j];
- if (ch == ':')
- break;
- if (isalnum(ch) ||
- (ch == '-'))
- continue;
- return 0;
- }
- }
- return 1;
-}
-
-static int prepare_to_commit(const char *index_file, const char *prefix,
- struct wt_status *s)
-{
- struct stat statbuf;
- int commitable, saved_color_setting;
- struct strbuf sb = STRBUF_INIT;
- char *buffer;
- FILE *fp;
- const char *hook_arg1 = NULL;
- const char *hook_arg2 = NULL;
- int ident_shown = 0;
-
- if (!no_verify && run_hook(index_file, "pre-commit", NULL))
- return 0;
-
- if (message.len) {
- strbuf_addbuf(&sb, &message);
- hook_arg1 = "message";
- } else if (logfile && !strcmp(logfile, "-")) {
- if (isatty(0))
- fprintf(stderr, "(reading log message from standard input)\n");
- if (strbuf_read(&sb, 0, 0) < 0)
- die_errno("could not read log from standard input");
- hook_arg1 = "message";
- } else if (logfile) {
- if (strbuf_read_file(&sb, logfile, 0) < 0)
- die_errno("could not read log file '%s'",
- logfile);
- hook_arg1 = "message";
- } else if (use_message) {
- buffer = strstr(use_message_buffer, "\n\n");
- if (!buffer || buffer[2] == '\0')
- die("commit has empty message");
- strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
- hook_arg1 = "commit";
- hook_arg2 = use_message;
- } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
- if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
- die_errno("could not read MERGE_MSG");
- hook_arg1 = "merge";
- } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
- if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
- die_errno("could not read SQUASH_MSG");
- hook_arg1 = "squash";
- } else if (template_file && !stat(template_file, &statbuf)) {
- if (strbuf_read_file(&sb, template_file, 0) < 0)
- die_errno("could not read '%s'", template_file);
- hook_arg1 = "template";
- }
-
- /*
- * This final case does not modify the template message,
- * it just sets the argument to the prepare-commit-msg hook.
- */
- else if (in_merge)
- hook_arg1 = "merge";
-
- fp = fopen(git_path(commit_editmsg), "w");
- if (fp == NULL)
- die_errno("could not open '%s'", git_path(commit_editmsg));
-
- if (cleanup_mode != CLEANUP_NONE)
- stripspace(&sb, 0);
-
- if (signoff) {
- struct strbuf sob = STRBUF_INIT;
- int i;
-
- strbuf_addstr(&sob, sign_off_header);
- strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
- getenv("GIT_COMMITTER_EMAIL")));
- strbuf_addch(&sob, '\n');
- for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
- ; /* do nothing */
- if (prefixcmp(sb.buf + i, sob.buf)) {
- if (!i || !ends_rfc2822_footer(&sb))
- strbuf_addch(&sb, '\n');
- strbuf_addbuf(&sb, &sob);
- }
- strbuf_release(&sob);
- }
-
- if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
- die_errno("could not write commit template");
-
- strbuf_release(&sb);
-
- determine_author_info();
-
- /* This checks if committer ident is explicitly given */
- git_committer_info(0);
- if (use_editor && include_status) {
- char *author_ident;
- const char *committer_ident;
-
- if (in_merge)
- fprintf(fp,
- "#\n"
- "# It looks like you may be committing a MERGE.\n"
- "# If this is not correct, please remove the file\n"
- "# %s\n"
- "# and try again.\n"
- "#\n",
- git_path("MERGE_HEAD"));
-
- fprintf(fp,
- "\n"
- "# Please enter the commit message for your changes.");
- if (cleanup_mode == CLEANUP_ALL)
- fprintf(fp,
- " Lines starting\n"
- "# with '#' will be ignored, and an empty"
- " message aborts the commit.\n");
- else /* CLEANUP_SPACE, that is. */
- fprintf(fp,
- " Lines starting\n"
- "# with '#' will be kept; you may remove them"
- " yourself if you want to.\n"
- "# An empty message aborts the commit.\n");
- if (only_include_assumed)
- fprintf(fp, "# %s\n", only_include_assumed);
-
- author_ident = xstrdup(fmt_name(author_name, author_email));
- committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"),
- getenv("GIT_COMMITTER_EMAIL"));
- if (strcmp(author_ident, committer_ident))
- fprintf(fp,
- "%s"
- "# Author: %s\n",
- ident_shown++ ? "" : "#\n",
- author_ident);
- free(author_ident);
-
- if (!user_ident_sufficiently_given())
- fprintf(fp,
- "%s"
- "# Committer: %s\n",
- ident_shown++ ? "" : "#\n",
- committer_ident);
-
- if (ident_shown)
- fprintf(fp, "#\n");
-
- saved_color_setting = s->use_color;
- s->use_color = 0;
- commitable = run_status(fp, index_file, prefix, 1, s);
- s->use_color = saved_color_setting;
- } else {
- unsigned char sha1[20];
- const char *parent = "HEAD";
-
- if (!active_nr && read_cache() < 0)
- die("Cannot read index");
-
- if (amend)
- parent = "HEAD^1";
-
- if (get_sha1(parent, sha1))
- commitable = !!active_nr;
- else
- commitable = index_differs_from(parent, 0);
- }
-
- fclose(fp);
-
- if (!commitable && !in_merge && !allow_empty &&
- !(amend && is_a_merge(head_sha1))) {
- run_status(stdout, index_file, prefix, 0, s);
- return 0;
- }
-
- /*
- * Re-read the index as pre-commit hook could have updated it,
- * and write it out as a tree. We must do this before we invoke
- * the editor and after we invoke run_status above.
- */
- discard_cache();
- read_cache_from(index_file);
- if (!active_cache_tree)
- active_cache_tree = cache_tree();
- if (cache_tree_update(active_cache_tree,
- active_cache, active_nr, 0, 0) < 0) {
- error("Error building trees");
- return 0;
- }
-
- if (run_hook(index_file, "prepare-commit-msg",
- git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
- return 0;
-
- if (use_editor) {
- char index[PATH_MAX];
- const char *env[2] = { index, NULL };
- snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
- if (launch_editor(git_path(commit_editmsg), NULL, env)) {
- fprintf(stderr,
- "Please supply the message using either -m or -F option.\n");
- exit(1);
- }
- }
-
- if (!no_verify &&
- run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
- return 0;
- }
-
- return 1;
-}
-
-/*
- * Find out if the message in the strbuf contains only whitespace and
- * Signed-off-by lines.
- */
-static int message_is_empty(struct strbuf *sb)
-{
- struct strbuf tmpl = STRBUF_INIT;
- const char *nl;
- int eol, i, start = 0;
-
- if (cleanup_mode == CLEANUP_NONE && sb->len)
- return 0;
-
- /* See if the template is just a prefix of the message. */
- if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
- stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
- if (start + tmpl.len <= sb->len &&
- memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
- start += tmpl.len;
- }
- strbuf_release(&tmpl);
-
- /* Check if the rest is just whitespace and Signed-of-by's. */
- for (i = start; i < sb->len; i++) {
- nl = memchr(sb->buf + i, '\n', sb->len - i);
- if (nl)
- eol = nl - sb->buf;
- else
- eol = sb->len;
-
- if (strlen(sign_off_header) <= eol - i &&
- !prefixcmp(sb->buf + i, sign_off_header)) {
- i = eol;
- continue;
- }
- while (i < eol)
- if (!isspace(sb->buf[i++]))
- return 0;
- }
-
- return 1;
-}
-
-static const char *find_author_by_nickname(const char *name)
-{
- struct rev_info revs;
- struct commit *commit;
- struct strbuf buf = STRBUF_INIT;
- const char *av[20];
- int ac = 0;
-
- init_revisions(&revs, NULL);
- strbuf_addf(&buf, "--author=%s", name);
- av[++ac] = "--all";
- av[++ac] = "-i";
- av[++ac] = buf.buf;
- av[++ac] = NULL;
- setup_revisions(ac, av, &revs, NULL);
- prepare_revision_walk(&revs);
- commit = get_revision(&revs);
- if (commit) {
- struct pretty_print_context ctx = {0};
- ctx.date_mode = DATE_NORMAL;
- strbuf_release(&buf);
- format_commit_message(commit, "%an <%ae>", &buf, &ctx);
- return strbuf_detach(&buf, NULL);
- }
- die("No existing author found with '%s'", name);
-}
-
-
-static void handle_untracked_files_arg(struct wt_status *s)
-{
- if (!untracked_files_arg)
- ; /* default already initialized */
- else if (!strcmp(untracked_files_arg, "no"))
- s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
- else if (!strcmp(untracked_files_arg, "normal"))
- s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
- else if (!strcmp(untracked_files_arg, "all"))
- s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
- else
- die("Invalid untracked files mode '%s'", untracked_files_arg);
-}
-
-static int parse_and_validate_options(int argc, const char *argv[],
- const char * const usage[],
- const char *prefix,
- struct wt_status *s)
-{
- int f = 0;
-
- argc = parse_options(argc, argv, prefix, builtin_commit_options, usage,
- 0);
-
- if (force_author && !strchr(force_author, '>'))
- force_author = find_author_by_nickname(force_author);
-
- if (force_author && renew_authorship)
- die("Using both --reset-author and --author does not make sense");
-
- if (logfile || message.len || use_message)
- use_editor = 0;
- if (edit_flag)
- use_editor = 1;
- if (!use_editor)
- setenv("GIT_EDITOR", ":", 1);
-
- if (get_sha1("HEAD", head_sha1))
- initial_commit = 1;
-
- /* Sanity check options */
- if (amend && initial_commit)
- die("You have nothing to amend.");
- if (amend && in_merge)
- die("You are in the middle of a merge -- cannot amend.");
-
- if (use_message)
- f++;
- if (edit_message)
- f++;
- if (logfile)
- f++;
- if (f > 1)
- die("Only one of -c/-C/-F can be used.");
- if (message.len && f > 0)
- die("Option -m cannot be combined with -c/-C/-F.");
- if (edit_message)
- use_message = edit_message;
- if (amend && !use_message)
- use_message = "HEAD";
- if (!use_message && renew_authorship)
- die("--reset-author can be used only with -C, -c or --amend.");
- if (use_message) {
- unsigned char sha1[20];
- static char utf8[] = "UTF-8";
- const char *out_enc;
- char *enc, *end;
- struct commit *commit;
-
- if (get_sha1(use_message, sha1))
- die("could not lookup commit %s", use_message);
- commit = lookup_commit_reference(sha1);
- if (!commit || parse_commit(commit))
- die("could not parse commit %s", use_message);
-
- enc = strstr(commit->buffer, "\nencoding");
- if (enc) {
- end = strchr(enc + 10, '\n');
- enc = xstrndup(enc + 10, end - (enc + 10));
- } else {
- enc = utf8;
- }
- out_enc = git_commit_encoding ? git_commit_encoding : utf8;
-
- if (strcmp(out_enc, enc))
- use_message_buffer =
- reencode_string(commit->buffer, out_enc, enc);
-
- /*
- * If we failed to reencode the buffer, just copy it
- * byte for byte so the user can try to fix it up.
- * This also handles the case where input and output
- * encodings are identical.
- */
- if (use_message_buffer == NULL)
- use_message_buffer = xstrdup(commit->buffer);
- if (enc != utf8)
- free(enc);
- }
-
- if (!!also + !!only + !!all + !!interactive > 1)
- die("Only one of --include/--only/--all/--interactive can be used.");
- if (argc == 0 && (also || (only && !amend)))
- die("No paths with --include/--only does not make sense.");
- if (argc == 0 && only && amend)
- only_include_assumed = "Clever... amending the last one with dirty index.";
- if (argc > 0 && !also && !only)
- only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
- if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
- cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
- else if (!strcmp(cleanup_arg, "verbatim"))
- cleanup_mode = CLEANUP_NONE;
- else if (!strcmp(cleanup_arg, "whitespace"))
- cleanup_mode = CLEANUP_SPACE;
- else if (!strcmp(cleanup_arg, "strip"))
- cleanup_mode = CLEANUP_ALL;
- else
- die("Invalid cleanup mode %s", cleanup_arg);
-
- handle_untracked_files_arg(s);
-
- if (all && argc > 0)
- die("Paths with -a does not make sense.");
- else if (interactive && argc > 0)
- die("Paths with --interactive does not make sense.");
-
- if (null_termination && status_format == STATUS_FORMAT_LONG)
- status_format = STATUS_FORMAT_PORCELAIN;
- if (status_format != STATUS_FORMAT_LONG)
- dry_run = 1;
-
- return argc;
-}
-
-static int dry_run_commit(int argc, const char **argv, const char *prefix,
- struct wt_status *s)
-{
- int commitable;
- const char *index_file;
-
- index_file = prepare_index(argc, argv, prefix, 1);
- commitable = run_status(stdout, index_file, prefix, 0, s);
- rollback_index_files();
-
- return commitable ? 0 : 1;
-}
-
-static int parse_status_slot(const char *var, int offset)
-{
- if (!strcasecmp(var+offset, "header"))
- return WT_STATUS_HEADER;
- if (!strcasecmp(var+offset, "updated")
- || !strcasecmp(var+offset, "added"))
- return WT_STATUS_UPDATED;
- if (!strcasecmp(var+offset, "changed"))
- return WT_STATUS_CHANGED;
- if (!strcasecmp(var+offset, "untracked"))
- return WT_STATUS_UNTRACKED;
- if (!strcasecmp(var+offset, "nobranch"))
- return WT_STATUS_NOBRANCH;
- if (!strcasecmp(var+offset, "unmerged"))
- return WT_STATUS_UNMERGED;
- return -1;
-}
-
-static int git_status_config(const char *k, const char *v, void *cb)
-{
- struct wt_status *s = cb;
-
- if (!strcmp(k, "status.submodulesummary")) {
- int is_bool;
- s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
- if (is_bool && s->submodule_summary)
- s->submodule_summary = -1;
- return 0;
- }
- if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
- s->use_color = git_config_colorbool(k, v, -1);
- return 0;
- }
- if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
- int slot = parse_status_slot(k, 13);
- if (slot < 0)
- return 0;
- if (!v)
- return config_error_nonbool(k);
- color_parse(v, k, s->color_palette[slot]);
- return 0;
- }
- if (!strcmp(k, "status.relativepaths")) {
- s->relative_paths = git_config_bool(k, v);
- return 0;
- }
- if (!strcmp(k, "status.showuntrackedfiles")) {
- if (!v)
- return config_error_nonbool(k);
- else if (!strcmp(v, "no"))
- s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
- else if (!strcmp(v, "normal"))
- s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
- else if (!strcmp(v, "all"))
- s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
- else
- return error("Invalid untracked files mode '%s'", v);
- return 0;
- }
- return git_diff_ui_config(k, v, NULL);
-}
-
-int cmd_status(int argc, const char **argv, const char *prefix)
-{
- struct wt_status s;
- unsigned char sha1[20];
- static struct option builtin_status_options[] = {
- OPT__VERBOSE(&verbose),
- OPT_SET_INT('s', "short", &status_format,
- "show status concisely", STATUS_FORMAT_SHORT),
- OPT_SET_INT(0, "porcelain", &status_format,
- "show porcelain output format",
- STATUS_FORMAT_PORCELAIN),
- OPT_BOOLEAN('z', "null", &null_termination,
- "terminate entries with NUL"),
- { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
- "mode",
- "show untracked files, optional modes: all, normal, no. (Default: all)",
- PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
- OPT_END(),
- };
-
- if (null_termination && status_format == STATUS_FORMAT_LONG)
- status_format = STATUS_FORMAT_PORCELAIN;
-
- wt_status_prepare(&s);
- git_config(git_status_config, &s);
- in_merge = file_exists(git_path("MERGE_HEAD"));
- argc = parse_options(argc, argv, prefix,
- builtin_status_options,
- builtin_status_usage, 0);
- handle_untracked_files_arg(&s);
-
- if (*argv)
- s.pathspec = get_pathspec(prefix, argv);
-
- read_cache_preload(s.pathspec);
- refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
- s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
- s.in_merge = in_merge;
- wt_status_collect(&s);
-
- if (s.relative_paths)
- s.prefix = prefix;
- if (s.use_color == -1)
- s.use_color = git_use_color_default;
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
- switch (status_format) {
- case STATUS_FORMAT_SHORT:
- wt_shortstatus_print(&s, null_termination);
- break;
- case STATUS_FORMAT_PORCELAIN:
- wt_porcelain_print(&s, null_termination);
- break;
- case STATUS_FORMAT_LONG:
- s.verbose = verbose;
- wt_status_print(&s);
- break;
- }
- return 0;
-}
-
-static void print_summary(const char *prefix, const unsigned char *sha1)
-{
- struct rev_info rev;
- struct commit *commit;
- struct strbuf format = STRBUF_INIT;
- unsigned char junk_sha1[20];
- const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
- struct pretty_print_context pctx = {0};
- struct strbuf author_ident = STRBUF_INIT;
- struct strbuf committer_ident = STRBUF_INIT;
-
- commit = lookup_commit(sha1);
- if (!commit)
- die("couldn't look up newly created commit");
- if (!commit || parse_commit(commit))
- die("could not parse newly created commit");
-
- strbuf_addstr(&format, "format:%h] %s");
-
- format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
- format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
- if (strbuf_cmp(&author_ident, &committer_ident)) {
- strbuf_addstr(&format, "\n Author: ");
- strbuf_addbuf_percentquote(&format, &author_ident);
- }
- if (!user_ident_sufficiently_given()) {
- strbuf_addstr(&format, "\n Committer: ");
- strbuf_addbuf_percentquote(&format, &committer_ident);
- if (advice_implicit_identity) {
- strbuf_addch(&format, '\n');
- strbuf_addstr(&format, implicit_ident_advice);
- }
- }
- strbuf_release(&author_ident);
- strbuf_release(&committer_ident);
-
- init_revisions(&rev, prefix);
- setup_revisions(0, NULL, &rev, NULL);
-
- rev.abbrev = 0;
- rev.diff = 1;
- rev.diffopt.output_format =
- DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
-
- rev.verbose_header = 1;
- rev.show_root_diff = 1;
- get_commit_format(format.buf, &rev);
- rev.always_show_header = 0;
- rev.diffopt.detect_rename = 1;
- rev.diffopt.rename_limit = 100;
- rev.diffopt.break_opt = 0;
- diff_setup_done(&rev.diffopt);
-
- printf("[%s%s ",
- !prefixcmp(head, "refs/heads/") ?
- head + 11 :
- !strcmp(head, "HEAD") ?
- "detached HEAD" :
- head,
- initial_commit ? " (root-commit)" : "");
-
- if (!log_tree_commit(&rev, commit)) {
- struct pretty_print_context ctx = {0};
- struct strbuf buf = STRBUF_INIT;
- ctx.date_mode = DATE_NORMAL;
- format_commit_message(commit, format.buf + 7, &buf, &ctx);
- printf("%s\n", buf.buf);
- strbuf_release(&buf);
- }
- strbuf_release(&format);
-}
-
-static int git_commit_config(const char *k, const char *v, void *cb)
-{
- struct wt_status *s = cb;
-
- if (!strcmp(k, "commit.template"))
- return git_config_pathname(&template_file, k, v);
- if (!strcmp(k, "commit.status")) {
- include_status = git_config_bool(k, v);
- return 0;
- }
-
- return git_status_config(k, v, s);
-}
-
-int cmd_commit(int argc, const char **argv, const char *prefix)
-{
- struct strbuf sb = STRBUF_INIT;
- const char *index_file, *reflog_msg;
- char *nl, *p;
- unsigned char commit_sha1[20];
- struct ref_lock *ref_lock;
- struct commit_list *parents = NULL, **pptr = &parents;
- struct stat statbuf;
- int allow_fast_forward = 1;
- struct wt_status s;
-
- wt_status_prepare(&s);
- git_config(git_commit_config, &s);
- in_merge = file_exists(git_path("MERGE_HEAD"));
- s.in_merge = in_merge;
-
- if (s.use_color == -1)
- s.use_color = git_use_color_default;
- argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
- prefix, &s);
- if (dry_run) {
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
- return dry_run_commit(argc, argv, prefix, &s);
- }
- index_file = prepare_index(argc, argv, prefix, 0);
-
- /* Set up everything for writing the commit object. This includes
- running hooks, writing the trees, and interacting with the user. */
- if (!prepare_to_commit(index_file, prefix, &s)) {
- rollback_index_files();
- return 1;
- }
-
- /* Determine parents */
- if (initial_commit) {
- reflog_msg = "commit (initial)";
- } else if (amend) {
- struct commit_list *c;
- struct commit *commit;
-
- reflog_msg = "commit (amend)";
- commit = lookup_commit(head_sha1);
- if (!commit || parse_commit(commit))
- die("could not parse HEAD commit");
-
- for (c = commit->parents; c; c = c->next)
- pptr = &commit_list_insert(c->item, pptr)->next;
- } else if (in_merge) {
- struct strbuf m = STRBUF_INIT;
- FILE *fp;
-
- reflog_msg = "commit (merge)";
- pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
- fp = fopen(git_path("MERGE_HEAD"), "r");
- if (fp == NULL)
- die_errno("could not open '%s' for reading",
- git_path("MERGE_HEAD"));
- while (strbuf_getline(&m, fp, '\n') != EOF) {
- unsigned char sha1[20];
- if (get_sha1_hex(m.buf, sha1) < 0)
- die("Corrupt MERGE_HEAD file (%s)", m.buf);
- pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
- }
- fclose(fp);
- strbuf_release(&m);
- if (!stat(git_path("MERGE_MODE"), &statbuf)) {
- if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
- die_errno("could not read MERGE_MODE");
- if (!strcmp(sb.buf, "no-ff"))
- allow_fast_forward = 0;
- }
- if (allow_fast_forward)
- parents = reduce_heads(parents);
- } else {
- reflog_msg = "commit";
- pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
- }
-
- /* Finally, get the commit message */
- strbuf_reset(&sb);
- if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
- int saved_errno = errno;
- rollback_index_files();
- die("could not read commit message: %s", strerror(saved_errno));
- }
-
- /* Truncate the message just before the diff, if any. */
- if (verbose) {
- p = strstr(sb.buf, "\ndiff --git ");
- if (p != NULL)
- strbuf_setlen(&sb, p - sb.buf + 1);
- }
-
- if (cleanup_mode != CLEANUP_NONE)
- stripspace(&sb, cleanup_mode == CLEANUP_ALL);
- if (message_is_empty(&sb)) {
- rollback_index_files();
- fprintf(stderr, "Aborting commit due to empty commit message.\n");
- exit(1);
- }
-
- if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
- fmt_ident(author_name, author_email, author_date,
- IDENT_ERROR_ON_NO_NAME))) {
- rollback_index_files();
- die("failed to write commit object");
- }
-
- ref_lock = lock_any_ref_for_update("HEAD",
- initial_commit ? NULL : head_sha1,
- 0);
-
- nl = strchr(sb.buf, '\n');
- if (nl)
- strbuf_setlen(&sb, nl + 1 - sb.buf);
- else
- strbuf_addch(&sb, '\n');
- strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
- strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
-
- if (!ref_lock) {
- rollback_index_files();
- die("cannot lock HEAD ref");
- }
- if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
- rollback_index_files();
- die("cannot update HEAD ref");
- }
-
- unlink(git_path("MERGE_HEAD"));
- unlink(git_path("MERGE_MSG"));
- unlink(git_path("MERGE_MODE"));
- unlink(git_path("SQUASH_MSG"));
-
- if (commit_index_files())
- die ("Repository has been updated, but unable to write\n"
- "new_index file. Check that disk is not full or quota is\n"
- "not exceeded, and then \"git reset HEAD\" to recover.");
-
- rerere(0);
- run_hook(get_index_file(), "post-commit", NULL);
- if (!quiet)
- print_summary(prefix, commit_sha1);
-
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "color.h"
-#include "parse-options.h"
-
-static const char *const builtin_config_usage[] = {
- "git config [options]",
- NULL
-};
-
-static char *key;
-static regex_t *key_regexp;
-static regex_t *regexp;
-static int show_keys;
-static int use_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 int use_global_config, use_system_config;
-static const char *given_config_file;
-static int actions, types;
-static const char *get_color_slot, *get_colorbool_slot;
-static int end_null;
-
-#define ACTION_GET (1<<0)
-#define ACTION_GET_ALL (1<<1)
-#define ACTION_GET_REGEXP (1<<2)
-#define ACTION_REPLACE_ALL (1<<3)
-#define ACTION_ADD (1<<4)
-#define ACTION_UNSET (1<<5)
-#define ACTION_UNSET_ALL (1<<6)
-#define ACTION_RENAME_SECTION (1<<7)
-#define ACTION_REMOVE_SECTION (1<<8)
-#define ACTION_LIST (1<<9)
-#define ACTION_EDIT (1<<10)
-#define ACTION_SET (1<<11)
-#define ACTION_SET_ALL (1<<12)
-#define ACTION_GET_COLOR (1<<13)
-#define ACTION_GET_COLORBOOL (1<<14)
-
-#define TYPE_BOOL (1<<0)
-#define TYPE_INT (1<<1)
-#define TYPE_BOOL_OR_INT (1<<2)
-#define TYPE_PATH (1<<3)
-
-static struct option builtin_config_options[] = {
- OPT_GROUP("Config file location"),
- OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"),
- OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"),
- OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"),
- OPT_GROUP("Action"),
- OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET),
- OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL),
- OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP),
- OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL),
- OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD),
- OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET),
- OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL),
- OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION),
- OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION),
- OPT_BIT('l', "list", &actions, "list all", ACTION_LIST),
- OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT),
- OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"),
- OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"),
- OPT_GROUP("Type"),
- OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL),
- OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT),
- OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT),
- OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
- OPT_GROUP("Other"),
- OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
- OPT_END(),
-};
-
-static void check_argc(int argc, int min, int max) {
- if (argc >= min && argc <= max)
- return;
- error("wrong number of arguments");
- usage_with_options(builtin_config_usage, builtin_config_options);
-}
-
-static int show_all_config(const char *key_, const char *value_, void *cb)
-{
- if (value_)
- printf("%s%c%s%c", key_, delim, value_, term);
- else
- printf("%s%c", key_, term);
- return 0;
-}
-
-static int show_config(const char *key_, const char *value_, void *cb)
-{
- char value[256];
- const char *vptr = value;
- int must_free_vptr = 0;
- int dup_error = 0;
-
- if (!use_key_regexp && strcmp(key_, key))
- return 0;
- if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
- return 0;
- if (regexp != NULL &&
- (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
- return 0;
-
- if (show_keys) {
- if (value_)
- printf("%s%c", key_, key_delim);
- else
- printf("%s", key_);
- }
- if (seen && !do_all)
- dup_error = 1;
- if (types == TYPE_INT)
- sprintf(value, "%d", git_config_int(key_, value_?value_:""));
- else if (types == TYPE_BOOL)
- vptr = git_config_bool(key_, value_) ? "true" : "false";
- else if (types == TYPE_BOOL_OR_INT) {
- int is_bool, v;
- v = git_config_bool_or_int(key_, value_, &is_bool);
- if (is_bool)
- vptr = v ? "true" : "false";
- else
- sprintf(value, "%d", v);
- } else if (types == TYPE_PATH) {
- git_config_pathname(&vptr, key_, value_);
- must_free_vptr = 1;
- }
- else
- vptr = value_?value_:"";
- seen++;
- if (dup_error) {
- error("More than one value for the key %s: %s",
- key_, vptr);
- }
- else
- printf("%s%c", vptr, term);
- if (must_free_vptr)
- /* If vptr must be freed, it's a pointer to a
- * dynamically allocated buffer, it's safe to cast to
- * const.
- */
- free((char *)vptr);
-
- return 0;
-}
-
-static int get_value(const char *key_, const char *regex_)
-{
- int ret = -1;
- char *tl;
- char *global = NULL, *repo_config = NULL;
- const char *system_wide = NULL, *local;
-
- local = config_exclusive_filename;
- if (!local) {
- const char *home = getenv("HOME");
- local = repo_config = git_pathdup("config");
- if (git_config_global() && home)
- global = xstrdup(mkpath("%s/.gitconfig", home));
- if (git_config_system())
- system_wide = git_etc_gitconfig();
- }
-
- key = xstrdup(key_);
- for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl)
- *tl = tolower(*tl);
- for (tl=key; *tl && *tl != '.'; ++tl)
- *tl = tolower(*tl);
-
- if (use_key_regexp) {
- key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
- if (regcomp(key_regexp, key, REG_EXTENDED)) {
- fprintf(stderr, "Invalid key pattern: %s\n", key_);
- goto free_strings;
- }
- }
-
- if (regex_) {
- if (regex_[0] == '!') {
- do_not_match = 1;
- regex_++;
- }
-
- regexp = (regex_t*)xmalloc(sizeof(regex_t));
- if (regcomp(regexp, regex_, REG_EXTENDED)) {
- fprintf(stderr, "Invalid pattern: %s\n", regex_);
- goto free_strings;
- }
- }
-
- if (do_all && system_wide)
- git_config_from_file(show_config, system_wide, NULL);
- if (do_all && global)
- git_config_from_file(show_config, global, NULL);
- git_config_from_file(show_config, local, NULL);
- if (!do_all && !seen && global)
- git_config_from_file(show_config, global, NULL);
- if (!do_all && !seen && system_wide)
- git_config_from_file(show_config, system_wide, NULL);
-
- free(key);
- if (regexp) {
- regfree(regexp);
- free(regexp);
- }
-
- if (do_all)
- ret = !seen;
- else
- ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1;
-
-free_strings:
- free(repo_config);
- free(global);
- return ret;
-}
-
-static char *normalize_value(const char *key, const char *value)
-{
- char *normalized;
-
- if (!value)
- return NULL;
-
- if (types == 0 || types == TYPE_PATH)
- /*
- * We don't do normalization for TYPE_PATH here: If
- * the path is like ~/foobar/, we prefer to store
- * "~/foobar/" in the config file, and to expand the ~
- * when retrieving the value.
- */
- normalized = xstrdup(value);
- else {
- normalized = xmalloc(64);
- if (types == TYPE_INT) {
- int v = git_config_int(key, value);
- sprintf(normalized, "%d", v);
- }
- else if (types == TYPE_BOOL)
- sprintf(normalized, "%s",
- git_config_bool(key, value) ? "true" : "false");
- else if (types == TYPE_BOOL_OR_INT) {
- int is_bool, v;
- v = git_config_bool_or_int(key, value, &is_bool);
- if (!is_bool)
- sprintf(normalized, "%d", v);
- else
- sprintf(normalized, "%s", v ? "true" : "false");
- }
- }
-
- return normalized;
-}
-
-static int get_color_found;
-static const char *get_color_slot;
-static const char *get_colorbool_slot;
-static char parsed_color[COLOR_MAXLEN];
-
-static int git_get_color_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, get_color_slot)) {
- if (!value)
- config_error_nonbool(var);
- color_parse(value, var, parsed_color);
- get_color_found = 1;
- }
- return 0;
-}
-
-static void get_color(const char *def_color)
-{
- get_color_found = 0;
- parsed_color[0] = '\0';
- git_config(git_get_color_config, NULL);
-
- if (!get_color_found && def_color)
- color_parse(def_color, "command line", parsed_color);
-
- fputs(parsed_color, stdout);
-}
-
-static int stdout_is_tty;
-static int get_colorbool_found;
-static int get_diff_color_found;
-static int git_get_colorbool_config(const char *var, const char *value,
- void *cb)
-{
- if (!strcmp(var, get_colorbool_slot)) {
- get_colorbool_found =
- git_config_colorbool(var, value, stdout_is_tty);
- }
- if (!strcmp(var, "diff.color")) {
- get_diff_color_found =
- git_config_colorbool(var, value, stdout_is_tty);
- }
- if (!strcmp(var, "color.ui")) {
- git_use_color_default = git_config_colorbool(var, value, stdout_is_tty);
- return 0;
- }
- return 0;
-}
-
-static int get_colorbool(int print)
-{
- get_colorbool_found = -1;
- get_diff_color_found = -1;
- git_config(git_get_colorbool_config, NULL);
-
- if (get_colorbool_found < 0) {
- if (!strcmp(get_colorbool_slot, "color.diff"))
- get_colorbool_found = get_diff_color_found;
- if (get_colorbool_found < 0)
- get_colorbool_found = git_use_color_default;
- }
-
- if (print) {
- printf("%s\n", get_colorbool_found ? "true" : "false");
- return 0;
- } else
- return get_colorbool_found ? 0 : 1;
-}
-
-int cmd_config(int argc, const char **argv, const char *unused_prefix)
-{
- int nongit;
- char *value;
- const char *prefix = setup_git_directory_gently(&nongit);
-
- config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
-
- argc = parse_options(argc, argv, prefix, builtin_config_options,
- builtin_config_usage,
- PARSE_OPT_STOP_AT_NON_OPTION);
-
- if (use_global_config + use_system_config + !!given_config_file > 1) {
- error("only one config file at a time.");
- usage_with_options(builtin_config_usage, builtin_config_options);
- }
-
- if (use_global_config) {
- char *home = getenv("HOME");
- if (home) {
- char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
- config_exclusive_filename = user_config;
- } else {
- die("$HOME not set");
- }
- }
- else if (use_system_config)
- config_exclusive_filename = git_etc_gitconfig();
- else if (given_config_file) {
- if (!is_absolute_path(given_config_file) && prefix)
- config_exclusive_filename = prefix_filename(prefix,
- strlen(prefix),
- given_config_file);
- else
- config_exclusive_filename = given_config_file;
- }
-
- if (end_null) {
- term = '\0';
- delim = '\n';
- key_delim = '\n';
- }
-
- if (HAS_MULTI_BITS(types)) {
- error("only one type at a time.");
- usage_with_options(builtin_config_usage, builtin_config_options);
- }
-
- if (get_color_slot)
- actions |= ACTION_GET_COLOR;
- if (get_colorbool_slot)
- actions |= ACTION_GET_COLORBOOL;
-
- if ((get_color_slot || get_colorbool_slot) && types) {
- error("--get-color and variable type are incoherent");
- usage_with_options(builtin_config_usage, builtin_config_options);
- }
-
- if (HAS_MULTI_BITS(actions)) {
- error("only one action at a time.");
- usage_with_options(builtin_config_usage, builtin_config_options);
- }
- if (actions == 0)
- switch (argc) {
- case 1: actions = ACTION_GET; break;
- case 2: actions = ACTION_SET; break;
- case 3: actions = ACTION_SET_ALL; break;
- default:
- usage_with_options(builtin_config_usage, builtin_config_options);
- }
-
- if (actions == ACTION_LIST) {
- check_argc(argc, 0, 0);
- if (git_config(show_all_config, NULL) < 0) {
- if (config_exclusive_filename)
- die_errno("unable to read config file '%s'",
- config_exclusive_filename);
- else
- die("error processing config file(s)");
- }
- }
- else if (actions == ACTION_EDIT) {
- check_argc(argc, 0, 0);
- if (!config_exclusive_filename && nongit)
- die("not in a git directory");
- git_config(git_default_config, NULL);
- launch_editor(config_exclusive_filename ?
- config_exclusive_filename : git_path("config"),
- NULL, NULL);
- }
- else if (actions == ACTION_SET) {
- check_argc(argc, 2, 2);
- value = normalize_value(argv[0], argv[1]);
- return git_config_set(argv[0], value);
- }
- else if (actions == ACTION_SET_ALL) {
- check_argc(argc, 2, 3);
- value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar(argv[0], value, argv[2], 0);
- }
- else if (actions == ACTION_ADD) {
- check_argc(argc, 2, 2);
- value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar(argv[0], value, "^$", 0);
- }
- else if (actions == ACTION_REPLACE_ALL) {
- check_argc(argc, 2, 3);
- value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar(argv[0], value, argv[2], 1);
- }
- else if (actions == ACTION_GET) {
- check_argc(argc, 1, 2);
- return get_value(argv[0], argv[1]);
- }
- else if (actions == ACTION_GET_ALL) {
- do_all = 1;
- check_argc(argc, 1, 2);
- return get_value(argv[0], argv[1]);
- }
- else if (actions == ACTION_GET_REGEXP) {
- show_keys = 1;
- use_key_regexp = 1;
- do_all = 1;
- check_argc(argc, 1, 2);
- return get_value(argv[0], argv[1]);
- }
- else if (actions == ACTION_UNSET) {
- check_argc(argc, 1, 2);
- if (argc == 2)
- return git_config_set_multivar(argv[0], NULL, argv[1], 0);
- else
- return git_config_set(argv[0], NULL);
- }
- else if (actions == ACTION_UNSET_ALL) {
- check_argc(argc, 1, 2);
- return git_config_set_multivar(argv[0], NULL, argv[1], 1);
- }
- else if (actions == ACTION_RENAME_SECTION) {
- int ret;
- check_argc(argc, 2, 2);
- ret = git_config_rename_section(argv[0], argv[1]);
- if (ret < 0)
- return ret;
- if (ret == 0)
- die("No such section!");
- }
- else if (actions == ACTION_REMOVE_SECTION) {
- int ret;
- check_argc(argc, 1, 1);
- ret = git_config_rename_section(argv[0], NULL);
- if (ret < 0)
- return ret;
- if (ret == 0)
- die("No such section!");
- }
- else if (actions == ACTION_GET_COLOR) {
- get_color(argv[0]);
- }
- else if (actions == ACTION_GET_COLORBOOL) {
- if (argc == 1)
- stdout_is_tty = git_config_bool("command line", argv[0]);
- else if (argc == 0)
- stdout_is_tty = isatty(1);
- return get_colorbool(argc != 0);
- }
-
- return 0;
-}
+++ /dev/null
-/*
- * Builtin "git count-objects".
- *
- * Copyright (c) 2006 Junio C Hamano
- */
-
-#include "cache.h"
-#include "dir.h"
-#include "builtin.h"
-#include "parse-options.h"
-
-static void count_objects(DIR *d, char *path, int len, int verbose,
- unsigned long *loose,
- off_t *loose_size,
- unsigned long *packed_loose,
- unsigned long *garbage)
-{
- struct dirent *ent;
- while ((ent = readdir(d)) != NULL) {
- char hex[41];
- unsigned char sha1[20];
- const char *cp;
- int bad = 0;
-
- if (is_dot_or_dotdot(ent->d_name))
- continue;
- for (cp = ent->d_name; *cp; cp++) {
- int ch = *cp;
- if (('0' <= ch && ch <= '9') ||
- ('a' <= ch && ch <= 'f'))
- continue;
- bad = 1;
- break;
- }
- if (cp - ent->d_name != 38)
- bad = 1;
- else {
- struct stat st;
- memcpy(path + len + 3, ent->d_name, 38);
- path[len + 2] = '/';
- path[len + 41] = 0;
- if (lstat(path, &st) || !S_ISREG(st.st_mode))
- bad = 1;
- else
- (*loose_size) += xsize_t(on_disk_bytes(st));
- }
- if (bad) {
- if (verbose) {
- error("garbage found: %.*s/%s",
- len + 2, path, ent->d_name);
- (*garbage)++;
- }
- continue;
- }
- (*loose)++;
- if (!verbose)
- continue;
- memcpy(hex, path+len, 2);
- memcpy(hex+2, ent->d_name, 38);
- hex[40] = 0;
- if (get_sha1_hex(hex, sha1))
- die("internal error");
- if (has_sha1_pack(sha1))
- (*packed_loose)++;
- }
-}
-
-static char const * const count_objects_usage[] = {
- "git count-objects [-v]",
- NULL
-};
-
-int cmd_count_objects(int argc, const char **argv, const char *prefix)
-{
- int i, verbose = 0;
- const char *objdir = get_object_directory();
- int len = strlen(objdir);
- char *path = xmalloc(len + 50);
- unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
- off_t loose_size = 0;
- struct option opts[] = {
- OPT__VERBOSE(&verbose),
- OPT_END(),
- };
-
- argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0);
- /* we do not take arguments other than flags for now */
- if (argc)
- usage_with_options(count_objects_usage, opts);
- memcpy(path, objdir, len);
- if (len && objdir[len-1] != '/')
- path[len++] = '/';
- for (i = 0; i < 256; i++) {
- DIR *d;
- sprintf(path + len, "%02x", i);
- d = opendir(path);
- if (!d)
- continue;
- count_objects(d, path, len, verbose,
- &loose, &loose_size, &packed_loose, &garbage);
- closedir(d);
- }
- if (verbose) {
- struct packed_git *p;
- unsigned long num_pack = 0;
- off_t size_pack = 0;
- if (!packed_git)
- prepare_packed_git();
- for (p = packed_git; p; p = p->next) {
- if (!p->pack_local)
- continue;
- if (open_pack_index(p))
- continue;
- packed += p->num_objects;
- size_pack += p->pack_size + p->index_size;
- num_pack++;
- }
- printf("count: %lu\n", loose);
- printf("size: %lu\n", (unsigned long) (loose_size / 1024));
- printf("in-pack: %lu\n", packed);
- printf("packs: %lu\n", num_pack);
- printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024));
- printf("prune-packable: %lu\n", packed_loose);
- printf("garbage: %lu\n", garbage);
- }
- else
- printf("%lu objects, %lu kilobytes\n",
- loose, (unsigned long) (loose_size / 1024));
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "commit.h"
-#include "tag.h"
-#include "refs.h"
-#include "builtin.h"
-#include "exec_cmd.h"
-#include "parse-options.h"
-#include "diff.h"
-
-#define SEEN (1u<<0)
-#define MAX_TAGS (FLAG_BITS - 1)
-
-static const char * const describe_usage[] = {
- "git describe [options] <committish>*",
- "git describe [options] --dirty",
- NULL
-};
-
-static int debug; /* Display lots of verbose info */
-static int all; /* Any valid ref can be used */
-static int tags; /* Allow lightweight tags */
-static int longformat;
-static int abbrev = DEFAULT_ABBREV;
-static int max_candidates = 10;
-static int found_names;
-static const char *pattern;
-static int always;
-static const char *dirty;
-
-/* diff-index command arguments to check if working tree is dirty. */
-static const char *diff_index_args[] = {
- "diff-index", "--quiet", "HEAD", "--", NULL
-};
-
-
-struct commit_name {
- struct tag *tag;
- int prio; /* annotated tag = 2, tag = 1, head = 0 */
- unsigned char sha1[20];
- char path[FLEX_ARRAY]; /* more */
-};
-static const char *prio_names[] = {
- "head", "lightweight", "annotated",
-};
-
-static void add_to_known_names(const char *path,
- struct commit *commit,
- int prio,
- const unsigned char *sha1)
-{
- struct commit_name *e = commit->util;
- if (!e || e->prio < prio) {
- size_t len = strlen(path)+1;
- free(e);
- e = xmalloc(sizeof(struct commit_name) + len);
- e->tag = NULL;
- e->prio = prio;
- hashcpy(e->sha1, sha1);
- memcpy(e->path, path, len);
- commit->util = e;
- }
- found_names = 1;
-}
-
-static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
- int might_be_tag = !prefixcmp(path, "refs/tags/");
- struct commit *commit;
- struct object *object;
- unsigned char peeled[20];
- int is_tag, prio;
-
- if (!all && !might_be_tag)
- return 0;
-
- if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
- commit = lookup_commit_reference_gently(peeled, 1);
- if (!commit)
- return 0;
- is_tag = !!hashcmp(sha1, commit->object.sha1);
- } else {
- commit = lookup_commit_reference_gently(sha1, 1);
- object = parse_object(sha1);
- if (!commit || !object)
- return 0;
- is_tag = object->type == OBJ_TAG;
- }
-
- /* If --all, then any refs are used.
- * If --tags, then any tags are used.
- * Otherwise only annotated tags are used.
- */
- if (might_be_tag) {
- if (is_tag)
- prio = 2;
- else
- prio = 1;
-
- if (pattern && fnmatch(pattern, path + 10, 0))
- prio = 0;
- }
- else
- prio = 0;
-
- if (!all) {
- if (!prio)
- return 0;
- }
- add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1);
- return 0;
-}
-
-struct possible_tag {
- struct commit_name *name;
- int depth;
- int found_order;
- unsigned flag_within;
-};
-
-static int compare_pt(const void *a_, const void *b_)
-{
- struct possible_tag *a = (struct possible_tag *)a_;
- struct possible_tag *b = (struct possible_tag *)b_;
- if (a->depth != b->depth)
- return a->depth - b->depth;
- if (a->found_order != b->found_order)
- return a->found_order - b->found_order;
- return 0;
-}
-
-static unsigned long finish_depth_computation(
- struct commit_list **list,
- struct possible_tag *best)
-{
- unsigned long seen_commits = 0;
- while (*list) {
- struct commit *c = pop_commit(list);
- struct commit_list *parents = c->parents;
- seen_commits++;
- if (c->object.flags & best->flag_within) {
- struct commit_list *a = *list;
- while (a) {
- struct commit *i = a->item;
- if (!(i->object.flags & best->flag_within))
- break;
- a = a->next;
- }
- if (!a)
- break;
- } else
- best->depth++;
- while (parents) {
- struct commit *p = parents->item;
- parse_commit(p);
- if (!(p->object.flags & SEEN))
- insert_by_date(p, list);
- p->object.flags |= c->object.flags;
- parents = parents->next;
- }
- }
- return seen_commits;
-}
-
-static void display_name(struct commit_name *n)
-{
- if (n->prio == 2 && !n->tag) {
- n->tag = lookup_tag(n->sha1);
- if (!n->tag || parse_tag(n->tag) || !n->tag->tag)
- die("annotated tag %s not available", n->path);
- if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
- warning("tag '%s' is really '%s' here", n->tag->tag, n->path);
- }
-
- if (n->tag)
- printf("%s", n->tag->tag);
- else
- printf("%s", n->path);
-}
-
-static void show_suffix(int depth, const unsigned char *sha1)
-{
- printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev));
-}
-
-static void describe(const char *arg, int last_one)
-{
- unsigned char sha1[20];
- struct commit *cmit, *gave_up_on = NULL;
- struct commit_list *list;
- struct commit_name *n;
- struct possible_tag all_matches[MAX_TAGS];
- unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
- unsigned long seen_commits = 0;
- unsigned int unannotated_cnt = 0;
-
- if (get_sha1(arg, sha1))
- die("Not a valid object name %s", arg);
- cmit = lookup_commit_reference(sha1);
- if (!cmit)
- die("%s is not a valid '%s' object", arg, commit_type);
-
- n = cmit->util;
- if (n && (tags || all || n->prio == 2)) {
- /*
- * Exact match to an existing ref.
- */
- display_name(n);
- if (longformat)
- show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1);
- if (dirty)
- printf("%s", dirty);
- printf("\n");
- return;
- }
-
- if (!max_candidates)
- die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1));
- if (debug)
- fprintf(stderr, "searching to describe %s\n", arg);
-
- list = NULL;
- cmit->object.flags = SEEN;
- commit_list_insert(cmit, &list);
- while (list) {
- struct commit *c = pop_commit(&list);
- struct commit_list *parents = c->parents;
- seen_commits++;
- n = c->util;
- if (n) {
- if (!tags && !all && n->prio < 2) {
- unannotated_cnt++;
- } else if (match_cnt < max_candidates) {
- struct possible_tag *t = &all_matches[match_cnt++];
- t->name = n;
- t->depth = seen_commits - 1;
- t->flag_within = 1u << match_cnt;
- t->found_order = match_cnt;
- c->object.flags |= t->flag_within;
- if (n->prio == 2)
- annotated_cnt++;
- }
- else {
- gave_up_on = c;
- break;
- }
- }
- for (cur_match = 0; cur_match < match_cnt; cur_match++) {
- struct possible_tag *t = &all_matches[cur_match];
- if (!(c->object.flags & t->flag_within))
- t->depth++;
- }
- if (annotated_cnt && !list) {
- if (debug)
- fprintf(stderr, "finished search at %s\n",
- sha1_to_hex(c->object.sha1));
- break;
- }
- while (parents) {
- struct commit *p = parents->item;
- parse_commit(p);
- if (!(p->object.flags & SEEN))
- insert_by_date(p, &list);
- p->object.flags |= c->object.flags;
- parents = parents->next;
- }
- }
-
- if (!match_cnt) {
- const unsigned char *sha1 = cmit->object.sha1;
- if (always) {
- printf("%s", find_unique_abbrev(sha1, abbrev));
- if (dirty)
- printf("%s", dirty);
- printf("\n");
- return;
- }
- if (unannotated_cnt)
- die("No annotated tags can describe '%s'.\n"
- "However, there were unannotated tags: try --tags.",
- sha1_to_hex(sha1));
- else
- die("No tags can describe '%s'.\n"
- "Try --always, or create some tags.",
- sha1_to_hex(sha1));
- }
-
- qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
-
- if (gave_up_on) {
- insert_by_date(gave_up_on, &list);
- seen_commits--;
- }
- seen_commits += finish_depth_computation(&list, &all_matches[0]);
- free_commit_list(list);
-
- if (debug) {
- for (cur_match = 0; cur_match < match_cnt; cur_match++) {
- struct possible_tag *t = &all_matches[cur_match];
- fprintf(stderr, " %-11s %8d %s\n",
- prio_names[t->name->prio],
- t->depth, t->name->path);
- }
- fprintf(stderr, "traversed %lu commits\n", seen_commits);
- if (gave_up_on) {
- fprintf(stderr,
- "more than %i tags found; listed %i most recent\n"
- "gave up search at %s\n",
- max_candidates, max_candidates,
- sha1_to_hex(gave_up_on->object.sha1));
- }
- }
-
- display_name(all_matches[0].name);
- if (abbrev)
- show_suffix(all_matches[0].depth, cmit->object.sha1);
- if (dirty)
- printf("%s", dirty);
- printf("\n");
-
- if (!last_one)
- clear_commit_marks(cmit, -1);
-}
-
-int cmd_describe(int argc, const char **argv, const char *prefix)
-{
- int contains = 0;
- struct option options[] = {
- OPT_BOOLEAN(0, "contains", &contains, "find the tag that comes after the commit"),
- OPT_BOOLEAN(0, "debug", &debug, "debug search strategy on stderr"),
- OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"),
- OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"),
- OPT_BOOLEAN(0, "long", &longformat, "always use long format"),
- OPT__ABBREV(&abbrev),
- OPT_SET_INT(0, "exact-match", &max_candidates,
- "only output exact matches", 0),
- OPT_INTEGER(0, "candidates", &max_candidates,
- "consider <n> most recent tags (default: 10)"),
- OPT_STRING(0, "match", &pattern, "pattern",
- "only consider tags matching <pattern>"),
- OPT_BOOLEAN(0, "always", &always,
- "show abbreviated commit object as fallback"),
- {OPTION_STRING, 0, "dirty", &dirty, "mark",
- "append <mark> on dirty working tree (default: \"-dirty\")",
- PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"},
- OPT_END(),
- };
-
- argc = parse_options(argc, argv, prefix, options, describe_usage, 0);
- if (max_candidates < 0)
- max_candidates = 0;
- else if (max_candidates > MAX_TAGS)
- max_candidates = MAX_TAGS;
-
- save_commit_buffer = 0;
-
- if (longformat && abbrev == 0)
- die("--long is incompatible with --abbrev=0");
-
- if (contains) {
- const char **args = xmalloc((7 + argc) * sizeof(char *));
- int i = 0;
- args[i++] = "name-rev";
- args[i++] = "--name-only";
- args[i++] = "--no-undefined";
- if (always)
- args[i++] = "--always";
- if (!all) {
- args[i++] = "--tags";
- if (pattern) {
- char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1);
- sprintf(s, "--refs=refs/tags/%s", pattern);
- args[i++] = s;
- }
- }
- memcpy(args + i, argv, argc * sizeof(char *));
- args[i + argc] = NULL;
- return cmd_name_rev(i + argc, args, prefix);
- }
-
- for_each_ref(get_name, NULL);
- if (!found_names && !always)
- die("No names found, cannot describe anything.");
-
- if (argc == 0) {
- if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix))
- dirty = NULL;
- describe("HEAD", 1);
- } else if (dirty) {
- die("--dirty is incompatible with committishes");
- } else {
- while (argc-- > 0) {
- describe(*argv++, argc == 0);
- }
- }
- return 0;
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "diff.h"
-#include "commit.h"
-#include "revision.h"
-#include "builtin.h"
-
-static const char diff_files_usage[] =
-"git diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
-COMMON_DIFF_OPTIONS_HELP;
-
-int cmd_diff_files(int argc, const char **argv, const char *prefix)
-{
- struct rev_info rev;
- int result;
- unsigned options = 0;
-
- init_revisions(&rev, prefix);
- git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
- rev.abbrev = 0;
-
- argc = setup_revisions(argc, argv, &rev, NULL);
- while (1 < argc && argv[1][0] == '-') {
- if (!strcmp(argv[1], "--base"))
- rev.max_count = 1;
- else if (!strcmp(argv[1], "--ours"))
- rev.max_count = 2;
- else if (!strcmp(argv[1], "--theirs"))
- rev.max_count = 3;
- else if (!strcmp(argv[1], "-q"))
- options |= DIFF_SILENT_ON_REMOVED;
- else
- usage(diff_files_usage);
- argv++; argc--;
- }
- if (!rev.diffopt.output_format)
- rev.diffopt.output_format = DIFF_FORMAT_RAW;
-
- /*
- * Make sure there are NO revision (i.e. pending object) parameter,
- * rev.max_count is reasonable (0 <= n <= 3), and
- * there is no other revision filtering parameters.
- */
- if (rev.pending.nr ||
- rev.min_age != -1 || rev.max_age != -1 ||
- 3 < rev.max_count)
- usage(diff_files_usage);
-
- /*
- * "diff-files --base -p" should not combine merges because it
- * was not asked to. "diff-files -c -p" should not densify
- * (the user should ask with "diff-files --cc" explicitly).
- */
- if (rev.max_count == -1 && !rev.combine_merges &&
- (rev.diffopt.output_format & DIFF_FORMAT_PATCH))
- rev.combine_merges = rev.dense_combined_merges = 1;
-
- if (read_cache_preload(rev.diffopt.paths) < 0) {
- perror("read_cache_preload");
- return -1;
- }
- result = run_diff_files(&rev, options);
- return diff_result_code(&rev.diffopt, result);
-}
+++ /dev/null
-#include "cache.h"
-#include "diff.h"
-#include "commit.h"
-#include "revision.h"
-#include "builtin.h"
-
-static const char diff_cache_usage[] =
-"git diff-index [-m] [--cached] "
-"[<common diff options>] <tree-ish> [<path>...]"
-COMMON_DIFF_OPTIONS_HELP;
-
-int cmd_diff_index(int argc, const char **argv, const char *prefix)
-{
- struct rev_info rev;
- int cached = 0;
- int i;
- int result;
-
- init_revisions(&rev, prefix);
- git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
- rev.abbrev = 0;
-
- 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
- usage(diff_cache_usage);
- }
- if (!rev.diffopt.output_format)
- rev.diffopt.output_format = DIFF_FORMAT_RAW;
-
- /*
- * Make sure there is one revision (i.e. pending object),
- * and there is no revision filtering parameters.
- */
- if (rev.pending.nr != 1 ||
- rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
- usage(diff_cache_usage);
- if (!cached)
- setup_work_tree();
- if (read_cache() < 0) {
- perror("read_cache");
- return -1;
- }
- result = run_diff_index(&rev, cached);
- return diff_result_code(&rev.diffopt, result);
-}
+++ /dev/null
-#include "cache.h"
-#include "diff.h"
-#include "commit.h"
-#include "log-tree.h"
-#include "builtin.h"
-
-static struct rev_info log_tree_opt;
-
-static int diff_tree_commit_sha1(const unsigned char *sha1)
-{
- struct commit *commit = lookup_commit_reference(sha1);
- if (!commit)
- return -1;
- return log_tree_commit(&log_tree_opt, commit);
-}
-
-/* Diff one or more commits. */
-static int stdin_diff_commit(struct commit *commit, char *line, int len)
-{
- unsigned char sha1[20];
- if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) {
- /* Graft the fake parents locally to the commit */
- int pos = 41;
- struct commit_list **pptr, *parents;
-
- /* Free the real parent list */
- for (parents = commit->parents; parents; ) {
- struct commit_list *tmp = parents->next;
- free(parents);
- parents = tmp;
- }
- commit->parents = NULL;
- pptr = &(commit->parents);
- while (line[pos] && !get_sha1_hex(line + pos, sha1)) {
- struct commit *parent = lookup_commit(sha1);
- if (parent) {
- pptr = &commit_list_insert(parent, pptr)->next;
- }
- pos += 41;
- }
- }
- return log_tree_commit(&log_tree_opt, commit);
-}
-
-/* Diff two trees. */
-static int stdin_diff_trees(struct tree *tree1, char *line, int len)
-{
- unsigned char sha1[20];
- struct tree *tree2;
- if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1))
- return error("Need exactly two trees, separated by a space");
- tree2 = lookup_tree(sha1);
- if (!tree2 || parse_tree(tree2))
- return -1;
- printf("%s %s\n", sha1_to_hex(tree1->object.sha1),
- sha1_to_hex(tree2->object.sha1));
- diff_tree_sha1(tree1->object.sha1, tree2->object.sha1,
- "", &log_tree_opt.diffopt);
- log_tree_diff_flush(&log_tree_opt);
- return 0;
-}
-
-static int diff_tree_stdin(char *line)
-{
- int len = strlen(line);
- unsigned char sha1[20];
- struct object *obj;
-
- if (!len || line[len-1] != '\n')
- return -1;
- line[len-1] = 0;
- if (get_sha1_hex(line, sha1))
- return -1;
- obj = lookup_unknown_object(sha1);
- if (!obj || !obj->parsed)
- obj = parse_object(sha1);
- if (!obj)
- return -1;
- if (obj->type == OBJ_COMMIT)
- return stdin_diff_commit((struct commit *)obj, line, len);
- if (obj->type == OBJ_TREE)
- return stdin_diff_trees((struct tree *)obj, line, len);
- error("Object %s is a %s, not a commit or tree",
- sha1_to_hex(sha1), typename(obj->type));
- return -1;
-}
-
-static const char diff_tree_usage[] =
-"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
-"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
-" -r diff recursively\n"
-" --root include the initial commit as diff against /dev/null\n"
-COMMON_DIFF_OPTIONS_HELP;
-
-int cmd_diff_tree(int argc, const char **argv, const char *prefix)
-{
- int nr_sha1;
- char line[1000];
- struct object *tree1, *tree2;
- static struct rev_info *opt = &log_tree_opt;
- int read_stdin = 0;
-
- init_revisions(opt, prefix);
- git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
- opt->abbrev = 0;
- opt->diff = 1;
- opt->disable_stdin = 1;
- argc = setup_revisions(argc, argv, opt, NULL);
-
- while (--argc > 0) {
- const char *arg = *++argv;
-
- if (!strcmp(arg, "--stdin")) {
- read_stdin = 1;
- continue;
- }
- usage(diff_tree_usage);
- }
-
- if (!opt->diffopt.output_format)
- opt->diffopt.output_format = DIFF_FORMAT_RAW;
-
- /*
- * NOTE! We expect "a ^b" to be equal to "a..b", so we
- * reverse the order of the objects if the second one
- * is marked UNINTERESTING.
- */
- nr_sha1 = opt->pending.nr;
- switch (nr_sha1) {
- case 0:
- if (!read_stdin)
- usage(diff_tree_usage);
- break;
- case 1:
- tree1 = opt->pending.objects[0].item;
- diff_tree_commit_sha1(tree1->sha1);
- break;
- case 2:
- tree1 = opt->pending.objects[0].item;
- tree2 = opt->pending.objects[1].item;
- if (tree2->flags & UNINTERESTING) {
- struct object *tmp = tree2;
- tree2 = tree1;
- tree1 = tmp;
- }
- diff_tree_sha1(tree1->sha1,
- tree2->sha1,
- "", &opt->diffopt);
- log_tree_diff_flush(opt);
- break;
- }
-
- if (read_stdin) {
- if (opt->diffopt.detect_rename)
- opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
- DIFF_SETUP_USE_CACHE);
- while (fgets(line, sizeof(line), stdin)) {
- unsigned char sha1[20];
-
- if (get_sha1_hex(line, sha1)) {
- fputs(line, stdout);
- fflush(stdout);
- }
- else
- diff_tree_stdin(line);
- }
- }
-
- return diff_result_code(&opt->diffopt, 0);
-}
+++ /dev/null
-/*
- * Builtin "git diff"
- *
- * Copyright (c) 2006 Junio C Hamano
- */
-#include "cache.h"
-#include "color.h"
-#include "commit.h"
-#include "blob.h"
-#include "tag.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "revision.h"
-#include "log-tree.h"
-#include "builtin.h"
-
-struct blobinfo {
- unsigned char sha1[20];
- const char *name;
- unsigned mode;
-};
-
-static const char builtin_diff_usage[] =
-"git diff <options> <rev>{0,2} -- <path>*";
-
-static void stuff_change(struct diff_options *opt,
- unsigned old_mode, unsigned new_mode,
- const unsigned char *old_sha1,
- const unsigned char *new_sha1,
- const char *old_name,
- const char *new_name)
-{
- struct diff_filespec *one, *two;
-
- if (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) &&
- !hashcmp(old_sha1, new_sha1) && (old_mode == new_mode))
- return;
-
- if (DIFF_OPT_TST(opt, REVERSE_DIFF)) {
- unsigned tmp;
- const unsigned char *tmp_u;
- const char *tmp_c;
- tmp = old_mode; old_mode = new_mode; new_mode = tmp;
- tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;
- tmp_c = old_name; old_name = new_name; new_name = tmp_c;
- }
-
- if (opt->prefix &&
- (strncmp(old_name, opt->prefix, opt->prefix_length) ||
- strncmp(new_name, opt->prefix, opt->prefix_length)))
- return;
-
- one = alloc_filespec(old_name);
- two = alloc_filespec(new_name);
- fill_filespec(one, old_sha1, old_mode);
- fill_filespec(two, new_sha1, new_mode);
-
- diff_queue(&diff_queued_diff, one, two);
-}
-
-static int builtin_diff_b_f(struct rev_info *revs,
- int argc, const char **argv,
- struct blobinfo *blob,
- const char *path)
-{
- /* Blob vs file in the working tree*/
- struct stat st;
-
- if (argc > 1)
- usage(builtin_diff_usage);
-
- if (lstat(path, &st))
- die_errno("failed to stat '%s'", path);
- if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
- die("'%s': not a regular file or symlink", path);
-
- diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
-
- if (blob[0].mode == S_IFINVALID)
- blob[0].mode = canon_mode(st.st_mode);
-
- stuff_change(&revs->diffopt,
- blob[0].mode, canon_mode(st.st_mode),
- blob[0].sha1, null_sha1,
- path, path);
- diffcore_std(&revs->diffopt);
- diff_flush(&revs->diffopt);
- return 0;
-}
-
-static int builtin_diff_blobs(struct rev_info *revs,
- int argc, const char **argv,
- struct blobinfo *blob)
-{
- unsigned mode = canon_mode(S_IFREG | 0644);
-
- if (argc > 1)
- usage(builtin_diff_usage);
-
- if (blob[0].mode == S_IFINVALID)
- blob[0].mode = mode;
-
- if (blob[1].mode == S_IFINVALID)
- blob[1].mode = mode;
-
- stuff_change(&revs->diffopt,
- blob[0].mode, blob[1].mode,
- blob[0].sha1, blob[1].sha1,
- blob[0].name, blob[1].name);
- diffcore_std(&revs->diffopt);
- diff_flush(&revs->diffopt);
- return 0;
-}
-
-static int builtin_diff_index(struct rev_info *revs,
- int argc, const char **argv)
-{
- int cached = 0;
- while (1 < argc) {
- const char *arg = argv[1];
- if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged"))
- cached = 1;
- else
- usage(builtin_diff_usage);
- argv++; argc--;
- }
- if (!cached)
- setup_work_tree();
- /*
- * Make sure there is one revision (i.e. pending object),
- * and there is no revision filtering parameters.
- */
- if (revs->pending.nr != 1 ||
- revs->max_count != -1 || revs->min_age != -1 ||
- revs->max_age != -1)
- usage(builtin_diff_usage);
- if (read_cache_preload(revs->diffopt.paths) < 0) {
- perror("read_cache_preload");
- return -1;
- }
- return run_diff_index(revs, cached);
-}
-
-static int builtin_diff_tree(struct rev_info *revs,
- int argc, const char **argv,
- struct object_array_entry *ent)
-{
- const unsigned char *(sha1[2]);
- int swap = 0;
-
- if (argc > 1)
- usage(builtin_diff_usage);
-
- /* We saw two trees, ent[0] and ent[1].
- * if ent[1] is uninteresting, they are swapped
- */
- if (ent[1].item->flags & UNINTERESTING)
- swap = 1;
- sha1[swap] = ent[0].item->sha1;
- sha1[1-swap] = ent[1].item->sha1;
- diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
- log_tree_diff_flush(revs);
- return 0;
-}
-
-static int builtin_diff_combined(struct rev_info *revs,
- int argc, const char **argv,
- struct object_array_entry *ent,
- int ents)
-{
- const unsigned char (*parent)[20];
- int i;
-
- if (argc > 1)
- usage(builtin_diff_usage);
-
- if (!revs->dense_combined_merges && !revs->combine_merges)
- revs->dense_combined_merges = revs->combine_merges = 1;
- parent = xmalloc(ents * sizeof(*parent));
- for (i = 0; i < ents; i++)
- hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
- diff_tree_combined(parent[0], parent + 1, ents - 1,
- revs->dense_combined_merges, revs);
- return 0;
-}
-
-static void refresh_index_quietly(void)
-{
- struct lock_file *lock_file;
- int fd;
-
- lock_file = xcalloc(1, sizeof(struct lock_file));
- fd = hold_locked_index(lock_file, 0);
- if (fd < 0)
- return;
- discard_cache();
- read_cache();
- refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
-
- if (active_cache_changed &&
- !write_cache(fd, active_cache, active_nr))
- commit_locked_index(lock_file);
-
- rollback_lock_file(lock_file);
-}
-
-static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv)
-{
- int result;
- unsigned int options = 0;
-
- while (1 < argc && argv[1][0] == '-') {
- if (!strcmp(argv[1], "--base"))
- revs->max_count = 1;
- else if (!strcmp(argv[1], "--ours"))
- revs->max_count = 2;
- else if (!strcmp(argv[1], "--theirs"))
- revs->max_count = 3;
- else if (!strcmp(argv[1], "-q"))
- options |= DIFF_SILENT_ON_REMOVED;
- else if (!strcmp(argv[1], "-h"))
- usage(builtin_diff_usage);
- else
- return error("invalid option: %s", argv[1]);
- argv++; argc--;
- }
-
- /*
- * "diff --base" should not combine merges because it was not
- * asked to. "diff -c" should not densify (if the user wants
- * dense one, --cc can be explicitly asked for, or just rely
- * on the default).
- */
- if (revs->max_count == -1 && !revs->combine_merges &&
- (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
- revs->combine_merges = revs->dense_combined_merges = 1;
-
- setup_work_tree();
- if (read_cache_preload(revs->diffopt.paths) < 0) {
- perror("read_cache_preload");
- return -1;
- }
- result = run_diff_files(revs, options);
- return diff_result_code(&revs->diffopt, result);
-}
-
-int cmd_diff(int argc, const char **argv, const char *prefix)
-{
- int i;
- struct rev_info rev;
- struct object_array_entry ent[100];
- int ents = 0, blobs = 0, paths = 0;
- const char *path = NULL;
- struct blobinfo blob[2];
- int nongit;
- int result = 0;
-
- /*
- * We could get N tree-ish in the rev.pending_objects list.
- * Also there could be M blobs there, and P pathspecs.
- *
- * N=0, M=0:
- * cache vs files (diff-files)
- * N=0, M=2:
- * compare two random blobs. P must be zero.
- * N=0, M=1, P=1:
- * compare a blob with a working tree file.
- *
- * N=1, M=0:
- * tree vs cache (diff-index --cached)
- *
- * N=2, M=0:
- * tree vs tree (diff-tree)
- *
- * N=0, M=0, P=2:
- * compare two filesystem entities (aka --no-index).
- *
- * Other cases are errors.
- */
-
- prefix = setup_git_directory_gently(&nongit);
- git_config(git_diff_ui_config, NULL);
-
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
- init_revisions(&rev, prefix);
-
- /* If this is a no-index diff, just run it and exit there. */
- diff_no_index(&rev, argc, argv, nongit, prefix);
-
- /* Otherwise, we are doing the usual "git" diff */
- rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
-
- /* Default to let external and textconv be used */
- DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
- DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
-
- if (nongit)
- die("Not a git repository");
- argc = setup_revisions(argc, argv, &rev, NULL);
- if (!rev.diffopt.output_format) {
- rev.diffopt.output_format = DIFF_FORMAT_PATCH;
- if (diff_setup_done(&rev.diffopt) < 0)
- die("diff_setup_done failed");
- }
-
- DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
-
- /*
- * If the user asked for our exit code then don't start a
- * pager or we would end up reporting its exit code instead.
- */
- if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS) &&
- check_pager_config("diff") != 0)
- setup_pager();
-
- /*
- * Do we have --cached and not have a pending object, then
- * default to HEAD by hand. Eek.
- */
- if (!rev.pending.nr) {
- int i;
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (!strcmp(arg, "--"))
- break;
- else if (!strcmp(arg, "--cached") ||
- !strcmp(arg, "--staged")) {
- add_head_to_pending(&rev);
- if (!rev.pending.nr)
- die("No HEAD commit to compare with (yet)");
- break;
- }
- }
- }
-
- for (i = 0; i < rev.pending.nr; i++) {
- struct object_array_entry *list = rev.pending.objects+i;
- struct object *obj = list->item;
- const char *name = list->name;
- int flags = (obj->flags & UNINTERESTING);
- if (!obj->parsed)
- obj = parse_object(obj->sha1);
- obj = deref_tag(obj, NULL, 0);
- if (!obj)
- die("invalid object '%s' given.", name);
- if (obj->type == OBJ_COMMIT)
- obj = &((struct commit *)obj)->tree->object;
- if (obj->type == OBJ_TREE) {
- if (ARRAY_SIZE(ent) <= ents)
- die("more than %d trees given: '%s'",
- (int) ARRAY_SIZE(ent), name);
- obj->flags |= flags;
- ent[ents].item = obj;
- ent[ents].name = name;
- ents++;
- continue;
- }
- if (obj->type == OBJ_BLOB) {
- if (2 <= blobs)
- die("more than two blobs given: '%s'", name);
- hashcpy(blob[blobs].sha1, obj->sha1);
- blob[blobs].name = name;
- blob[blobs].mode = list->mode;
- blobs++;
- continue;
-
- }
- die("unhandled object '%s' given.", name);
- }
- if (rev.prune_data) {
- const char **pathspec = rev.prune_data;
- while (*pathspec) {
- if (!path)
- path = *pathspec;
- paths++;
- pathspec++;
- }
- }
-
- /*
- * Now, do the arguments look reasonable?
- */
- if (!ents) {
- switch (blobs) {
- case 0:
- result = builtin_diff_files(&rev, argc, argv);
- break;
- case 1:
- if (paths != 1)
- usage(builtin_diff_usage);
- result = builtin_diff_b_f(&rev, argc, argv, blob, path);
- break;
- case 2:
- if (paths)
- usage(builtin_diff_usage);
- result = builtin_diff_blobs(&rev, argc, argv, blob);
- break;
- default:
- usage(builtin_diff_usage);
- }
- }
- else if (blobs)
- usage(builtin_diff_usage);
- else if (ents == 1)
- result = builtin_diff_index(&rev, argc, argv);
- else if (ents == 2)
- result = builtin_diff_tree(&rev, argc, argv, ent);
- else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) {
- /* diff A...B where there is one sane merge base between
- * A and B. We have ent[0] == merge-base, ent[1] == A,
- * and ent[2] == B. Show diff between the base and B.
- */
- ent[1] = ent[2];
- result = builtin_diff_tree(&rev, argc, argv, ent);
- }
- else
- result = builtin_diff_combined(&rev, argc, argv,
- ent, ents);
- result = diff_result_code(&rev.diffopt, result);
- if (1 < rev.diffopt.skip_stat_unmatch)
- refresh_index_quietly();
- return result;
-}
+++ /dev/null
-/*
- * "git fast-export" builtin command
- *
- * Copyright (C) 2007 Johannes E. Schindelin
- */
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "object.h"
-#include "tag.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "log-tree.h"
-#include "revision.h"
-#include "decorate.h"
-#include "string-list.h"
-#include "utf8.h"
-#include "parse-options.h"
-
-static const char *fast_export_usage[] = {
- "git fast-export [rev-list-opts]",
- NULL
-};
-
-static int progress;
-static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
-static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
-static int fake_missing_tagger;
-static int no_data;
-
-static int parse_opt_signed_tag_mode(const struct option *opt,
- const char *arg, int unset)
-{
- if (unset || !strcmp(arg, "abort"))
- signed_tag_mode = ABORT;
- else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore"))
- signed_tag_mode = VERBATIM;
- else if (!strcmp(arg, "warn"))
- signed_tag_mode = WARN;
- else if (!strcmp(arg, "strip"))
- signed_tag_mode = STRIP;
- else
- return error("Unknown signed-tag mode: %s", arg);
- return 0;
-}
-
-static int parse_opt_tag_of_filtered_mode(const struct option *opt,
- const char *arg, int unset)
-{
- if (unset || !strcmp(arg, "abort"))
- tag_of_filtered_mode = ABORT;
- else if (!strcmp(arg, "drop"))
- tag_of_filtered_mode = DROP;
- else if (!strcmp(arg, "rewrite"))
- tag_of_filtered_mode = REWRITE;
- else
- return error("Unknown tag-of-filtered mode: %s", arg);
- return 0;
-}
-
-static struct decoration idnums;
-static uint32_t last_idnum;
-
-static int has_unshown_parent(struct commit *commit)
-{
- struct commit_list *parent;
-
- for (parent = commit->parents; parent; parent = parent->next)
- if (!(parent->item->object.flags & SHOWN) &&
- !(parent->item->object.flags & UNINTERESTING))
- return 1;
- return 0;
-}
-
-/* Since intptr_t is C99, we do not use it here */
-static inline uint32_t *mark_to_ptr(uint32_t mark)
-{
- return ((uint32_t *)NULL) + mark;
-}
-
-static inline uint32_t ptr_to_mark(void * mark)
-{
- return (uint32_t *)mark - (uint32_t *)NULL;
-}
-
-static inline void mark_object(struct object *object, uint32_t mark)
-{
- add_decoration(&idnums, object, mark_to_ptr(mark));
-}
-
-static inline void mark_next_object(struct object *object)
-{
- mark_object(object, ++last_idnum);
-}
-
-static int get_object_mark(struct object *object)
-{
- void *decoration = lookup_decoration(&idnums, object);
- if (!decoration)
- return 0;
- return ptr_to_mark(decoration);
-}
-
-static void show_progress(void)
-{
- static int counter = 0;
- if (!progress)
- return;
- if ((++counter % progress) == 0)
- printf("progress %d objects\n", counter);
-}
-
-static void handle_object(const unsigned char *sha1)
-{
- unsigned long size;
- enum object_type type;
- char *buf;
- struct object *object;
-
- if (no_data)
- return;
-
- if (is_null_sha1(sha1))
- return;
-
- object = parse_object(sha1);
- if (!object)
- die ("Could not read blob %s", sha1_to_hex(sha1));
-
- if (object->flags & SHOWN)
- return;
-
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf)
- die ("Could not read blob %s", sha1_to_hex(sha1));
-
- mark_next_object(object);
-
- printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size);
- if (size && fwrite(buf, size, 1, stdout) != 1)
- die_errno ("Could not write blob '%s'", sha1_to_hex(sha1));
- printf("\n");
-
- show_progress();
-
- object->flags |= SHOWN;
- free(buf);
-}
-
-static void show_filemodify(struct diff_queue_struct *q,
- struct diff_options *options, void *data)
-{
- int i;
- for (i = 0; i < q->nr; i++) {
- struct diff_filespec *ospec = q->queue[i]->one;
- struct diff_filespec *spec = q->queue[i]->two;
-
- switch (q->queue[i]->status) {
- case DIFF_STATUS_DELETED:
- printf("D %s\n", spec->path);
- break;
-
- case DIFF_STATUS_COPIED:
- case DIFF_STATUS_RENAMED:
- printf("%c \"%s\" \"%s\"\n", q->queue[i]->status,
- ospec->path, spec->path);
-
- if (!hashcmp(ospec->sha1, spec->sha1) &&
- ospec->mode == spec->mode)
- break;
- /* fallthrough */
-
- case DIFF_STATUS_TYPE_CHANGED:
- case DIFF_STATUS_MODIFIED:
- case DIFF_STATUS_ADDED:
- /*
- * Links refer to objects in another repositories;
- * output the SHA-1 verbatim.
- */
- if (no_data || S_ISGITLINK(spec->mode))
- printf("M %06o %s %s\n", spec->mode,
- sha1_to_hex(spec->sha1), spec->path);
- else {
- struct object *object = lookup_object(spec->sha1);
- printf("M %06o :%d %s\n", spec->mode,
- get_object_mark(object), spec->path);
- }
- break;
-
- default:
- die("Unexpected comparison status '%c' for %s, %s",
- q->queue[i]->status,
- ospec->path ? ospec->path : "none",
- spec->path ? spec->path : "none");
- }
- }
-}
-
-static const char *find_encoding(const char *begin, const char *end)
-{
- const char *needle = "\nencoding ";
- char *bol, *eol;
-
- bol = memmem(begin, end ? end - begin : strlen(begin),
- needle, strlen(needle));
- if (!bol)
- return git_commit_encoding;
- bol += strlen(needle);
- eol = strchrnul(bol, '\n');
- *eol = '\0';
- return bol;
-}
-
-static void handle_commit(struct commit *commit, struct rev_info *rev)
-{
- int saved_output_format = rev->diffopt.output_format;
- const char *author, *author_end, *committer, *committer_end;
- const char *encoding, *message;
- char *reencoded = NULL;
- struct commit_list *p;
- int i;
-
- rev->diffopt.output_format = DIFF_FORMAT_CALLBACK;
-
- parse_commit(commit);
- author = strstr(commit->buffer, "\nauthor ");
- if (!author)
- die ("Could not find author in commit %s",
- sha1_to_hex(commit->object.sha1));
- author++;
- author_end = strchrnul(author, '\n');
- committer = strstr(author_end, "\ncommitter ");
- if (!committer)
- die ("Could not find committer in commit %s",
- sha1_to_hex(commit->object.sha1));
- committer++;
- committer_end = strchrnul(committer, '\n');
- message = strstr(committer_end, "\n\n");
- encoding = find_encoding(committer_end, message);
- if (message)
- message += 2;
-
- if (commit->parents &&
- get_object_mark(&commit->parents->item->object) != 0) {
- parse_commit(commit->parents->item);
- diff_tree_sha1(commit->parents->item->tree->object.sha1,
- commit->tree->object.sha1, "", &rev->diffopt);
- }
- else
- diff_root_tree_sha1(commit->tree->object.sha1,
- "", &rev->diffopt);
-
- /* Export the referenced blobs, and remember the marks. */
- for (i = 0; i < diff_queued_diff.nr; i++)
- if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode))
- handle_object(diff_queued_diff.queue[i]->two->sha1);
-
- mark_next_object(&commit->object);
- if (!is_encoding_utf8(encoding))
- reencoded = reencode_string(message, "UTF-8", encoding);
- if (!commit->parents)
- printf("reset %s\n", (const char*)commit->util);
- printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s",
- (const char *)commit->util, last_idnum,
- (int)(author_end - author), author,
- (int)(committer_end - committer), committer,
- (unsigned)(reencoded
- ? strlen(reencoded) : message
- ? strlen(message) : 0),
- reencoded ? reencoded : message ? message : "");
- free(reencoded);
-
- for (i = 0, p = commit->parents; p; p = p->next) {
- int mark = get_object_mark(&p->item->object);
- if (!mark)
- continue;
- if (i == 0)
- printf("from :%d\n", mark);
- else
- printf("merge :%d\n", mark);
- i++;
- }
-
- log_tree_diff_flush(rev);
- rev->diffopt.output_format = saved_output_format;
-
- printf("\n");
-
- show_progress();
-}
-
-static void handle_tail(struct object_array *commits, struct rev_info *revs)
-{
- struct commit *commit;
- while (commits->nr) {
- commit = (struct commit *)commits->objects[commits->nr - 1].item;
- if (has_unshown_parent(commit))
- return;
- handle_commit(commit, revs);
- commits->nr--;
- }
-}
-
-static void handle_tag(const char *name, struct tag *tag)
-{
- unsigned long size;
- enum object_type type;
- char *buf;
- const char *tagger, *tagger_end, *message;
- size_t message_size = 0;
- struct object *tagged;
- int tagged_mark;
- struct commit *p;
-
- /* Trees have no identifer in fast-export output, thus we have no way
- * to output tags of trees, tags of tags of trees, etc. Simply omit
- * such tags.
- */
- tagged = tag->tagged;
- while (tagged->type == OBJ_TAG) {
- tagged = ((struct tag *)tagged)->tagged;
- }
- if (tagged->type == OBJ_TREE) {
- warning("Omitting tag %s,\nsince tags of trees (or tags of tags of trees, etc.) are not supported.",
- sha1_to_hex(tag->object.sha1));
- return;
- }
-
- buf = read_sha1_file(tag->object.sha1, &type, &size);
- if (!buf)
- die ("Could not read tag %s", sha1_to_hex(tag->object.sha1));
- message = memmem(buf, size, "\n\n", 2);
- if (message) {
- message += 2;
- message_size = strlen(message);
- }
- tagger = memmem(buf, message ? message - buf : size, "\ntagger ", 8);
- if (!tagger) {
- if (fake_missing_tagger)
- tagger = "tagger Unspecified Tagger "
- "<unspecified-tagger> 0 +0000";
- else
- tagger = "";
- tagger_end = tagger + strlen(tagger);
- } else {
- tagger++;
- tagger_end = strchrnul(tagger, '\n');
- }
-
- /* handle signed tags */
- if (message) {
- const char *signature = strstr(message,
- "\n-----BEGIN PGP SIGNATURE-----\n");
- if (signature)
- switch(signed_tag_mode) {
- case ABORT:
- die ("Encountered signed tag %s; use "
- "--signed-tag=<mode> to handle it.",
- sha1_to_hex(tag->object.sha1));
- case WARN:
- warning ("Exporting signed tag %s",
- sha1_to_hex(tag->object.sha1));
- /* fallthru */
- case VERBATIM:
- break;
- case STRIP:
- message_size = signature + 1 - message;
- break;
- }
- }
-
- /* handle tag->tagged having been filtered out due to paths specified */
- tagged = tag->tagged;
- tagged_mark = get_object_mark(tagged);
- if (!tagged_mark) {
- switch(tag_of_filtered_mode) {
- case ABORT:
- die ("Tag %s tags unexported object; use "
- "--tag-of-filtered-object=<mode> to handle it.",
- sha1_to_hex(tag->object.sha1));
- case DROP:
- /* Ignore this tag altogether */
- return;
- case REWRITE:
- if (tagged->type != OBJ_COMMIT) {
- die ("Tag %s tags unexported %s!",
- sha1_to_hex(tag->object.sha1),
- typename(tagged->type));
- }
- p = (struct commit *)tagged;
- for (;;) {
- if (p->parents && p->parents->next)
- break;
- if (p->object.flags & UNINTERESTING)
- break;
- if (!(p->object.flags & TREESAME))
- break;
- if (!p->parents)
- die ("Can't find replacement commit for tag %s\n",
- sha1_to_hex(tag->object.sha1));
- p = p->parents->item;
- }
- tagged_mark = get_object_mark(&p->object);
- }
- }
-
- if (!prefixcmp(name, "refs/tags/"))
- name += 10;
- printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n",
- name, tagged_mark,
- (int)(tagger_end - tagger), tagger,
- tagger == tagger_end ? "" : "\n",
- (int)message_size, (int)message_size, message ? message : "");
-}
-
-static void get_tags_and_duplicates(struct object_array *pending,
- struct string_list *extra_refs)
-{
- struct tag *tag;
- int i;
-
- for (i = 0; i < pending->nr; i++) {
- struct object_array_entry *e = pending->objects + i;
- unsigned char sha1[20];
- struct commit *commit = commit;
- char *full_name;
-
- if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
- continue;
-
- switch (e->item->type) {
- case OBJ_COMMIT:
- commit = (struct commit *)e->item;
- break;
- case OBJ_TAG:
- tag = (struct tag *)e->item;
-
- /* handle nested tags */
- while (tag && tag->object.type == OBJ_TAG) {
- parse_object(tag->object.sha1);
- string_list_append(full_name, extra_refs)->util = tag;
- tag = (struct tag *)tag->tagged;
- }
- if (!tag)
- die ("Tag %s points nowhere?", e->name);
- switch(tag->object.type) {
- case OBJ_COMMIT:
- commit = (struct commit *)tag;
- break;
- case OBJ_BLOB:
- handle_object(tag->object.sha1);
- continue;
- default: /* OBJ_TAG (nested tags) is already handled */
- warning("Tag points to object of unexpected type %s, skipping.",
- typename(tag->object.type));
- continue;
- }
- break;
- default:
- warning("%s: Unexpected object of type %s, skipping.",
- e->name,
- typename(e->item->type));
- continue;
- }
- if (commit->util)
- /* more than one name for the same object */
- string_list_append(full_name, extra_refs)->util = commit;
- else
- commit->util = full_name;
- }
-}
-
-static void handle_tags_and_duplicates(struct string_list *extra_refs)
-{
- struct commit *commit;
- int i;
-
- for (i = extra_refs->nr - 1; i >= 0; i--) {
- const char *name = extra_refs->items[i].string;
- struct object *object = extra_refs->items[i].util;
- switch (object->type) {
- case OBJ_TAG:
- handle_tag(name, (struct tag *)object);
- break;
- case OBJ_COMMIT:
- /* create refs pointing to already seen commits */
- commit = (struct commit *)object;
- printf("reset %s\nfrom :%d\n\n", name,
- get_object_mark(&commit->object));
- show_progress();
- break;
- }
- }
-}
-
-static void export_marks(char *file)
-{
- unsigned int i;
- uint32_t mark;
- struct object_decoration *deco = idnums.hash;
- FILE *f;
- int e = 0;
-
- f = fopen(file, "w");
- if (!f)
- error("Unable to open marks file %s for writing.", file);
-
- for (i = 0; i < idnums.size; i++) {
- if (deco->base && deco->base->type == 1) {
- mark = ptr_to_mark(deco->decoration);
- if (fprintf(f, ":%"PRIu32" %s\n", mark,
- sha1_to_hex(deco->base->sha1)) < 0) {
- e = 1;
- break;
- }
- }
- deco++;
- }
-
- e |= ferror(f);
- e |= fclose(f);
- if (e)
- error("Unable to write marks file %s.", file);
-}
-
-static void import_marks(char *input_file)
-{
- char line[512];
- FILE *f = fopen(input_file, "r");
- if (!f)
- die_errno("cannot read '%s'", input_file);
-
- while (fgets(line, sizeof(line), f)) {
- uint32_t mark;
- char *line_end, *mark_end;
- unsigned char sha1[20];
- struct object *object;
-
- line_end = strchr(line, '\n');
- if (line[0] != ':' || !line_end)
- die("corrupt mark line: %s", line);
- *line_end = '\0';
-
- mark = strtoumax(line + 1, &mark_end, 10);
- if (!mark || mark_end == line + 1
- || *mark_end != ' ' || get_sha1(mark_end + 1, sha1))
- die("corrupt mark line: %s", line);
-
- object = parse_object(sha1);
- if (!object)
- die ("Could not read blob %s", sha1_to_hex(sha1));
-
- if (object->flags & SHOWN)
- error("Object %s already has a mark", sha1);
-
- mark_object(object, mark);
- if (last_idnum < mark)
- last_idnum = mark;
-
- object->flags |= SHOWN;
- }
- fclose(f);
-}
-
-int cmd_fast_export(int argc, const char **argv, const char *prefix)
-{
- struct rev_info revs;
- struct object_array commits = { 0, 0, NULL };
- struct string_list extra_refs = { NULL, 0, 0, 0 };
- struct commit *commit;
- char *export_filename = NULL, *import_filename = NULL;
- struct option options[] = {
- OPT_INTEGER(0, "progress", &progress,
- "show progress after <n> objects"),
- OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode",
- "select handling of signed tags",
- parse_opt_signed_tag_mode),
- OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode",
- "select handling of tags that tag filtered objects",
- parse_opt_tag_of_filtered_mode),
- OPT_STRING(0, "export-marks", &export_filename, "FILE",
- "Dump marks to this file"),
- OPT_STRING(0, "import-marks", &import_filename, "FILE",
- "Import marks from this file"),
- OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
- "Fake a tagger when tags lack one"),
- { OPTION_NEGBIT, 0, "data", &no_data, NULL,
- "Skip output of blob data",
- PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
- OPT_END()
- };
-
- if (argc == 1)
- usage_with_options (fast_export_usage, options);
-
- /* we handle encodings */
- git_config(git_default_config, NULL);
-
- init_revisions(&revs, prefix);
- revs.topo_order = 1;
- revs.show_source = 1;
- revs.rewrite_parents = 1;
- argc = setup_revisions(argc, argv, &revs, NULL);
- argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0);
- if (argc > 1)
- usage_with_options (fast_export_usage, options);
-
- if (import_filename)
- import_marks(import_filename);
-
- get_tags_and_duplicates(&revs.pending, &extra_refs);
-
- if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
- revs.diffopt.format_callback = show_filemodify;
- DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
- while ((commit = get_revision(&revs))) {
- if (has_unshown_parent(commit)) {
- add_object_array(&commit->object, NULL, &commits);
- }
- else {
- handle_commit(commit, &revs);
- handle_tail(&commits, &revs);
- }
- }
-
- handle_tags_and_duplicates(&extra_refs);
-
- if (export_filename)
- export_marks(export_filename);
-
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "refs.h"
-#include "pkt-line.h"
-#include "commit.h"
-#include "tag.h"
-#include "exec_cmd.h"
-#include "pack.h"
-#include "sideband.h"
-#include "fetch-pack.h"
-#include "remote.h"
-#include "run-command.h"
-
-static int transfer_unpack_limit = -1;
-static int fetch_unpack_limit = -1;
-static int unpack_limit = 100;
-static int prefer_ofs_delta = 1;
-static struct fetch_pack_args args = {
- /* .uploadpack = */ "git-upload-pack",
-};
-
-static const char fetch_pack_usage[] =
-"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
-
-#define COMPLETE (1U << 0)
-#define COMMON (1U << 1)
-#define COMMON_REF (1U << 2)
-#define SEEN (1U << 3)
-#define POPPED (1U << 4)
-
-static int marked;
-
-/*
- * After sending this many "have"s if we do not get any new ACK , we
- * give up traversing our history.
- */
-#define MAX_IN_VAIN 256
-
-static struct commit_list *rev_list;
-static int non_common_revs, multi_ack, use_sideband;
-
-static void rev_list_push(struct commit *commit, int mark)
-{
- if (!(commit->object.flags & mark)) {
- commit->object.flags |= mark;
-
- if (!(commit->object.parsed))
- if (parse_commit(commit))
- return;
-
- insert_by_date(commit, &rev_list);
-
- if (!(commit->object.flags & COMMON))
- non_common_revs++;
- }
-}
-
-static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct object *o = deref_tag(parse_object(sha1), path, 0);
-
- if (o && o->type == OBJ_COMMIT)
- rev_list_push((struct commit *)o, SEEN);
-
- return 0;
-}
-
-static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct object *o = deref_tag(parse_object(sha1), path, 0);
-
- if (o && o->type == OBJ_COMMIT)
- clear_commit_marks((struct commit *)o,
- COMMON | COMMON_REF | SEEN | POPPED);
- return 0;
-}
-
-/*
- This function marks a rev and its ancestors as common.
- In some cases, it is desirable to mark only the ancestors (for example
- when only the server does not yet know that they are common).
-*/
-
-static void mark_common(struct commit *commit,
- int ancestors_only, int dont_parse)
-{
- if (commit != NULL && !(commit->object.flags & COMMON)) {
- struct object *o = (struct object *)commit;
-
- if (!ancestors_only)
- o->flags |= COMMON;
-
- if (!(o->flags & SEEN))
- rev_list_push(commit, SEEN);
- else {
- struct commit_list *parents;
-
- if (!ancestors_only && !(o->flags & POPPED))
- non_common_revs--;
- if (!o->parsed && !dont_parse)
- if (parse_commit(commit))
- return;
-
- for (parents = commit->parents;
- parents;
- parents = parents->next)
- mark_common(parents->item, 0, dont_parse);
- }
- }
-}
-
-/*
- Get the next rev to send, ignoring the common.
-*/
-
-static const unsigned char *get_rev(void)
-{
- struct commit *commit = NULL;
-
- while (commit == NULL) {
- unsigned int mark;
- struct commit_list *parents;
-
- if (rev_list == NULL || non_common_revs == 0)
- return NULL;
-
- commit = rev_list->item;
- if (!commit->object.parsed)
- parse_commit(commit);
- parents = commit->parents;
-
- commit->object.flags |= POPPED;
- if (!(commit->object.flags & COMMON))
- non_common_revs--;
-
- if (commit->object.flags & COMMON) {
- /* do not send "have", and ignore ancestors */
- commit = NULL;
- mark = COMMON | SEEN;
- } else if (commit->object.flags & COMMON_REF)
- /* send "have", and ignore ancestors */
- mark = COMMON | SEEN;
- else
- /* send "have", also for its ancestors */
- mark = SEEN;
-
- while (parents) {
- if (!(parents->item->object.flags & SEEN))
- rev_list_push(parents->item, mark);
- if (mark & COMMON)
- mark_common(parents->item, 1, 0);
- parents = parents->next;
- }
-
- rev_list = rev_list->next;
- }
-
- return commit->object.sha1;
-}
-
-enum ack_type {
- NAK = 0,
- ACK,
- ACK_continue,
- ACK_common,
- ACK_ready
-};
-
-static void consume_shallow_list(int fd)
-{
- if (args.stateless_rpc && args.depth > 0) {
- /* If we sent a depth we will get back "duplicate"
- * shallow and unshallow commands every time there
- * is a block of have lines exchanged.
- */
- char line[1000];
- while (packet_read_line(fd, line, sizeof(line))) {
- if (!prefixcmp(line, "shallow "))
- continue;
- if (!prefixcmp(line, "unshallow "))
- continue;
- die("git fetch-pack: expected shallow list");
- }
- }
-}
-
-static enum ack_type get_ack(int fd, unsigned char *result_sha1)
-{
- static char line[1000];
- int len = packet_read_line(fd, line, sizeof(line));
-
- if (!len)
- die("git fetch-pack: expected ACK/NAK, got EOF");
- if (line[len-1] == '\n')
- line[--len] = 0;
- if (!strcmp(line, "NAK"))
- return NAK;
- if (!prefixcmp(line, "ACK ")) {
- if (!get_sha1_hex(line+4, result_sha1)) {
- if (strstr(line+45, "continue"))
- return ACK_continue;
- if (strstr(line+45, "common"))
- return ACK_common;
- if (strstr(line+45, "ready"))
- return ACK_ready;
- return ACK;
- }
- }
- die("git fetch_pack: expected ACK/NAK, got '%s'", line);
-}
-
-static void send_request(int fd, struct strbuf *buf)
-{
- if (args.stateless_rpc) {
- send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX);
- packet_flush(fd);
- } else
- safe_write(fd, buf->buf, buf->len);
-}
-
-static int find_common(int fd[2], unsigned char *result_sha1,
- struct ref *refs)
-{
- int fetching;
- int count = 0, flushes = 0, retval;
- const unsigned char *sha1;
- unsigned in_vain = 0;
- int got_continue = 0;
- struct strbuf req_buf = STRBUF_INIT;
- size_t state_len = 0;
-
- if (args.stateless_rpc && multi_ack == 1)
- die("--stateless-rpc requires multi_ack_detailed");
- if (marked)
- for_each_ref(clear_marks, NULL);
- marked = 1;
-
- for_each_ref(rev_list_insert_ref, NULL);
-
- fetching = 0;
- for ( ; refs ; refs = refs->next) {
- unsigned char *remote = refs->old_sha1;
- const char *remote_hex;
- struct object *o;
-
- /*
- * If that object is complete (i.e. it is an ancestor of a
- * local ref), we tell them we have it but do not have to
- * tell them about its ancestors, which they already know
- * about.
- *
- * We use lookup_object here because we are only
- * interested in the case we *know* the object is
- * reachable and we have already scanned it.
- */
- if (((o = lookup_object(remote)) != NULL) &&
- (o->flags & COMPLETE)) {
- continue;
- }
-
- remote_hex = sha1_to_hex(remote);
- if (!fetching) {
- struct strbuf c = STRBUF_INIT;
- if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed");
- if (multi_ack == 1) strbuf_addstr(&c, " multi_ack");
- if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k");
- if (use_sideband == 1) strbuf_addstr(&c, " side-band");
- if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack");
- if (args.no_progress) strbuf_addstr(&c, " no-progress");
- if (args.include_tag) strbuf_addstr(&c, " include-tag");
- if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta");
- packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf);
- strbuf_release(&c);
- } else
- packet_buf_write(&req_buf, "want %s\n", remote_hex);
- fetching++;
- }
-
- if (!fetching) {
- strbuf_release(&req_buf);
- packet_flush(fd[1]);
- return 1;
- }
-
- if (is_repository_shallow())
- write_shallow_commits(&req_buf, 1);
- if (args.depth > 0)
- packet_buf_write(&req_buf, "deepen %d", args.depth);
- packet_buf_flush(&req_buf);
- state_len = req_buf.len;
-
- if (args.depth > 0) {
- char line[1024];
- unsigned char sha1[20];
-
- send_request(fd[1], &req_buf);
- while (packet_read_line(fd[0], line, sizeof(line))) {
- if (!prefixcmp(line, "shallow ")) {
- if (get_sha1_hex(line + 8, sha1))
- die("invalid shallow line: %s", line);
- register_shallow(sha1);
- continue;
- }
- if (!prefixcmp(line, "unshallow ")) {
- if (get_sha1_hex(line + 10, sha1))
- die("invalid unshallow line: %s", line);
- if (!lookup_object(sha1))
- die("object not found: %s", line);
- /* make sure that it is parsed as shallow */
- if (!parse_object(sha1))
- die("error in object: %s", line);
- if (unregister_shallow(sha1))
- die("no shallow found: %s", line);
- continue;
- }
- die("expected shallow/unshallow, got %s", line);
- }
- } else if (!args.stateless_rpc)
- send_request(fd[1], &req_buf);
-
- if (!args.stateless_rpc) {
- /* If we aren't using the stateless-rpc interface
- * we don't need to retain the headers.
- */
- strbuf_setlen(&req_buf, 0);
- state_len = 0;
- }
-
- flushes = 0;
- retval = -1;
- while ((sha1 = get_rev())) {
- packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1));
- if (args.verbose)
- fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
- in_vain++;
- if (!(31 & ++count)) {
- int ack;
-
- packet_buf_flush(&req_buf);
- send_request(fd[1], &req_buf);
- strbuf_setlen(&req_buf, state_len);
- flushes++;
-
- /*
- * We keep one window "ahead" of the other side, and
- * will wait for an ACK only on the next one
- */
- if (!args.stateless_rpc && count == 32)
- continue;
-
- consume_shallow_list(fd[0]);
- do {
- ack = get_ack(fd[0], result_sha1);
- if (args.verbose && ack)
- fprintf(stderr, "got ack %d %s\n", ack,
- sha1_to_hex(result_sha1));
- switch (ack) {
- case ACK:
- flushes = 0;
- multi_ack = 0;
- retval = 0;
- goto done;
- case ACK_common:
- case ACK_ready:
- case ACK_continue: {
- struct commit *commit =
- lookup_commit(result_sha1);
- if (args.stateless_rpc
- && ack == ACK_common
- && !(commit->object.flags & COMMON)) {
- /* We need to replay the have for this object
- * on the next RPC request so the peer knows
- * it is in common with us.
- */
- const char *hex = sha1_to_hex(result_sha1);
- packet_buf_write(&req_buf, "have %s\n", hex);
- state_len = req_buf.len;
- }
- mark_common(commit, 0, 1);
- retval = 0;
- in_vain = 0;
- got_continue = 1;
- break;
- }
- }
- } while (ack);
- flushes--;
- if (got_continue && MAX_IN_VAIN < in_vain) {
- if (args.verbose)
- fprintf(stderr, "giving up\n");
- break; /* give up */
- }
- }
- }
-done:
- packet_buf_write(&req_buf, "done\n");
- send_request(fd[1], &req_buf);
- if (args.verbose)
- fprintf(stderr, "done\n");
- if (retval != 0) {
- multi_ack = 0;
- flushes++;
- }
- strbuf_release(&req_buf);
-
- consume_shallow_list(fd[0]);
- while (flushes || multi_ack) {
- int ack = get_ack(fd[0], result_sha1);
- if (ack) {
- if (args.verbose)
- fprintf(stderr, "got ack (%d) %s\n", ack,
- sha1_to_hex(result_sha1));
- if (ack == ACK)
- return 0;
- multi_ack = 1;
- continue;
- }
- flushes--;
- }
- /* it is no error to fetch into a completely empty repo */
- return count ? retval : 0;
-}
-
-static struct commit_list *complete;
-
-static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct object *o = parse_object(sha1);
-
- while (o && o->type == OBJ_TAG) {
- struct tag *t = (struct tag *) o;
- if (!t->tagged)
- break; /* broken repository */
- o->flags |= COMPLETE;
- o = parse_object(t->tagged->sha1);
- }
- if (o && o->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *)o;
- commit->object.flags |= COMPLETE;
- insert_by_date(commit, &complete);
- }
- return 0;
-}
-
-static void mark_recent_complete_commits(unsigned long cutoff)
-{
- while (complete && cutoff <= complete->item->date) {
- if (args.verbose)
- fprintf(stderr, "Marking %s as complete\n",
- sha1_to_hex(complete->item->object.sha1));
- pop_most_recent_commit(&complete, COMPLETE);
- }
-}
-
-static void filter_refs(struct ref **refs, int nr_match, char **match)
-{
- struct ref **return_refs;
- struct ref *newlist = NULL;
- struct ref **newtail = &newlist;
- struct ref *ref, *next;
- struct ref *fastarray[32];
-
- if (nr_match && !args.fetch_all) {
- if (ARRAY_SIZE(fastarray) < nr_match)
- return_refs = xcalloc(nr_match, sizeof(struct ref *));
- else {
- return_refs = fastarray;
- memset(return_refs, 0, sizeof(struct ref *) * nr_match);
- }
- }
- else
- return_refs = NULL;
-
- for (ref = *refs; ref; ref = next) {
- next = ref->next;
- if (!memcmp(ref->name, "refs/", 5) &&
- check_ref_format(ref->name + 5))
- ; /* trash */
- else if (args.fetch_all &&
- (!args.depth || prefixcmp(ref->name, "refs/tags/") )) {
- *newtail = ref;
- ref->next = NULL;
- newtail = &ref->next;
- continue;
- }
- else {
- int order = path_match(ref->name, nr_match, match);
- if (order) {
- return_refs[order-1] = ref;
- continue; /* we will link it later */
- }
- }
- free(ref);
- }
-
- if (!args.fetch_all) {
- int i;
- for (i = 0; i < nr_match; i++) {
- ref = return_refs[i];
- if (ref) {
- *newtail = ref;
- ref->next = NULL;
- newtail = &ref->next;
- }
- }
- if (return_refs != fastarray)
- free(return_refs);
- }
- *refs = newlist;
-}
-
-static int everything_local(struct ref **refs, int nr_match, char **match)
-{
- struct ref *ref;
- int retval;
- unsigned long cutoff = 0;
-
- save_commit_buffer = 0;
-
- for (ref = *refs; ref; ref = ref->next) {
- struct object *o;
-
- o = parse_object(ref->old_sha1);
- if (!o)
- continue;
-
- /* We already have it -- which may mean that we were
- * in sync with the other side at some time after
- * that (it is OK if we guess wrong here).
- */
- if (o->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *)o;
- if (!cutoff || cutoff < commit->date)
- cutoff = commit->date;
- }
- }
-
- if (!args.depth) {
- for_each_ref(mark_complete, NULL);
- if (cutoff)
- mark_recent_complete_commits(cutoff);
- }
-
- /*
- * Mark all complete remote refs as common refs.
- * Don't mark them common yet; the server has to be told so first.
- */
- for (ref = *refs; ref; ref = ref->next) {
- struct object *o = deref_tag(lookup_object(ref->old_sha1),
- NULL, 0);
-
- if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
- continue;
-
- if (!(o->flags & SEEN)) {
- rev_list_push((struct commit *)o, COMMON_REF | SEEN);
-
- mark_common((struct commit *)o, 1, 1);
- }
- }
-
- filter_refs(refs, nr_match, match);
-
- for (retval = 1, ref = *refs; ref ; ref = ref->next) {
- const unsigned char *remote = ref->old_sha1;
- unsigned char local[20];
- struct object *o;
-
- o = lookup_object(remote);
- if (!o || !(o->flags & COMPLETE)) {
- retval = 0;
- if (!args.verbose)
- continue;
- fprintf(stderr,
- "want %s (%s)\n", sha1_to_hex(remote),
- ref->name);
- continue;
- }
-
- hashcpy(ref->new_sha1, local);
- if (!args.verbose)
- continue;
- fprintf(stderr,
- "already have %s (%s)\n", sha1_to_hex(remote),
- ref->name);
- }
- return retval;
-}
-
-static int sideband_demux(int in, int out, void *data)
-{
- int *xd = data;
-
- int ret = recv_sideband("fetch-pack", xd[0], out);
- close(out);
- return ret;
-}
-
-static int get_pack(int xd[2], char **pack_lockfile)
-{
- struct async demux;
- const char *argv[20];
- char keep_arg[256];
- char hdr_arg[256];
- const char **av;
- int do_keep = args.keep_pack;
- struct child_process cmd;
-
- memset(&demux, 0, sizeof(demux));
- if (use_sideband) {
- /* xd[] is talking with upload-pack; subprocess reads from
- * xd[0], spits out band#2 to stderr, and feeds us band#1
- * through demux->out.
- */
- demux.proc = sideband_demux;
- demux.data = xd;
- demux.out = -1;
- if (start_async(&demux))
- die("fetch-pack: unable to fork off sideband"
- " demultiplexer");
- }
- else
- demux.out = xd[0];
-
- memset(&cmd, 0, sizeof(cmd));
- cmd.argv = argv;
- av = argv;
- *hdr_arg = 0;
- if (!args.keep_pack && unpack_limit) {
- struct pack_header header;
-
- if (read_pack_header(demux.out, &header))
- die("protocol error: bad pack header");
- snprintf(hdr_arg, sizeof(hdr_arg),
- "--pack_header=%"PRIu32",%"PRIu32,
- ntohl(header.hdr_version), ntohl(header.hdr_entries));
- if (ntohl(header.hdr_entries) < unpack_limit)
- do_keep = 0;
- else
- do_keep = 1;
- }
-
- if (do_keep) {
- if (pack_lockfile)
- cmd.out = -1;
- *av++ = "index-pack";
- *av++ = "--stdin";
- if (!args.quiet && !args.no_progress)
- *av++ = "-v";
- if (args.use_thin_pack)
- *av++ = "--fix-thin";
- if (args.lock_pack || unpack_limit) {
- int s = sprintf(keep_arg,
- "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid());
- if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
- strcpy(keep_arg + s, "localhost");
- *av++ = keep_arg;
- }
- }
- else {
- *av++ = "unpack-objects";
- if (args.quiet)
- *av++ = "-q";
- }
- if (*hdr_arg)
- *av++ = hdr_arg;
- *av++ = NULL;
-
- cmd.in = demux.out;
- cmd.git_cmd = 1;
- if (start_command(&cmd))
- die("fetch-pack: unable to fork off %s", argv[0]);
- if (do_keep && pack_lockfile) {
- *pack_lockfile = index_pack_lockfile(cmd.out);
- close(cmd.out);
- }
-
- if (finish_command(&cmd))
- die("%s failed", argv[0]);
- if (use_sideband && finish_async(&demux))
- die("error in sideband demultiplexer");
- return 0;
-}
-
-static struct ref *do_fetch_pack(int fd[2],
- const struct ref *orig_ref,
- int nr_match,
- char **match,
- char **pack_lockfile)
-{
- struct ref *ref = copy_ref_list(orig_ref);
- unsigned char sha1[20];
-
- if (is_repository_shallow() && !server_supports("shallow"))
- die("Server does not support shallow clients");
- if (server_supports("multi_ack_detailed")) {
- if (args.verbose)
- fprintf(stderr, "Server supports multi_ack_detailed\n");
- multi_ack = 2;
- }
- else if (server_supports("multi_ack")) {
- if (args.verbose)
- fprintf(stderr, "Server supports multi_ack\n");
- multi_ack = 1;
- }
- if (server_supports("side-band-64k")) {
- if (args.verbose)
- fprintf(stderr, "Server supports side-band-64k\n");
- use_sideband = 2;
- }
- else if (server_supports("side-band")) {
- if (args.verbose)
- fprintf(stderr, "Server supports side-band\n");
- use_sideband = 1;
- }
- if (server_supports("ofs-delta")) {
- if (args.verbose)
- fprintf(stderr, "Server supports ofs-delta\n");
- } else
- prefer_ofs_delta = 0;
- if (everything_local(&ref, nr_match, match)) {
- packet_flush(fd[1]);
- goto all_done;
- }
- if (find_common(fd, sha1, ref) < 0)
- if (!args.keep_pack)
- /* When cloning, it is not unusual to have
- * no common commit.
- */
- warning("no common commits");
-
- if (args.stateless_rpc)
- packet_flush(fd[1]);
- if (get_pack(fd, pack_lockfile))
- die("git fetch-pack: fetch failed.");
-
- all_done:
- return ref;
-}
-
-static int remove_duplicates(int nr_heads, char **heads)
-{
- int src, dst;
-
- for (src = dst = 0; src < nr_heads; src++) {
- /* If heads[src] is different from any of
- * heads[0..dst], push it in.
- */
- int i;
- for (i = 0; i < dst; i++) {
- if (!strcmp(heads[i], heads[src]))
- break;
- }
- if (i < dst)
- continue;
- if (src != dst)
- heads[dst] = heads[src];
- dst++;
- }
- return dst;
-}
-
-static int fetch_pack_config(const char *var, const char *value, void *cb)
-{
- if (strcmp(var, "fetch.unpacklimit") == 0) {
- fetch_unpack_limit = git_config_int(var, value);
- return 0;
- }
-
- if (strcmp(var, "transfer.unpacklimit") == 0) {
- transfer_unpack_limit = git_config_int(var, value);
- return 0;
- }
-
- if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
- prefer_ofs_delta = git_config_bool(var, value);
- return 0;
- }
-
- return git_default_config(var, value, cb);
-}
-
-static struct lock_file lock;
-
-static void fetch_pack_setup(void)
-{
- static int did_setup;
- if (did_setup)
- return;
- git_config(fetch_pack_config, NULL);
- if (0 <= transfer_unpack_limit)
- unpack_limit = transfer_unpack_limit;
- else if (0 <= fetch_unpack_limit)
- unpack_limit = fetch_unpack_limit;
- did_setup = 1;
-}
-
-int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
-{
- int i, ret, nr_heads;
- struct ref *ref = NULL;
- char *dest = NULL, **heads;
- int fd[2];
- char *pack_lockfile = NULL;
- char **pack_lockfile_ptr = NULL;
- struct child_process *conn;
-
- nr_heads = 0;
- heads = NULL;
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (*arg == '-') {
- if (!prefixcmp(arg, "--upload-pack=")) {
- args.uploadpack = arg + 14;
- continue;
- }
- if (!prefixcmp(arg, "--exec=")) {
- args.uploadpack = arg + 7;
- continue;
- }
- if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
- args.quiet = 1;
- continue;
- }
- if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
- args.lock_pack = args.keep_pack;
- args.keep_pack = 1;
- continue;
- }
- if (!strcmp("--thin", arg)) {
- args.use_thin_pack = 1;
- continue;
- }
- if (!strcmp("--include-tag", arg)) {
- args.include_tag = 1;
- continue;
- }
- if (!strcmp("--all", arg)) {
- args.fetch_all = 1;
- continue;
- }
- if (!strcmp("-v", arg)) {
- args.verbose = 1;
- continue;
- }
- if (!prefixcmp(arg, "--depth=")) {
- args.depth = strtol(arg + 8, NULL, 0);
- continue;
- }
- if (!strcmp("--no-progress", arg)) {
- args.no_progress = 1;
- continue;
- }
- if (!strcmp("--stateless-rpc", arg)) {
- args.stateless_rpc = 1;
- continue;
- }
- if (!strcmp("--lock-pack", arg)) {
- args.lock_pack = 1;
- pack_lockfile_ptr = &pack_lockfile;
- continue;
- }
- usage(fetch_pack_usage);
- }
- dest = (char *)arg;
- heads = (char **)(argv + i + 1);
- nr_heads = argc - i - 1;
- break;
- }
- if (!dest)
- usage(fetch_pack_usage);
-
- if (args.stateless_rpc) {
- conn = NULL;
- fd[0] = 0;
- fd[1] = 1;
- } else {
- conn = git_connect(fd, (char *)dest, args.uploadpack,
- args.verbose ? CONNECT_VERBOSE : 0);
- }
-
- get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
-
- ref = fetch_pack(&args, fd, conn, ref, dest,
- nr_heads, heads, pack_lockfile_ptr);
- if (pack_lockfile) {
- printf("lock %s\n", pack_lockfile);
- fflush(stdout);
- }
- close(fd[0]);
- close(fd[1]);
- if (finish_connect(conn))
- ref = NULL;
- ret = !ref;
-
- if (!ret && nr_heads) {
- /* If the heads to pull were given, we should have
- * consumed all of them by matching the remote.
- * Otherwise, 'git fetch remote no-such-ref' would
- * silently succeed without issuing an error.
- */
- for (i = 0; i < nr_heads; i++)
- if (heads[i] && heads[i][0]) {
- error("no such remote ref %s", heads[i]);
- ret = 1;
- }
- }
- while (ref) {
- printf("%s %s\n",
- sha1_to_hex(ref->old_sha1), ref->name);
- ref = ref->next;
- }
-
- return ret;
-}
-
-struct ref *fetch_pack(struct fetch_pack_args *my_args,
- int fd[], struct child_process *conn,
- const struct ref *ref,
- const char *dest,
- int nr_heads,
- char **heads,
- char **pack_lockfile)
-{
- struct stat st;
- struct ref *ref_cpy;
-
- fetch_pack_setup();
- if (&args != my_args)
- memcpy(&args, my_args, sizeof(args));
- if (args.depth > 0) {
- if (stat(git_path("shallow"), &st))
- st.st_mtime = 0;
- }
-
- if (heads && nr_heads)
- nr_heads = remove_duplicates(nr_heads, heads);
- if (!ref) {
- packet_flush(fd[1]);
- die("no matching remote head");
- }
- ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile);
-
- if (args.depth > 0) {
- struct cache_time mtime;
- struct strbuf sb = STRBUF_INIT;
- char *shallow = git_path("shallow");
- int fd;
-
- mtime.sec = st.st_mtime;
- mtime.nsec = ST_MTIME_NSEC(st);
- if (stat(shallow, &st)) {
- if (mtime.sec)
- die("shallow file was removed during fetch");
- } else if (st.st_mtime != mtime.sec
-#ifdef USE_NSEC
- || ST_MTIME_NSEC(st) != mtime.nsec
-#endif
- )
- die("shallow file was changed during fetch");
-
- fd = hold_lock_file_for_update(&lock, shallow,
- LOCK_DIE_ON_ERROR);
- if (!write_shallow_commits(&sb, 0)
- || write_in_full(fd, sb.buf, sb.len) != sb.len) {
- unlink_or_warn(shallow);
- rollback_lock_file(&lock);
- } else {
- commit_lock_file(&lock);
- }
- strbuf_release(&sb);
- }
-
- reprepare_packed_git();
- return ref_cpy;
-}
+++ /dev/null
-/*
- * "git fetch"
- */
-#include "cache.h"
-#include "refs.h"
-#include "commit.h"
-#include "builtin.h"
-#include "string-list.h"
-#include "remote.h"
-#include "transport.h"
-#include "run-command.h"
-#include "parse-options.h"
-#include "sigchain.h"
-
-static const char * const builtin_fetch_usage[] = {
- "git fetch [options] [<repository> <refspec>...]",
- "git fetch [options] <group>",
- "git fetch --multiple [options] [<repository> | <group>]...",
- "git fetch --all [options]",
- NULL
-};
-
-enum {
- TAGS_UNSET = 0,
- TAGS_DEFAULT = 1,
- TAGS_SET = 2
-};
-
-static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
-static int tags = TAGS_DEFAULT;
-static const char *depth;
-static const char *upload_pack;
-static struct strbuf default_rla = STRBUF_INIT;
-static struct transport *transport;
-
-static struct option builtin_fetch_options[] = {
- OPT__VERBOSITY(&verbosity),
- OPT_BOOLEAN(0, "all", &all,
- "fetch from all remotes"),
- OPT_BOOLEAN('a', "append", &append,
- "append to .git/FETCH_HEAD instead of overwriting"),
- OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
- "path to upload pack on remote end"),
- OPT_BOOLEAN('f', "force", &force,
- "force overwrite of local branch"),
- OPT_BOOLEAN('m', "multiple", &multiple,
- "fetch from multiple remotes"),
- OPT_SET_INT('t', "tags", &tags,
- "fetch all tags and associated objects", TAGS_SET),
- OPT_SET_INT('n', NULL, &tags,
- "do not fetch all tags (--no-tags)", TAGS_UNSET),
- OPT_BOOLEAN('p', "prune", &prune,
- "prune tracking branches no longer on remote"),
- OPT_BOOLEAN(0, "dry-run", &dry_run,
- "dry run"),
- OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
- OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
- "allow updating of HEAD ref"),
- OPT_STRING(0, "depth", &depth, "DEPTH",
- "deepen history of shallow clone"),
- OPT_END()
-};
-
-static void unlock_pack(void)
-{
- if (transport)
- transport_unlock_pack(transport);
-}
-
-static void unlock_pack_on_signal(int signo)
-{
- unlock_pack();
- sigchain_pop(signo);
- raise(signo);
-}
-
-static void add_merge_config(struct ref **head,
- const struct ref *remote_refs,
- struct branch *branch,
- struct ref ***tail)
-{
- int i;
-
- for (i = 0; i < branch->merge_nr; i++) {
- struct ref *rm, **old_tail = *tail;
- struct refspec refspec;
-
- for (rm = *head; rm; rm = rm->next) {
- if (branch_merge_matches(branch, i, rm->name)) {
- rm->merge = 1;
- break;
- }
- }
- if (rm)
- continue;
-
- /*
- * Not fetched to a tracking branch? We need to fetch
- * it anyway to allow this branch's "branch.$name.merge"
- * to be honored by 'git pull', but we do not have to
- * fail if branch.$name.merge is misconfigured to point
- * at a nonexisting branch. If we were indeed called by
- * 'git pull', it will notice the misconfiguration because
- * there is no entry in the resulting FETCH_HEAD marked
- * for merging.
- */
- refspec.src = branch->merge[i]->src;
- refspec.dst = NULL;
- refspec.pattern = 0;
- refspec.force = 0;
- get_fetch_map(remote_refs, &refspec, tail, 1);
- for (rm = *old_tail; rm; rm = rm->next)
- rm->merge = 1;
- }
-}
-
-static void find_non_local_tags(struct transport *transport,
- struct ref **head,
- struct ref ***tail);
-
-static struct ref *get_ref_map(struct transport *transport,
- struct refspec *refs, int ref_count, int tags,
- int *autotags)
-{
- int i;
- struct ref *rm;
- struct ref *ref_map = NULL;
- struct ref **tail = &ref_map;
-
- const struct ref *remote_refs = transport_get_remote_refs(transport);
-
- if (ref_count || tags == TAGS_SET) {
- for (i = 0; i < ref_count; i++) {
- get_fetch_map(remote_refs, &refs[i], &tail, 0);
- if (refs[i].dst && refs[i].dst[0])
- *autotags = 1;
- }
- /* Merge everything on the command line, but not --tags */
- for (rm = ref_map; rm; rm = rm->next)
- rm->merge = 1;
- if (tags == TAGS_SET)
- get_fetch_map(remote_refs, tag_refspec, &tail, 0);
- } else {
- /* Use the defaults */
- struct remote *remote = transport->remote;
- struct branch *branch = branch_get(NULL);
- int has_merge = branch_has_merge_config(branch);
- if (remote && (remote->fetch_refspec_nr || has_merge)) {
- for (i = 0; i < remote->fetch_refspec_nr; i++) {
- get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0);
- if (remote->fetch[i].dst &&
- remote->fetch[i].dst[0])
- *autotags = 1;
- if (!i && !has_merge && ref_map &&
- !remote->fetch[0].pattern)
- ref_map->merge = 1;
- }
- /*
- * if the remote we're fetching from is the same
- * as given in branch.<name>.remote, we add the
- * ref given in branch.<name>.merge, too.
- */
- if (has_merge &&
- !strcmp(branch->remote_name, remote->name))
- add_merge_config(&ref_map, remote_refs, branch, &tail);
- } else {
- ref_map = get_remote_ref(remote_refs, "HEAD");
- if (!ref_map)
- die("Couldn't find remote ref HEAD");
- ref_map->merge = 1;
- tail = &ref_map->next;
- }
- }
- if (tags == TAGS_DEFAULT && *autotags)
- find_non_local_tags(transport, &ref_map, &tail);
- ref_remove_duplicates(ref_map);
-
- return ref_map;
-}
-
-#define STORE_REF_ERROR_OTHER 1
-#define STORE_REF_ERROR_DF_CONFLICT 2
-
-static int s_update_ref(const char *action,
- struct ref *ref,
- int check_old)
-{
- char msg[1024];
- char *rla = getenv("GIT_REFLOG_ACTION");
- static struct ref_lock *lock;
-
- if (dry_run)
- return 0;
- if (!rla)
- rla = default_rla.buf;
- snprintf(msg, sizeof(msg), "%s: %s", rla, action);
- lock = lock_any_ref_for_update(ref->name,
- check_old ? ref->old_sha1 : NULL, 0);
- if (!lock)
- return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
- STORE_REF_ERROR_OTHER;
- if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
- return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
- STORE_REF_ERROR_OTHER;
- return 0;
-}
-
-#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
-#define REFCOL_WIDTH 10
-
-static int update_local_ref(struct ref *ref,
- const char *remote,
- char *display)
-{
- struct commit *current = NULL, *updated;
- enum object_type type;
- struct branch *current_branch = branch_get(NULL);
- const char *pretty_ref = prettify_refname(ref->name);
-
- *display = 0;
- type = sha1_object_info(ref->new_sha1, NULL);
- if (type < 0)
- die("object %s not found", sha1_to_hex(ref->new_sha1));
-
- if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
- if (verbosity > 0)
- sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH,
- "[up to date]", REFCOL_WIDTH, remote,
- pretty_ref);
- return 0;
- }
-
- if (current_branch &&
- !strcmp(ref->name, current_branch->name) &&
- !(update_head_ok || is_bare_repository()) &&
- !is_null_sha1(ref->old_sha1)) {
- /*
- * If this is the head, and it's not okay to update
- * the head, and the old value of the head isn't empty...
- */
- sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)",
- SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
- pretty_ref);
- return 1;
- }
-
- if (!is_null_sha1(ref->old_sha1) &&
- !prefixcmp(ref->name, "refs/tags/")) {
- int r;
- r = s_update_ref("updating tag", ref, 0);
- sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-',
- SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
- pretty_ref, r ? " (unable to update local ref)" : "");
- return r;
- }
-
- current = lookup_commit_reference_gently(ref->old_sha1, 1);
- updated = lookup_commit_reference_gently(ref->new_sha1, 1);
- if (!current || !updated) {
- const char *msg;
- const char *what;
- int r;
- if (!strncmp(ref->name, "refs/tags/", 10)) {
- msg = "storing tag";
- what = "[new tag]";
- }
- else {
- msg = "storing head";
- what = "[new branch]";
- }
-
- r = s_update_ref(msg, ref, 0);
- sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
- SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
- r ? " (unable to update local ref)" : "");
- return r;
- }
-
- if (in_merge_bases(current, &updated, 1)) {
- char quickref[83];
- int r;
- strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
- strcat(quickref, "..");
- strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
- r = s_update_ref("fast-forward", ref, 1);
- sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
- SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
- pretty_ref, r ? " (unable to update local ref)" : "");
- return r;
- } else if (force || ref->force) {
- char quickref[84];
- int r;
- strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
- strcat(quickref, "...");
- strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
- r = s_update_ref("forced-update", ref, 1);
- sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+',
- SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
- pretty_ref,
- r ? "unable to update local ref" : "forced update");
- return r;
- } else {
- sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)",
- SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
- pretty_ref);
- return 1;
- }
-}
-
-static int store_updated_refs(const char *raw_url, const char *remote_name,
- struct ref *ref_map)
-{
- FILE *fp;
- struct commit *commit;
- int url_len, i, note_len, shown_url = 0, rc = 0;
- char note[1024];
- const char *what, *kind;
- struct ref *rm;
- char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
-
- fp = fopen(filename, "a");
- if (!fp)
- return error("cannot open %s: %s\n", filename, strerror(errno));
-
- if (raw_url)
- url = transport_anonymize_url(raw_url);
- else
- url = xstrdup("foreign");
- for (rm = ref_map; rm; rm = rm->next) {
- struct ref *ref = NULL;
-
- if (rm->peer_ref) {
- ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
- strcpy(ref->name, rm->peer_ref->name);
- hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
- hashcpy(ref->new_sha1, rm->old_sha1);
- ref->force = rm->peer_ref->force;
- }
-
- commit = lookup_commit_reference_gently(rm->old_sha1, 1);
- if (!commit)
- rm->merge = 0;
-
- if (!strcmp(rm->name, "HEAD")) {
- kind = "";
- what = "";
- }
- else if (!prefixcmp(rm->name, "refs/heads/")) {
- kind = "branch";
- what = rm->name + 11;
- }
- else if (!prefixcmp(rm->name, "refs/tags/")) {
- kind = "tag";
- what = rm->name + 10;
- }
- else if (!prefixcmp(rm->name, "refs/remotes/")) {
- kind = "remote branch";
- what = rm->name + 13;
- }
- else {
- kind = "";
- what = rm->name;
- }
-
- url_len = strlen(url);
- for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
- ;
- url_len = i + 1;
- if (4 < i && !strncmp(".git", url + i - 3, 4))
- url_len = i - 3;
-
- note_len = 0;
- if (*what) {
- if (*kind)
- note_len += sprintf(note + note_len, "%s ",
- kind);
- note_len += sprintf(note + note_len, "'%s' of ", what);
- }
- note[note_len] = '\0';
- fprintf(fp, "%s\t%s\t%s",
- sha1_to_hex(commit ? commit->object.sha1 :
- rm->old_sha1),
- rm->merge ? "" : "not-for-merge",
- note);
- for (i = 0; i < url_len; ++i)
- if ('\n' == url[i])
- fputs("\\n", fp);
- else
- fputc(url[i], fp);
- fputc('\n', fp);
-
- if (ref)
- rc |= update_local_ref(ref, what, note);
- else
- sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
- SUMMARY_WIDTH, *kind ? kind : "branch",
- REFCOL_WIDTH, *what ? what : "HEAD");
- if (*note) {
- if (verbosity >= 0 && !shown_url) {
- fprintf(stderr, "From %.*s\n",
- url_len, url);
- shown_url = 1;
- }
- if (verbosity >= 0)
- fprintf(stderr, " %s\n", note);
- }
- }
- free(url);
- fclose(fp);
- if (rc & STORE_REF_ERROR_DF_CONFLICT)
- error("some local refs could not be updated; try running\n"
- " 'git remote prune %s' to remove any old, conflicting "
- "branches", remote_name);
- return rc;
-}
-
-/*
- * We would want to bypass the object transfer altogether if
- * everything we are going to fetch already exists and is connected
- * locally.
- *
- * The refs we are going to fetch are in ref_map. If running
- *
- * $ git rev-list --objects --stdin --not --all
- *
- * (feeding all the refs in ref_map on its standard input)
- * does not error out, that means everything reachable from the
- * refs we are going to fetch exists and is connected to some of
- * our existing refs.
- */
-static int quickfetch(struct ref *ref_map)
-{
- struct child_process revlist;
- struct ref *ref;
- int err;
- const char *argv[] = {"rev-list",
- "--quiet", "--objects", "--stdin", "--not", "--all", NULL};
-
- /*
- * If we are deepening a shallow clone we already have these
- * objects reachable. Running rev-list here will return with
- * a good (0) exit status and we'll bypass the fetch that we
- * really need to perform. Claiming failure now will ensure
- * we perform the network exchange to deepen our history.
- */
- if (depth)
- return -1;
-
- if (!ref_map)
- return 0;
-
- memset(&revlist, 0, sizeof(revlist));
- revlist.argv = argv;
- revlist.git_cmd = 1;
- revlist.no_stdout = 1;
- revlist.no_stderr = 1;
- revlist.in = -1;
-
- err = start_command(&revlist);
- if (err) {
- error("could not run rev-list");
- return err;
- }
-
- /*
- * If rev-list --stdin encounters an unknown commit, it terminates,
- * which will cause SIGPIPE in the write loop below.
- */
- sigchain_push(SIGPIPE, SIG_IGN);
-
- for (ref = ref_map; ref; ref = ref->next) {
- if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
- write_str_in_full(revlist.in, "\n") < 0) {
- if (errno != EPIPE && errno != EINVAL)
- error("failed write to rev-list: %s", strerror(errno));
- err = -1;
- break;
- }
- }
-
- if (close(revlist.in)) {
- error("failed to close rev-list's stdin: %s", strerror(errno));
- err = -1;
- }
-
- sigchain_pop(SIGPIPE);
-
- return finish_command(&revlist) || err;
-}
-
-static int fetch_refs(struct transport *transport, struct ref *ref_map)
-{
- int ret = quickfetch(ref_map);
- if (ret)
- ret = transport_fetch_refs(transport, ref_map);
- if (!ret)
- ret |= store_updated_refs(transport->url,
- transport->remote->name,
- ref_map);
- transport_unlock_pack(transport);
- return ret;
-}
-
-static int prune_refs(struct transport *transport, struct ref *ref_map)
-{
- int result = 0;
- struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
- const char *dangling_msg = dry_run
- ? " (%s will become dangling)\n"
- : " (%s has become dangling)\n";
-
- for (ref = stale_refs; ref; ref = ref->next) {
- if (!dry_run)
- result |= delete_ref(ref->name, NULL, 0);
- if (verbosity >= 0) {
- fprintf(stderr, " x %-*s %-*s -> %s\n",
- SUMMARY_WIDTH, "[deleted]",
- REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
- warn_dangling_symref(stderr, dangling_msg, ref->name);
- }
- }
- free_refs(stale_refs);
- return result;
-}
-
-static int add_existing(const char *refname, const unsigned char *sha1,
- int flag, void *cbdata)
-{
- struct string_list *list = (struct string_list *)cbdata;
- struct string_list_item *item = string_list_insert(refname, list);
- item->util = (void *)sha1;
- return 0;
-}
-
-static int will_fetch(struct ref **head, const unsigned char *sha1)
-{
- struct ref *rm = *head;
- while (rm) {
- if (!hashcmp(rm->old_sha1, sha1))
- return 1;
- rm = rm->next;
- }
- return 0;
-}
-
-struct tag_data {
- struct ref **head;
- struct ref ***tail;
-};
-
-static int add_to_tail(struct string_list_item *item, void *cb_data)
-{
- struct tag_data *data = (struct tag_data *)cb_data;
- struct ref *rm = NULL;
-
- /* We have already decided to ignore this item */
- if (!item->util)
- return 0;
-
- rm = alloc_ref(item->string);
- rm->peer_ref = alloc_ref(item->string);
- hashcpy(rm->old_sha1, item->util);
-
- **data->tail = rm;
- *data->tail = &rm->next;
-
- return 0;
-}
-
-static void find_non_local_tags(struct transport *transport,
- struct ref **head,
- struct ref ***tail)
-{
- struct string_list existing_refs = { NULL, 0, 0, 0 };
- struct string_list remote_refs = { NULL, 0, 0, 0 };
- struct tag_data data = {head, tail};
- const struct ref *ref;
- struct string_list_item *item = NULL;
-
- for_each_ref(add_existing, &existing_refs);
- for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
- if (prefixcmp(ref->name, "refs/tags"))
- continue;
-
- /*
- * The peeled ref always follows the matching base
- * ref, so if we see a peeled ref that we don't want
- * to fetch then we can mark the ref entry in the list
- * as one to ignore by setting util to NULL.
- */
- if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) {
- if (item && !has_sha1_file(ref->old_sha1) &&
- !will_fetch(head, ref->old_sha1) &&
- !has_sha1_file(item->util) &&
- !will_fetch(head, item->util))
- item->util = NULL;
- item = NULL;
- continue;
- }
-
- /*
- * If item is non-NULL here, then we previously saw a
- * ref not followed by a peeled reference, so we need
- * to check if it is a lightweight tag that we want to
- * fetch.
- */
- if (item && !has_sha1_file(item->util) &&
- !will_fetch(head, item->util))
- item->util = NULL;
-
- item = NULL;
-
- /* skip duplicates and refs that we already have */
- if (string_list_has_string(&remote_refs, ref->name) ||
- string_list_has_string(&existing_refs, ref->name))
- continue;
-
- item = string_list_insert(ref->name, &remote_refs);
- item->util = (void *)ref->old_sha1;
- }
- string_list_clear(&existing_refs, 0);
-
- /*
- * We may have a final lightweight tag that needs to be
- * checked to see if it needs fetching.
- */
- if (item && !has_sha1_file(item->util) &&
- !will_fetch(head, item->util))
- item->util = NULL;
-
- /*
- * For all the tags in the remote_refs string list, call
- * add_to_tail to add them to the list of refs to be fetched
- */
- for_each_string_list(add_to_tail, &remote_refs, &data);
-
- string_list_clear(&remote_refs, 0);
-}
-
-static void check_not_current_branch(struct ref *ref_map)
-{
- struct branch *current_branch = branch_get(NULL);
-
- if (is_bare_repository() || !current_branch)
- return;
-
- for (; ref_map; ref_map = ref_map->next)
- if (ref_map->peer_ref && !strcmp(current_branch->refname,
- ref_map->peer_ref->name))
- die("Refusing to fetch into current branch %s "
- "of non-bare repository", current_branch->refname);
-}
-
-static int truncate_fetch_head(void)
-{
- char *filename = git_path("FETCH_HEAD");
- FILE *fp = fopen(filename, "w");
-
- if (!fp)
- return error("cannot open %s: %s\n", filename, strerror(errno));
- fclose(fp);
- return 0;
-}
-
-static int do_fetch(struct transport *transport,
- struct refspec *refs, int ref_count)
-{
- struct string_list existing_refs = { NULL, 0, 0, 0 };
- struct string_list_item *peer_item = NULL;
- struct ref *ref_map;
- struct ref *rm;
- int autotags = (transport->remote->fetch_tags == 1);
-
- for_each_ref(add_existing, &existing_refs);
-
- if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
- tags = TAGS_SET;
- if (transport->remote->fetch_tags == -1)
- tags = TAGS_UNSET;
-
- if (!transport->get_refs_list || !transport->fetch)
- die("Don't know how to fetch from %s", transport->url);
-
- /* if not appending, truncate FETCH_HEAD */
- if (!append && !dry_run) {
- int errcode = truncate_fetch_head();
- if (errcode)
- return errcode;
- }
-
- ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
- if (!update_head_ok)
- check_not_current_branch(ref_map);
-
- for (rm = ref_map; rm; rm = rm->next) {
- if (rm->peer_ref) {
- peer_item = string_list_lookup(rm->peer_ref->name,
- &existing_refs);
- if (peer_item)
- hashcpy(rm->peer_ref->old_sha1,
- peer_item->util);
- }
- }
-
- if (tags == TAGS_DEFAULT && autotags)
- transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
- if (fetch_refs(transport, ref_map)) {
- free_refs(ref_map);
- return 1;
- }
- if (prune)
- prune_refs(transport, ref_map);
- free_refs(ref_map);
-
- /* if neither --no-tags nor --tags was specified, do automated tag
- * following ... */
- if (tags == TAGS_DEFAULT && autotags) {
- struct ref **tail = &ref_map;
- ref_map = NULL;
- find_non_local_tags(transport, &ref_map, &tail);
- if (ref_map) {
- transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
- transport_set_option(transport, TRANS_OPT_DEPTH, "0");
- fetch_refs(transport, ref_map);
- }
- free_refs(ref_map);
- }
-
- return 0;
-}
-
-static void set_option(const char *name, const char *value)
-{
- int r = transport_set_option(transport, name, value);
- if (r < 0)
- die("Option \"%s\" value \"%s\" is not valid for %s",
- name, value, transport->url);
- if (r > 0)
- warning("Option \"%s\" is ignored for %s\n",
- name, transport->url);
-}
-
-static int get_one_remote_for_fetch(struct remote *remote, void *priv)
-{
- struct string_list *list = priv;
- if (!remote->skip_default_update)
- string_list_append(remote->name, list);
- return 0;
-}
-
-struct remote_group_data {
- const char *name;
- struct string_list *list;
-};
-
-static int get_remote_group(const char *key, const char *value, void *priv)
-{
- struct remote_group_data *g = priv;
-
- if (!prefixcmp(key, "remotes.") &&
- !strcmp(key + 8, g->name)) {
- /* split list by white space */
- int space = strcspn(value, " \t\n");
- while (*value) {
- if (space > 1) {
- string_list_append(xstrndup(value, space),
- g->list);
- }
- value += space + (value[space] != '\0');
- space = strcspn(value, " \t\n");
- }
- }
-
- return 0;
-}
-
-static int add_remote_or_group(const char *name, struct string_list *list)
-{
- int prev_nr = list->nr;
- struct remote_group_data g = { name, list };
-
- git_config(get_remote_group, &g);
- if (list->nr == prev_nr) {
- struct remote *remote;
- if (!remote_is_configured(name))
- return 0;
- remote = remote_get(name);
- string_list_append(remote->name, list);
- }
- return 1;
-}
-
-static int fetch_multiple(struct string_list *list)
-{
- int i, result = 0;
- const char *argv[11] = { "fetch", "--append" };
- int argc = 2;
-
- if (dry_run)
- argv[argc++] = "--dry-run";
- if (prune)
- argv[argc++] = "--prune";
- if (update_head_ok)
- argv[argc++] = "--update-head-ok";
- if (force)
- argv[argc++] = "--force";
- if (keep)
- argv[argc++] = "--keep";
- if (verbosity >= 2)
- argv[argc++] = "-v";
- if (verbosity >= 1)
- argv[argc++] = "-v";
- else if (verbosity < 0)
- argv[argc++] = "-q";
-
- if (!append && !dry_run) {
- int errcode = truncate_fetch_head();
- if (errcode)
- return errcode;
- }
-
- for (i = 0; i < list->nr; i++) {
- const char *name = list->items[i].string;
- argv[argc] = name;
- argv[argc + 1] = NULL;
- if (verbosity >= 0)
- printf("Fetching %s\n", name);
- if (run_command_v_opt(argv, RUN_GIT_CMD)) {
- error("Could not fetch %s", name);
- result = 1;
- }
- }
-
- return result;
-}
-
-static int fetch_one(struct remote *remote, int argc, const char **argv)
-{
- int i;
- static const char **refs = NULL;
- int ref_nr = 0;
- int exit_code;
-
- if (!remote)
- die("Where do you want to fetch from today?");
-
- transport = transport_get(remote, NULL);
- if (verbosity >= 2)
- transport->verbose = verbosity <= 3 ? verbosity : 3;
- if (verbosity < 0)
- transport->verbose = -1;
- if (upload_pack)
- set_option(TRANS_OPT_UPLOADPACK, upload_pack);
- if (keep)
- set_option(TRANS_OPT_KEEP, "yes");
- if (depth)
- set_option(TRANS_OPT_DEPTH, depth);
-
- if (argc > 0) {
- int j = 0;
- refs = xcalloc(argc + 1, sizeof(const char *));
- for (i = 0; i < argc; i++) {
- if (!strcmp(argv[i], "tag")) {
- char *ref;
- i++;
- if (i >= argc)
- die("You need to specify a tag name.");
- ref = xmalloc(strlen(argv[i]) * 2 + 22);
- strcpy(ref, "refs/tags/");
- strcat(ref, argv[i]);
- strcat(ref, ":refs/tags/");
- strcat(ref, argv[i]);
- refs[j++] = ref;
- } else
- refs[j++] = argv[i];
- }
- refs[j] = NULL;
- ref_nr = j;
- }
-
- sigchain_push_common(unlock_pack_on_signal);
- atexit(unlock_pack);
- exit_code = do_fetch(transport,
- parse_fetch_refspec(ref_nr, refs), ref_nr);
- transport_disconnect(transport);
- transport = NULL;
- return exit_code;
-}
-
-int cmd_fetch(int argc, const char **argv, const char *prefix)
-{
- int i;
- struct string_list list = { NULL, 0, 0, 0 };
- struct remote *remote;
- int result = 0;
-
- /* Record the command line for the reflog */
- strbuf_addstr(&default_rla, "fetch");
- for (i = 1; i < argc; i++)
- strbuf_addf(&default_rla, " %s", argv[i]);
-
- argc = parse_options(argc, argv, prefix,
- builtin_fetch_options, builtin_fetch_usage, 0);
-
- if (all) {
- if (argc == 1)
- die("fetch --all does not take a repository argument");
- else if (argc > 1)
- die("fetch --all does not make sense with refspecs");
- (void) for_each_remote(get_one_remote_for_fetch, &list);
- result = fetch_multiple(&list);
- } else if (argc == 0) {
- /* No arguments -- use default remote */
- remote = remote_get(NULL);
- result = fetch_one(remote, argc, argv);
- } else if (multiple) {
- /* All arguments are assumed to be remotes or groups */
- for (i = 0; i < argc; i++)
- if (!add_remote_or_group(argv[i], &list))
- die("No such remote or remote group: %s", argv[i]);
- result = fetch_multiple(&list);
- } else {
- /* Single remote or group */
- (void) add_remote_or_group(argv[0], &list);
- if (list.nr > 1) {
- /* More than one remote */
- if (argc > 1)
- die("Fetching a group and specifying refspecs does not make sense");
- result = fetch_multiple(&list);
- } else {
- /* Zero or one remotes */
- remote = remote_get(argv[0]);
- result = fetch_one(remote, argc-1, argv+1);
- }
- }
-
- /* All names were strdup()ed or strndup()ed */
- list.strdup_strings = 1;
- string_list_clear(&list, 0);
-
- return result;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "tag.h"
-
-static const char * const fmt_merge_msg_usage[] = {
- "git fmt-merge-msg [--log|--no-log] [--file <file>]",
- NULL
-};
-
-static int merge_summary;
-
-static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
-{
- static int found_merge_log = 0;
- if (!strcmp("merge.log", key)) {
- found_merge_log = 1;
- merge_summary = git_config_bool(key, value);
- }
- if (!found_merge_log && !strcmp("merge.summary", key))
- merge_summary = git_config_bool(key, value);
- return 0;
-}
-
-struct list {
- char **list;
- void **payload;
- unsigned nr, alloc;
-};
-
-static void append_to_list(struct list *list, char *value, void *payload)
-{
- if (list->nr == list->alloc) {
- list->alloc += 32;
- list->list = xrealloc(list->list, sizeof(char *) * list->alloc);
- list->payload = xrealloc(list->payload,
- sizeof(char *) * list->alloc);
- }
- list->payload[list->nr] = payload;
- list->list[list->nr++] = value;
-}
-
-static int find_in_list(struct list *list, char *value)
-{
- int i;
-
- for (i = 0; i < list->nr; i++)
- if (!strcmp(list->list[i], value))
- return i;
-
- return -1;
-}
-
-static void free_list(struct list *list)
-{
- int i;
-
- if (list->alloc == 0)
- return;
-
- for (i = 0; i < list->nr; i++) {
- free(list->list[i]);
- free(list->payload[i]);
- }
- free(list->list);
- free(list->payload);
- list->nr = list->alloc = 0;
-}
-
-struct src_data {
- struct list branch, tag, r_branch, generic;
- int head_status;
-};
-
-static struct list srcs = { NULL, NULL, 0, 0};
-static struct list origins = { NULL, NULL, 0, 0};
-
-static int handle_line(char *line)
-{
- int i, len = strlen(line);
- unsigned char *sha1;
- char *src, *origin;
- struct src_data *src_data;
- int pulling_head = 0;
-
- if (len < 43 || line[40] != '\t')
- return 1;
-
- if (!prefixcmp(line + 41, "not-for-merge"))
- return 0;
-
- if (line[41] != '\t')
- return 2;
-
- line[40] = 0;
- sha1 = xmalloc(20);
- i = get_sha1(line, sha1);
- line[40] = '\t';
- if (i)
- return 3;
-
- if (line[len - 1] == '\n')
- line[len - 1] = 0;
- line += 42;
-
- src = strstr(line, " of ");
- if (src) {
- *src = 0;
- src += 4;
- pulling_head = 0;
- } else {
- src = line;
- pulling_head = 1;
- }
-
- i = find_in_list(&srcs, src);
- if (i < 0) {
- i = srcs.nr;
- append_to_list(&srcs, xstrdup(src),
- xcalloc(1, sizeof(struct src_data)));
- }
- src_data = srcs.payload[i];
-
- if (pulling_head) {
- origin = xstrdup(src);
- src_data->head_status |= 1;
- } else if (!prefixcmp(line, "branch ")) {
- origin = xstrdup(line + 7);
- append_to_list(&src_data->branch, origin, NULL);
- src_data->head_status |= 2;
- } else if (!prefixcmp(line, "tag ")) {
- origin = line;
- append_to_list(&src_data->tag, xstrdup(origin + 4), NULL);
- src_data->head_status |= 2;
- } else if (!prefixcmp(line, "remote branch ")) {
- origin = xstrdup(line + 14);
- append_to_list(&src_data->r_branch, origin, NULL);
- src_data->head_status |= 2;
- } else {
- origin = xstrdup(src);
- append_to_list(&src_data->generic, xstrdup(line), NULL);
- src_data->head_status |= 2;
- }
-
- if (!strcmp(".", src) || !strcmp(src, origin)) {
- int len = strlen(origin);
- if (origin[0] == '\'' && origin[len - 1] == '\'') {
- origin = xmemdupz(origin + 1, len - 2);
- } else {
- origin = xstrdup(origin);
- }
- } else {
- char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5);
- sprintf(new_origin, "%s of %s", origin, src);
- origin = new_origin;
- }
- append_to_list(&origins, origin, sha1);
- return 0;
-}
-
-static void print_joined(const char *singular, const char *plural,
- struct list *list, struct strbuf *out)
-{
- if (list->nr == 0)
- return;
- if (list->nr == 1) {
- strbuf_addf(out, "%s%s", singular, list->list[0]);
- } else {
- int i;
- strbuf_addstr(out, plural);
- for (i = 0; i < list->nr - 1; i++)
- strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]);
- strbuf_addf(out, " and %s", list->list[list->nr - 1]);
- }
-}
-
-static void shortlog(const char *name, unsigned char *sha1,
- struct commit *head, struct rev_info *rev, int limit,
- struct strbuf *out)
-{
- int i, count = 0;
- struct commit *commit;
- struct object *branch;
- struct list subjects = { NULL, NULL, 0, 0 };
- int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
-
- branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
- if (!branch || branch->type != OBJ_COMMIT)
- return;
-
- setup_revisions(0, NULL, rev, NULL);
- rev->ignore_merges = 1;
- add_pending_object(rev, branch, name);
- add_pending_object(rev, &head->object, "^HEAD");
- head->object.flags |= UNINTERESTING;
- if (prepare_revision_walk(rev))
- die("revision walk setup failed");
- while ((commit = get_revision(rev)) != NULL) {
- char *oneline, *bol, *eol;
-
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
- count++;
- if (subjects.nr > limit)
- continue;
-
- bol = strstr(commit->buffer, "\n\n");
- if (bol) {
- unsigned char c;
- do {
- c = *++bol;
- } while (isspace(c));
- if (!c)
- bol = NULL;
- }
-
- if (!bol) {
- append_to_list(&subjects, xstrdup(sha1_to_hex(
- commit->object.sha1)),
- NULL);
- continue;
- }
-
- eol = strchr(bol, '\n');
- if (eol) {
- oneline = xmemdupz(bol, eol - bol);
- } else {
- oneline = xstrdup(bol);
- }
- append_to_list(&subjects, oneline, NULL);
- }
-
- if (count > limit)
- strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
- else
- strbuf_addf(out, "\n* %s:\n", name);
-
- for (i = 0; i < subjects.nr; i++)
- if (i >= limit)
- strbuf_addf(out, " ...\n");
- else
- strbuf_addf(out, " %s\n", subjects.list[i]);
-
- clear_commit_marks((struct commit *)branch, flags);
- clear_commit_marks(head, flags);
- free_commit_list(rev->commits);
- rev->commits = NULL;
- rev->pending.nr = 0;
-
- free_list(&subjects);
-}
-
-int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
- int limit = 20, i = 0, pos = 0;
- char *sep = "";
- unsigned char head_sha1[20];
- const char *current_branch;
-
- /* get current branch */
- current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
- if (!current_branch)
- die("No current branch");
- if (!prefixcmp(current_branch, "refs/heads/"))
- current_branch += 11;
-
- /* get a line */
- while (pos < in->len) {
- int len;
- char *newline, *p = in->buf + pos;
-
- newline = strchr(p, '\n');
- len = newline ? newline - p : strlen(p);
- pos += len + !!newline;
- i++;
- p[len] = 0;
- if (handle_line(p))
- die ("Error in line %d: %.*s", i, len, p);
- }
-
- strbuf_addstr(out, "Merge ");
- for (i = 0; i < srcs.nr; i++) {
- struct src_data *src_data = srcs.payload[i];
- const char *subsep = "";
-
- strbuf_addstr(out, sep);
- sep = "; ";
-
- if (src_data->head_status == 1) {
- strbuf_addstr(out, srcs.list[i]);
- continue;
- }
- if (src_data->head_status == 3) {
- subsep = ", ";
- strbuf_addstr(out, "HEAD");
- }
- if (src_data->branch.nr) {
- strbuf_addstr(out, subsep);
- subsep = ", ";
- print_joined("branch ", "branches ", &src_data->branch,
- out);
- }
- if (src_data->r_branch.nr) {
- strbuf_addstr(out, subsep);
- subsep = ", ";
- print_joined("remote branch ", "remote branches ",
- &src_data->r_branch, out);
- }
- if (src_data->tag.nr) {
- strbuf_addstr(out, subsep);
- subsep = ", ";
- print_joined("tag ", "tags ", &src_data->tag, out);
- }
- if (src_data->generic.nr) {
- strbuf_addstr(out, subsep);
- print_joined("commit ", "commits ", &src_data->generic,
- out);
- }
- if (strcmp(".", srcs.list[i]))
- strbuf_addf(out, " of %s", srcs.list[i]);
- }
-
- if (!strcmp("master", current_branch))
- strbuf_addch(out, '\n');
- else
- strbuf_addf(out, " into %s\n", current_branch);
-
- if (merge_summary) {
- struct commit *head;
- struct rev_info rev;
-
- head = lookup_commit(head_sha1);
- init_revisions(&rev, NULL);
- rev.commit_format = CMIT_FMT_ONELINE;
- rev.ignore_merges = 1;
- rev.limited = 1;
-
- for (i = 0; i < origins.nr; i++)
- shortlog(origins.list[i], origins.payload[i],
- head, &rev, limit, out);
- }
- return 0;
-}
-
-int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
-{
- const char *inpath = NULL;
- struct option options[] = {
- OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"),
- OPT_BOOLEAN(0, "summary", &merge_summary, "alias for --log"),
- OPT_FILENAME('F', "file", &inpath, "file to read from"),
- OPT_END()
- };
-
- FILE *in = stdin;
- struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
- int ret;
-
- git_config(fmt_merge_msg_config, NULL);
- argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
- 0);
- if (argc > 0)
- usage_with_options(fmt_merge_msg_usage, options);
-
- if (inpath && strcmp(inpath, "-")) {
- in = fopen(inpath, "r");
- if (!in)
- die_errno("cannot open '%s'", inpath);
- }
-
- if (strbuf_read(&input, fileno(in), 0) < 0)
- die_errno("could not read input file");
- ret = fmt_merge_msg(merge_summary, &input, &output);
- if (ret)
- return ret;
- write_in_full(STDOUT_FILENO, output.buf, output.len);
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "refs.h"
-#include "object.h"
-#include "tag.h"
-#include "commit.h"
-#include "tree.h"
-#include "blob.h"
-#include "quote.h"
-#include "parse-options.h"
-#include "remote.h"
-
-/* Quoting styles */
-#define QUOTE_NONE 0
-#define QUOTE_SHELL 1
-#define QUOTE_PERL 2
-#define QUOTE_PYTHON 4
-#define QUOTE_TCL 8
-
-typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
-
-struct atom_value {
- const char *s;
- unsigned long ul; /* used for sorting when not FIELD_STR */
-};
-
-struct ref_sort {
- struct ref_sort *next;
- int atom; /* index into used_atom array */
- unsigned reverse : 1;
-};
-
-struct refinfo {
- char *refname;
- unsigned char objectname[20];
- struct atom_value *value;
-};
-
-static struct {
- const char *name;
- cmp_type cmp_type;
-} valid_atom[] = {
- { "refname" },
- { "objecttype" },
- { "objectsize", FIELD_ULONG },
- { "objectname" },
- { "tree" },
- { "parent" },
- { "numparent", FIELD_ULONG },
- { "object" },
- { "type" },
- { "tag" },
- { "author" },
- { "authorname" },
- { "authoremail" },
- { "authordate", FIELD_TIME },
- { "committer" },
- { "committername" },
- { "committeremail" },
- { "committerdate", FIELD_TIME },
- { "tagger" },
- { "taggername" },
- { "taggeremail" },
- { "taggerdate", FIELD_TIME },
- { "creator" },
- { "creatordate", FIELD_TIME },
- { "subject" },
- { "body" },
- { "contents" },
- { "upstream" },
-};
-
-/*
- * An atom is a valid field atom listed above, possibly prefixed with
- * a "*" to denote deref_tag().
- *
- * We parse given format string and sort specifiers, and make a list
- * of properties that we need to extract out of objects. refinfo
- * structure will hold an array of values extracted that can be
- * indexed with the "atom number", which is an index into this
- * array.
- */
-static const char **used_atom;
-static cmp_type *used_atom_type;
-static int used_atom_cnt, sort_atom_limit, need_tagged;
-
-/*
- * Used to parse format string and sort specifiers
- */
-static int parse_atom(const char *atom, const char *ep)
-{
- const char *sp;
- int i, at;
-
- sp = atom;
- if (*sp == '*' && sp < ep)
- sp++; /* deref */
- if (ep <= sp)
- die("malformed field name: %.*s", (int)(ep-atom), atom);
-
- /* Do we have the atom already used elsewhere? */
- for (i = 0; i < used_atom_cnt; i++) {
- int len = strlen(used_atom[i]);
- if (len == ep - atom && !memcmp(used_atom[i], atom, len))
- return i;
- }
-
- /* Is the atom a valid one? */
- for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
- int len = strlen(valid_atom[i].name);
- /*
- * If the atom name has a colon, strip it and everything after
- * it off - it specifies the format for this entry, and
- * shouldn't be used for checking against the valid_atom
- * table.
- */
- const char *formatp = strchr(sp, ':');
- if (!formatp || ep < formatp)
- formatp = ep;
- if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len))
- break;
- }
-
- if (ARRAY_SIZE(valid_atom) <= i)
- die("unknown field name: %.*s", (int)(ep-atom), atom);
-
- /* Add it in, including the deref prefix */
- at = used_atom_cnt;
- used_atom_cnt++;
- used_atom = xrealloc(used_atom,
- (sizeof *used_atom) * used_atom_cnt);
- used_atom_type = xrealloc(used_atom_type,
- (sizeof(*used_atom_type) * used_atom_cnt));
- used_atom[at] = xmemdupz(atom, ep - atom);
- used_atom_type[at] = valid_atom[i].cmp_type;
- return at;
-}
-
-/*
- * In a format string, find the next occurrence of %(atom).
- */
-static const char *find_next(const char *cp)
-{
- while (*cp) {
- if (*cp == '%') {
- /* %( is the start of an atom;
- * %% is a quoted per-cent.
- */
- if (cp[1] == '(')
- return cp;
- else if (cp[1] == '%')
- cp++; /* skip over two % */
- /* otherwise this is a singleton, literal % */
- }
- cp++;
- }
- return NULL;
-}
-
-/*
- * Make sure the format string is well formed, and parse out
- * the used atoms.
- */
-static int verify_format(const char *format)
-{
- const char *cp, *sp;
- for (cp = format; *cp && (sp = find_next(cp)); ) {
- const char *ep = strchr(sp, ')');
- if (!ep)
- return error("malformed format string %s", sp);
- /* sp points at "%(" and ep points at the closing ")" */
- parse_atom(sp + 2, ep);
- cp = ep + 1;
- }
- return 0;
-}
-
-/*
- * Given an object name, read the object data and size, and return a
- * "struct object". If the object data we are returning is also borrowed
- * by the "struct object" representation, set *eaten as well---it is a
- * signal from parse_object_buffer to us not to free the buffer.
- */
-static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
-{
- enum object_type type;
- void *buf = read_sha1_file(sha1, &type, sz);
-
- if (buf)
- *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
- else
- *obj = NULL;
- return buf;
-}
-
-/* See grab_values */
-static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
- int i;
-
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
- if (!strcmp(name, "objecttype"))
- v->s = typename(obj->type);
- else if (!strcmp(name, "objectsize")) {
- char *s = xmalloc(40);
- sprintf(s, "%lu", sz);
- v->ul = sz;
- v->s = s;
- }
- else if (!strcmp(name, "objectname")) {
- char *s = xmalloc(41);
- strcpy(s, sha1_to_hex(obj->sha1));
- v->s = s;
- }
- }
-}
-
-/* See grab_values */
-static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
- int i;
- struct tag *tag = (struct tag *) obj;
-
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
- if (!strcmp(name, "tag"))
- v->s = tag->tag;
- else if (!strcmp(name, "type") && tag->tagged)
- v->s = typename(tag->tagged->type);
- else if (!strcmp(name, "object") && tag->tagged) {
- char *s = xmalloc(41);
- strcpy(s, sha1_to_hex(tag->tagged->sha1));
- v->s = s;
- }
- }
-}
-
-static int num_parents(struct commit *commit)
-{
- struct commit_list *parents;
- int i;
-
- for (i = 0, parents = commit->parents;
- parents;
- parents = parents->next)
- i++;
- return i;
-}
-
-/* See grab_values */
-static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
- int i;
- struct commit *commit = (struct commit *) obj;
-
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
- if (!strcmp(name, "tree")) {
- char *s = xmalloc(41);
- strcpy(s, sha1_to_hex(commit->tree->object.sha1));
- v->s = s;
- }
- if (!strcmp(name, "numparent")) {
- char *s = xmalloc(40);
- v->ul = num_parents(commit);
- sprintf(s, "%lu", v->ul);
- v->s = s;
- }
- else if (!strcmp(name, "parent")) {
- int num = num_parents(commit);
- int i;
- struct commit_list *parents;
- char *s = xmalloc(41 * num + 1);
- v->s = s;
- for (i = 0, parents = commit->parents;
- parents;
- parents = parents->next, i = i + 41) {
- struct commit *parent = parents->item;
- strcpy(s+i, sha1_to_hex(parent->object.sha1));
- if (parents->next)
- s[i+40] = ' ';
- }
- if (!i)
- *s = '\0';
- }
- }
-}
-
-static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
-{
- const char *eol;
- while (*buf) {
- if (!strncmp(buf, who, wholen) &&
- buf[wholen] == ' ')
- return buf + wholen + 1;
- eol = strchr(buf, '\n');
- if (!eol)
- return "";
- eol++;
- if (*eol == '\n')
- return ""; /* end of header */
- buf = eol;
- }
- return "";
-}
-
-static const char *copy_line(const char *buf)
-{
- const char *eol = strchrnul(buf, '\n');
- return xmemdupz(buf, eol - buf);
-}
-
-static const char *copy_name(const char *buf)
-{
- const char *cp;
- for (cp = buf; *cp && *cp != '\n'; cp++) {
- if (!strncmp(cp, " <", 2))
- return xmemdupz(buf, cp - buf);
- }
- return "";
-}
-
-static const char *copy_email(const char *buf)
-{
- const char *email = strchr(buf, '<');
- const char *eoemail;
- if (!email)
- return "";
- eoemail = strchr(email, '>');
- if (!eoemail)
- return "";
- return xmemdupz(email, eoemail + 1 - email);
-}
-
-static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
-{
- const char *eoemail = strstr(buf, "> ");
- char *zone;
- unsigned long timestamp;
- long tz;
- enum date_mode date_mode = DATE_NORMAL;
- const char *formatp;
-
- /*
- * We got here because atomname ends in "date" or "date<something>";
- * it's not possible that <something> is not ":<format>" because
- * parse_atom() wouldn't have allowed it, so we can assume that no
- * ":" means no format is specified, and use the default.
- */
- formatp = strchr(atomname, ':');
- if (formatp != NULL) {
- formatp++;
- date_mode = parse_date_format(formatp);
- }
-
- if (!eoemail)
- goto bad;
- timestamp = strtoul(eoemail + 2, &zone, 10);
- if (timestamp == ULONG_MAX)
- goto bad;
- tz = strtol(zone, NULL, 10);
- if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
- goto bad;
- v->s = xstrdup(show_date(timestamp, tz, date_mode));
- v->ul = timestamp;
- return;
- bad:
- v->s = "";
- v->ul = 0;
-}
-
-/* See grab_values */
-static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
- int i;
- int wholen = strlen(who);
- const char *wholine = NULL;
-
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
- if (strncmp(who, name, wholen))
- continue;
- if (name[wholen] != 0 &&
- strcmp(name + wholen, "name") &&
- strcmp(name + wholen, "email") &&
- prefixcmp(name + wholen, "date"))
- continue;
- if (!wholine)
- wholine = find_wholine(who, wholen, buf, sz);
- if (!wholine)
- return; /* no point looking for it */
- if (name[wholen] == 0)
- v->s = copy_line(wholine);
- else if (!strcmp(name + wholen, "name"))
- v->s = copy_name(wholine);
- else if (!strcmp(name + wholen, "email"))
- v->s = copy_email(wholine);
- else if (!prefixcmp(name + wholen, "date"))
- grab_date(wholine, v, name);
- }
-
- /* For a tag or a commit object, if "creator" or "creatordate" is
- * requested, do something special.
- */
- if (strcmp(who, "tagger") && strcmp(who, "committer"))
- return; /* "author" for commit object is not wanted */
- if (!wholine)
- wholine = find_wholine(who, wholen, buf, sz);
- if (!wholine)
- return;
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
-
- if (!prefixcmp(name, "creatordate"))
- grab_date(wholine, v, name);
- else if (!strcmp(name, "creator"))
- v->s = copy_line(wholine);
- }
-}
-
-static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
-{
- while (*buf) {
- const char *eol = strchr(buf, '\n');
- if (!eol)
- return;
- if (eol[1] == '\n') {
- buf = eol + 1;
- break; /* found end of header */
- }
- buf = eol + 1;
- }
- while (*buf == '\n')
- buf++;
- if (!*buf)
- return;
- *sub = buf; /* first non-empty line */
- buf = strchr(buf, '\n');
- if (!buf) {
- *body = "";
- return; /* no body */
- }
- while (*buf == '\n')
- buf++; /* skip blank between subject and body */
- *body = buf;
-}
-
-/* See grab_values */
-static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
- int i;
- const char *subpos = NULL, *bodypos = NULL;
-
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
- if (strcmp(name, "subject") &&
- strcmp(name, "body") &&
- strcmp(name, "contents"))
- continue;
- if (!subpos)
- find_subpos(buf, sz, &subpos, &bodypos);
- if (!subpos)
- return;
-
- if (!strcmp(name, "subject"))
- v->s = copy_line(subpos);
- else if (!strcmp(name, "body"))
- v->s = xstrdup(bodypos);
- else if (!strcmp(name, "contents"))
- v->s = xstrdup(subpos);
- }
-}
-
-/* We want to have empty print-string for field requests
- * that do not apply (e.g. "authordate" for a tag object)
- */
-static void fill_missing_values(struct atom_value *val)
-{
- int i;
- for (i = 0; i < used_atom_cnt; i++) {
- struct atom_value *v = &val[i];
- if (v->s == NULL)
- v->s = "";
- }
-}
-
-/*
- * val is a list of atom_value to hold returned values. Extract
- * the values for atoms in used_atom array out of (obj, buf, sz).
- * when deref is false, (obj, buf, sz) is the object that is
- * pointed at by the ref itself; otherwise it is the object the
- * ref (which is a tag) refers to.
- */
-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
- grab_common_values(val, deref, obj, buf, sz);
- switch (obj->type) {
- case OBJ_TAG:
- grab_tag_values(val, deref, obj, buf, sz);
- grab_sub_body_contents(val, deref, obj, buf, sz);
- grab_person("tagger", val, deref, obj, buf, sz);
- break;
- case OBJ_COMMIT:
- grab_commit_values(val, deref, obj, buf, sz);
- grab_sub_body_contents(val, deref, obj, buf, sz);
- grab_person("author", val, deref, obj, buf, sz);
- grab_person("committer", val, deref, obj, buf, sz);
- break;
- case OBJ_TREE:
- // grab_tree_values(val, deref, obj, buf, sz);
- break;
- case OBJ_BLOB:
- // grab_blob_values(val, deref, obj, buf, sz);
- break;
- default:
- die("Eh? Object of type %d?", obj->type);
- }
-}
-
-/*
- * Parse the object referred by ref, and grab needed value.
- */
-static void populate_value(struct refinfo *ref)
-{
- void *buf;
- struct object *obj;
- int eaten, i;
- unsigned long size;
- const unsigned char *tagged;
-
- ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
-
- /* Fill in specials first */
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &ref->value[i];
- int deref = 0;
- const char *refname;
- const char *formatp;
-
- if (*name == '*') {
- deref = 1;
- name++;
- }
-
- if (!prefixcmp(name, "refname"))
- refname = ref->refname;
- else if (!prefixcmp(name, "upstream")) {
- struct branch *branch;
- /* only local branches may have an upstream */
- if (prefixcmp(ref->refname, "refs/heads/"))
- continue;
- branch = branch_get(ref->refname + 11);
-
- if (!branch || !branch->merge || !branch->merge[0] ||
- !branch->merge[0]->dst)
- continue;
- refname = branch->merge[0]->dst;
- }
- else
- continue;
-
- formatp = strchr(name, ':');
- /* look for "short" refname format */
- if (formatp) {
- formatp++;
- if (!strcmp(formatp, "short"))
- refname = shorten_unambiguous_ref(refname,
- warn_ambiguous_refs);
- else
- die("unknown %.*s format %s",
- (int)(formatp - name), name, formatp);
- }
-
- if (!deref)
- v->s = refname;
- else {
- int len = strlen(refname);
- char *s = xmalloc(len + 4);
- sprintf(s, "%s^{}", refname);
- v->s = s;
- }
- }
-
- for (i = 0; i < used_atom_cnt; i++) {
- struct atom_value *v = &ref->value[i];
- if (v->s == NULL)
- goto need_obj;
- }
- return;
-
- need_obj:
- buf = get_obj(ref->objectname, &obj, &size, &eaten);
- if (!buf)
- die("missing object %s for %s",
- sha1_to_hex(ref->objectname), ref->refname);
- if (!obj)
- die("parse_object_buffer failed on %s for %s",
- sha1_to_hex(ref->objectname), ref->refname);
-
- grab_values(ref->value, 0, obj, buf, size);
- if (!eaten)
- free(buf);
-
- /* If there is no atom that wants to know about tagged
- * object, we are done.
- */
- if (!need_tagged || (obj->type != OBJ_TAG))
- return;
-
- /* If it is a tag object, see if we use a value that derefs
- * the object, and if we do grab the object it refers to.
- */
- tagged = ((struct tag *)obj)->tagged->sha1;
-
- /* NEEDSWORK: This derefs tag only once, which
- * is good to deal with chains of trust, but
- * is not consistent with what deref_tag() does
- * which peels the onion to the core.
- */
- buf = get_obj(tagged, &obj, &size, &eaten);
- if (!buf)
- die("missing object %s for %s",
- sha1_to_hex(tagged), ref->refname);
- if (!obj)
- die("parse_object_buffer failed on %s for %s",
- sha1_to_hex(tagged), ref->refname);
- grab_values(ref->value, 1, obj, buf, size);
- if (!eaten)
- free(buf);
-}
-
-/*
- * Given a ref, return the value for the atom. This lazily gets value
- * out of the object by calling populate value.
- */
-static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
-{
- if (!ref->value) {
- populate_value(ref);
- fill_missing_values(ref->value);
- }
- *v = &ref->value[atom];
-}
-
-struct grab_ref_cbdata {
- struct refinfo **grab_array;
- const char **grab_pattern;
- int grab_cnt;
-};
-
-/*
- * A call-back given to for_each_ref(). It is unfortunate that we
- * need to use global variables to pass extra information to this
- * function.
- */
-static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct grab_ref_cbdata *cb = cb_data;
- struct refinfo *ref;
- int cnt;
-
- if (*cb->grab_pattern) {
- const char **pattern;
- int namelen = strlen(refname);
- for (pattern = cb->grab_pattern; *pattern; pattern++) {
- const char *p = *pattern;
- int plen = strlen(p);
-
- if ((plen <= namelen) &&
- !strncmp(refname, p, plen) &&
- (refname[plen] == '\0' ||
- refname[plen] == '/' ||
- p[plen-1] == '/'))
- break;
- if (!fnmatch(p, refname, FNM_PATHNAME))
- break;
- }
- if (!*pattern)
- return 0;
- }
-
- /* We do not open the object yet; sort may only need refname
- * to do its job and the resulting list may yet to be pruned
- * by maxcount logic.
- */
- ref = xcalloc(1, sizeof(*ref));
- ref->refname = xstrdup(refname);
- hashcpy(ref->objectname, sha1);
-
- cnt = cb->grab_cnt;
- cb->grab_array = xrealloc(cb->grab_array,
- sizeof(*cb->grab_array) * (cnt + 1));
- cb->grab_array[cnt++] = ref;
- cb->grab_cnt = cnt;
- return 0;
-}
-
-static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
-{
- struct atom_value *va, *vb;
- int cmp;
- cmp_type cmp_type = used_atom_type[s->atom];
-
- get_value(a, s->atom, &va);
- get_value(b, s->atom, &vb);
- switch (cmp_type) {
- case FIELD_STR:
- cmp = strcmp(va->s, vb->s);
- break;
- default:
- if (va->ul < vb->ul)
- cmp = -1;
- else if (va->ul == vb->ul)
- cmp = 0;
- else
- cmp = 1;
- break;
- }
- return (s->reverse) ? -cmp : cmp;
-}
-
-static struct ref_sort *ref_sort;
-static int compare_refs(const void *a_, const void *b_)
-{
- struct refinfo *a = *((struct refinfo **)a_);
- struct refinfo *b = *((struct refinfo **)b_);
- struct ref_sort *s;
-
- for (s = ref_sort; s; s = s->next) {
- int cmp = cmp_ref_sort(s, a, b);
- if (cmp)
- return cmp;
- }
- return 0;
-}
-
-static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
-{
- ref_sort = sort;
- qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
-}
-
-static void print_value(struct refinfo *ref, int atom, int quote_style)
-{
- struct atom_value *v;
- get_value(ref, atom, &v);
- switch (quote_style) {
- case QUOTE_NONE:
- fputs(v->s, stdout);
- break;
- case QUOTE_SHELL:
- sq_quote_print(stdout, v->s);
- break;
- case QUOTE_PERL:
- perl_quote_print(stdout, v->s);
- break;
- case QUOTE_PYTHON:
- python_quote_print(stdout, v->s);
- break;
- case QUOTE_TCL:
- tcl_quote_print(stdout, v->s);
- break;
- }
-}
-
-static int hex1(char ch)
-{
- if ('0' <= ch && ch <= '9')
- return ch - '0';
- else if ('a' <= ch && ch <= 'f')
- return ch - 'a' + 10;
- else if ('A' <= ch && ch <= 'F')
- return ch - 'A' + 10;
- return -1;
-}
-static int hex2(const char *cp)
-{
- if (cp[0] && cp[1])
- return (hex1(cp[0]) << 4) | hex1(cp[1]);
- else
- return -1;
-}
-
-static void emit(const char *cp, const char *ep)
-{
- while (*cp && (!ep || cp < ep)) {
- if (*cp == '%') {
- if (cp[1] == '%')
- cp++;
- else {
- int ch = hex2(cp + 1);
- if (0 <= ch) {
- putchar(ch);
- cp += 3;
- continue;
- }
- }
- }
- putchar(*cp);
- cp++;
- }
-}
-
-static void show_ref(struct refinfo *info, const char *format, int quote_style)
-{
- const char *cp, *sp, *ep;
-
- for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
- ep = strchr(sp, ')');
- if (cp < sp)
- emit(cp, sp);
- print_value(info, parse_atom(sp + 2, ep), quote_style);
- }
- if (*cp) {
- sp = cp + strlen(cp);
- emit(cp, sp);
- }
- putchar('\n');
-}
-
-static struct ref_sort *default_sort(void)
-{
- static const char cstr_name[] = "refname";
-
- struct ref_sort *sort = xcalloc(1, sizeof(*sort));
-
- sort->next = NULL;
- sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
- return sort;
-}
-
-static int opt_parse_sort(const struct option *opt, const char *arg, int unset)
-{
- struct ref_sort **sort_tail = opt->value;
- struct ref_sort *s;
- int len;
-
- if (!arg) /* should --no-sort void the list ? */
- return -1;
-
- *sort_tail = s = xcalloc(1, sizeof(*s));
-
- if (*arg == '-') {
- s->reverse = 1;
- arg++;
- }
- len = strlen(arg);
- s->atom = parse_atom(arg, arg+len);
- return 0;
-}
-
-static char const * const for_each_ref_usage[] = {
- "git for-each-ref [options] [<pattern>]",
- NULL
-};
-
-int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
-{
- int i, num_refs;
- const char *format = "%(objectname) %(objecttype)\t%(refname)";
- struct ref_sort *sort = NULL, **sort_tail = &sort;
- int maxcount = 0, quote_style = 0;
- struct refinfo **refs;
- struct grab_ref_cbdata cbdata;
-
- struct option opts[] = {
- OPT_BIT('s', "shell", "e_style,
- "quote placeholders suitably for shells", QUOTE_SHELL),
- OPT_BIT('p', "perl", "e_style,
- "quote placeholders suitably for perl", QUOTE_PERL),
- OPT_BIT(0 , "python", "e_style,
- "quote placeholders suitably for python", QUOTE_PYTHON),
- OPT_BIT(0 , "tcl", "e_style,
- "quote placeholders suitably for tcl", QUOTE_TCL),
-
- OPT_GROUP(""),
- OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"),
- OPT_STRING( 0 , "format", &format, "format", "format to use for the output"),
- OPT_CALLBACK(0 , "sort", sort_tail, "key",
- "field name to sort on", &opt_parse_sort),
- OPT_END(),
- };
-
- parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
- if (maxcount < 0) {
- error("invalid --count argument: `%d'", maxcount);
- usage_with_options(for_each_ref_usage, opts);
- }
- if (HAS_MULTI_BITS(quote_style)) {
- error("more than one quoting style?");
- usage_with_options(for_each_ref_usage, opts);
- }
- if (verify_format(format))
- usage_with_options(for_each_ref_usage, opts);
-
- if (!sort)
- sort = default_sort();
- sort_atom_limit = used_atom_cnt;
-
- /* for warn_ambiguous_refs */
- git_config(git_default_config, NULL);
-
- memset(&cbdata, 0, sizeof(cbdata));
- cbdata.grab_pattern = argv;
- for_each_rawref(grab_single_ref, &cbdata);
- refs = cbdata.grab_array;
- num_refs = cbdata.grab_cnt;
-
- for (i = 0; i < used_atom_cnt; i++) {
- if (used_atom[i][0] == '*') {
- need_tagged = 1;
- break;
- }
- }
-
- sort_refs(sort, refs, num_refs);
-
- if (!maxcount || num_refs < maxcount)
- maxcount = num_refs;
- for (i = 0; i < maxcount; i++)
- show_ref(refs[i], format, quote_style);
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "tree.h"
-#include "blob.h"
-#include "tag.h"
-#include "refs.h"
-#include "pack.h"
-#include "cache-tree.h"
-#include "tree-walk.h"
-#include "fsck.h"
-#include "parse-options.h"
-#include "dir.h"
-
-#define REACHABLE 0x0001
-#define SEEN 0x0002
-
-static int show_root;
-static int show_tags;
-static int show_unreachable;
-static int include_reflogs = 1;
-static int check_full = 1;
-static int check_strict;
-static int keep_cache_objects;
-static unsigned char head_sha1[20];
-static const char *head_points_at;
-static int errors_found;
-static int write_lost_and_found;
-static int verbose;
-#define ERROR_OBJECT 01
-#define ERROR_REACHABLE 02
-
-#ifdef NO_D_INO_IN_DIRENT
-#define SORT_DIRENT 0
-#define DIRENT_SORT_HINT(de) 0
-#else
-#define SORT_DIRENT 1
-#define DIRENT_SORT_HINT(de) ((de)->d_ino)
-#endif
-
-static void objreport(struct object *obj, const char *severity,
- const char *err, va_list params)
-{
- fprintf(stderr, "%s in %s %s: ",
- severity, typename(obj->type), sha1_to_hex(obj->sha1));
- vfprintf(stderr, err, params);
- fputs("\n", stderr);
-}
-
-__attribute__((format (printf, 2, 3)))
-static int objerror(struct object *obj, const char *err, ...)
-{
- va_list params;
- va_start(params, err);
- errors_found |= ERROR_OBJECT;
- objreport(obj, "error", err, params);
- va_end(params);
- return -1;
-}
-
-__attribute__((format (printf, 3, 4)))
-static int fsck_error_func(struct object *obj, int type, const char *err, ...)
-{
- va_list params;
- va_start(params, err);
- objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params);
- va_end(params);
- return (type == FSCK_WARN) ? 0 : 1;
-}
-
-static struct object_array pending;
-
-static int mark_object(struct object *obj, int type, void *data)
-{
- struct object *parent = data;
-
- if (!obj) {
- printf("broken link from %7s %s\n",
- typename(parent->type), sha1_to_hex(parent->sha1));
- printf("broken link from %7s %s\n",
- (type == OBJ_ANY ? "unknown" : typename(type)), "unknown");
- errors_found |= ERROR_REACHABLE;
- return 1;
- }
-
- if (type != OBJ_ANY && obj->type != type)
- objerror(parent, "wrong object type in link");
-
- if (obj->flags & REACHABLE)
- return 0;
- obj->flags |= REACHABLE;
- if (!obj->parsed) {
- if (parent && !has_sha1_file(obj->sha1)) {
- printf("broken link from %7s %s\n",
- typename(parent->type), sha1_to_hex(parent->sha1));
- printf(" to %7s %s\n",
- typename(obj->type), sha1_to_hex(obj->sha1));
- errors_found |= ERROR_REACHABLE;
- }
- return 1;
- }
-
- add_object_array(obj, (void *) parent, &pending);
- return 0;
-}
-
-static void mark_object_reachable(struct object *obj)
-{
- mark_object(obj, OBJ_ANY, NULL);
-}
-
-static int traverse_one_object(struct object *obj, struct object *parent)
-{
- int result;
- struct tree *tree = NULL;
-
- if (obj->type == OBJ_TREE) {
- obj->parsed = 0;
- tree = (struct tree *)obj;
- if (parse_tree(tree) < 0)
- return 1; /* error already displayed */
- }
- result = fsck_walk(obj, mark_object, obj);
- if (tree) {
- free(tree->buffer);
- tree->buffer = NULL;
- }
- return result;
-}
-
-static int traverse_reachable(void)
-{
- int result = 0;
- while (pending.nr) {
- struct object_array_entry *entry;
- struct object *obj, *parent;
-
- entry = pending.objects + --pending.nr;
- obj = entry->item;
- parent = (struct object *) entry->name;
- result |= traverse_one_object(obj, parent);
- }
- return !!result;
-}
-
-static int mark_used(struct object *obj, int type, void *data)
-{
- if (!obj)
- return 1;
- obj->used = 1;
- return 0;
-}
-
-/*
- * Check a single reachable object
- */
-static void check_reachable_object(struct object *obj)
-{
- /*
- * We obviously want the object to be parsed,
- * except if it was in a pack-file and we didn't
- * do a full fsck
- */
- if (!obj->parsed) {
- if (has_sha1_pack(obj->sha1))
- return; /* it is in pack - forget about it */
- printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
- errors_found |= ERROR_REACHABLE;
- return;
- }
-}
-
-/*
- * Check a single unreachable object
- */
-static void check_unreachable_object(struct object *obj)
-{
- /*
- * Missing unreachable object? Ignore it. It's not like
- * we miss it (since it can't be reached), nor do we want
- * to complain about it being unreachable (since it does
- * not exist).
- */
- if (!obj->parsed)
- return;
-
- /*
- * Unreachable object that exists? Show it if asked to,
- * since this is something that is prunable.
- */
- if (show_unreachable) {
- printf("unreachable %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
- return;
- }
-
- /*
- * "!used" means that nothing at all points to it, including
- * other unreachable objects. In other words, it's the "tip"
- * of some set of unreachable objects, usually a commit that
- * got dropped.
- *
- * Such starting points are more interesting than some random
- * set of unreachable objects, so we show them even if the user
- * hasn't asked for _all_ unreachable objects. If you have
- * deleted a branch by mistake, this is a prime candidate to
- * start looking at, for example.
- */
- 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_errno("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) {
- if (fwrite(buf, size, 1, f) != 1)
- die_errno("Could not write '%s'",
- filename);
- free(buf);
- }
- } else
- fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
- if (fclose(f))
- die_errno("Could not finish '%s'",
- filename);
- }
- return;
- }
-
- /*
- * Otherwise? It's there, it's unreachable, and some other unreachable
- * object points to it. Ignore it - it's not interesting, and we showed
- * all the interesting cases above.
- */
-}
-
-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
- check_unreachable_object(obj);
-}
-
-static void check_connectivity(void)
-{
- int i, max;
-
- /* Traverse the pending reachable objects */
- traverse_reachable();
-
- /* 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);
-
- if (obj)
- check_object(obj);
- }
-}
-
-static int fsck_sha1(const unsigned char *sha1)
-{
- struct object *obj = parse_object(sha1);
- if (!obj) {
- errors_found |= ERROR_OBJECT;
- return error("%s: object corrupt or missing",
- sha1_to_hex(sha1));
- }
- if (obj->flags & SEEN)
- return 0;
- obj->flags |= SEEN;
-
- if (verbose)
- fprintf(stderr, "Checking %s %s\n",
- typename(obj->type), sha1_to_hex(obj->sha1));
-
- if (fsck_walk(obj, mark_used, NULL))
- objerror(obj, "broken links");
- if (fsck_object(obj, check_strict, fsck_error_func))
- return -1;
-
- if (obj->type == OBJ_TREE) {
- struct tree *item = (struct tree *) obj;
-
- free(item->buffer);
- item->buffer = NULL;
- }
-
- if (obj->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *) obj;
-
- free(commit->buffer);
- commit->buffer = NULL;
-
- if (!commit->parents && show_root)
- printf("root %s\n", sha1_to_hex(commit->object.sha1));
- }
-
- if (obj->type == OBJ_TAG) {
- struct tag *tag = (struct tag *) obj;
-
- if (show_tags && tag->tagged) {
- printf("tagged %s %s", typename(tag->tagged->type), sha1_to_hex(tag->tagged->sha1));
- printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1));
- }
- }
-
- return 0;
-}
-
-/*
- * This is the sorting chunk size: make it reasonably
- * big so that we can sort well..
- */
-#define MAX_SHA1_ENTRIES (1024)
-
-struct sha1_entry {
- unsigned long ino;
- unsigned char sha1[20];
-};
-
-static struct {
- unsigned long nr;
- struct sha1_entry *entry[MAX_SHA1_ENTRIES];
-} sha1_list;
-
-static int ino_compare(const void *_a, const void *_b)
-{
- const struct sha1_entry *a = _a, *b = _b;
- unsigned long ino1 = a->ino, ino2 = b->ino;
- return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0;
-}
-
-static void fsck_sha1_list(void)
-{
- int i, nr = sha1_list.nr;
-
- if (SORT_DIRENT)
- qsort(sha1_list.entry, nr,
- sizeof(struct sha1_entry *), ino_compare);
- for (i = 0; i < nr; i++) {
- struct sha1_entry *entry = sha1_list.entry[i];
- unsigned char *sha1 = entry->sha1;
-
- sha1_list.entry[i] = NULL;
- fsck_sha1(sha1);
- free(entry);
- }
- sha1_list.nr = 0;
-}
-
-static void add_sha1_list(unsigned char *sha1, unsigned long ino)
-{
- struct sha1_entry *entry = xmalloc(sizeof(*entry));
- int nr;
-
- entry->ino = ino;
- hashcpy(entry->sha1, sha1);
- nr = sha1_list.nr;
- if (nr == MAX_SHA1_ENTRIES) {
- fsck_sha1_list();
- nr = 0;
- }
- sha1_list.entry[nr] = entry;
- sha1_list.nr = ++nr;
-}
-
-static void fsck_dir(int i, char *path)
-{
- DIR *dir = opendir(path);
- struct dirent *de;
-
- if (!dir)
- return;
-
- if (verbose)
- fprintf(stderr, "Checking directory %s\n", path);
-
- while ((de = readdir(dir)) != NULL) {
- char name[100];
- unsigned char sha1[20];
-
- if (is_dot_or_dotdot(de->d_name))
- continue;
- if (strlen(de->d_name) == 38) {
- sprintf(name, "%02x", i);
- memcpy(name+2, de->d_name, 39);
- if (get_sha1_hex(name, sha1) < 0)
- break;
- add_sha1_list(sha1, DIRENT_SORT_HINT(de));
- continue;
- }
- if (!prefixcmp(de->d_name, "tmp_obj_"))
- continue;
- fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
- }
- closedir(dir);
-}
-
-static int default_refs;
-
-static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
- const char *email, unsigned long timestamp, int tz,
- const char *message, void *cb_data)
-{
- 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) {
- obj->used = 1;
- mark_object_reachable(obj);
- }
- }
- obj = lookup_object(nsha1);
- if (obj) {
- obj->used = 1;
- mark_object_reachable(obj);
- }
- return 0;
-}
-
-static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data)
-{
- for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL);
- return 0;
-}
-
-static int is_branch(const char *refname)
-{
- return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/");
-}
-
-static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct object *obj;
-
- obj = parse_object(sha1);
- if (!obj) {
- error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
- /* We'll continue with the rest despite the error.. */
- return 0;
- }
- if (obj->type != OBJ_COMMIT && is_branch(refname))
- error("%s: not a commit", refname);
- default_refs++;
- obj->used = 1;
- mark_object_reachable(obj);
-
- return 0;
-}
-
-static void get_default_heads(void)
-{
- if (head_points_at && !is_null_sha1(head_sha1))
- fsck_handle_ref("HEAD", head_sha1, 0, NULL);
- for_each_ref(fsck_handle_ref, NULL);
- if (include_reflogs)
- for_each_reflog(fsck_handle_reflog, NULL);
-
- /*
- * Not having any default heads isn't really fatal, but
- * it does mean that "--unreachable" no longer makes any
- * sense (since in this case everything will obviously
- * be unreachable by definition.
- *
- * Showing dangling objects is valid, though (as those
- * dangling objects are likely lost heads).
- *
- * So we just print a warning about it, and clear the
- * "show_unreachable" flag.
- */
- if (!default_refs) {
- fprintf(stderr, "notice: No default references\n");
- show_unreachable = 0;
- }
-}
-
-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);
- fsck_dir(i, dir);
- }
- fsck_sha1_list();
-}
-
-static int fsck_head_link(void)
-{
- int flag;
- int null_is_error = 0;
-
- if (verbose)
- fprintf(stderr, "Checking HEAD link\n");
-
- head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
- if (!head_points_at)
- return error("Invalid HEAD");
- if (!strcmp(head_points_at, "HEAD"))
- /* detached HEAD */
- null_is_error = 1;
- else if (prefixcmp(head_points_at, "refs/heads/"))
- return error("HEAD points to something strange (%s)",
- head_points_at);
- if (is_null_sha1(head_sha1)) {
- if (null_is_error)
- return error("HEAD: detached HEAD points at nothing");
- fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
- head_points_at + 11);
- }
- return 0;
-}
-
-static int fsck_cache_tree(struct cache_tree *it)
-{
- 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) {
- error("%s: invalid sha1 pointer in cache-tree",
- sha1_to_hex(it->sha1));
- return 1;
- }
- mark_object_reachable(obj);
- obj->used = 1;
- if (obj->type != OBJ_TREE)
- err |= objerror(obj, "non-tree in cache-tree");
- }
- for (i = 0; i < it->subtree_nr; i++)
- err |= fsck_cache_tree(it->down[i]->cache_tree);
- return err;
-}
-
-static char const * const fsck_usage[] = {
- "git fsck [options] [<object>...]",
- NULL
-};
-
-static struct option fsck_opts[] = {
- OPT__VERBOSE(&verbose),
- OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"),
- OPT_BOOLEAN(0, "tags", &show_tags, "report tags"),
- OPT_BOOLEAN(0, "root", &show_root, "report root nodes"),
- OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"),
- OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"),
- OPT_BOOLEAN(0, "full", &check_full, "also consider packs and alternate objects"),
- OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"),
- OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
- "write dangling objects in .git/lost-found"),
- OPT_END(),
-};
-
-int cmd_fsck(int argc, const char **argv, const char *prefix)
-{
- int i, heads;
- struct alternate_object_database *alt;
-
- errors_found = 0;
- read_replace_refs = 0;
-
- argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);
- if (write_lost_and_found) {
- check_full = 1;
- include_reflogs = 0;
- }
-
- fsck_head_link();
- fsck_object_dir(get_object_directory());
-
- prepare_alt_odb();
- for (alt = alt_odb_list; alt; alt = alt->next) {
- char namebuf[PATH_MAX];
- int namelen = alt->name - alt->base;
- memcpy(namebuf, alt->base, namelen);
- namebuf[namelen - 1] = 0;
- fsck_object_dir(namebuf);
- }
-
- if (check_full) {
- struct packed_git *p;
-
- prepare_packed_git();
- for (p = packed_git; p; p = p->next)
- /* verify gives error messages itself */
- verify_pack(p);
-
- for (p = packed_git; p; p = p->next) {
- uint32_t j, num;
- if (open_pack_index(p))
- continue;
- num = p->num_objects;
- for (j = 0; j < num; j++)
- fsck_sha1(nth_packed_object_sha1(p, j));
- }
- }
-
- heads = 0;
- for (i = 0; i < argc; i++) {
- const char *arg = argv[i];
- unsigned char sha1[20];
- if (!get_sha1(arg, sha1)) {
- struct object *obj = lookup_object(sha1);
-
- /* Error is printed by lookup_object(). */
- if (!obj)
- continue;
-
- obj->used = 1;
- mark_object_reachable(obj);
- heads++;
- continue;
- }
- error("invalid parameter: expected sha1, got '%s'", arg);
- }
-
- /*
- * If we've not been given any explicit head information, do the
- * default ones from .git/refs. We also consider the index file
- * in this case (ie this implies --cache).
- */
- if (!heads) {
- get_default_heads();
- keep_cache_objects = 1;
- }
-
- if (keep_cache_objects) {
- read_cache();
- for (i = 0; i < active_nr; i++) {
- unsigned int mode;
- struct blob *blob;
- struct object *obj;
-
- mode = active_cache[i]->ce_mode;
- if (S_ISGITLINK(mode))
- continue;
- blob = lookup_blob(active_cache[i]->sha1);
- if (!blob)
- continue;
- obj = &blob->object;
- obj->used = 1;
- mark_object_reachable(obj);
- }
- if (active_cache_tree)
- fsck_cache_tree(active_cache_tree);
- }
-
- check_connectivity();
- return errors_found;
-}
+++ /dev/null
-/*
- * git gc builtin command
- *
- * Cleanup unreachable files and optimize the repository.
- *
- * Copyright (c) 2007 James Bowes
- *
- * Based on git-gc.sh, which is
- *
- * Copyright (c) 2006 Shawn O. Pearce
- */
-
-#include "builtin.h"
-#include "cache.h"
-#include "parse-options.h"
-#include "run-command.h"
-
-#define FAILED_RUN "failed to run %s"
-
-static const char * const builtin_gc_usage[] = {
- "git gc [options]",
- NULL
-};
-
-static int pack_refs = 1;
-static int aggressive_window = 250;
-static int gc_auto_threshold = 6700;
-static int gc_auto_pack_limit = 50;
-static const char *prune_expire = "2.weeks.ago";
-
-#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[MAX_ADD] = {"repack", "-d", "-l", NULL};
-static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
-static const char *argv_rerere[] = {"rerere", "gc", NULL};
-
-static int gc_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "gc.packrefs")) {
- if (value && !strcmp(value, "notbare"))
- pack_refs = -1;
- else
- pack_refs = git_config_bool(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.aggressivewindow")) {
- aggressive_window = git_config_int(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.auto")) {
- gc_auto_threshold = git_config_int(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.autopacklimit")) {
- gc_auto_pack_limit = git_config_int(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.pruneexpire")) {
- if (value && strcmp(value, "now")) {
- unsigned long now = approxidate("now");
- if (approxidate(value) >= now)
- return error("Invalid %s: '%s'", var, value);
- }
- return git_config_string(&prune_expire, var, value);
- }
- return git_default_config(var, value, cb);
-}
-
-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;
-}
-
-static int too_many_loose_objects(void)
-{
- /*
- * Quickly check if a "gc" is needed, by estimating how
- * many loose objects there are. Because SHA-1 is evenly
- * distributed, we can check only one and get a reasonable
- * estimate.
- */
- char path[PATH_MAX];
- const char *objdir = get_object_directory();
- DIR *dir;
- struct dirent *ent;
- int auto_threshold;
- int num_loose = 0;
- int needed = 0;
-
- if (gc_auto_threshold <= 0)
- return 0;
-
- if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) {
- warning("insanely long object directory %.*s", 50, objdir);
- return 0;
- }
- dir = opendir(path);
- if (!dir)
- return 0;
-
- auto_threshold = (gc_auto_threshold + 255) / 256;
- while ((ent = readdir(dir)) != NULL) {
- if (strspn(ent->d_name, "0123456789abcdef") != 38 ||
- ent->d_name[38] != '\0')
- continue;
- if (++num_loose > auto_threshold) {
- needed = 1;
- break;
- }
- }
- closedir(dir);
- return needed;
-}
-
-static int too_many_packs(void)
-{
- struct packed_git *p;
- int cnt;
-
- if (gc_auto_pack_limit <= 0)
- return 0;
-
- prepare_packed_git();
- for (cnt = 0, p = packed_git; p; p = p->next) {
- if (!p->pack_local)
- continue;
- if (p->pack_keep)
- continue;
- /*
- * Perhaps check the size of the pack and count only
- * very small ones here?
- */
- cnt++;
- }
- return gc_auto_pack_limit <= cnt;
-}
-
-static int need_to_gc(void)
-{
- /*
- * Setting gc.auto to 0 or negative can disable the
- * automatic gc.
- */
- if (gc_auto_threshold <= 0)
- return 0;
-
- /*
- * If there are too many loose objects, but not too many
- * packs, we run "repack -d -l". If there are too many packs,
- * we run "repack -A -d -l". Otherwise we tell the caller
- * there is no need.
- */
- if (too_many_packs())
- append_option(argv_repack,
- prune_expire && !strcmp(prune_expire, "now") ?
- "-a" : "-A",
- MAX_ADD);
- else if (!too_many_loose_objects())
- return 0;
-
- if (run_hook(NULL, "pre-auto-gc", NULL))
- return 0;
- return 1;
-}
-
-int cmd_gc(int argc, const char **argv, const char *prefix)
-{
- int aggressive = 0;
- int auto_gc = 0;
- int quiet = 0;
- char buf[80];
-
- struct option builtin_gc_options[] = {
- OPT__QUIET(&quiet),
- { OPTION_STRING, 0, "prune", &prune_expire, "date",
- "prune unreferenced objects",
- PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
- OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
- OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
- OPT_END()
- };
-
- git_config(gc_config, NULL);
-
- if (pack_refs < 0)
- pack_refs = !is_bare_repository();
-
- argc = parse_options(argc, argv, prefix, builtin_gc_options,
- builtin_gc_usage, 0);
- if (argc > 0)
- usage_with_options(builtin_gc_usage, builtin_gc_options);
-
- if (aggressive) {
- append_option(argv_repack, "-f", MAX_ADD);
- append_option(argv_repack, "--depth=250", MAX_ADD);
- if (aggressive_window > 0) {
- sprintf(buf, "--window=%d", aggressive_window);
- append_option(argv_repack, buf, MAX_ADD);
- }
- }
- if (quiet)
- append_option(argv_repack, "-q", MAX_ADD);
-
- if (auto_gc) {
- /*
- * Auto-gc should be least intrusive as possible.
- */
- if (!need_to_gc())
- return 0;
- fprintf(stderr,
- "Auto packing the repository for optimum performance.%s\n",
- quiet
- ? ""
- : (" You may also\n"
- "run \"git gc\" manually. See "
- "\"git help gc\" for more information."));
- } else
- append_option(argv_repack,
- prune_expire && !strcmp(prune_expire, "now")
- ? "-a" : "-A",
- MAX_ADD);
-
- if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_pack_refs[0]);
-
- if (run_command_v_opt(argv_reflog, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_reflog[0]);
-
- if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_repack[0]);
-
- if (prune_expire) {
- argv_prune[2] = prune_expire;
- if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_prune[0]);
- }
-
- if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_rerere[0]);
-
- if (auto_gc && too_many_loose_objects())
- warning("There are too many unreachable loose objects; "
- "run 'git prune' to remove them.");
-
- return 0;
-}
+++ /dev/null
-/*
- * Builtin "git grep"
- *
- * Copyright (c) 2006 Junio C Hamano
- */
-#include "cache.h"
-#include "blob.h"
-#include "tree.h"
-#include "commit.h"
-#include "tag.h"
-#include "tree-walk.h"
-#include "builtin.h"
-#include "parse-options.h"
-#include "userdiff.h"
-#include "grep.h"
-#include "quote.h"
-
-#ifndef NO_PTHREADS
-#include "thread-utils.h"
-#include <pthread.h>
-#endif
-
-static char const * const grep_usage[] = {
- "git grep [options] [-e] <pattern> [<rev>...] [[--] path...]",
- NULL
-};
-
-static int use_threads = 1;
-
-#ifndef NO_PTHREADS
-#define THREADS 8
-static pthread_t threads[THREADS];
-
-static void *load_sha1(const unsigned char *sha1, unsigned long *size,
- const char *name);
-static void *load_file(const char *filename, size_t *sz);
-
-enum work_type {WORK_SHA1, WORK_FILE};
-
-/* We use one producer thread and THREADS consumer
- * threads. The producer adds struct work_items to 'todo' and the
- * consumers pick work items from the same array.
- */
-struct work_item
-{
- enum work_type type;
- char *name;
-
- /* if type == WORK_SHA1, then 'identifier' is a SHA1,
- * otherwise type == WORK_FILE, and 'identifier' is a NUL
- * terminated filename.
- */
- void *identifier;
- char done;
- struct strbuf out;
-};
-
-/* In the range [todo_done, todo_start) in 'todo' we have work_items
- * that have been or are processed by a consumer thread. We haven't
- * written the result for these to stdout yet.
- *
- * The work_items in [todo_start, todo_end) are waiting to be picked
- * up by a consumer thread.
- *
- * The ranges are modulo TODO_SIZE.
- */
-#define TODO_SIZE 128
-static struct work_item todo[TODO_SIZE];
-static int todo_start;
-static int todo_end;
-static int todo_done;
-
-/* Has all work items been added? */
-static int all_work_added;
-
-/* This lock protects all the variables above. */
-static pthread_mutex_t grep_mutex;
-
-/* Used to serialize calls to read_sha1_file. */
-static pthread_mutex_t read_sha1_mutex;
-
-#define grep_lock() pthread_mutex_lock(&grep_mutex)
-#define grep_unlock() pthread_mutex_unlock(&grep_mutex)
-#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex)
-#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex)
-
-/* Signalled when a new work_item is added to todo. */
-static pthread_cond_t cond_add;
-
-/* Signalled when the result from one work_item is written to
- * stdout.
- */
-static pthread_cond_t cond_write;
-
-/* Signalled when we are finished with everything. */
-static pthread_cond_t cond_result;
-
-static void add_work(enum work_type type, char *name, void *id)
-{
- grep_lock();
-
- while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) {
- pthread_cond_wait(&cond_write, &grep_mutex);
- }
-
- todo[todo_end].type = type;
- todo[todo_end].name = name;
- todo[todo_end].identifier = id;
- todo[todo_end].done = 0;
- strbuf_reset(&todo[todo_end].out);
- todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
-
- pthread_cond_signal(&cond_add);
- grep_unlock();
-}
-
-static struct work_item *get_work(void)
-{
- struct work_item *ret;
-
- grep_lock();
- while (todo_start == todo_end && !all_work_added) {
- pthread_cond_wait(&cond_add, &grep_mutex);
- }
-
- if (todo_start == todo_end && all_work_added) {
- ret = NULL;
- } else {
- ret = &todo[todo_start];
- todo_start = (todo_start + 1) % ARRAY_SIZE(todo);
- }
- grep_unlock();
- return ret;
-}
-
-static void grep_sha1_async(struct grep_opt *opt, char *name,
- const unsigned char *sha1)
-{
- unsigned char *s;
- s = xmalloc(20);
- memcpy(s, sha1, 20);
- add_work(WORK_SHA1, name, s);
-}
-
-static void grep_file_async(struct grep_opt *opt, char *name,
- const char *filename)
-{
- add_work(WORK_FILE, name, xstrdup(filename));
-}
-
-static void work_done(struct work_item *w)
-{
- int old_done;
-
- grep_lock();
- w->done = 1;
- old_done = todo_done;
- for(; todo[todo_done].done && todo_done != todo_start;
- todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
- w = &todo[todo_done];
- write_or_die(1, w->out.buf, w->out.len);
- free(w->name);
- free(w->identifier);
- }
-
- if (old_done != todo_done)
- pthread_cond_signal(&cond_write);
-
- if (all_work_added && todo_done == todo_end)
- pthread_cond_signal(&cond_result);
-
- grep_unlock();
-}
-
-static void *run(void *arg)
-{
- int hit = 0;
- struct grep_opt *opt = arg;
-
- while (1) {
- struct work_item *w = get_work();
- if (!w)
- break;
-
- opt->output_priv = w;
- if (w->type == WORK_SHA1) {
- unsigned long sz;
- void* data = load_sha1(w->identifier, &sz, w->name);
-
- if (data) {
- hit |= grep_buffer(opt, w->name, data, sz);
- free(data);
- }
- } else if (w->type == WORK_FILE) {
- size_t sz;
- void* data = load_file(w->identifier, &sz);
- if (data) {
- hit |= grep_buffer(opt, w->name, data, sz);
- free(data);
- }
- } else {
- assert(0);
- }
-
- work_done(w);
- }
- free_grep_patterns(arg);
- free(arg);
-
- return (void*) (intptr_t) hit;
-}
-
-static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size)
-{
- struct work_item *w = opt->output_priv;
- strbuf_add(&w->out, buf, size);
-}
-
-static void start_threads(struct grep_opt *opt)
-{
- int i;
-
- pthread_mutex_init(&grep_mutex, NULL);
- pthread_mutex_init(&read_sha1_mutex, NULL);
- pthread_cond_init(&cond_add, NULL);
- pthread_cond_init(&cond_write, NULL);
- pthread_cond_init(&cond_result, NULL);
-
- for (i = 0; i < ARRAY_SIZE(todo); i++) {
- strbuf_init(&todo[i].out, 0);
- }
-
- for (i = 0; i < ARRAY_SIZE(threads); i++) {
- int err;
- struct grep_opt *o = grep_opt_dup(opt);
- o->output = strbuf_out;
- compile_grep_patterns(o);
- err = pthread_create(&threads[i], NULL, run, o);
-
- if (err)
- die("grep: failed to create thread: %s",
- strerror(err));
- }
-}
-
-static int wait_all(void)
-{
- int hit = 0;
- int i;
-
- grep_lock();
- all_work_added = 1;
-
- /* Wait until all work is done. */
- while (todo_done != todo_end)
- pthread_cond_wait(&cond_result, &grep_mutex);
-
- /* Wake up all the consumer threads so they can see that there
- * is no more work to do.
- */
- pthread_cond_broadcast(&cond_add);
- grep_unlock();
-
- for (i = 0; i < ARRAY_SIZE(threads); i++) {
- void *h;
- pthread_join(threads[i], &h);
- hit |= (int) (intptr_t) h;
- }
-
- pthread_mutex_destroy(&grep_mutex);
- pthread_mutex_destroy(&read_sha1_mutex);
- pthread_cond_destroy(&cond_add);
- pthread_cond_destroy(&cond_write);
- pthread_cond_destroy(&cond_result);
-
- return hit;
-}
-#else /* !NO_PTHREADS */
-#define read_sha1_lock()
-#define read_sha1_unlock()
-
-static int wait_all(void)
-{
- return 0;
-}
-#endif
-
-static int grep_config(const char *var, const char *value, void *cb)
-{
- struct grep_opt *opt = cb;
-
- switch (userdiff_config(var, value)) {
- case 0: break;
- case -1: return -1;
- default: return 0;
- }
-
- if (!strcmp(var, "color.grep")) {
- opt->color = git_config_colorbool(var, value, -1);
- return 0;
- }
- if (!strcmp(var, "color.grep.match")) {
- if (!value)
- return config_error_nonbool(var);
- color_parse(value, var, opt->color_match);
- return 0;
- }
- return git_color_default_config(var, value, cb);
-}
-
-/*
- * Return non-zero if max_depth is negative or path has no more then max_depth
- * slashes.
- */
-static int accept_subdir(const char *path, int max_depth)
-{
- if (max_depth < 0)
- return 1;
-
- while ((path = strchr(path, '/')) != NULL) {
- max_depth--;
- if (max_depth < 0)
- return 0;
- path++;
- }
- return 1;
-}
-
-/*
- * Return non-zero if name is a subdirectory of match and is not too deep.
- */
-static int is_subdir(const char *name, int namelen,
- const char *match, int matchlen, int max_depth)
-{
- if (matchlen > namelen || strncmp(name, match, matchlen))
- return 0;
-
- if (name[matchlen] == '\0') /* exact match */
- return 1;
-
- if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
- return accept_subdir(name + matchlen + 1, max_depth);
-
- return 0;
-}
-
-/*
- * git grep pathspecs are somewhat different from diff-tree pathspecs;
- * pathname wildcards are allowed.
- */
-static int pathspec_matches(const char **paths, const char *name, int max_depth)
-{
- int namelen, i;
- if (!paths || !*paths)
- return accept_subdir(name, max_depth);
- namelen = strlen(name);
- for (i = 0; paths[i]; i++) {
- const char *match = paths[i];
- int matchlen = strlen(match);
- const char *cp, *meta;
-
- if (is_subdir(name, namelen, match, matchlen, max_depth))
- return 1;
- if (!fnmatch(match, name, 0))
- return 1;
- if (name[namelen-1] != '/')
- continue;
-
- /* We are being asked if the directory ("name") is worth
- * descending into.
- *
- * Find the longest leading directory name that does
- * not have metacharacter in the pathspec; the name
- * we are looking at must overlap with that directory.
- */
- for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
- char ch = *cp;
- if (ch == '*' || ch == '[' || ch == '?') {
- meta = cp;
- break;
- }
- }
- if (!meta)
- meta = cp; /* fully literal */
-
- if (namelen <= meta - match) {
- /* Looking at "Documentation/" and
- * the pattern says "Documentation/howto/", or
- * "Documentation/diff*.txt". The name we
- * have should match prefix.
- */
- if (!memcmp(match, name, namelen))
- return 1;
- continue;
- }
-
- if (meta - match < namelen) {
- /* Looking at "Documentation/howto/" and
- * the pattern says "Documentation/h*";
- * match up to "Do.../h"; this avoids descending
- * into "Documentation/technical/".
- */
- if (!memcmp(match, name, meta - match))
- return 1;
- continue;
- }
- }
- return 0;
-}
-
-static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
-{
- void *data;
-
- if (use_threads) {
- read_sha1_lock();
- data = read_sha1_file(sha1, type, size);
- read_sha1_unlock();
- } else {
- data = read_sha1_file(sha1, type, size);
- }
- return data;
-}
-
-static void *load_sha1(const unsigned char *sha1, unsigned long *size,
- const char *name)
-{
- enum object_type type;
- void *data = lock_and_read_sha1_file(sha1, &type, size);
-
- if (!data)
- error("'%s': unable to read %s", name, sha1_to_hex(sha1));
-
- return data;
-}
-
-static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
- const char *filename, int tree_name_len)
-{
- struct strbuf pathbuf = STRBUF_INIT;
- char *name;
-
- if (opt->relative && opt->prefix_length) {
- quote_path_relative(filename + tree_name_len, -1, &pathbuf,
- opt->prefix);
- strbuf_insert(&pathbuf, 0, filename, tree_name_len);
- } else {
- strbuf_addstr(&pathbuf, filename);
- }
-
- name = strbuf_detach(&pathbuf, NULL);
-
-#ifndef NO_PTHREADS
- if (use_threads) {
- grep_sha1_async(opt, name, sha1);
- return 0;
- } else
-#endif
- {
- int hit;
- unsigned long sz;
- void *data = load_sha1(sha1, &sz, name);
- if (!data)
- hit = 0;
- else
- hit = grep_buffer(opt, name, data, sz);
-
- free(data);
- free(name);
- return hit;
- }
-}
-
-static void *load_file(const char *filename, size_t *sz)
-{
- struct stat st;
- char *data;
- int i;
-
- if (lstat(filename, &st) < 0) {
- err_ret:
- if (errno != ENOENT)
- error("'%s': %s", filename, strerror(errno));
- return 0;
- }
- if (!S_ISREG(st.st_mode))
- return 0;
- *sz = xsize_t(st.st_size);
- i = open(filename, O_RDONLY);
- if (i < 0)
- goto err_ret;
- data = xmalloc(*sz + 1);
- if (st.st_size != read_in_full(i, data, *sz)) {
- error("'%s': short read %s", filename, strerror(errno));
- close(i);
- free(data);
- return 0;
- }
- close(i);
- data[*sz] = 0;
- return data;
-}
-
-static int grep_file(struct grep_opt *opt, const char *filename)
-{
- struct strbuf buf = STRBUF_INIT;
- char *name;
-
- if (opt->relative && opt->prefix_length)
- quote_path_relative(filename, -1, &buf, opt->prefix);
- else
- strbuf_addstr(&buf, filename);
- name = strbuf_detach(&buf, NULL);
-
-#ifndef NO_PTHREADS
- if (use_threads) {
- grep_file_async(opt, name, filename);
- return 0;
- } else
-#endif
- {
- int hit;
- size_t sz;
- void *data = load_file(filename, &sz);
- if (!data)
- hit = 0;
- else
- hit = grep_buffer(opt, name, data, sz);
-
- free(data);
- free(name);
- return hit;
- }
-}
-
-static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
-{
- int hit = 0;
- int nr;
- read_cache();
-
- for (nr = 0; nr < active_nr; nr++) {
- struct cache_entry *ce = active_cache[nr];
- if (!S_ISREG(ce->ce_mode))
- continue;
- if (!pathspec_matches(paths, ce->name, opt->max_depth))
- continue;
- /*
- * If CE_VALID is on, we assume worktree file and its cache entry
- * are identical, even if worktree file has been modified, so use
- * cache version instead
- */
- if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
- if (ce_stage(ce))
- continue;
- hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
- }
- else
- hit |= grep_file(opt, ce->name);
- if (ce_stage(ce)) {
- do {
- nr++;
- } while (nr < active_nr &&
- !strcmp(ce->name, active_cache[nr]->name));
- nr--; /* compensate for loop control */
- }
- if (hit && opt->status_only)
- break;
- }
- free_grep_patterns(opt);
- return hit;
-}
-
-static int grep_tree(struct grep_opt *opt, const char **paths,
- struct tree_desc *tree,
- const char *tree_name, const char *base)
-{
- int len;
- int hit = 0;
- struct name_entry entry;
- char *down;
- int tn_len = strlen(tree_name);
- struct strbuf pathbuf;
-
- strbuf_init(&pathbuf, PATH_MAX + tn_len);
-
- if (tn_len) {
- strbuf_add(&pathbuf, tree_name, tn_len);
- strbuf_addch(&pathbuf, ':');
- tn_len = pathbuf.len;
- }
- strbuf_addstr(&pathbuf, base);
- len = pathbuf.len;
-
- while (tree_entry(tree, &entry)) {
- int te_len = tree_entry_len(entry.path, entry.sha1);
- pathbuf.len = len;
- strbuf_add(&pathbuf, entry.path, te_len);
-
- if (S_ISDIR(entry.mode))
- /* Match "abc/" against pathspec to
- * decide if we want to descend into "abc"
- * directory.
- */
- strbuf_addch(&pathbuf, '/');
-
- down = pathbuf.buf + tn_len;
- if (!pathspec_matches(paths, down, opt->max_depth))
- ;
- else if (S_ISREG(entry.mode))
- hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
- else if (S_ISDIR(entry.mode)) {
- enum object_type type;
- struct tree_desc sub;
- void *data;
- unsigned long size;
-
- data = lock_and_read_sha1_file(entry.sha1, &type, &size);
- if (!data)
- die("unable to read tree (%s)",
- sha1_to_hex(entry.sha1));
- init_tree_desc(&sub, data, size);
- hit |= grep_tree(opt, paths, &sub, tree_name, down);
- free(data);
- }
- if (hit && opt->status_only)
- break;
- }
- strbuf_release(&pathbuf);
- return hit;
-}
-
-static int grep_object(struct grep_opt *opt, const char **paths,
- struct object *obj, const char *name)
-{
- if (obj->type == OBJ_BLOB)
- return grep_sha1(opt, obj->sha1, name, 0);
- if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
- struct tree_desc tree;
- void *data;
- unsigned long size;
- int hit;
- data = read_object_with_reference(obj->sha1, tree_type,
- &size, NULL);
- if (!data)
- die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
- init_tree_desc(&tree, data, size);
- hit = grep_tree(opt, paths, &tree, name, "");
- free(data);
- return hit;
- }
- die("unable to grep from object of type %s", typename(obj->type));
-}
-
-static int context_callback(const struct option *opt, const char *arg,
- int unset)
-{
- struct grep_opt *grep_opt = opt->value;
- int value;
- const char *endp;
-
- if (unset) {
- grep_opt->pre_context = grep_opt->post_context = 0;
- return 0;
- }
- value = strtol(arg, (char **)&endp, 10);
- if (*endp) {
- return error("switch `%c' expects a numerical value",
- opt->short_name);
- }
- grep_opt->pre_context = grep_opt->post_context = value;
- return 0;
-}
-
-static int file_callback(const struct option *opt, const char *arg, int unset)
-{
- struct grep_opt *grep_opt = opt->value;
- FILE *patterns;
- int lno = 0;
- struct strbuf sb = STRBUF_INIT;
-
- patterns = fopen(arg, "r");
- if (!patterns)
- die_errno("cannot open '%s'", arg);
- while (strbuf_getline(&sb, patterns, '\n') == 0) {
- /* ignore empty line like grep does */
- if (sb.len == 0)
- continue;
- append_grep_pattern(grep_opt, strbuf_detach(&sb, NULL), arg,
- ++lno, GREP_PATTERN);
- }
- fclose(patterns);
- strbuf_release(&sb);
- return 0;
-}
-
-static int not_callback(const struct option *opt, const char *arg, int unset)
-{
- struct grep_opt *grep_opt = opt->value;
- append_grep_pattern(grep_opt, "--not", "command line", 0, GREP_NOT);
- return 0;
-}
-
-static int and_callback(const struct option *opt, const char *arg, int unset)
-{
- struct grep_opt *grep_opt = opt->value;
- append_grep_pattern(grep_opt, "--and", "command line", 0, GREP_AND);
- return 0;
-}
-
-static int open_callback(const struct option *opt, const char *arg, int unset)
-{
- struct grep_opt *grep_opt = opt->value;
- append_grep_pattern(grep_opt, "(", "command line", 0, GREP_OPEN_PAREN);
- return 0;
-}
-
-static int close_callback(const struct option *opt, const char *arg, int unset)
-{
- struct grep_opt *grep_opt = opt->value;
- append_grep_pattern(grep_opt, ")", "command line", 0, GREP_CLOSE_PAREN);
- return 0;
-}
-
-static int pattern_callback(const struct option *opt, const char *arg,
- int unset)
-{
- struct grep_opt *grep_opt = opt->value;
- append_grep_pattern(grep_opt, arg, "-e option", 0, GREP_PATTERN);
- return 0;
-}
-
-static int help_callback(const struct option *opt, const char *arg, int unset)
-{
- return -1;
-}
-
-int cmd_grep(int argc, const char **argv, const char *prefix)
-{
- int hit = 0;
- int cached = 0;
- int seen_dashdash = 0;
- int external_grep_allowed__ignored;
- struct grep_opt opt;
- struct object_array list = { 0, 0, NULL };
- const char **paths = NULL;
- int i;
- int dummy;
- struct option options[] = {
- OPT_BOOLEAN(0, "cached", &cached,
- "search in index instead of in the work tree"),
- OPT_GROUP(""),
- OPT_BOOLEAN('v', "invert-match", &opt.invert,
- "show non-matching lines"),
- OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case,
- "case insensitive matching"),
- OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp,
- "match patterns only at word boundaries"),
- OPT_SET_INT('a', "text", &opt.binary,
- "process binary files as text", GREP_BINARY_TEXT),
- OPT_SET_INT('I', NULL, &opt.binary,
- "don't match patterns in binary files",
- GREP_BINARY_NOMATCH),
- { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth",
- "descend at most <depth> levels", PARSE_OPT_NONEG,
- NULL, 1 },
- OPT_GROUP(""),
- OPT_BIT('E', "extended-regexp", &opt.regflags,
- "use extended POSIX regular expressions", REG_EXTENDED),
- OPT_NEGBIT('G', "basic-regexp", &opt.regflags,
- "use basic POSIX regular expressions (default)",
- REG_EXTENDED),
- OPT_BOOLEAN('F', "fixed-strings", &opt.fixed,
- "interpret patterns as fixed strings"),
- OPT_GROUP(""),
- OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"),
- OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1),
- OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1),
- OPT_NEGBIT(0, "full-name", &opt.relative,
- "show filenames relative to top directory", 1),
- OPT_BOOLEAN('l', "files-with-matches", &opt.name_only,
- "show only filenames instead of matching lines"),
- OPT_BOOLEAN(0, "name-only", &opt.name_only,
- "synonym for --files-with-matches"),
- OPT_BOOLEAN('L', "files-without-match",
- &opt.unmatch_name_only,
- "show only the names of files without match"),
- OPT_BOOLEAN('z', "null", &opt.null_following_name,
- "print NUL after filenames"),
- OPT_BOOLEAN('c', "count", &opt.count,
- "show the number of matches instead of matching lines"),
- OPT_SET_INT(0, "color", &opt.color, "highlight matches", 1),
- OPT_GROUP(""),
- OPT_CALLBACK('C', NULL, &opt, "n",
- "show <n> context lines before and after matches",
- context_callback),
- OPT_INTEGER('B', NULL, &opt.pre_context,
- "show <n> context lines before matches"),
- OPT_INTEGER('A', NULL, &opt.post_context,
- "show <n> context lines after matches"),
- OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM",
- context_callback),
- OPT_BOOLEAN('p', "show-function", &opt.funcname,
- "show a line with the function name before matches"),
- OPT_GROUP(""),
- OPT_CALLBACK('f', NULL, &opt, "file",
- "read patterns from file", file_callback),
- { OPTION_CALLBACK, 'e', NULL, &opt, "pattern",
- "match <pattern>", PARSE_OPT_NONEG, pattern_callback },
- { OPTION_CALLBACK, 0, "and", &opt, NULL,
- "combine patterns specified with -e",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
- OPT_BOOLEAN(0, "or", &dummy, ""),
- { OPTION_CALLBACK, 0, "not", &opt, NULL, "",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback },
- { OPTION_CALLBACK, '(', NULL, &opt, NULL, "",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
- open_callback },
- { OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
- close_callback },
- OPT_BOOLEAN('q', "quiet", &opt.status_only,
- "indicate hit with exit status without output"),
- OPT_BOOLEAN(0, "all-match", &opt.all_match,
- "show only matches from files that match all patterns"),
- OPT_GROUP(""),
- OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored,
- "allow calling of grep(1) (ignored by this build)"),
- { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage",
- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
- OPT_END()
- };
-
- /*
- * 'git grep -h', unlike 'git grep -h <pattern>', is a request
- * to show usage information and exit.
- */
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage_with_options(grep_usage, options);
-
- memset(&opt, 0, sizeof(opt));
- opt.prefix = prefix;
- opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
- opt.relative = 1;
- opt.pathname = 1;
- opt.pattern_tail = &opt.pattern_list;
- opt.header_tail = &opt.header_list;
- opt.regflags = REG_NEWLINE;
- opt.max_depth = -1;
-
- strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
- opt.color = -1;
- git_config(grep_config, &opt);
- if (opt.color == -1)
- opt.color = git_use_color_default;
-
- /*
- * If there is no -- then the paths must exist in the working
- * tree. If there is no explicit pattern specified with -e or
- * -f, we take the first unrecognized non option to be the
- * pattern, but then what follows it must be zero or more
- * valid refs up to the -- (if exists), and then existing
- * paths. If there is an explicit pattern, then the first
- * unrecognized non option is the beginning of the refs list
- * that continues up to the -- (if exists), and then paths.
- */
- argc = parse_options(argc, argv, prefix, options, grep_usage,
- PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_STOP_AT_NON_OPTION |
- PARSE_OPT_NO_INTERNAL_HELP);
-
- /*
- * skip a -- separator; we know it cannot be
- * separating revisions from pathnames if
- * we haven't even had any patterns yet
- */
- if (argc > 0 && !opt.pattern_list && !strcmp(argv[0], "--")) {
- argv++;
- argc--;
- }
-
- /* First unrecognized non-option token */
- if (argc > 0 && !opt.pattern_list) {
- append_grep_pattern(&opt, argv[0], "command line", 0,
- GREP_PATTERN);
- argv++;
- argc--;
- }
-
- if (!opt.pattern_list)
- die("no pattern given.");
- if (!opt.fixed && opt.ignore_case)
- opt.regflags |= REG_ICASE;
- if ((opt.regflags != REG_NEWLINE) && opt.fixed)
- die("cannot mix --fixed-strings and regexp");
-
-#ifndef NO_PTHREADS
- if (online_cpus() == 1 || !grep_threads_ok(&opt))
- use_threads = 0;
-
- if (use_threads)
- start_threads(&opt);
-#else
- use_threads = 0;
-#endif
-
- compile_grep_patterns(&opt);
-
- /* Check revs and then paths */
- for (i = 0; i < argc; i++) {
- const char *arg = argv[i];
- unsigned char sha1[20];
- /* Is it a rev? */
- if (!get_sha1(arg, sha1)) {
- struct object *object = parse_object(sha1);
- if (!object)
- die("bad object %s", arg);
- add_object_array(object, arg, &list);
- continue;
- }
- if (!strcmp(arg, "--")) {
- i++;
- seen_dashdash = 1;
- }
- break;
- }
-
- /* The rest are paths */
- if (!seen_dashdash) {
- int j;
- for (j = i; j < argc; j++)
- verify_filename(prefix, argv[j]);
- }
-
- if (i < argc)
- paths = get_pathspec(prefix, argv + i);
- else if (prefix) {
- paths = xcalloc(2, sizeof(const char *));
- paths[0] = prefix;
- paths[1] = NULL;
- }
-
- if (!list.nr) {
- int hit;
- if (!cached)
- setup_work_tree();
-
- hit = grep_cache(&opt, paths, cached);
- if (use_threads)
- hit |= wait_all();
- return !hit;
- }
-
- if (cached)
- die("both --cached and trees are given.");
-
- for (i = 0; i < list.nr; i++) {
- struct object *real_obj;
- real_obj = deref_tag(list.objects[i].item, NULL, 0);
- if (grep_object(&opt, paths, real_obj, list.objects[i].name)) {
- hit = 1;
- if (opt.status_only)
- break;
- }
- }
-
- if (use_threads)
- hit |= wait_all();
- free_grep_patterns(&opt);
- return !hit;
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- * Copyright (C) Junio C Hamano, 2005
- */
-#include "cache.h"
-#include "blob.h"
-#include "quote.h"
-#include "parse-options.h"
-#include "exec_cmd.h"
-
-static void hash_fd(int fd, const char *type, int write_object, const char *path)
-{
- struct stat st;
- unsigned char sha1[20];
- if (fstat(fd, &st) < 0 ||
- index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
- die(write_object
- ? "Unable to add %s to database"
- : "Unable to hash %s", path);
- printf("%s\n", sha1_to_hex(sha1));
- maybe_flush_or_die(stdout, "hash to stdout");
-}
-
-static void hash_object(const char *path, const char *type, int write_object,
- const char *vpath)
-{
- int fd;
- fd = open(path, O_RDONLY);
- if (fd < 0)
- die_errno("Cannot open '%s'", path);
- hash_fd(fd, type, write_object, vpath);
-}
-
-static void hash_stdin_paths(const char *type, int write_objects)
-{
- struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
-
- while (strbuf_getline(&buf, stdin, '\n') != EOF) {
- if (buf.buf[0] == '"') {
- strbuf_reset(&nbuf);
- if (unquote_c_style(&nbuf, buf.buf, NULL))
- die("line is badly quoted");
- strbuf_swap(&buf, &nbuf);
- }
- hash_object(buf.buf, type, write_objects, buf.buf);
- }
- strbuf_release(&buf);
- strbuf_release(&nbuf);
-}
-
-static const char * const hash_object_usage[] = {
- "git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...",
- "git hash-object --stdin-paths < <list-of-paths>",
- NULL
-};
-
-static const char *type;
-static int write_object;
-static int hashstdin;
-static int stdin_paths;
-static int no_filters;
-static const char *vpath;
-
-static const struct option hash_object_options[] = {
- OPT_STRING('t', NULL, &type, "type", "object type"),
- OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"),
- OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"),
- OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"),
- OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"),
- OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"),
- OPT_END()
-};
-
-int cmd_hash_object(int argc, const char **argv, const char *prefix)
-{
- int i;
- int prefix_length = -1;
- const char *errstr = NULL;
-
- type = blob_type;
-
- argc = parse_options(argc, argv, NULL, hash_object_options,
- hash_object_usage, 0);
-
- if (write_object) {
- prefix = setup_git_directory();
- prefix_length = prefix ? strlen(prefix) : 0;
- if (vpath && prefix)
- vpath = prefix_filename(prefix, prefix_length, vpath);
- }
-
- git_config(git_default_config, NULL);
-
- if (stdin_paths) {
- if (hashstdin)
- errstr = "Can't use --stdin-paths with --stdin";
- else if (argc)
- errstr = "Can't specify files with --stdin-paths";
- else if (vpath)
- errstr = "Can't use --stdin-paths with --path";
- else if (no_filters)
- errstr = "Can't use --stdin-paths with --no-filters";
- }
- else {
- if (hashstdin > 1)
- errstr = "Multiple --stdin arguments are not supported";
- if (vpath && no_filters)
- errstr = "Can't use --path with --no-filters";
- }
-
- if (errstr) {
- error("%s", errstr);
- usage_with_options(hash_object_usage, hash_object_options);
- }
-
- if (hashstdin)
- hash_fd(0, type, write_object, vpath);
-
- for (i = 0 ; i < argc; i++) {
- const char *arg = argv[i];
-
- if (0 <= prefix_length)
- arg = prefix_filename(prefix, prefix_length, arg);
- hash_object(arg, type, write_object,
- no_filters ? NULL : vpath ? vpath : arg);
- }
-
- if (stdin_paths)
- hash_stdin_paths(type, write_object);
-
- return 0;
-}
+++ /dev/null
-/*
- * builtin-help.c
- *
- * Builtin help command
- */
-#include "cache.h"
-#include "builtin.h"
-#include "exec_cmd.h"
-#include "common-cmds.h"
-#include "parse-options.h"
-#include "run-command.h"
-#include "help.h"
-
-static struct man_viewer_list {
- struct man_viewer_list *next;
- char name[FLEX_ARRAY];
-} *man_viewer_list;
-
-static struct man_viewer_info_list {
- struct man_viewer_info_list *next;
- const char *info;
- char name[FLEX_ARRAY];
-} *man_viewer_info_list;
-
-enum help_format {
- HELP_FORMAT_NONE,
- HELP_FORMAT_MAN,
- HELP_FORMAT_INFO,
- HELP_FORMAT_WEB,
-};
-
-static int show_all = 0;
-static enum help_format help_format = HELP_FORMAT_NONE;
-static struct option builtin_help_options[] = {
- OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
- OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
- OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
- HELP_FORMAT_WEB),
- OPT_SET_INT('i', "info", &help_format, "show info page",
- HELP_FORMAT_INFO),
- OPT_END(),
-};
-
-static const char * const builtin_help_usage[] = {
- "git help [--all] [--man|--web|--info] [command]",
- NULL
-};
-
-static enum help_format parse_help_format(const char *format)
-{
- if (!strcmp(format, "man"))
- return HELP_FORMAT_MAN;
- if (!strcmp(format, "info"))
- return HELP_FORMAT_INFO;
- if (!strcmp(format, "web") || !strcmp(format, "html"))
- return HELP_FORMAT_WEB;
- die("unrecognized help format '%s'", format);
-}
-
-static const char *get_man_viewer_info(const char *name)
-{
- struct man_viewer_info_list *viewer;
-
- for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
- {
- if (!strcasecmp(name, viewer->name))
- return viewer->info;
- }
- return NULL;
-}
-
-static int check_emacsclient_version(void)
-{
- struct strbuf buffer = STRBUF_INIT;
- struct child_process ec_process;
- const char *argv_ec[] = { "emacsclient", "--version", NULL };
- int version;
-
- /* emacsclient prints its version number on stderr */
- memset(&ec_process, 0, sizeof(ec_process));
- ec_process.argv = argv_ec;
- ec_process.err = -1;
- ec_process.stdout_to_stderr = 1;
- if (start_command(&ec_process))
- return error("Failed to start emacsclient.");
-
- strbuf_read(&buffer, ec_process.err, 20);
- close(ec_process.err);
-
- /*
- * Don't bother checking return value, because "emacsclient --version"
- * seems to always exits with code 1.
- */
- finish_command(&ec_process);
-
- if (prefixcmp(buffer.buf, "emacsclient")) {
- strbuf_release(&buffer);
- return error("Failed to parse emacsclient version.");
- }
-
- strbuf_remove(&buffer, 0, strlen("emacsclient"));
- version = atoi(buffer.buf);
-
- if (version < 22) {
- strbuf_release(&buffer);
- return error("emacsclient version '%d' too old (< 22).",
- version);
- }
-
- strbuf_release(&buffer);
- return 0;
-}
-
-static void exec_woman_emacs(const char *path, const char *page)
-{
- if (!check_emacsclient_version()) {
- /* This works only with emacsclient version >= 22. */
- struct strbuf man_page = STRBUF_INIT;
-
- if (!path)
- path = "emacsclient";
- strbuf_addf(&man_page, "(woman \"%s\")", page);
- execlp(path, "emacsclient", "-e", man_page.buf, NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
- }
-}
-
-static void exec_man_konqueror(const char *path, const char *page)
-{
- const char *display = getenv("DISPLAY");
- if (display && *display) {
- struct strbuf man_page = STRBUF_INIT;
- const char *filename = "kfmclient";
-
- /* It's simpler to launch konqueror using kfmclient. */
- if (path) {
- const char *file = strrchr(path, '/');
- if (file && !strcmp(file + 1, "konqueror")) {
- char *new = xstrdup(path);
- char *dest = strrchr(new, '/');
-
- /* strlen("konqueror") == strlen("kfmclient") */
- strcpy(dest + 1, "kfmclient");
- path = new;
- }
- if (file)
- filename = file;
- } else
- path = "kfmclient";
- strbuf_addf(&man_page, "man:%s(1)", page);
- execlp(path, filename, "newTab", man_page.buf, NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
- }
-}
-
-static void exec_man_man(const char *path, const char *page)
-{
- if (!path)
- path = "man";
- execlp(path, "man", page, NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
-}
-
-static void exec_man_cmd(const char *cmd, const char *page)
-{
- struct strbuf shell_cmd = STRBUF_INIT;
- strbuf_addf(&shell_cmd, "%s %s", cmd, page);
- execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL);
- warning("failed to exec '%s': %s", cmd, strerror(errno));
-}
-
-static void add_man_viewer(const char *name)
-{
- struct man_viewer_list **p = &man_viewer_list;
- size_t len = strlen(name);
-
- while (*p)
- p = &((*p)->next);
- *p = xcalloc(1, (sizeof(**p) + len + 1));
- strncpy((*p)->name, name, len);
-}
-
-static int supported_man_viewer(const char *name, size_t len)
-{
- return (!strncasecmp("man", name, len) ||
- !strncasecmp("woman", name, len) ||
- !strncasecmp("konqueror", name, len));
-}
-
-static void do_add_man_viewer_info(const char *name,
- size_t len,
- const char *value)
-{
- struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
-
- strncpy(new->name, name, len);
- new->info = xstrdup(value);
- new->next = man_viewer_info_list;
- man_viewer_info_list = new;
-}
-
-static int add_man_viewer_path(const char *name,
- size_t len,
- const char *value)
-{
- if (supported_man_viewer(name, len))
- do_add_man_viewer_info(name, len, value);
- else
- warning("'%s': path for unsupported man viewer.\n"
- "Please consider using 'man.<tool>.cmd' instead.",
- name);
-
- return 0;
-}
-
-static int add_man_viewer_cmd(const char *name,
- size_t len,
- const char *value)
-{
- if (supported_man_viewer(name, len))
- warning("'%s': cmd for supported man viewer.\n"
- "Please consider using 'man.<tool>.path' instead.",
- name);
- else
- do_add_man_viewer_info(name, len, value);
-
- return 0;
-}
-
-static int add_man_viewer_info(const char *var, const char *value)
-{
- const char *name = var + 4;
- const char *subkey = strrchr(name, '.');
-
- if (!subkey)
- return 0;
-
- if (!strcmp(subkey, ".path")) {
- if (!value)
- return config_error_nonbool(var);
- return add_man_viewer_path(name, subkey - name, value);
- }
- if (!strcmp(subkey, ".cmd")) {
- if (!value)
- return config_error_nonbool(var);
- return add_man_viewer_cmd(name, subkey - name, value);
- }
-
- return 0;
-}
-
-static int git_help_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "help.format")) {
- if (!value)
- return config_error_nonbool(var);
- help_format = parse_help_format(value);
- return 0;
- }
- if (!strcmp(var, "man.viewer")) {
- if (!value)
- return config_error_nonbool(var);
- add_man_viewer(value);
- return 0;
- }
- if (!prefixcmp(var, "man."))
- return add_man_viewer_info(var, value);
-
- return git_default_config(var, value, cb);
-}
-
-static struct cmdnames main_cmds, other_cmds;
-
-void list_common_cmds_help(void)
-{
- int i, longest = 0;
-
- for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
- if (longest < strlen(common_cmds[i].name))
- longest = strlen(common_cmds[i].name);
- }
-
- puts("The most commonly used git commands are:");
- for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
- printf(" %s ", common_cmds[i].name);
- mput_char(' ', longest - strlen(common_cmds[i].name));
- puts(common_cmds[i].help);
- }
-}
-
-static int is_git_command(const char *s)
-{
- return is_in_cmdlist(&main_cmds, s) ||
- is_in_cmdlist(&other_cmds, s);
-}
-
-static const char *prepend(const char *prefix, const char *cmd)
-{
- size_t pre_len = strlen(prefix);
- size_t cmd_len = strlen(cmd);
- char *p = xmalloc(pre_len + cmd_len + 1);
- memcpy(p, prefix, pre_len);
- strcpy(p + pre_len, cmd);
- return p;
-}
-
-static const char *cmd_to_page(const char *git_cmd)
-{
- if (!git_cmd)
- return "git";
- else if (!prefixcmp(git_cmd, "git"))
- return git_cmd;
- else if (is_git_command(git_cmd))
- return prepend("git-", git_cmd);
- else
- return prepend("git", git_cmd);
-}
-
-static void setup_man_path(void)
-{
- struct strbuf new_path = STRBUF_INIT;
- const char *old_path = getenv("MANPATH");
-
- /* We should always put ':' after our path. If there is no
- * old_path, the ':' at the end will let 'man' to try
- * system-wide paths after ours to find the manual page. If
- * there is old_path, we need ':' as delimiter. */
- strbuf_addstr(&new_path, system_path(GIT_MAN_PATH));
- strbuf_addch(&new_path, ':');
- if (old_path)
- strbuf_addstr(&new_path, old_path);
-
- setenv("MANPATH", new_path.buf, 1);
-
- strbuf_release(&new_path);
-}
-
-static void exec_viewer(const char *name, const char *page)
-{
- const char *info = get_man_viewer_info(name);
-
- if (!strcasecmp(name, "man"))
- exec_man_man(info, page);
- else if (!strcasecmp(name, "woman"))
- exec_woman_emacs(info, page);
- else if (!strcasecmp(name, "konqueror"))
- exec_man_konqueror(info, page);
- else if (info)
- exec_man_cmd(info, page);
- else
- warning("'%s': unknown man viewer.", name);
-}
-
-static void show_man_page(const char *git_cmd)
-{
- struct man_viewer_list *viewer;
- const char *page = cmd_to_page(git_cmd);
- const char *fallback = getenv("GIT_MAN_VIEWER");
-
- setup_man_path();
- for (viewer = man_viewer_list; viewer; viewer = viewer->next)
- {
- exec_viewer(viewer->name, page); /* will return when unable */
- }
- if (fallback)
- exec_viewer(fallback, page);
- exec_viewer("man", page);
- die("no man viewer handled the request");
-}
-
-static void show_info_page(const char *git_cmd)
-{
- const char *page = cmd_to_page(git_cmd);
- setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
- execlp("info", "info", "gitman", page, NULL);
- die("no info viewer handled the request");
-}
-
-static void get_html_page_path(struct strbuf *page_path, const char *page)
-{
- struct stat st;
- const char *html_path = system_path(GIT_HTML_PATH);
-
- /* Check that we have a git documentation directory. */
- if (stat(mkpath("%s/git.html", html_path), &st)
- || !S_ISREG(st.st_mode))
- die("'%s': not a documentation directory.", html_path);
-
- strbuf_init(page_path, 0);
- strbuf_addf(page_path, "%s/%s.html", html_path, page);
-}
-
-/*
- * If open_html is not defined in a platform-specific way (see for
- * example compat/mingw.h), we use the script web--browse to display
- * HTML.
- */
-#ifndef open_html
-static void open_html(const char *path)
-{
- execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
-}
-#endif
-
-static void show_html_page(const char *git_cmd)
-{
- const char *page = cmd_to_page(git_cmd);
- struct strbuf page_path; /* it leaks but we exec bellow */
-
- get_html_page_path(&page_path, page);
-
- open_html(page_path.buf);
-}
-
-int cmd_help(int argc, const char **argv, const char *prefix)
-{
- int nongit;
- const char *alias;
- enum help_format parsed_help_format;
- load_command_list("git-", &main_cmds, &other_cmds);
-
- argc = parse_options(argc, argv, prefix, builtin_help_options,
- builtin_help_usage, 0);
- parsed_help_format = help_format;
-
- if (show_all) {
- printf("usage: %s\n\n", git_usage_string);
- list_commands("git commands", &main_cmds, &other_cmds);
- printf("%s\n", git_more_info_string);
- return 0;
- }
-
- if (!argv[0]) {
- printf("usage: %s\n\n", git_usage_string);
- list_common_cmds_help();
- printf("\n%s\n", git_more_info_string);
- return 0;
- }
-
- setup_git_directory_gently(&nongit);
- git_config(git_help_config, NULL);
-
- if (parsed_help_format != HELP_FORMAT_NONE)
- help_format = parsed_help_format;
-
- alias = alias_lookup(argv[0]);
- if (alias && !is_git_command(argv[0])) {
- printf("`git %s' is aliased to `%s'\n", argv[0], alias);
- return 0;
- }
-
- switch (help_format) {
- case HELP_FORMAT_NONE:
- case HELP_FORMAT_MAN:
- show_man_page(argv[0]);
- break;
- case HELP_FORMAT_INFO:
- show_info_page(argv[0]);
- break;
- case HELP_FORMAT_WEB:
- show_html_page(argv[0]);
- break;
- }
-
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "delta.h"
-#include "pack.h"
-#include "csum-file.h"
-#include "blob.h"
-#include "commit.h"
-#include "tag.h"
-#include "tree.h"
-#include "progress.h"
-#include "fsck.h"
-#include "exec_cmd.h"
-
-static const char index_pack_usage[] =
-"git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
-
-struct object_entry
-{
- struct pack_idx_entry idx;
- unsigned long size;
- unsigned int hdr_size;
- enum object_type type;
- enum object_type real_type;
-};
-
-union delta_base {
- unsigned char sha1[20];
- off_t offset;
-};
-
-struct base_data {
- struct base_data *base;
- struct base_data *child;
- struct object_entry *obj;
- void *data;
- unsigned long size;
-};
-
-/*
- * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
- * to memcmp() only the first 20 bytes.
- */
-#define UNION_BASE_SZ 20
-
-#define FLAG_LINK (1u<<20)
-#define FLAG_CHECKED (1u<<21)
-
-struct delta_entry
-{
- union delta_base base;
- int obj_no;
-};
-
-static struct object_entry *objects;
-static struct delta_entry *deltas;
-static struct base_data *base_cache;
-static size_t base_cache_used;
-static int nr_objects;
-static int nr_deltas;
-static int nr_resolved_deltas;
-
-static int from_stdin;
-static int strict;
-static int verbose;
-
-static struct progress *progress;
-
-/* We always read in 4kB chunks. */
-static unsigned char input_buffer[4096];
-static unsigned int input_offset, input_len;
-static off_t consumed_bytes;
-static git_SHA_CTX input_ctx;
-static uint32_t input_crc32;
-static int input_fd, output_fd, pack_fd;
-
-static int mark_link(struct object *obj, int type, void *data)
-{
- if (!obj)
- return -1;
-
- if (type != OBJ_ANY && obj->type != type)
- die("object type mismatch at %s", sha1_to_hex(obj->sha1));
-
- obj->flags |= FLAG_LINK;
- return 0;
-}
-
-/* The content of each linked object must have been checked
- or it must be already present in the object database */
-static void check_object(struct object *obj)
-{
- if (!obj)
- return;
-
- if (!(obj->flags & FLAG_LINK))
- return;
-
- if (!(obj->flags & FLAG_CHECKED)) {
- unsigned long size;
- int type = sha1_object_info(obj->sha1, &size);
- if (type != obj->type || type <= 0)
- die("object of unexpected type");
- obj->flags |= FLAG_CHECKED;
- return;
- }
-}
-
-static void check_objects(void)
-{
- unsigned i, max;
-
- max = get_max_object_index();
- for (i = 0; i < max; i++)
- check_object(get_indexed_object(i));
-}
-
-
-/* Discard current buffer used content. */
-static void flush(void)
-{
- if (input_offset) {
- if (output_fd >= 0)
- write_or_die(output_fd, input_buffer, input_offset);
- git_SHA1_Update(&input_ctx, input_buffer, input_offset);
- memmove(input_buffer, input_buffer + input_offset, input_len);
- input_offset = 0;
- }
-}
-
-/*
- * Make sure at least "min" bytes are available in the buffer, and
- * return the pointer to the buffer.
- */
-static void *fill(int min)
-{
- if (min <= input_len)
- return input_buffer + input_offset;
- if (min > sizeof(input_buffer))
- die("cannot fill %d bytes", min);
- flush();
- do {
- ssize_t ret = xread(input_fd, input_buffer + input_len,
- sizeof(input_buffer) - input_len);
- if (ret <= 0) {
- if (!ret)
- die("early EOF");
- die_errno("read error on input");
- }
- input_len += ret;
- if (from_stdin)
- display_throughput(progress, consumed_bytes + input_len);
- } while (input_len < min);
- return input_buffer;
-}
-
-static void use(int bytes)
-{
- if (bytes > input_len)
- die("used more bytes than were available");
- input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
- input_len -= bytes;
- input_offset += bytes;
-
- /* make sure off_t is sufficiently large not to wrap */
- if (consumed_bytes > consumed_bytes + bytes)
- die("pack too large for current definition of off_t");
- consumed_bytes += bytes;
-}
-
-static const char *open_pack_file(const char *pack_name)
-{
- if (from_stdin) {
- input_fd = 0;
- if (!pack_name) {
- static char tmpfile[PATH_MAX];
- output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
- "pack/tmp_pack_XXXXXX");
- pack_name = xstrdup(tmpfile);
- } else
- output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
- if (output_fd < 0)
- die_errno("unable to create '%s'", pack_name);
- pack_fd = output_fd;
- } else {
- input_fd = open(pack_name, O_RDONLY);
- if (input_fd < 0)
- die_errno("cannot open packfile '%s'", pack_name);
- output_fd = -1;
- pack_fd = input_fd;
- }
- git_SHA1_Init(&input_ctx);
- return pack_name;
-}
-
-static void parse_pack_header(void)
-{
- struct pack_header *hdr = fill(sizeof(struct pack_header));
-
- /* Header consistency check */
- if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
- die("pack signature mismatch");
- if (!pack_version_ok(hdr->hdr_version))
- die("pack version %"PRIu32" unsupported",
- ntohl(hdr->hdr_version));
-
- nr_objects = ntohl(hdr->hdr_entries);
- use(sizeof(struct pack_header));
-}
-
-static NORETURN void bad_object(unsigned long offset, const char *format,
- ...) __attribute__((format (printf, 2, 3)));
-
-static void bad_object(unsigned long offset, const char *format, ...)
-{
- va_list params;
- char buf[1024];
-
- va_start(params, format);
- vsnprintf(buf, sizeof(buf), format, params);
- va_end(params);
- die("pack has bad object at offset %lu: %s", offset, buf);
-}
-
-static void free_base_data(struct base_data *c)
-{
- if (c->data) {
- free(c->data);
- c->data = NULL;
- base_cache_used -= c->size;
- }
-}
-
-static void prune_base_data(struct base_data *retain)
-{
- struct base_data *b;
- for (b = base_cache;
- base_cache_used > delta_base_cache_limit && b;
- b = b->child) {
- if (b->data && b != retain)
- free_base_data(b);
- }
-}
-
-static void link_base_data(struct base_data *base, struct base_data *c)
-{
- if (base)
- base->child = c;
- else
- base_cache = c;
-
- c->base = base;
- c->child = NULL;
- if (c->data)
- base_cache_used += c->size;
- prune_base_data(c);
-}
-
-static void unlink_base_data(struct base_data *c)
-{
- struct base_data *base = c->base;
- if (base)
- base->child = NULL;
- else
- base_cache = NULL;
- free_base_data(c);
-}
-
-static void *unpack_entry_data(unsigned long offset, unsigned long size)
-{
- z_stream stream;
- void *buf = xmalloc(size);
-
- memset(&stream, 0, sizeof(stream));
- stream.next_out = buf;
- stream.avail_out = size;
- stream.next_in = fill(1);
- stream.avail_in = input_len;
- git_inflate_init(&stream);
-
- for (;;) {
- int ret = git_inflate(&stream, 0);
- use(input_len - stream.avail_in);
- if (stream.total_out == size && ret == Z_STREAM_END)
- break;
- if (ret != Z_OK)
- bad_object(offset, "inflate returned %d", ret);
- stream.next_in = fill(1);
- stream.avail_in = input_len;
- }
- git_inflate_end(&stream);
- return buf;
-}
-
-static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
-{
- unsigned char *p;
- unsigned long size, c;
- off_t base_offset;
- unsigned shift;
- void *data;
-
- obj->idx.offset = consumed_bytes;
- input_crc32 = crc32(0, Z_NULL, 0);
-
- p = fill(1);
- c = *p;
- use(1);
- obj->type = (c >> 4) & 7;
- size = (c & 15);
- shift = 4;
- while (c & 0x80) {
- p = fill(1);
- c = *p;
- use(1);
- size += (c & 0x7f) << shift;
- shift += 7;
- }
- obj->size = size;
-
- switch (obj->type) {
- case OBJ_REF_DELTA:
- hashcpy(delta_base->sha1, fill(20));
- use(20);
- break;
- case OBJ_OFS_DELTA:
- memset(delta_base, 0, sizeof(*delta_base));
- p = fill(1);
- c = *p;
- use(1);
- base_offset = c & 127;
- while (c & 128) {
- base_offset += 1;
- if (!base_offset || MSB(base_offset, 7))
- 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->idx.offset - base_offset;
- if (delta_base->offset <= 0 || 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_BLOB:
- case OBJ_TAG:
- break;
- default:
- bad_object(obj->idx.offset, "unknown object type %d", obj->type);
- }
- obj->hdr_size = consumed_bytes - obj->idx.offset;
-
- 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)
-{
- off_t 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;
- int st;
-
- src = xmalloc(len);
- data = src;
- do {
- ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy);
- if (n < 0)
- die_errno("cannot pread pack file");
- if (!n)
- die("premature end of pack file, %lu bytes missing",
- len - rdy);
- rdy += n;
- } while (rdy < len);
- data = xmalloc(obj->size);
- memset(&stream, 0, sizeof(stream));
- stream.next_out = data;
- stream.avail_out = obj->size;
- stream.next_in = src;
- stream.avail_in = len;
- git_inflate_init(&stream);
- while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK);
- git_inflate_end(&stream);
- if (st != Z_STREAM_END || stream.total_out != obj->size)
- die("serious inflate inconsistency");
- free(src);
- return data;
-}
-
-static int find_delta(const union delta_base *base)
-{
- int first = 0, last = nr_deltas;
-
- while (first < last) {
- int next = (first + last) / 2;
- struct delta_entry *delta = &deltas[next];
- int cmp;
-
- cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
- if (!cmp)
- return next;
- if (cmp < 0) {
- last = next;
- continue;
- }
- first = next+1;
- }
- return -first-1;
-}
-
-static void find_delta_children(const union delta_base *base,
- int *first_index, int *last_index)
-{
- int first = find_delta(base);
- int last = first;
- int end = nr_deltas - 1;
-
- if (first < 0) {
- *first_index = 0;
- *last_index = -1;
- return;
- }
- while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
- --first;
- while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
- ++last;
- *first_index = first;
- *last_index = last;
-}
-
-static void sha1_object(const void *data, unsigned long size,
- enum object_type type, unsigned char *sha1)
-{
- hash_sha1_file(data, size, typename(type), sha1);
- if (has_sha1_file(sha1)) {
- void *has_data;
- enum object_type has_type;
- unsigned long has_size;
- has_data = read_sha1_file(sha1, &has_type, &has_size);
- if (!has_data)
- die("cannot read existing object %s", sha1_to_hex(sha1));
- if (size != has_size || type != has_type ||
- memcmp(data, has_data, size) != 0)
- die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
- free(has_data);
- }
- if (strict) {
- if (type == OBJ_BLOB) {
- struct blob *blob = lookup_blob(sha1);
- if (blob)
- blob->object.flags |= FLAG_CHECKED;
- else
- die("invalid blob object %s", sha1_to_hex(sha1));
- } else {
- struct object *obj;
- int eaten;
- void *buf = (void *) data;
-
- /*
- * we do not need to free the memory here, as the
- * buf is deleted by the caller.
- */
- obj = parse_object_buffer(sha1, type, size, buf, &eaten);
- if (!obj)
- die("invalid %s", typename(type));
- if (fsck_object(obj, 1, fsck_error_function))
- die("Error in object");
- if (fsck_walk(obj, mark_link, NULL))
- die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1));
-
- if (obj->type == OBJ_TREE) {
- struct tree *item = (struct tree *) obj;
- item->buffer = NULL;
- }
- if (obj->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *) obj;
- commit->buffer = NULL;
- }
- obj->flags |= FLAG_CHECKED;
- }
- }
-}
-
-static void *get_base_data(struct base_data *c)
-{
- if (!c->data) {
- struct object_entry *obj = c->obj;
-
- if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
- void *base = get_base_data(c->base);
- void *raw = get_data_from_pack(obj);
- c->data = patch_delta(
- base, c->base->size,
- raw, obj->size,
- &c->size);
- free(raw);
- if (!c->data)
- bad_object(obj->idx.offset, "failed to apply delta");
- } else {
- c->data = get_data_from_pack(obj);
- c->size = obj->size;
- }
-
- base_cache_used += c->size;
- prune_base_data(c);
- }
- return c->data;
-}
-
-static void resolve_delta(struct object_entry *delta_obj,
- struct base_data *base, struct base_data *result)
-{
- void *base_data, *delta_data;
-
- delta_obj->real_type = base->obj->real_type;
- delta_data = get_data_from_pack(delta_obj);
- base_data = get_base_data(base);
- result->obj = delta_obj;
- result->data = patch_delta(base_data, base->size,
- delta_data, delta_obj->size, &result->size);
- free(delta_data);
- if (!result->data)
- bad_object(delta_obj->idx.offset, "failed to apply delta");
- sha1_object(result->data, result->size, delta_obj->real_type,
- delta_obj->idx.sha1);
- nr_resolved_deltas++;
-}
-
-static void find_unresolved_deltas(struct base_data *base,
- struct base_data *prev_base)
-{
- int i, ref_first, ref_last, ofs_first, ofs_last;
-
- /*
- * This is a recursive function. Those brackets should help reducing
- * stack usage by limiting the scope of the delta_base union.
- */
- {
- union delta_base base_spec;
-
- hashcpy(base_spec.sha1, base->obj->idx.sha1);
- find_delta_children(&base_spec, &ref_first, &ref_last);
-
- memset(&base_spec, 0, sizeof(base_spec));
- base_spec.offset = base->obj->idx.offset;
- find_delta_children(&base_spec, &ofs_first, &ofs_last);
- }
-
- if (ref_last == -1 && ofs_last == -1) {
- free(base->data);
- return;
- }
-
- link_base_data(prev_base, base);
-
- for (i = ref_first; i <= ref_last; i++) {
- struct object_entry *child = objects + deltas[i].obj_no;
- if (child->real_type == OBJ_REF_DELTA) {
- struct base_data result;
- resolve_delta(child, base, &result);
- if (i == ref_last && ofs_last == -1)
- free_base_data(base);
- find_unresolved_deltas(&result, base);
- }
- }
-
- for (i = ofs_first; i <= ofs_last; i++) {
- struct object_entry *child = objects + deltas[i].obj_no;
- if (child->real_type == OBJ_OFS_DELTA) {
- struct base_data result;
- resolve_delta(child, base, &result);
- if (i == ofs_last)
- free_base_data(base);
- find_unresolved_deltas(&result, base);
- }
- }
-
- unlink_base_data(base);
-}
-
-static int compare_delta_entry(const void *a, const void *b)
-{
- const struct delta_entry *delta_a = a;
- const struct delta_entry *delta_b = b;
- return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
-}
-
-/* Parse all objects and return the pack content SHA1 hash */
-static void parse_pack_objects(unsigned char *sha1)
-{
- int i;
- struct delta_entry *delta = deltas;
- struct stat st;
-
- /*
- * First pass:
- * - find locations of all objects;
- * - calculate SHA1 of all non-delta objects;
- * - remember base (SHA1 or offset) for all deltas.
- */
- if (verbose)
- progress = start_progress(
- from_stdin ? "Receiving objects" : "Indexing objects",
- nr_objects);
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *obj = &objects[i];
- void *data = unpack_raw_entry(obj, &delta->base);
- obj->real_type = obj->type;
- if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
- nr_deltas++;
- delta->obj_no = i;
- delta++;
- } else
- sha1_object(data, obj->size, obj->type, obj->idx.sha1);
- free(data);
- display_progress(progress, i+1);
- }
- objects[i].idx.offset = consumed_bytes;
- stop_progress(&progress);
-
- /* Check pack integrity */
- flush();
- git_SHA1_Final(sha1, &input_ctx);
- if (hashcmp(fill(20), sha1))
- die("pack is corrupted (SHA1 mismatch)");
- use(20);
-
- /* If input_fd is a file, we should have reached its end now. */
- if (fstat(input_fd, &st))
- die_errno("cannot fstat packfile");
- if (S_ISREG(st.st_mode) &&
- lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
- die("pack has junk at the end");
-
- if (!nr_deltas)
- return;
-
- /* Sort deltas by base SHA1/offset for fast searching */
- qsort(deltas, nr_deltas, sizeof(struct delta_entry),
- compare_delta_entry);
-
- /*
- * Second pass:
- * - for all non-delta objects, look if it is used as a base for
- * deltas;
- * - if used as a base, uncompress the object and apply all deltas,
- * recursively checking if the resulting object is used as a base
- * for some more deltas.
- */
- if (verbose)
- progress = start_progress("Resolving deltas", nr_deltas);
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *obj = &objects[i];
- struct base_data base_obj;
-
- if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
- continue;
- base_obj.obj = obj;
- base_obj.data = NULL;
- find_unresolved_deltas(&base_obj, NULL);
- display_progress(progress, nr_resolved_deltas);
- }
-}
-
-static int write_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);
- deflateEnd(&stream);
-
- size = stream.total_out;
- sha1write(f, out, size);
- free(out);
- return size;
-}
-
-static struct object_entry *append_obj_to_pack(struct sha1file *f,
- const unsigned char *sha1, void *buf,
- unsigned long size, enum object_type type)
-{
- struct object_entry *obj = &objects[nr_objects++];
- unsigned char header[10];
- unsigned long s = size;
- int n = 0;
- unsigned char c = (type << 4) | (s & 15);
- s >>= 4;
- while (s) {
- header[n++] = c | 0x80;
- c = s & 0x7f;
- s >>= 7;
- }
- header[n++] = c;
- crc32_begin(f);
- sha1write(f, header, n);
- obj[0].size = size;
- obj[0].hdr_size = n;
- obj[0].type = type;
- obj[0].real_type = type;
- obj[1].idx.offset = obj[0].idx.offset + n;
- obj[1].idx.offset += write_compressed(f, buf, size);
- obj[0].idx.crc32 = crc32_end(f);
- sha1flush(f);
- hashcpy(obj->idx.sha1, sha1);
- return obj;
-}
-
-static int delta_pos_compare(const void *_a, const void *_b)
-{
- struct delta_entry *a = *(struct delta_entry **)_a;
- struct delta_entry *b = *(struct delta_entry **)_b;
- return a->obj_no - b->obj_no;
-}
-
-static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
-{
- struct delta_entry **sorted_by_pos;
- int i, n = 0;
-
- /*
- * Since many unresolved deltas may well be themselves base objects
- * for more unresolved deltas, we really want to include the
- * smallest number of base objects that would cover as much delta
- * as possible by picking the
- * trunc deltas first, allowing for other deltas to resolve without
- * additional base objects. Since most base objects are to be found
- * before deltas depending on them, a good heuristic is to start
- * resolving deltas in the same order as their position in the pack.
- */
- sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
- for (i = 0; i < nr_deltas; i++) {
- if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
- continue;
- sorted_by_pos[n++] = &deltas[i];
- }
- qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
-
- for (i = 0; i < n; i++) {
- struct delta_entry *d = sorted_by_pos[i];
- enum object_type type;
- struct base_data base_obj;
-
- if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
- continue;
- base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
- if (!base_obj.data)
- continue;
-
- if (check_sha1_signature(d->base.sha1, base_obj.data,
- base_obj.size, typename(type)))
- die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
- base_obj.obj = append_obj_to_pack(f, d->base.sha1,
- base_obj.data, base_obj.size, type);
- find_unresolved_deltas(&base_obj, NULL);
- display_progress(progress, nr_resolved_deltas);
- }
- free(sorted_by_pos);
-}
-
-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,
- unsigned char *sha1)
-{
- const char *report = "pack";
- char name[PATH_MAX];
- int err;
-
- if (!from_stdin) {
- close(input_fd);
- } else {
- fsync_or_die(output_fd, curr_pack_name);
- err = close(output_fd);
- if (err)
- die_errno("error while closing pack file");
- }
-
- if (keep_msg) {
- int keep_fd, keep_msg_len = strlen(keep_msg);
-
- if (!keep_name)
- keep_fd = odb_pack_keep(name, sizeof(name), sha1);
- else
- keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
-
- if (keep_fd < 0) {
- if (errno != EEXIST)
- die_errno("cannot write keep file '%s'",
- keep_name);
- } else {
- if (keep_msg_len > 0) {
- write_or_die(keep_fd, keep_msg, keep_msg_len);
- write_or_die(keep_fd, "\n", 1);
- }
- if (close(keep_fd) != 0)
- die_errno("cannot close written keep file '%s'",
- keep_name);
- report = "keep";
- }
- }
-
- if (final_pack_name != curr_pack_name) {
- if (!final_pack_name) {
- snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
- get_object_directory(), sha1_to_hex(sha1));
- final_pack_name = name;
- }
- if (move_temp_to_file(curr_pack_name, final_pack_name))
- die("cannot store pack file");
- } else if (from_stdin)
- chmod(final_pack_name, 0444);
-
- if (final_index_name != curr_index_name) {
- if (!final_index_name) {
- snprintf(name, sizeof(name), "%s/pack/pack-%s.idx",
- get_object_directory(), sha1_to_hex(sha1));
- final_index_name = name;
- }
- if (move_temp_to_file(curr_index_name, final_index_name))
- die("cannot store index file");
- } else
- chmod(final_index_name, 0444);
-
- if (!from_stdin) {
- printf("%s\n", sha1_to_hex(sha1));
- } else {
- char buf[48];
- int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
- report, sha1_to_hex(sha1));
- write_or_die(1, buf, len);
-
- /*
- * Let's just mimic git-unpack-objects here and write
- * the last part of the input buffer to stdout.
- */
- while (input_len) {
- err = xwrite(1, input_buffer + input_offset, input_len);
- if (err <= 0)
- break;
- input_len -= err;
- input_offset += err;
- }
- }
-}
-
-static int git_index_pack_config(const char *k, const char *v, void *cb)
-{
- if (!strcmp(k, "pack.indexversion")) {
- pack_idx_default_version = git_config_int(k, v);
- if (pack_idx_default_version > 2)
- die("bad pack.indexversion=%"PRIu32,
- pack_idx_default_version);
- return 0;
- }
- return git_default_config(k, v, cb);
-}
-
-int cmd_index_pack(int argc, const char **argv, const char *prefix)
-{
- int i, fix_thin_pack = 0;
- const char *curr_pack, *curr_index;
- const char *index_name = NULL, *pack_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 pack_sha1[20];
-
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage(index_pack_usage);
-
- /*
- * We wish to read the repository's config file if any, and
- * for that it is necessary to call setup_git_directory_gently().
- * However if the cwd was inside .git/objects/pack/ then we need
- * to go back there or all the pack name arguments will be wrong.
- * And in that case we cannot rely on any prefix returned by
- * setup_git_directory_gently() either.
- */
- {
- char cwd[PATH_MAX+1];
- int nongit;
-
- if (!getcwd(cwd, sizeof(cwd)-1))
- die("Unable to get current working directory");
- setup_git_directory_gently(&nongit);
- git_config(git_index_pack_config, NULL);
- if (chdir(cwd))
- die("Cannot come back to cwd");
- }
-
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (*arg == '-') {
- if (!strcmp(arg, "--stdin")) {
- from_stdin = 1;
- } else if (!strcmp(arg, "--fix-thin")) {
- fix_thin_pack = 1;
- } else if (!strcmp(arg, "--strict")) {
- strict = 1;
- } else if (!strcmp(arg, "--keep")) {
- keep_msg = "";
- } else if (!prefixcmp(arg, "--keep=")) {
- keep_msg = arg + 7;
- } else if (!prefixcmp(arg, "--pack_header=")) {
- struct pack_header *hdr;
- char *c;
-
- hdr = (struct pack_header *)input_buffer;
- hdr->hdr_signature = htonl(PACK_SIGNATURE);
- hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
- if (*c != ',')
- die("bad %s", arg);
- hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
- if (*c)
- die("bad %s", arg);
- input_len = sizeof(*hdr);
- } else if (!strcmp(arg, "-v")) {
- verbose = 1;
- } else if (!strcmp(arg, "-o")) {
- if (index_name || (i+1) >= argc)
- usage(index_pack_usage);
- index_name = argv[++i];
- } else if (!prefixcmp(arg, "--index-version=")) {
- char *c;
- pack_idx_default_version = strtoul(arg + 16, &c, 10);
- if (pack_idx_default_version > 2)
- die("bad %s", arg);
- if (*c == ',')
- 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);
- continue;
- }
-
- if (pack_name)
- usage(index_pack_usage);
- pack_name = arg;
- }
-
- if (!pack_name && !from_stdin)
- usage(index_pack_usage);
- if (fix_thin_pack && !from_stdin)
- die("--fix-thin cannot be used without --stdin");
- if (!index_name && pack_name) {
- int len = strlen(pack_name);
- if (!has_extension(pack_name, ".pack"))
- die("packfile name '%s' does not end with '.pack'",
- pack_name);
- index_name_buf = xmalloc(len);
- memcpy(index_name_buf, pack_name, len - 5);
- strcpy(index_name_buf + len - 5, ".idx");
- index_name = index_name_buf;
- }
- if (keep_msg && !keep_name && pack_name) {
- int len = strlen(pack_name);
- if (!has_extension(pack_name, ".pack"))
- die("packfile name '%s' does not end with '.pack'",
- pack_name);
- keep_name_buf = xmalloc(len);
- memcpy(keep_name_buf, pack_name, len - 5);
- strcpy(keep_name_buf + len - 5, ".keep");
- keep_name = keep_name_buf;
- }
-
- curr_pack = open_pack_file(pack_name);
- parse_pack_header();
- objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
- deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
- parse_pack_objects(pack_sha1);
- if (nr_deltas == nr_resolved_deltas) {
- stop_progress(&progress);
- /* Flush remaining pack final 20-byte SHA1. */
- flush();
- } else {
- if (fix_thin_pack) {
- struct sha1file *f;
- unsigned char read_sha1[20], tail_sha1[20];
- char msg[48];
- int nr_unresolved = nr_deltas - nr_resolved_deltas;
- int nr_objects_initial = nr_objects;
- if (nr_unresolved <= 0)
- die("confusion beyond insanity");
- objects = xrealloc(objects,
- (nr_objects + nr_unresolved + 1)
- * sizeof(*objects));
- f = sha1fd(output_fd, curr_pack);
- fix_unresolved_deltas(f, nr_unresolved);
- sprintf(msg, "completed with %d local objects",
- nr_objects - nr_objects_initial);
- stop_progress_msg(&progress, msg);
- sha1close(f, tail_sha1, 0);
- hashcpy(read_sha1, pack_sha1);
- fixup_pack_header_footer(output_fd, pack_sha1,
- curr_pack, nr_objects,
- read_sha1, consumed_bytes-20);
- if (hashcmp(read_sha1, tail_sha1) != 0)
- die("Unexpected tail checksum for %s "
- "(disk corruption?)", curr_pack);
- }
- if (nr_deltas != nr_resolved_deltas)
- die("pack has %d unresolved deltas",
- nr_deltas - nr_resolved_deltas);
- }
- free(deltas);
- if (strict)
- check_objects();
-
- 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, pack_sha1);
- free(idx_objects);
-
- final(pack_name, curr_pack,
- index_name, curr_index,
- keep_name, keep_msg,
- pack_sha1);
- free(objects);
- free(index_name_buf);
- free(keep_name_buf);
- if (pack_name == NULL)
- free((void *) curr_pack);
- if (index_name == NULL)
- free((void *) curr_index);
-
- return 0;
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "builtin.h"
-#include "exec_cmd.h"
-#include "parse-options.h"
-
-#ifndef DEFAULT_GIT_TEMPLATE_DIR
-#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
-#endif
-
-#ifdef NO_TRUSTABLE_FILEMODE
-#define TEST_FILEMODE 0
-#else
-#define TEST_FILEMODE 1
-#endif
-
-static int init_is_bare_repository = 0;
-static int init_shared_repository = -1;
-
-static void safe_create_dir(const char *dir, int share)
-{
- if (mkdir(dir, 0777) < 0) {
- if (errno != EEXIST) {
- perror(dir);
- exit(1);
- }
- }
- else if (share && adjust_shared_perm(dir))
- die("Could not make %s writable by group", dir);
-}
-
-static void copy_templates_1(char *path, int baselen,
- char *template, int template_baselen,
- DIR *dir)
-{
- struct dirent *de;
-
- /* Note: if ".git/hooks" file exists in the repository being
- * re-initialized, /etc/core-git/templates/hooks/update would
- * cause "git init" to fail here. I think this is sane but
- * it means that the set of templates we ship by default, along
- * with the way the namespace under .git/ is organized, should
- * be really carefully chosen.
- */
- safe_create_dir(path, 1);
- while ((de = readdir(dir)) != NULL) {
- struct stat st_git, st_template;
- int namelen;
- int exists = 0;
-
- if (de->d_name[0] == '.')
- continue;
- namelen = strlen(de->d_name);
- if ((PATH_MAX <= baselen + namelen) ||
- (PATH_MAX <= template_baselen + namelen))
- die("insanely long template name %s", de->d_name);
- memcpy(path + baselen, de->d_name, namelen+1);
- memcpy(template + template_baselen, de->d_name, namelen+1);
- if (lstat(path, &st_git)) {
- if (errno != ENOENT)
- die_errno("cannot stat '%s'", path);
- }
- else
- exists = 1;
-
- if (lstat(template, &st_template))
- die_errno("cannot stat template '%s'", template);
-
- if (S_ISDIR(st_template.st_mode)) {
- DIR *subdir = opendir(template);
- int baselen_sub = baselen + namelen;
- int template_baselen_sub = template_baselen + namelen;
- if (!subdir)
- die_errno("cannot opendir '%s'", template);
- path[baselen_sub++] =
- template[template_baselen_sub++] = '/';
- path[baselen_sub] =
- template[template_baselen_sub] = 0;
- copy_templates_1(path, baselen_sub,
- template, template_baselen_sub,
- subdir);
- closedir(subdir);
- }
- else if (exists)
- continue;
- else if (S_ISLNK(st_template.st_mode)) {
- char lnk[256];
- int len;
- len = readlink(template, lnk, sizeof(lnk));
- if (len < 0)
- die_errno("cannot readlink '%s'", template);
- if (sizeof(lnk) <= len)
- die("insanely long symlink %s", template);
- lnk[len] = 0;
- if (symlink(lnk, path))
- die_errno("cannot symlink '%s' '%s'", lnk, path);
- }
- else if (S_ISREG(st_template.st_mode)) {
- if (copy_file(path, template, st_template.st_mode))
- die_errno("cannot copy '%s' to '%s'", template,
- path);
- }
- else
- error("ignoring template %s", template);
- }
-}
-
-static void copy_templates(const char *template_dir)
-{
- char path[PATH_MAX];
- char template_path[PATH_MAX];
- int template_len;
- DIR *dir;
- const char *git_dir = get_git_dir();
- int len = strlen(git_dir);
-
- if (!template_dir)
- template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
- if (!template_dir)
- template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
- if (!template_dir[0])
- return;
- template_len = strlen(template_dir);
- if (PATH_MAX <= (template_len+strlen("/config")))
- die("insanely long template path %s", template_dir);
- strcpy(template_path, template_dir);
- if (template_path[template_len-1] != '/') {
- template_path[template_len++] = '/';
- template_path[template_len] = 0;
- }
- dir = opendir(template_path);
- if (!dir) {
- warning("templates not found %s", template_dir);
- return;
- }
-
- /* Make sure that template is from the correct vintage */
- strcpy(template_path + template_len, "config");
- repository_format_version = 0;
- git_config_from_file(check_repository_format_version,
- template_path, NULL);
- template_path[template_len] = 0;
-
- if (repository_format_version &&
- repository_format_version != GIT_REPO_VERSION) {
- warning("not copying templates of "
- "a wrong format version %d from '%s'",
- repository_format_version,
- template_dir);
- closedir(dir);
- return;
- }
-
- memcpy(path, git_dir, len);
- if (len && path[len - 1] != '/')
- path[len++] = '/';
- path[len] = 0;
- copy_templates_1(path, len,
- template_path, template_len,
- dir);
- closedir(dir);
-}
-
-static int create_default_files(const char *template_path)
-{
- const char *git_dir = get_git_dir();
- unsigned len = strlen(git_dir);
- static char path[PATH_MAX];
- struct stat st1;
- char repo_version_string[10];
- char junk[2];
- int reinit;
- int filemode;
-
- if (len > sizeof(path)-50)
- die("insane git directory %s", git_dir);
- memcpy(path, git_dir, len);
-
- if (len && path[len-1] != '/')
- path[len++] = '/';
-
- /*
- * Create .git/refs/{heads,tags}
- */
- safe_create_dir(git_path("refs"), 1);
- safe_create_dir(git_path("refs/heads"), 1);
- safe_create_dir(git_path("refs/tags"), 1);
-
- /* First copy the templates -- we might have the default
- * config file there, in which case we would want to read
- * from it after installing.
- */
- copy_templates(template_path);
-
- git_config(git_default_config, NULL);
- is_bare_repository_cfg = init_is_bare_repository;
-
- /* reading existing config may have overwrote it */
- if (init_shared_repository != -1)
- shared_repository = init_shared_repository;
-
- /*
- * We would have created the above under user's umask -- under
- * shared-repository settings, we would need to fix them up.
- */
- if (shared_repository) {
- adjust_shared_perm(get_git_dir());
- adjust_shared_perm(git_path("refs"));
- adjust_shared_perm(git_path("refs/heads"));
- adjust_shared_perm(git_path("refs/tags"));
- }
-
- /*
- * Create the default symlink from ".git/HEAD" to the "master"
- * branch, if it does not exist yet.
- */
- strcpy(path + len, "HEAD");
- reinit = (!access(path, R_OK)
- || readlink(path, junk, sizeof(junk)-1) != -1);
- if (!reinit) {
- if (create_symref("HEAD", "refs/heads/master", NULL) < 0)
- exit(1);
- }
-
- /* This forces creation of new config file */
- sprintf(repo_version_string, "%d", GIT_REPO_VERSION);
- git_config_set("core.repositoryformatversion", repo_version_string);
-
- path[len] = 0;
- strcpy(path + len, "config");
-
- /* Check filemode trustability */
- filemode = TEST_FILEMODE;
- if (TEST_FILEMODE && !lstat(path, &st1)) {
- struct stat st2;
- filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) &&
- !lstat(path, &st2) &&
- st1.st_mode != st2.st_mode);
- }
- git_config_set("core.filemode", filemode ? "true" : "false");
-
- 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 (prefixcmp(git_dir, work_tree) ||
- strcmp(git_dir + strlen(work_tree), "/.git")) {
- git_config_set("core.worktree", work_tree);
- }
- }
-
- if (!reinit) {
- /* Check if symlink is supported in the work tree */
- path[len] = 0;
- strcpy(path + len, "tXXXXXX");
- if (!close(xmkstemp(path)) &&
- !unlink(path) &&
- !symlink("testing", path) &&
- !lstat(path, &st1) &&
- S_ISLNK(st1.st_mode))
- unlink(path); /* good */
- else
- git_config_set("core.symlinks", "false");
-
- /* Check if the filesystem is case-insensitive */
- path[len] = 0;
- strcpy(path + len, "CoNfIg");
- if (!access(path, F_OK))
- git_config_set("core.ignorecase", "true");
- }
-
- return reinit;
-}
-
-int init_db(const char *template_dir, unsigned int flags)
-{
- const char *sha1_dir;
- char *path;
- int len, reinit;
-
- safe_create_dir(get_git_dir(), 0);
-
- init_is_bare_repository = is_bare_repository();
-
- /* Check to see if the repository version is right.
- * Note that a newly created repository does not have
- * config file, so this will not fail. What we are catching
- * is an attempt to reinitialize new repository with an old tool.
- */
- check_repository_format();
-
- reinit = create_default_files(template_dir);
-
- sha1_dir = get_object_directory();
- len = strlen(sha1_dir);
- path = xmalloc(len + 40);
- memcpy(path, sha1_dir, len);
-
- safe_create_dir(sha1_dir, 1);
- strcpy(path+len, "/pack");
- safe_create_dir(path, 1);
- strcpy(path+len, "/info");
- safe_create_dir(path, 1);
-
- if (shared_repository) {
- char buf[10];
- /* We do not spell "group" and such, so that
- * the configuration can be read by older version
- * of git. Note, we use octal numbers for new share modes,
- * and compatibility values for PERM_GROUP and
- * PERM_EVERYBODY.
- */
- if (shared_repository < 0)
- /* force to the mode value */
- sprintf(buf, "0%o", -shared_repository);
- else if (shared_repository == PERM_GROUP)
- sprintf(buf, "%d", OLD_PERM_GROUP);
- else if (shared_repository == PERM_EVERYBODY)
- sprintf(buf, "%d", OLD_PERM_EVERYBODY);
- else
- die("oops");
- git_config_set("core.sharedrepository", buf);
- git_config_set("receive.denyNonFastforwards", "true");
- }
-
- if (!(flags & INIT_DB_QUIET))
- printf("%s%s Git repository in %s/\n",
- reinit ? "Reinitialized existing" : "Initialized empty",
- shared_repository ? " shared" : "",
- get_git_dir());
-
- return 0;
-}
-
-static int guess_repository_type(const char *git_dir)
-{
- char cwd[PATH_MAX];
- const char *slash;
-
- /*
- * "GIT_DIR=. git init" is always bare.
- * "GIT_DIR=`pwd` git init" too.
- */
- if (!strcmp(".", git_dir))
- return 1;
- if (!getcwd(cwd, sizeof(cwd)))
- die_errno("cannot tell cwd");
- if (!strcmp(git_dir, cwd))
- return 1;
- /*
- * "GIT_DIR=.git or GIT_DIR=something/.git is usually not.
- */
- if (!strcmp(git_dir, ".git"))
- return 0;
- slash = strrchr(git_dir, '/');
- if (slash && !strcmp(slash, "/.git"))
- return 0;
-
- /*
- * Otherwise it is often bare. At this point
- * we are just guessing.
- */
- return 1;
-}
-
-static int shared_callback(const struct option *opt, const char *arg, int unset)
-{
- *((int *) opt->value) = (arg) ? git_config_perm("arg", arg) : PERM_GROUP;
- return 0;
-}
-
-static const char *const init_db_usage[] = {
- "git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]",
- NULL
-};
-
-/*
- * If you want to, you can share the DB area with any number of branches.
- * That has advantages: you can save space by sharing all the SHA1 objects.
- * On the other hand, it might just make lookup slower and messier. You
- * be the judge. The default case is to have one DB per managed directory.
- */
-int cmd_init_db(int argc, const char **argv, const char *prefix)
-{
- const char *git_dir;
- const char *template_dir = NULL;
- unsigned int flags = 0;
- const struct option init_db_options[] = {
- OPT_STRING(0, "template", &template_dir, "template-directory",
- "provide the directory from which templates will be used"),
- OPT_SET_INT(0, "bare", &is_bare_repository_cfg,
- "create a bare repository", 1),
- { OPTION_CALLBACK, 0, "shared", &init_shared_repository,
- "permissions",
- "specify that the git repository is to be shared amongst several users",
- PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
- OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET),
- OPT_END()
- };
-
- argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
-
- if (argc == 1) {
- int mkdir_tried = 0;
- retry:
- if (chdir(argv[0]) < 0) {
- if (!mkdir_tried) {
- int saved;
- /*
- * At this point we haven't read any configuration,
- * and we know shared_repository should always be 0;
- * but just in case we play safe.
- */
- saved = shared_repository;
- shared_repository = 0;
- switch (safe_create_leading_directories_const(argv[0])) {
- case -3:
- errno = EEXIST;
- /* fallthru */
- case -1:
- die_errno("cannot mkdir %s", argv[0]);
- break;
- default:
- break;
- }
- shared_repository = saved;
- if (mkdir(argv[0], 0777) < 0)
- die_errno("cannot mkdir %s", argv[0]);
- mkdir_tried = 1;
- goto retry;
- }
- die_errno("cannot chdir to %s", argv[0]);
- }
- } else if (0 < argc) {
- usage(init_db_usage[0]);
- }
- if (is_bare_repository_cfg == 1) {
- static char git_dir[PATH_MAX+1];
-
- setenv(GIT_DIR_ENVIRONMENT,
- getcwd(git_dir, sizeof(git_dir)), 0);
- }
-
- if (init_shared_repository != -1)
- shared_repository = init_shared_repository;
-
- /*
- * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
- * without --bare. Catch the error early.
- */
- git_dir = getenv(GIT_DIR_ENVIRONMENT);
- if ((!git_dir || is_bare_repository_cfg == 1)
- && getenv(GIT_WORK_TREE_ENVIRONMENT))
- die("%s (or --work-tree=<directory>) not allowed without "
- "specifying %s (or --git-dir=<directory>)",
- GIT_WORK_TREE_ENVIRONMENT,
- GIT_DIR_ENVIRONMENT);
-
- /*
- * Set up the default .git directory contents
- */
- if (!git_dir)
- git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
-
- if (is_bare_repository_cfg < 0)
- is_bare_repository_cfg = guess_repository_type(git_dir);
-
- if (!is_bare_repository_cfg) {
- if (git_dir) {
- const char *git_dir_parent = strrchr(git_dir, '/');
- if (git_dir_parent) {
- char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
- git_work_tree_cfg = xstrdup(make_absolute_path(rel));
- free(rel);
- }
- }
- if (!git_work_tree_cfg) {
- git_work_tree_cfg = xcalloc(PATH_MAX, 1);
- if (!getcwd(git_work_tree_cfg, PATH_MAX))
- die_errno ("Cannot access current working directory");
- }
- if (access(get_git_work_tree(), X_OK))
- die_errno ("Cannot access work tree '%s'",
- get_git_work_tree());
- }
-
- set_git_dir(make_absolute_path(git_dir));
-
- return init_db(template_dir, flags);
-}
+++ /dev/null
-/*
- * Builtin "git log" and related commands (show, whatchanged)
- *
- * (C) Copyright 2006 Linus Torvalds
- * 2006 Junio Hamano
- */
-#include "cache.h"
-#include "color.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "log-tree.h"
-#include "builtin.h"
-#include "tag.h"
-#include "reflog-walk.h"
-#include "patch-ids.h"
-#include "run-command.h"
-#include "shortlog.h"
-#include "remote.h"
-#include "string-list.h"
-#include "parse-options.h"
-
-/* Set a default date-time format for git log ("log.date" config variable) */
-static const char *default_date_mode = NULL;
-
-static int default_show_root = 1;
-static const char *fmt_patch_subject_prefix = "PATCH";
-static const char *fmt_pretty;
-
-static const char * const builtin_log_usage =
- "git log [<options>] [<since>..<until>] [[--] <path>...]\n"
- " or: git show [options] <object>...";
-
-static void cmd_log_init(int argc, const char **argv, const char *prefix,
- struct rev_info *rev)
-{
- int i;
- int decoration_style = 0;
-
- rev->abbrev = DEFAULT_ABBREV;
- rev->commit_format = CMIT_FMT_DEFAULT;
- if (fmt_pretty)
- get_commit_format(fmt_pretty, rev);
- rev->verbose_header = 1;
- DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
- rev->show_root_diff = default_show_root;
- rev->subject_prefix = fmt_patch_subject_prefix;
- DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV);
-
- if (default_date_mode)
- rev->date_mode = parse_date_format(default_date_mode);
-
- /*
- * Check for -h before setup_revisions(), or "git log -h" will
- * fail when run without a git directory.
- */
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage(builtin_log_usage);
- argc = setup_revisions(argc, argv, rev, "HEAD");
-
- if (!rev->show_notes_given && !rev->pretty_given)
- rev->show_notes = 1;
-
- if (rev->diffopt.pickaxe || rev->diffopt.filter)
- rev->always_show_header = 0;
- if (DIFF_OPT_TST(&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 (!strcmp(arg, "--decorate")) {
- decoration_style = DECORATE_SHORT_REFS;
- } else if (!prefixcmp(arg, "--decorate=")) {
- const char *v = skip_prefix(arg, "--decorate=");
- if (!strcmp(v, "full"))
- decoration_style = DECORATE_FULL_REFS;
- else if (!strcmp(v, "short"))
- decoration_style = DECORATE_SHORT_REFS;
- else
- die("invalid --decorate option: %s", arg);
- } else if (!strcmp(arg, "--source")) {
- rev->show_source = 1;
- } else if (!strcmp(arg, "-h")) {
- usage(builtin_log_usage);
- } else
- die("unrecognized argument: %s", arg);
- }
- if (decoration_style) {
- rev->show_decorations = 1;
- load_ref_decorations(decoration_style);
- }
-}
-
-/*
- * This gives a rough estimate for how many commits we
- * will print out in the list.
- */
-static int estimate_commit_count(struct rev_info *rev, struct commit_list *list)
-{
- int n = 0;
-
- while (list) {
- struct commit *commit = list->item;
- unsigned int flags = commit->object.flags;
- list = list->next;
- if (!(flags & (TREESAME | UNINTERESTING)))
- n++;
- }
- return n;
-}
-
-static void show_early_header(struct rev_info *rev, const char *stage, int nr)
-{
- if (rev->shown_one) {
- rev->shown_one = 0;
- if (rev->commit_format != CMIT_FMT_ONELINE)
- putchar(rev->diffopt.line_termination);
- }
- printf("Final output: %d %s\n", nr, stage);
-}
-
-static struct itimerval early_output_timer;
-
-static void log_show_early(struct rev_info *revs, struct commit_list *list)
-{
- int i = revs->early_output;
- int show_header = 1;
-
- sort_in_topological_order(&list, revs->lifo);
- while (list && i) {
- struct commit *commit = list->item;
- switch (simplify_commit(revs, commit)) {
- case commit_show:
- if (show_header) {
- int n = estimate_commit_count(revs, list);
- show_early_header(revs, "incomplete", n);
- show_header = 0;
- }
- log_tree_commit(revs, commit);
- i--;
- break;
- case commit_ignore:
- break;
- case commit_error:
- return;
- }
- list = list->next;
- }
-
- /* Did we already get enough commits for the early output? */
- if (!i)
- return;
-
- /*
- * ..if no, then repeat it twice a second until we
- * do.
- *
- * NOTE! We don't use "it_interval", because if the
- * reader isn't listening, we want our output to be
- * throttled by the writing, and not have the timer
- * trigger every second even if we're blocked on a
- * reader!
- */
- early_output_timer.it_value.tv_sec = 0;
- early_output_timer.it_value.tv_usec = 500000;
- setitimer(ITIMER_REAL, &early_output_timer, NULL);
-}
-
-static void early_output(int signal)
-{
- show_early_output = log_show_early;
-}
-
-static void setup_early_output(struct rev_info *rev)
-{
- struct sigaction sa;
-
- /*
- * Set up the signal handler, minimally intrusively:
- * we only set a single volatile integer word (not
- * using sigatomic_t - trying to avoid unnecessary
- * system dependencies and headers), and using
- * SA_RESTART.
- */
- memset(&sa, 0, sizeof(sa));
- sa.sa_handler = early_output;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
- sigaction(SIGALRM, &sa, NULL);
-
- /*
- * If we can get the whole output in less than a
- * tenth of a second, don't even bother doing the
- * early-output thing..
- *
- * This is a one-time-only trigger.
- */
- early_output_timer.it_value.tv_sec = 0;
- early_output_timer.it_value.tv_usec = 100000;
- setitimer(ITIMER_REAL, &early_output_timer, NULL);
-}
-
-static void finish_early_output(struct rev_info *rev)
-{
- int n = estimate_commit_count(rev, rev->commits);
- signal(SIGALRM, SIG_IGN);
- show_early_header(rev, "done", n);
-}
-
-static int cmd_log_walk(struct rev_info *rev)
-{
- struct commit *commit;
-
- if (rev->early_output)
- setup_early_output(rev);
-
- if (prepare_revision_walk(rev))
- die("revision walk setup failed");
-
- if (rev->early_output)
- finish_early_output(rev);
-
- /*
- * For --check and --exit-code, the exit code is based on CHECK_FAILED
- * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to
- * retain that state information if replacing rev->diffopt in this loop
- */
- while ((commit = get_revision(rev)) != NULL) {
- log_tree_commit(rev, commit);
- if (!rev->reflog_info) {
- /* we allow cycles in reflog ancestry */
- free(commit->buffer);
- commit->buffer = NULL;
- }
- free_commit_list(commit->parents);
- commit->parents = NULL;
- }
- if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
- DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) {
- return 02;
- }
- return diff_result_code(&rev->diffopt, 0);
-}
-
-static int git_log_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "format.pretty"))
- return git_config_string(&fmt_pretty, var, value);
- if (!strcmp(var, "format.subjectprefix"))
- return git_config_string(&fmt_patch_subject_prefix, var, value);
- if (!strcmp(var, "log.date"))
- return git_config_string(&default_date_mode, var, value);
- if (!strcmp(var, "log.showroot")) {
- default_show_root = git_config_bool(var, value);
- return 0;
- }
- return git_diff_ui_config(var, value, cb);
-}
-
-int cmd_whatchanged(int argc, const char **argv, const char *prefix)
-{
- struct rev_info rev;
-
- git_config(git_log_config, NULL);
-
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
- init_revisions(&rev, prefix);
- rev.diff = 1;
- rev.simplify_history = 0;
- cmd_log_init(argc, argv, prefix, &rev);
- if (!rev.diffopt.output_format)
- rev.diffopt.output_format = DIFF_FORMAT_RAW;
- return cmd_log_walk(&rev);
-}
-
-static void show_tagger(char *buf, int len, struct rev_info *rev)
-{
- struct strbuf out = STRBUF_INIT;
-
- pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
- git_log_output_encoding ?
- git_log_output_encoding: git_commit_encoding);
- printf("%s", out.buf);
- strbuf_release(&out);
-}
-
-static int show_object(const unsigned char *sha1, int show_tag_object,
- struct rev_info *rev)
-{
- unsigned long size;
- enum object_type type;
- char *buf = read_sha1_file(sha1, &type, &size);
- int offset = 0;
-
- if (!buf)
- return error("Could not read object %s", sha1_to_hex(sha1));
-
- if (show_tag_object)
- while (offset < size && buf[offset] != '\n') {
- int new_offset = offset + 1;
- while (new_offset < size && buf[new_offset++] != '\n')
- ; /* do nothing */
- if (!prefixcmp(buf + offset, "tagger "))
- show_tagger(buf + offset + 7,
- new_offset - offset - 7, rev);
- offset = new_offset;
- }
-
- if (offset < size)
- fwrite(buf + offset, size - offset, 1, stdout);
- free(buf);
- return 0;
-}
-
-static int show_tree_object(const unsigned char *sha1,
- const char *base, int baselen,
- const char *pathname, unsigned mode, int stage, void *context)
-{
- printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
- return 0;
-}
-
-int cmd_show(int argc, const char **argv, const char *prefix)
-{
- struct rev_info rev;
- struct object_array_entry *objects;
- int i, count, ret = 0;
-
- git_config(git_log_config, NULL);
-
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
- init_revisions(&rev, prefix);
- rev.diff = 1;
- rev.combine_merges = 1;
- rev.dense_combined_merges = 1;
- rev.always_show_header = 1;
- rev.ignore_merges = 0;
- rev.no_walk = 1;
- cmd_log_init(argc, argv, prefix, &rev);
-
- count = rev.pending.nr;
- objects = rev.pending.objects;
- for (i = 0; i < count && !ret; i++) {
- struct object *o = objects[i].item;
- const char *name = objects[i].name;
- switch (o->type) {
- case OBJ_BLOB:
- ret = show_object(o->sha1, 0, NULL);
- break;
- case OBJ_TAG: {
- struct tag *t = (struct tag *)o;
-
- if (rev.shown_one)
- putchar('\n');
- printf("%stag %s%s\n",
- diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
- t->tag,
- diff_get_color_opt(&rev.diffopt, DIFF_RESET));
- ret = show_object(o->sha1, 1, &rev);
- rev.shown_one = 1;
- if (ret)
- break;
- o = parse_object(t->tagged->sha1);
- if (!o)
- ret = error("Could not read object %s",
- sha1_to_hex(t->tagged->sha1));
- objects[i].item = o;
- i--;
- break;
- }
- case OBJ_TREE:
- if (rev.shown_one)
- putchar('\n');
- printf("%stree %s%s\n\n",
- diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
- name,
- diff_get_color_opt(&rev.diffopt, DIFF_RESET));
- read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
- show_tree_object, NULL);
- rev.shown_one = 1;
- break;
- case OBJ_COMMIT:
- rev.pending.nr = rev.pending.alloc = 0;
- rev.pending.objects = NULL;
- add_object_array(o, name, &rev.pending);
- ret = cmd_log_walk(&rev);
- break;
- default:
- ret = error("Unknown type: %d", o->type);
- }
- }
- free(objects);
- return ret;
-}
-
-/*
- * This is equivalent to "git log -g --abbrev-commit --pretty=oneline"
- */
-int cmd_log_reflog(int argc, const char **argv, const char *prefix)
-{
- struct rev_info rev;
-
- git_config(git_log_config, NULL);
-
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
- init_revisions(&rev, prefix);
- init_reflog_walk(&rev.reflog_info);
- rev.abbrev_commit = 1;
- rev.verbose_header = 1;
- cmd_log_init(argc, argv, prefix, &rev);
-
- /*
- * This means that we override whatever commit format the user gave
- * on the cmd line. Sad, but cmd_log_init() currently doesn't
- * allow us to set a different default.
- */
- rev.commit_format = CMIT_FMT_ONELINE;
- rev.use_terminator = 1;
- rev.always_show_header = 1;
-
- /*
- * We get called through "git reflog", so unlike the other log
- * routines, we need to set up our pager manually..
- */
- setup_pager();
-
- return cmd_log_walk(&rev);
-}
-
-int cmd_log(int argc, const char **argv, const char *prefix)
-{
- struct rev_info rev;
-
- git_config(git_log_config, NULL);
-
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
- init_revisions(&rev, prefix);
- rev.always_show_header = 1;
- cmd_log_init(argc, argv, prefix, &rev);
- return cmd_log_walk(&rev);
-}
-
-/* format-patch */
-
-static const char *fmt_patch_suffix = ".patch";
-static int numbered = 0;
-static int auto_number = 1;
-
-static char *default_attach = NULL;
-
-static char **extra_hdr;
-static int extra_hdr_nr;
-static int extra_hdr_alloc;
-
-static char **extra_to;
-static int extra_to_nr;
-static int extra_to_alloc;
-
-static char **extra_cc;
-static int extra_cc_nr;
-static int extra_cc_alloc;
-
-static void add_header(const char *value)
-{
- int len = strlen(value);
- while (len && value[len - 1] == '\n')
- len--;
- if (!strncasecmp(value, "to: ", 4)) {
- ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc);
- extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4);
- return;
- }
- if (!strncasecmp(value, "cc: ", 4)) {
- ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
- extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4);
- return;
- }
- ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc);
- extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
-}
-
-#define THREAD_SHALLOW 1
-#define THREAD_DEEP 2
-static int thread = 0;
-static int do_signoff = 0;
-
-static int git_format_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "format.headers")) {
- if (!value)
- die("format.headers without value");
- add_header(value);
- return 0;
- }
- if (!strcmp(var, "format.suffix"))
- return git_config_string(&fmt_patch_suffix, var, value);
- if (!strcmp(var, "format.cc")) {
- if (!value)
- return config_error_nonbool(var);
- ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
- extra_cc[extra_cc_nr++] = xstrdup(value);
- return 0;
- }
- if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
- return 0;
- }
- if (!strcmp(var, "format.numbered")) {
- if (value && !strcasecmp(value, "auto")) {
- auto_number = 1;
- return 0;
- }
- numbered = git_config_bool(var, value);
- auto_number = auto_number && numbered;
- return 0;
- }
- if (!strcmp(var, "format.attach")) {
- if (value && *value)
- default_attach = xstrdup(value);
- else
- default_attach = xstrdup(git_version_string);
- return 0;
- }
- if (!strcmp(var, "format.thread")) {
- if (value && !strcasecmp(value, "deep")) {
- thread = THREAD_DEEP;
- return 0;
- }
- if (value && !strcasecmp(value, "shallow")) {
- thread = THREAD_SHALLOW;
- return 0;
- }
- thread = git_config_bool(var, value) && THREAD_SHALLOW;
- return 0;
- }
- if (!strcmp(var, "format.signoff")) {
- do_signoff = git_config_bool(var, value);
- return 0;
- }
-
- return git_log_config(var, value, cb);
-}
-
-static FILE *realstdout = NULL;
-static const char *output_directory = NULL;
-static int outdir_offset;
-
-static int reopen_stdout(struct commit *commit, struct rev_info *rev)
-{
- struct strbuf filename = STRBUF_INIT;
- int suffix_len = strlen(fmt_patch_suffix) + 1;
-
- if (output_directory) {
- strbuf_addstr(&filename, output_directory);
- if (filename.len >=
- PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
- return error("name of output directory is too long");
- if (filename.buf[filename.len - 1] != '/')
- strbuf_addch(&filename, '/');
- }
-
- get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
-
- if (!DIFF_OPT_TST(&rev->diffopt, QUICK))
- fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
-
- if (freopen(filename.buf, "w", stdout) == NULL)
- return error("Cannot open patch file %s", filename.buf);
-
- strbuf_release(&filename);
- return 0;
-}
-
-static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix)
-{
- struct rev_info check_rev;
- struct commit *commit;
- struct object *o1, *o2;
- unsigned flags1, flags2;
-
- if (rev->pending.nr != 2)
- die("Need exactly one range.");
-
- o1 = rev->pending.objects[0].item;
- flags1 = o1->flags;
- o2 = rev->pending.objects[1].item;
- flags2 = o2->flags;
-
- if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
- die("Not a range.");
-
- init_patch_ids(ids);
-
- /* given a range a..b get all patch ids for b..a */
- init_revisions(&check_rev, prefix);
- o1->flags ^= UNINTERESTING;
- o2->flags ^= UNINTERESTING;
- add_pending_object(&check_rev, o1, "o1");
- add_pending_object(&check_rev, o2, "o2");
- if (prepare_revision_walk(&check_rev))
- die("revision walk setup failed");
-
- while ((commit = get_revision(&check_rev)) != NULL) {
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
- add_commit_patch_id(commit, ids);
- }
-
- /* reset for next revision walk */
- clear_commit_marks((struct commit *)o1,
- SEEN | UNINTERESTING | SHOWN | ADDED);
- clear_commit_marks((struct commit *)o2,
- SEEN | UNINTERESTING | SHOWN | ADDED);
- o1->flags = flags1;
- o2->flags = flags2;
-}
-
-static void gen_message_id(struct rev_info *info, char *base)
-{
- const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
- const char *email_start = strrchr(committer, '<');
- const char *email_end = strrchr(committer, '>');
- struct strbuf buf = STRBUF_INIT;
- if (!email_start || !email_end || email_start > email_end - 1)
- die("Could not extract email from committer identity.");
- strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
- (unsigned long) time(NULL),
- (int)(email_end - email_start - 1), email_start + 1);
- info->message_id = strbuf_detach(&buf, NULL);
-}
-
-static void make_cover_letter(struct rev_info *rev, int use_stdout,
- int numbered, int numbered_files,
- struct commit *origin,
- int nr, struct commit **list, struct commit *head)
-{
- const char *committer;
- const char *subject_start = NULL;
- const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
- const char *msg;
- const char *extra_headers = rev->extra_headers;
- struct shortlog log;
- struct strbuf sb = STRBUF_INIT;
- int i;
- const char *encoding = "UTF-8";
- struct diff_options opts;
- int need_8bit_cte = 0;
- struct commit *commit = NULL;
-
- if (rev->commit_format != CMIT_FMT_EMAIL)
- die("Cover letter needs email format");
-
- committer = git_committer_info(0);
-
- if (!numbered_files) {
- /*
- * We fake a commit for the cover letter so we get the filename
- * desired.
- */
- commit = xcalloc(1, sizeof(*commit));
- commit->buffer = xmalloc(400);
- snprintf(commit->buffer, 400,
- "tree 0000000000000000000000000000000000000000\n"
- "parent %s\n"
- "author %s\n"
- "committer %s\n\n"
- "cover letter\n",
- sha1_to_hex(head->object.sha1), committer, committer);
- }
-
- if (!use_stdout && reopen_stdout(commit, rev))
- return;
-
- if (commit) {
-
- free(commit->buffer);
- free(commit);
- }
-
- log_write_email_headers(rev, head, &subject_start, &extra_headers,
- &need_8bit_cte);
-
- for (i = 0; !need_8bit_cte && i < nr; i++)
- if (has_non_ascii(list[i]->buffer))
- need_8bit_cte = 1;
-
- msg = body;
- pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
- encoding);
- pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
- encoding, need_8bit_cte);
- pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
- printf("%s\n", sb.buf);
-
- strbuf_release(&sb);
-
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = 72;
- log.in1 = 2;
- log.in2 = 4;
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
-
- shortlog_output(&log);
-
- /*
- * We can only do diffstat with a unique reference point
- */
- if (!origin)
- return;
-
- memcpy(&opts, &rev->diffopt, sizeof(opts));
- opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
-
- diff_setup_done(&opts);
-
- diff_tree_sha1(origin->tree->object.sha1,
- head->tree->object.sha1,
- "", &opts);
- diffcore_std(&opts);
- diff_flush(&opts);
-
- printf("\n");
-}
-
-static const char *clean_message_id(const char *msg_id)
-{
- char ch;
- const char *a, *z, *m;
-
- m = msg_id;
- while ((ch = *m) && (isspace(ch) || (ch == '<')))
- m++;
- a = m;
- z = NULL;
- while ((ch = *m)) {
- if (!isspace(ch) && (ch != '>'))
- z = m;
- m++;
- }
- if (!z)
- die("insane in-reply-to: %s", msg_id);
- if (++z == m)
- return a;
- return xmemdupz(a, z - a);
-}
-
-static const char *set_outdir(const char *prefix, const char *output_directory)
-{
- if (output_directory && is_absolute_path(output_directory))
- return output_directory;
-
- if (!prefix || !*prefix) {
- if (output_directory)
- return output_directory;
- /* The user did not explicitly ask for "./" */
- outdir_offset = 2;
- return "./";
- }
-
- outdir_offset = strlen(prefix);
- if (!output_directory)
- return prefix;
-
- return xstrdup(prefix_filename(prefix, outdir_offset,
- output_directory));
-}
-
-static const char * const builtin_format_patch_usage[] = {
- "git format-patch [options] [<since> | <revision range>]",
- NULL
-};
-
-static int keep_subject = 0;
-
-static int keep_callback(const struct option *opt, const char *arg, int unset)
-{
- ((struct rev_info *)opt->value)->total = -1;
- keep_subject = 1;
- return 0;
-}
-
-static int subject_prefix = 0;
-
-static int subject_prefix_callback(const struct option *opt, const char *arg,
- int unset)
-{
- subject_prefix = 1;
- ((struct rev_info *)opt->value)->subject_prefix = arg;
- return 0;
-}
-
-static int numbered_cmdline_opt = 0;
-
-static int numbered_callback(const struct option *opt, const char *arg,
- int unset)
-{
- *(int *)opt->value = numbered_cmdline_opt = unset ? 0 : 1;
- if (unset)
- auto_number = 0;
- return 0;
-}
-
-static int no_numbered_callback(const struct option *opt, const char *arg,
- int unset)
-{
- return numbered_callback(opt, arg, 1);
-}
-
-static int output_directory_callback(const struct option *opt, const char *arg,
- int unset)
-{
- const char **dir = (const char **)opt->value;
- if (*dir)
- die("Two output directories?");
- *dir = arg;
- return 0;
-}
-
-static int thread_callback(const struct option *opt, const char *arg, int unset)
-{
- int *thread = (int *)opt->value;
- if (unset)
- *thread = 0;
- else if (!arg || !strcmp(arg, "shallow"))
- *thread = THREAD_SHALLOW;
- else if (!strcmp(arg, "deep"))
- *thread = THREAD_DEEP;
- else
- return 1;
- return 0;
-}
-
-static int attach_callback(const struct option *opt, const char *arg, int unset)
-{
- struct rev_info *rev = (struct rev_info *)opt->value;
- if (unset)
- rev->mime_boundary = NULL;
- else if (arg)
- rev->mime_boundary = arg;
- else
- rev->mime_boundary = git_version_string;
- rev->no_inline = unset ? 0 : 1;
- return 0;
-}
-
-static int inline_callback(const struct option *opt, const char *arg, int unset)
-{
- struct rev_info *rev = (struct rev_info *)opt->value;
- if (unset)
- rev->mime_boundary = NULL;
- else if (arg)
- rev->mime_boundary = arg;
- else
- rev->mime_boundary = git_version_string;
- rev->no_inline = 0;
- return 0;
-}
-
-static int header_callback(const struct option *opt, const char *arg, int unset)
-{
- add_header(arg);
- return 0;
-}
-
-static int cc_callback(const struct option *opt, const char *arg, int unset)
-{
- ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
- extra_cc[extra_cc_nr++] = xstrdup(arg);
- return 0;
-}
-
-int cmd_format_patch(int argc, const char **argv, const char *prefix)
-{
- struct commit *commit;
- struct commit **list = NULL;
- struct rev_info rev;
- int nr = 0, total, i;
- int use_stdout = 0;
- int start_number = -1;
- int numbered_files = 0; /* _just_ numbers */
- int ignore_if_in_upstream = 0;
- int cover_letter = 0;
- int boundary_count = 0;
- int no_binary_diff = 0;
- struct commit *origin = NULL, *head = NULL;
- const char *in_reply_to = NULL;
- struct patch_ids ids;
- char *add_signoff = NULL;
- struct strbuf buf = STRBUF_INIT;
- int use_patch_format = 0;
- const struct option builtin_format_patch_options[] = {
- { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
- "use [PATCH n/m] even with a single patch",
- PARSE_OPT_NOARG, numbered_callback },
- { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL,
- "use [PATCH] even with multiple patches",
- PARSE_OPT_NOARG, no_numbered_callback },
- OPT_BOOLEAN('s', "signoff", &do_signoff, "add Signed-off-by:"),
- OPT_BOOLEAN(0, "stdout", &use_stdout,
- "print patches to standard out"),
- OPT_BOOLEAN(0, "cover-letter", &cover_letter,
- "generate a cover letter"),
- OPT_BOOLEAN(0, "numbered-files", &numbered_files,
- "use simple number sequence for output file names"),
- OPT_STRING(0, "suffix", &fmt_patch_suffix, "sfx",
- "use <sfx> instead of '.patch'"),
- OPT_INTEGER(0, "start-number", &start_number,
- "start numbering patches at <n> instead of 1"),
- { OPTION_CALLBACK, 0, "subject-prefix", &rev, "prefix",
- "Use [<prefix>] instead of [PATCH]",
- PARSE_OPT_NONEG, subject_prefix_callback },
- { OPTION_CALLBACK, 'o', "output-directory", &output_directory,
- "dir", "store resulting files in <dir>",
- PARSE_OPT_NONEG, output_directory_callback },
- { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL,
- "don't strip/add [PATCH]",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
- OPT_BOOLEAN(0, "no-binary", &no_binary_diff,
- "don't output binary diffs"),
- OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
- "don't include a patch matching a commit upstream"),
- { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL,
- "show patch format instead of default (patch + stat)",
- PARSE_OPT_NONEG | PARSE_OPT_NOARG },
- OPT_GROUP("Messaging"),
- { OPTION_CALLBACK, 0, "add-header", NULL, "header",
- "add email header", PARSE_OPT_NONEG,
- header_callback },
- { OPTION_CALLBACK, 0, "cc", NULL, "email", "add Cc: header",
- PARSE_OPT_NONEG, cc_callback },
- OPT_STRING(0, "in-reply-to", &in_reply_to, "message-id",
- "make first mail a reply to <message-id>"),
- { OPTION_CALLBACK, 0, "attach", &rev, "boundary",
- "attach the patch", PARSE_OPT_OPTARG,
- attach_callback },
- { OPTION_CALLBACK, 0, "inline", &rev, "boundary",
- "inline the patch",
- PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
- inline_callback },
- { OPTION_CALLBACK, 0, "thread", &thread, "style",
- "enable message threading, styles: shallow, deep",
- PARSE_OPT_OPTARG, thread_callback },
- OPT_END()
- };
-
- git_config(git_format_config, NULL);
- init_revisions(&rev, prefix);
- rev.commit_format = CMIT_FMT_EMAIL;
- rev.verbose_header = 1;
- rev.diff = 1;
- rev.combine_merges = 0;
- rev.ignore_merges = 1;
- DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
-
- rev.subject_prefix = fmt_patch_subject_prefix;
-
- if (default_attach) {
- rev.mime_boundary = default_attach;
- rev.no_inline = 1;
- }
-
- /*
- * Parse the arguments before setup_revisions(), or something
- * like "git format-patch -o a123 HEAD^.." may fail; a123 is
- * possibly a valid SHA1.
- */
- argc = parse_options(argc, argv, prefix, builtin_format_patch_options,
- builtin_format_patch_usage,
- PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
- PARSE_OPT_KEEP_DASHDASH);
-
- if (do_signoff) {
- const char *committer;
- const char *endpos;
- committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
- endpos = strchr(committer, '>');
- if (!endpos)
- die("bogus committer info %s", committer);
- add_signoff = xmemdupz(committer, endpos - committer + 1);
- }
-
- for (i = 0; i < extra_hdr_nr; i++) {
- strbuf_addstr(&buf, extra_hdr[i]);
- strbuf_addch(&buf, '\n');
- }
-
- if (extra_to_nr)
- strbuf_addstr(&buf, "To: ");
- for (i = 0; i < extra_to_nr; i++) {
- if (i)
- strbuf_addstr(&buf, " ");
- strbuf_addstr(&buf, extra_to[i]);
- if (i + 1 < extra_to_nr)
- strbuf_addch(&buf, ',');
- strbuf_addch(&buf, '\n');
- }
-
- if (extra_cc_nr)
- strbuf_addstr(&buf, "Cc: ");
- for (i = 0; i < extra_cc_nr; i++) {
- if (i)
- strbuf_addstr(&buf, " ");
- strbuf_addstr(&buf, extra_cc[i]);
- if (i + 1 < extra_cc_nr)
- strbuf_addch(&buf, ',');
- strbuf_addch(&buf, '\n');
- }
-
- rev.extra_headers = strbuf_detach(&buf, NULL);
-
- if (start_number < 0)
- start_number = 1;
-
- /*
- * If numbered is set solely due to format.numbered in config,
- * and it would conflict with --keep-subject (-k) from the
- * command line, reset "numbered".
- */
- if (numbered && keep_subject && !numbered_cmdline_opt)
- numbered = 0;
-
- if (numbered && keep_subject)
- die ("-n and -k are mutually exclusive.");
- if (keep_subject && subject_prefix)
- die ("--subject-prefix and -k are mutually exclusive.");
-
- argc = setup_revisions(argc, argv, &rev, "HEAD");
- if (argc > 1)
- die ("unrecognized argument: %s", argv[1]);
-
- if (rev.diffopt.output_format & DIFF_FORMAT_NAME)
- die("--name-only does not make sense");
- if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS)
- die("--name-status does not make sense");
- if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
- die("--check does not make sense");
-
- if (!use_patch_format &&
- (!rev.diffopt.output_format ||
- rev.diffopt.output_format == DIFF_FORMAT_PATCH))
- rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY;
-
- /* Always generate a patch */
- rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
-
- if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
- DIFF_OPT_SET(&rev.diffopt, BINARY);
-
- if (!use_stdout)
- output_directory = set_outdir(prefix, output_directory);
-
- if (output_directory) {
- if (use_stdout)
- die("standard output, or directory, which one?");
- if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
- die_errno("Could not create directory '%s'",
- output_directory);
- }
-
- if (rev.pending.nr == 1) {
- if (rev.max_count < 0 && !rev.show_root_diff) {
- /*
- * This is traditional behaviour of "git format-patch
- * origin" that prepares what the origin side still
- * does not have.
- */
- rev.pending.objects[0].item->flags |= UNINTERESTING;
- add_head_to_pending(&rev);
- }
- /*
- * Otherwise, it is "format-patch -22 HEAD", and/or
- * "format-patch --root HEAD". The user wants
- * get_revision() to do the usual traversal.
- */
- }
-
- /*
- * We cannot move this anywhere earlier because we do want to
- * know if --root was given explicitly from the command line.
- */
- rev.show_root_diff = 1;
-
- if (cover_letter) {
- /* remember the range */
- int i;
- for (i = 0; i < rev.pending.nr; i++) {
- struct object *o = rev.pending.objects[i].item;
- if (!(o->flags & UNINTERESTING))
- head = (struct commit *)o;
- }
- /* We can't generate a cover letter without any patches */
- if (!head)
- return 0;
- }
-
- if (ignore_if_in_upstream)
- get_patch_ids(&rev, &ids, prefix);
-
- if (!use_stdout)
- realstdout = xfdopen(xdup(1), "w");
-
- if (prepare_revision_walk(&rev))
- die("revision walk setup failed");
- rev.boundary = 1;
- while ((commit = get_revision(&rev)) != NULL) {
- if (commit->object.flags & BOUNDARY) {
- boundary_count++;
- origin = (boundary_count == 1) ? commit : NULL;
- continue;
- }
-
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
- if (ignore_if_in_upstream &&
- has_commit_patch_id(commit, &ids))
- continue;
-
- nr++;
- list = xrealloc(list, nr * sizeof(list[0]));
- list[nr - 1] = commit;
- }
- total = nr;
- if (!keep_subject && auto_number && total > 1)
- numbered = 1;
- if (numbered)
- rev.total = total + start_number - 1;
- if (in_reply_to || thread || cover_letter)
- rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
- if (in_reply_to) {
- const char *msgid = clean_message_id(in_reply_to);
- string_list_append(msgid, rev.ref_message_ids);
- }
- rev.numbered_files = numbered_files;
- rev.patch_suffix = fmt_patch_suffix;
- if (cover_letter) {
- if (thread)
- gen_message_id(&rev, "cover");
- make_cover_letter(&rev, use_stdout, numbered, numbered_files,
- origin, nr, list, head);
- total++;
- start_number--;
- }
- rev.add_signoff = add_signoff;
- while (0 <= --nr) {
- int shown;
- commit = list[nr];
- rev.nr = total - nr + (start_number - 1);
- /* Make the second and subsequent mails replies to the first */
- if (thread) {
- /* Have we already had a message ID? */
- if (rev.message_id) {
- /*
- * For deep threading: make every mail
- * a reply to the previous one, no
- * matter what other options are set.
- *
- * For shallow threading:
- *
- * Without --cover-letter and
- * --in-reply-to, make every mail a
- * reply to the one before.
- *
- * With --in-reply-to but no
- * --cover-letter, make every mail a
- * reply to the <reply-to>.
- *
- * With --cover-letter, make every
- * mail but the cover letter a reply
- * to the cover letter. The cover
- * letter is a reply to the
- * --in-reply-to, if specified.
- */
- if (thread == THREAD_SHALLOW
- && rev.ref_message_ids->nr > 0
- && (!cover_letter || rev.nr > 1))
- free(rev.message_id);
- else
- string_list_append(rev.message_id,
- rev.ref_message_ids);
- }
- gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
- }
-
- if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit,
- &rev))
- die("Failed to create output files");
- shown = log_tree_commit(&rev, commit);
- free(commit->buffer);
- commit->buffer = NULL;
-
- /* We put one extra blank line between formatted
- * patches and this flag is used by log-tree code
- * to see if it needs to emit a LF before showing
- * the log; when using one file per patch, we do
- * not want the extra blank line.
- */
- if (!use_stdout)
- rev.shown_one = 0;
- if (shown) {
- if (rev.mime_boundary)
- printf("\n--%s%s--\n\n\n",
- mime_boundary_leader,
- rev.mime_boundary);
- else
- printf("-- \n%s\n\n", git_version_string);
- }
- if (!use_stdout)
- fclose(stdout);
- }
- free(list);
- if (ignore_if_in_upstream)
- free_patch_ids(&ids);
- return 0;
-}
-
-static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
-{
- unsigned char sha1[20];
- if (get_sha1(arg, sha1) == 0) {
- struct commit *commit = lookup_commit_reference(sha1);
- if (commit) {
- commit->object.flags |= flags;
- add_pending_object(revs, &commit->object, arg);
- return 0;
- }
- }
- return -1;
-}
-
-static const char cherry_usage[] =
-"git cherry [-v] [<upstream> [<head> [<limit>]]]";
-int cmd_cherry(int argc, const char **argv, const char *prefix)
-{
- struct rev_info revs;
- struct patch_ids ids;
- struct commit *commit;
- struct commit_list *list = NULL;
- struct branch *current_branch;
- const char *upstream;
- const char *head = "HEAD";
- const char *limit = NULL;
- int verbose = 0;
-
- if (argc > 1 && !strcmp(argv[1], "-v")) {
- verbose = 1;
- argc--;
- argv++;
- }
-
- if (argc > 1 && !strcmp(argv[1], "-h"))
- usage(cherry_usage);
-
- switch (argc) {
- case 4:
- limit = argv[3];
- /* FALLTHROUGH */
- case 3:
- head = argv[2];
- /* FALLTHROUGH */
- case 2:
- upstream = argv[1];
- break;
- default:
- current_branch = branch_get(NULL);
- if (!current_branch || !current_branch->merge
- || !current_branch->merge[0]
- || !current_branch->merge[0]->dst) {
- fprintf(stderr, "Could not find a tracked"
- " remote branch, please"
- " specify <upstream> manually.\n");
- usage(cherry_usage);
- }
-
- upstream = current_branch->merge[0]->dst;
- }
-
- init_revisions(&revs, prefix);
- revs.diff = 1;
- revs.combine_merges = 0;
- revs.ignore_merges = 1;
- DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
-
- if (add_pending_commit(head, &revs, 0))
- die("Unknown commit %s", head);
- if (add_pending_commit(upstream, &revs, UNINTERESTING))
- die("Unknown commit %s", upstream);
-
- /* Don't say anything if head and upstream are the same. */
- if (revs.pending.nr == 2) {
- struct object_array_entry *o = revs.pending.objects;
- if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
- return 0;
- }
-
- get_patch_ids(&revs, &ids, prefix);
-
- if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
- die("Unknown commit %s", limit);
-
- /* reverse the list of commits */
- if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
- while ((commit = get_revision(&revs)) != NULL) {
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
- commit_list_insert(commit, &list);
- }
-
- while (list) {
- char sign = '+';
-
- commit = list->item;
- if (has_commit_patch_id(commit, &ids))
- sign = '-';
-
- if (verbose) {
- struct strbuf buf = STRBUF_INIT;
- struct pretty_print_context ctx = {0};
- pretty_print_commit(CMIT_FMT_ONELINE, commit,
- &buf, &ctx);
- printf("%c %s %s\n", sign,
- sha1_to_hex(commit->object.sha1), buf.buf);
- strbuf_release(&buf);
- }
- else {
- printf("%c %s\n", sign,
- sha1_to_hex(commit->object.sha1));
- }
-
- list = list->next;
- }
-
- free_patch_ids(&ids);
- return 0;
-}
+++ /dev/null
-/*
- * This merges the file listing in the directory cache index
- * with the actual working directory list, and shows different
- * combinations of the two.
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "quote.h"
-#include "dir.h"
-#include "builtin.h"
-#include "tree.h"
-#include "parse-options.h"
-#include "resolve-undo.h"
-#include "string-list.h"
-
-static int abbrev;
-static int show_deleted;
-static int show_cached;
-static int show_others;
-static int show_stage;
-static int show_unmerged;
-static int show_resolve_undo;
-static int show_modified;
-static int show_killed;
-static int show_valid_bit;
-static int line_terminator = '\n';
-
-static int prefix_len;
-static int prefix_offset;
-static const char **pathspec;
-static int error_unmatch;
-static char *ps_matched;
-static const char *with_tree;
-static int exc_given;
-
-static const char *tag_cached = "";
-static const char *tag_unmerged = "";
-static const char *tag_removed = "";
-static const char *tag_other = "";
-static const char *tag_killed = "";
-static const char *tag_modified = "";
-static const char *tag_skip_worktree = "";
-static const char *tag_resolve_undo = "";
-
-static void show_dir_entry(const char *tag, struct dir_entry *ent)
-{
- int len = prefix_len;
- int offset = prefix_offset;
-
- if (len >= ent->len)
- die("git ls-files: internal error - directory entry not superset of prefix");
-
- if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched))
- return;
-
- fputs(tag, stdout);
- write_name_quoted(ent->name + offset, stdout, line_terminator);
-}
-
-static void show_other_files(struct dir_struct *dir)
-{
- int i;
-
- for (i = 0; i < dir->nr; i++) {
- struct dir_entry *ent = dir->entries[i];
- if (!cache_name_is_other(ent->name, ent->len))
- continue;
- show_dir_entry(tag_other, ent);
- }
-}
-
-static void show_killed_files(struct dir_struct *dir)
-{
- int i;
- for (i = 0; i < dir->nr; i++) {
- struct dir_entry *ent = dir->entries[i];
- char *cp, *sp;
- int pos, len, killed = 0;
-
- for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
- sp = strchr(cp, '/');
- if (!sp) {
- /* If ent->name is prefix of an entry in the
- * cache, it will be killed.
- */
- pos = cache_name_pos(ent->name, ent->len);
- if (0 <= pos)
- die("bug in show-killed-files");
- pos = -pos - 1;
- while (pos < active_nr &&
- ce_stage(active_cache[pos]))
- pos++; /* skip unmerged */
- if (active_nr <= pos)
- break;
- /* pos points at a name immediately after
- * ent->name in the cache. Does it expect
- * ent->name to be a directory?
- */
- len = ce_namelen(active_cache[pos]);
- if ((ent->len < len) &&
- !strncmp(active_cache[pos]->name,
- ent->name, ent->len) &&
- active_cache[pos]->name[ent->len] == '/')
- killed = 1;
- break;
- }
- if (0 <= cache_name_pos(ent->name, sp - ent->name)) {
- /* If any of the leading directories in
- * ent->name is registered in the cache,
- * ent->name will be killed.
- */
- killed = 1;
- break;
- }
- }
- if (killed)
- show_dir_entry(tag_killed, dir->entries[i]);
- }
-}
-
-static void show_ce_entry(const char *tag, struct cache_entry *ce)
-{
- int len = prefix_len;
- int offset = prefix_offset;
-
- if (len >= ce_namelen(ce))
- die("git ls-files: internal error - cache entry not superset of prefix");
-
- if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched))
- return;
-
- if (tag && *tag && show_valid_bit &&
- (ce->ce_flags & CE_VALID)) {
- static char alttag[4];
- memcpy(alttag, tag, 3);
- if (isalpha(tag[0]))
- alttag[0] = tolower(tag[0]);
- else if (tag[0] == '?')
- alttag[0] = '!';
- else {
- alttag[0] = 'v';
- alttag[1] = tag[0];
- alttag[2] = ' ';
- alttag[3] = 0;
- }
- tag = alttag;
- }
-
- if (!show_stage) {
- fputs(tag, stdout);
- } else {
- printf("%s%06o %s %d\t",
- tag,
- ce->ce_mode,
- abbrev ? find_unique_abbrev(ce->sha1,abbrev)
- : sha1_to_hex(ce->sha1),
- ce_stage(ce));
- }
- write_name_quoted(ce->name + offset, stdout, line_terminator);
-}
-
-static int show_one_ru(struct string_list_item *item, void *cbdata)
-{
- int offset = prefix_offset;
- const char *path = item->string;
- struct resolve_undo_info *ui = item->util;
- int i, len;
-
- len = strlen(path);
- if (len < prefix_len)
- return 0; /* outside of the prefix */
- if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched))
- return 0; /* uninterested */
- for (i = 0; i < 3; i++) {
- if (!ui->mode[i])
- continue;
- printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
- abbrev
- ? find_unique_abbrev(ui->sha1[i], abbrev)
- : sha1_to_hex(ui->sha1[i]),
- i + 1);
- write_name_quoted(path + offset, stdout, line_terminator);
- }
- return 0;
-}
-
-static void show_ru_info(const char *prefix)
-{
- if (!the_index.resolve_undo)
- return;
- for_each_string_list(show_one_ru, the_index.resolve_undo, NULL);
-}
-
-static void show_files(struct dir_struct *dir, const char *prefix)
-{
- int i;
-
- /* For cached/deleted files we don't need to even do the readdir */
- if (show_others || show_killed) {
- fill_directory(dir, pathspec);
- if (show_others)
- show_other_files(dir);
- if (show_killed)
- show_killed_files(dir);
- }
- if (show_cached | show_stage) {
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- int dtype = ce_to_dtype(ce);
- if (dir->flags & DIR_SHOW_IGNORED &&
- !excluded(dir, ce->name, &dtype))
- continue;
- if (show_unmerged && !ce_stage(ce))
- continue;
- if (ce->ce_flags & CE_UPDATE)
- continue;
- show_ce_entry(ce_stage(ce) ? tag_unmerged :
- (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce);
- }
- }
- if (show_deleted | show_modified) {
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- struct stat st;
- int err;
- int dtype = ce_to_dtype(ce);
- if (dir->flags & DIR_SHOW_IGNORED &&
- !excluded(dir, ce->name, &dtype))
- continue;
- if (ce->ce_flags & CE_UPDATE)
- continue;
- if (ce_skip_worktree(ce))
- continue;
- err = lstat(ce->name, &st);
- if (show_deleted && err)
- show_ce_entry(tag_removed, ce);
- if (show_modified && ce_modified(ce, &st, 0))
- show_ce_entry(tag_modified, ce);
- }
- }
-}
-
-/*
- * Prune the index to only contain stuff starting with "prefix"
- */
-static void prune_cache(const char *prefix)
-{
- int pos = cache_name_pos(prefix, prefix_len);
- unsigned int first, last;
-
- if (pos < 0)
- pos = -pos-1;
- memmove(active_cache, active_cache + pos,
- (active_nr - pos) * sizeof(struct cache_entry *));
- active_nr -= pos;
- first = 0;
- last = active_nr;
- while (last > first) {
- int next = (last + first) >> 1;
- struct cache_entry *ce = active_cache[next];
- if (!strncmp(ce->name, prefix, prefix_len)) {
- first = next+1;
- continue;
- }
- last = next;
- }
- active_nr = last;
-}
-
-static const char *verify_pathspec(const char *prefix)
-{
- const char **p, *n, *prev;
- unsigned long max;
-
- prev = NULL;
- max = PATH_MAX;
- for (p = pathspec; (n = *p) != NULL; p++) {
- int i, len = 0;
- for (i = 0; i < max; i++) {
- char c = n[i];
- if (prev && prev[i] != c)
- break;
- if (!c || c == '*' || c == '?')
- break;
- if (c == '/')
- len = i+1;
- }
- prev = n;
- if (len < max) {
- max = len;
- if (!max)
- break;
- }
- }
-
- if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
- die("git ls-files: cannot generate relative filenames containing '..'");
-
- prefix_len = max;
- return max ? xmemdupz(prev, max) : NULL;
-}
-
-static void strip_trailing_slash_from_submodules(void)
-{
- const char **p;
-
- for (p = pathspec; *p != NULL; p++) {
- int len = strlen(*p), pos;
-
- if (len < 1 || (*p)[len - 1] != '/')
- continue;
- pos = cache_name_pos(*p, len - 1);
- if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
- *p = xstrndup(*p, len - 1);
- }
-}
-
-/*
- * Read the tree specified with --with-tree option
- * (typically, HEAD) into stage #1 and then
- * squash them down to stage #0. This is used for
- * --error-unmatch to list and check the path patterns
- * that were given from the command line. We are not
- * going to write this index out.
- */
-void overlay_tree_on_cache(const char *tree_name, const char *prefix)
-{
- struct tree *tree;
- unsigned char sha1[20];
- const char **match;
- struct cache_entry *last_stage0 = NULL;
- int i;
-
- if (get_sha1(tree_name, sha1))
- die("tree-ish %s not found.", tree_name);
- tree = parse_tree_indirect(sha1);
- if (!tree)
- die("bad tree-ish %s", tree_name);
-
- /* Hoist the unmerged entries up to stage #3 to make room */
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (!ce_stage(ce))
- continue;
- ce->ce_flags |= CE_STAGEMASK;
- }
-
- if (prefix) {
- static const char *(matchbuf[2]);
- matchbuf[0] = prefix;
- matchbuf[1] = NULL;
- match = matchbuf;
- } else
- match = NULL;
- if (read_tree(tree, 1, match))
- die("unable to read tree entries %s", tree_name);
-
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- switch (ce_stage(ce)) {
- case 0:
- last_stage0 = ce;
- /* fallthru */
- default:
- continue;
- case 1:
- /*
- * If there is stage #0 entry for this, we do not
- * need to show it. We use CE_UPDATE bit to mark
- * such an entry.
- */
- if (last_stage0 &&
- !strcmp(last_stage0->name, ce->name))
- ce->ce_flags |= CE_UPDATE;
- }
- }
-}
-
-int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset)
-{
- /*
- * Make sure all pathspec matched; otherwise it is an error.
- */
- int num, errors = 0;
- for (num = 0; pathspec[num]; num++) {
- int other, found_dup;
-
- if (ps_matched[num])
- continue;
- /*
- * The caller might have fed identical pathspec
- * twice. Do not barf on such a mistake.
- */
- for (found_dup = other = 0;
- !found_dup && pathspec[other];
- other++) {
- if (other == num || !ps_matched[other])
- continue;
- if (!strcmp(pathspec[other], pathspec[num]))
- /*
- * Ok, we have a match already.
- */
- found_dup = 1;
- }
- if (found_dup)
- continue;
-
- error("pathspec '%s' did not match any file(s) known to git.",
- pathspec[num] + prefix_offset);
- errors++;
- }
- return errors;
-}
-
-static const char * const ls_files_usage[] = {
- "git ls-files [options] [<file>]*",
- NULL
-};
-
-static int option_parse_z(const struct option *opt,
- const char *arg, int unset)
-{
- line_terminator = unset ? '\n' : '\0';
-
- return 0;
-}
-
-static int option_parse_exclude(const struct option *opt,
- const char *arg, int unset)
-{
- struct exclude_list *list = opt->value;
-
- exc_given = 1;
- add_exclude(arg, "", 0, list);
-
- return 0;
-}
-
-static int option_parse_exclude_from(const struct option *opt,
- const char *arg, int unset)
-{
- struct dir_struct *dir = opt->value;
-
- exc_given = 1;
- add_excludes_from_file(dir, arg);
-
- return 0;
-}
-
-static int option_parse_exclude_standard(const struct option *opt,
- const char *arg, int unset)
-{
- struct dir_struct *dir = opt->value;
-
- exc_given = 1;
- setup_standard_excludes(dir);
-
- return 0;
-}
-
-int cmd_ls_files(int argc, const char **argv, const char *prefix)
-{
- int require_work_tree = 0, show_tag = 0;
- struct dir_struct dir;
- struct option builtin_ls_files_options[] = {
- { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
- "paths are separated with NUL character",
- PARSE_OPT_NOARG, option_parse_z },
- OPT_BOOLEAN('t', NULL, &show_tag,
- "identify the file status with tags"),
- OPT_BOOLEAN('v', NULL, &show_valid_bit,
- "use lowercase letters for 'assume unchanged' files"),
- OPT_BOOLEAN('c', "cached", &show_cached,
- "show cached files in the output (default)"),
- OPT_BOOLEAN('d', "deleted", &show_deleted,
- "show deleted files in the output"),
- OPT_BOOLEAN('m', "modified", &show_modified,
- "show modified files in the output"),
- OPT_BOOLEAN('o', "others", &show_others,
- "show other files in the output"),
- OPT_BIT('i', "ignored", &dir.flags,
- "show ignored files in the output",
- DIR_SHOW_IGNORED),
- OPT_BOOLEAN('s', "stage", &show_stage,
- "show staged contents' object name in the output"),
- OPT_BOOLEAN('k', "killed", &show_killed,
- "show files on the filesystem that need to be removed"),
- OPT_BIT(0, "directory", &dir.flags,
- "show 'other' directories' name only",
- DIR_SHOW_OTHER_DIRECTORIES),
- OPT_NEGBIT(0, "empty-directory", &dir.flags,
- "don't show empty directories",
- DIR_HIDE_EMPTY_DIRECTORIES),
- OPT_BOOLEAN('u', "unmerged", &show_unmerged,
- "show unmerged files in the output"),
- OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
- "show resolve-undo information"),
- { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
- "skip files matching pattern",
- 0, option_parse_exclude },
- { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
- "exclude patterns are read from <file>",
- 0, option_parse_exclude_from },
- OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file",
- "read additional per-directory exclude patterns in <file>"),
- { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
- "add the standard git exclusions",
- PARSE_OPT_NOARG, option_parse_exclude_standard },
- { OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL,
- "make the output relative to the project top directory",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
- OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
- "if any <file> is not in the index, treat this as an error"),
- OPT_STRING(0, "with-tree", &with_tree, "tree-ish",
- "pretend that paths removed since <tree-ish> are still present"),
- OPT__ABBREV(&abbrev),
- OPT_END()
- };
-
- memset(&dir, 0, sizeof(dir));
- if (prefix)
- prefix_offset = strlen(prefix);
- git_config(git_default_config, NULL);
-
- if (read_cache() < 0)
- die("index file corrupt");
-
- argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
- ls_files_usage, 0);
- if (show_tag || show_valid_bit) {
- tag_cached = "H ";
- tag_unmerged = "M ";
- tag_removed = "R ";
- tag_modified = "C ";
- tag_other = "? ";
- tag_killed = "K ";
- tag_skip_worktree = "S ";
- tag_resolve_undo = "U ";
- }
- if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
- require_work_tree = 1;
- if (show_unmerged)
- /*
- * There's no point in showing unmerged unless
- * you also show the stage information.
- */
- show_stage = 1;
- if (dir.exclude_per_dir)
- exc_given = 1;
-
- if (require_work_tree && !is_inside_work_tree())
- setup_work_tree();
-
- pathspec = get_pathspec(prefix, argv);
-
- /* be nice with submodule paths ending in a slash */
- if (pathspec)
- strip_trailing_slash_from_submodules();
-
- /* Verify that the pathspec matches the prefix */
- if (pathspec)
- prefix = verify_pathspec(prefix);
-
- /* Treat unmatching pathspec elements as errors */
- if (pathspec && error_unmatch) {
- int num;
- for (num = 0; pathspec[num]; num++)
- ;
- ps_matched = xcalloc(1, num);
- }
-
- if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
- die("ls-files --ignored needs some exclude pattern");
-
- /* With no flags, we default to showing the cached files */
- if (!(show_stage | show_deleted | show_others | show_unmerged |
- show_killed | show_modified | show_resolve_undo))
- show_cached = 1;
-
- if (prefix)
- prune_cache(prefix);
- if (with_tree) {
- /*
- * Basic sanity check; show-stages and show-unmerged
- * would not make any sense with this option.
- */
- if (show_stage || show_unmerged)
- die("ls-files --with-tree is incompatible with -s or -u");
- overlay_tree_on_cache(with_tree, prefix);
- }
- show_files(&dir, prefix);
- if (show_resolve_undo)
- show_ru_info(prefix);
-
- if (ps_matched) {
- int bad;
- bad = report_path_error(ps_matched, pathspec, prefix_offset);
- if (bad)
- fprintf(stderr, "Did you forget to 'git add'?\n");
-
- return bad ? 1 : 0;
- }
-
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "transport.h"
-#include "remote.h"
-
-static const char ls_remote_usage[] =
-"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>] <repository> <refs>...";
-
-/*
- * Is there one among the list of patterns that match the tail part
- * of the path?
- */
-static int tail_match(const char **pattern, const char *path)
-{
- const char *p;
- char pathbuf[PATH_MAX];
-
- if (!pattern)
- return 1; /* no restriction */
-
- if (snprintf(pathbuf, sizeof(pathbuf), "/%s", path) > sizeof(pathbuf))
- return error("insanely long ref %.*s...", 20, path);
- while ((p = *(pattern++)) != NULL) {
- if (!fnmatch(p, pathbuf, 0))
- return 1;
- }
- return 0;
-}
-
-int cmd_ls_remote(int argc, const char **argv, const char *prefix)
-{
- int i;
- const char *dest = NULL;
- int nongit;
- unsigned flags = 0;
- const char *uploadpack = NULL;
- const char **pattern = NULL;
-
- struct remote *remote;
- struct transport *transport;
- const struct ref *ref;
-
- setup_git_directory_gently(&nongit);
-
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (*arg == '-') {
- if (!prefixcmp(arg, "--upload-pack=")) {
- uploadpack = arg + 14;
- continue;
- }
- if (!prefixcmp(arg, "--exec=")) {
- uploadpack = arg + 7;
- continue;
- }
- if (!strcmp("--tags", arg) || !strcmp("-t", arg)) {
- flags |= REF_TAGS;
- continue;
- }
- if (!strcmp("--heads", arg) || !strcmp("-h", arg)) {
- flags |= REF_HEADS;
- continue;
- }
- if (!strcmp("--refs", arg)) {
- flags |= REF_NORMAL;
- continue;
- }
- usage(ls_remote_usage);
- }
- dest = arg;
- i++;
- break;
- }
-
- if (!dest)
- usage(ls_remote_usage);
-
- if (argv[i]) {
- int j;
- pattern = xcalloc(sizeof(const char *), argc - i + 1);
- for (j = i; j < argc; j++) {
- int len = strlen(argv[j]);
- char *p = xmalloc(len + 3);
- sprintf(p, "*/%s", argv[j]);
- pattern[j - i] = p;
- }
- }
- remote = remote_get(dest);
- if (!remote->url_nr)
- die("remote %s has no configured URL", dest);
- transport = transport_get(remote, NULL);
- if (uploadpack != NULL)
- transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
-
- ref = transport_get_remote_refs(transport);
- if (transport_disconnect(transport))
- return 1;
- for ( ; ref; ref = ref->next) {
- if (!check_ref_type(ref, flags))
- continue;
- if (!tail_match(pattern, ref->name))
- continue;
- printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name);
- }
- return 0;
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "blob.h"
-#include "tree.h"
-#include "commit.h"
-#include "quote.h"
-#include "builtin.h"
-#include "parse-options.h"
-
-static int line_termination = '\n';
-#define LS_RECURSIVE 1
-#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 int chomp_prefix;
-static const char *ls_tree_prefix;
-
-static const char * const ls_tree_usage[] = {
- "git ls-tree [<options>] <tree-ish> [path...]",
- NULL
-};
-
-static int show_recursive(const char *base, int baselen, const char *pathname)
-{
- const char **s;
-
- if (ls_options & LS_RECURSIVE)
- return 1;
-
- s = pathspec;
- if (!s)
- return 0;
-
- for (;;) {
- const char *spec = *s++;
- int len, speclen;
-
- if (!spec)
- return 0;
- if (strncmp(base, spec, baselen))
- continue;
- len = strlen(pathname);
- spec += baselen;
- speclen = strlen(spec);
- if (speclen <= len)
- continue;
- if (memcmp(pathname, spec, len))
- continue;
- return 1;
- }
-}
-
-static int show_tree(const unsigned char *sha1, const char *base, int baselen,
- const char *pathname, unsigned mode, int stage, void *context)
-{
- int retval = 0;
- const char *type = blob_type;
-
- if (S_ISGITLINK(mode)) {
- /*
- * Maybe we want to have some recursive version here?
- *
- * Something similar to this incomplete example:
- *
- if (show_subprojects(base, baselen, pathname))
- retval = READ_TREE_RECURSIVE;
- *
- */
- type = commit_type;
- } else if (S_ISDIR(mode)) {
- if (show_recursive(base, baselen, pathname)) {
- retval = READ_TREE_RECURSIVE;
- if (!(ls_options & LS_SHOW_TREES))
- return retval;
- }
- type = tree_type;
- }
- else if (ls_options & LS_TREE_ONLY)
- return 0;
-
- if (chomp_prefix &&
- (baselen < chomp_prefix || memcmp(ls_tree_prefix, base, chomp_prefix)))
- return 0;
-
- if (!(ls_options & LS_NAME_ONLY)) {
- if (ls_options & LS_SHOW_SIZE) {
- char size_text[24];
- if (!strcmp(type, blob_type)) {
- unsigned long size;
- if (sha1_object_info(sha1, &size) == OBJ_BAD)
- strcpy(size_text, "BAD");
- else
- snprintf(size_text, sizeof(size_text),
- "%lu", size);
- } else
- strcpy(size_text, "-");
- printf("%06o %s %s %7s\t", mode, type,
- abbrev ? find_unique_abbrev(sha1, abbrev)
- : sha1_to_hex(sha1),
- size_text);
- } else
- printf("%06o %s %s\t", mode, type,
- abbrev ? find_unique_abbrev(sha1, abbrev)
- : sha1_to_hex(sha1));
- }
- write_name_quotedpfx(base + chomp_prefix, baselen - chomp_prefix,
- pathname, stdout, line_termination);
- return retval;
-}
-
-int cmd_ls_tree(int argc, const char **argv, const char *prefix)
-{
- unsigned char sha1[20];
- struct tree *tree;
- int full_tree = 0;
- const struct option ls_tree_options[] = {
- OPT_BIT('d', NULL, &ls_options, "only show trees",
- LS_TREE_ONLY),
- OPT_BIT('r', NULL, &ls_options, "recurse into subtrees",
- LS_RECURSIVE),
- OPT_BIT('t', NULL, &ls_options, "show trees when recursing",
- LS_SHOW_TREES),
- OPT_SET_INT('z', NULL, &line_termination,
- "terminate entries with NUL byte", 0),
- OPT_BIT('l', "long", &ls_options, "include object size",
- LS_SHOW_SIZE),
- OPT_BIT(0, "name-only", &ls_options, "list only filenames",
- LS_NAME_ONLY),
- OPT_BIT(0, "name-status", &ls_options, "list only filenames",
- LS_NAME_ONLY),
- OPT_SET_INT(0, "full-name", &chomp_prefix,
- "use full path names", 0),
- OPT_BOOLEAN(0, "full-tree", &full_tree,
- "list entire tree; not just current directory "
- "(implies --full-name)"),
- OPT__ABBREV(&abbrev),
- OPT_END()
- };
-
- git_config(git_default_config, NULL);
- ls_tree_prefix = prefix;
- if (prefix && *prefix)
- chomp_prefix = strlen(prefix);
-
- argc = parse_options(argc, argv, prefix, ls_tree_options,
- ls_tree_usage, 0);
- if (full_tree) {
- ls_tree_prefix = prefix = NULL;
- chomp_prefix = 0;
- }
- /* -d -r should imply -t, but -d by itself should not have to. */
- if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
- ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
- ls_options |= LS_SHOW_TREES;
-
- if (argc < 1)
- usage_with_options(ls_tree_usage, ls_tree_options);
- if (get_sha1(argv[0], sha1))
- die("Not a valid object name %s", argv[0]);
-
- pathspec = get_pathspec(prefix, argv + 1);
- tree = parse_tree_indirect(sha1);
- if (!tree)
- die("not a tree object");
- read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL);
-
- return 0;
-}
+++ /dev/null
-/*
- * Another stupid program, this one parsing the headers of an
- * email to figure out authorship and subject
- */
-#include "cache.h"
-#include "builtin.h"
-#include "utf8.h"
-#include "strbuf.h"
-
-static FILE *cmitmsg, *patchfile, *fin, *fout;
-
-static int keep_subject;
-static int keep_non_patch_brackets_in_subject;
-static const char *metainfo_charset;
-static struct strbuf line = STRBUF_INIT;
-static struct strbuf name = STRBUF_INIT;
-static struct strbuf email = STRBUF_INIT;
-
-static enum {
- TE_DONTCARE, TE_QP, TE_BASE64,
-} transfer_encoding;
-static enum {
- TYPE_TEXT, TYPE_OTHER,
-} message_type;
-
-static struct strbuf charset = STRBUF_INIT;
-static int patch_lines;
-static struct strbuf **p_hdr_data, **s_hdr_data;
-static int use_scissors;
-static int use_inbody_headers = 1;
-
-#define MAX_HDR_PARSED 10
-#define MAX_BOUNDARIES 5
-
-static void cleanup_space(struct strbuf *sb);
-
-
-static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
-{
- struct strbuf *src = name;
- if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
- strchr(name->buf, '<') || strchr(name->buf, '>'))
- src = email;
- else if (name == out)
- return;
- strbuf_reset(out);
- strbuf_addbuf(out, src);
-}
-
-static void parse_bogus_from(const struct strbuf *line)
-{
- /* John Doe <johndoe> */
-
- char *bra, *ket;
- /* This is fallback, so do not bother if we already have an
- * e-mail address.
- */
- if (email.len)
- return;
-
- bra = strchr(line->buf, '<');
- if (!bra)
- return;
- ket = strchr(bra, '>');
- if (!ket)
- return;
-
- strbuf_reset(&email);
- strbuf_add(&email, bra + 1, ket - bra - 1);
-
- strbuf_reset(&name);
- strbuf_add(&name, line->buf, bra - line->buf);
- strbuf_trim(&name);
- get_sane_name(&name, &name, &email);
-}
-
-static void handle_from(const struct strbuf *from)
-{
- char *at;
- size_t el;
- struct strbuf f;
-
- strbuf_init(&f, from->len);
- strbuf_addbuf(&f, from);
-
- at = strchr(f.buf, '@');
- if (!at) {
- parse_bogus_from(from);
- return;
- }
-
- /*
- * If we already have one email, don't take any confusing lines
- */
- if (email.len && strchr(at + 1, '@')) {
- strbuf_release(&f);
- return;
- }
-
- /* Pick up the string around '@', possibly delimited with <>
- * pair; that is the email part.
- */
- while (at > f.buf) {
- char c = at[-1];
- if (isspace(c))
- break;
- if (c == '<') {
- at[-1] = ' ';
- break;
- }
- at--;
- }
- el = strcspn(at, " \n\t\r\v\f>");
- strbuf_reset(&email);
- strbuf_add(&email, at, el);
- strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
-
- /* The remainder is name. It could be
- *
- * - "John Doe <john.doe@xz>" (a), or
- * - "john.doe@xz (John Doe)" (b), or
- * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
- *
- * but we have removed the email part, so
- *
- * - remove extra spaces which could stay after email (case 'c'), and
- * - trim from both ends, possibly removing the () pair at the end
- * (cases 'a' and 'b').
- */
- cleanup_space(&f);
- strbuf_trim(&f);
- if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
- strbuf_remove(&f, 0, 1);
- strbuf_setlen(&f, f.len - 1);
- }
-
- get_sane_name(&name, &f, &email);
- strbuf_release(&f);
-}
-
-static void handle_header(struct strbuf **out, const struct strbuf *line)
-{
- if (!*out) {
- *out = xmalloc(sizeof(struct strbuf));
- strbuf_init(*out, line->len);
- } else
- strbuf_reset(*out);
-
- strbuf_addbuf(*out, line);
-}
-
-/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
- * to have enough heuristics to grok MIME encoded patches often found
- * on our mailing lists. For example, we do not even treat header lines
- * case insensitively.
- */
-
-static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
-{
- const char *ends, *ap = strcasestr(line, name);
- size_t sz;
-
- if (!ap) {
- strbuf_setlen(attr, 0);
- return 0;
- }
- ap += strlen(name);
- if (*ap == '"') {
- ap++;
- ends = "\"";
- }
- else
- ends = "; \t";
- sz = strcspn(ap, ends);
- strbuf_add(attr, ap, sz);
- return 1;
-}
-
-static struct strbuf *content[MAX_BOUNDARIES];
-
-static struct strbuf **content_top = content;
-
-static void handle_content_type(struct strbuf *line)
-{
- struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
- strbuf_init(boundary, line->len);
-
- if (!strcasestr(line->buf, "text/"))
- message_type = TYPE_OTHER;
- if (slurp_attr(line->buf, "boundary=", boundary)) {
- strbuf_insert(boundary, 0, "--", 2);
- if (++content_top > &content[MAX_BOUNDARIES]) {
- fprintf(stderr, "Too many boundaries to handle\n");
- exit(1);
- }
- *content_top = boundary;
- boundary = NULL;
- }
- slurp_attr(line->buf, "charset=", &charset);
-
- if (boundary) {
- strbuf_release(boundary);
- free(boundary);
- }
-}
-
-static void handle_content_transfer_encoding(const struct strbuf *line)
-{
- if (strcasestr(line->buf, "base64"))
- transfer_encoding = TE_BASE64;
- else if (strcasestr(line->buf, "quoted-printable"))
- transfer_encoding = TE_QP;
- else
- transfer_encoding = TE_DONTCARE;
-}
-
-static int is_multipart_boundary(const struct strbuf *line)
-{
- return (((*content_top)->len <= line->len) &&
- !memcmp(line->buf, (*content_top)->buf, (*content_top)->len));
-}
-
-static void cleanup_subject(struct strbuf *subject)
-{
- size_t at = 0;
-
- while (at < subject->len) {
- char *pos;
- size_t remove;
-
- switch (subject->buf[at]) {
- case 'r': case 'R':
- if (subject->len <= at + 3)
- break;
- if (!memcmp(subject->buf + at + 1, "e:", 2)) {
- strbuf_remove(subject, at, 3);
- continue;
- }
- at++;
- break;
- case ' ': case '\t': case ':':
- strbuf_remove(subject, at, 1);
- continue;
- case '[':
- pos = strchr(subject->buf + at, ']');
- if (!pos)
- break;
- remove = pos - subject->buf + at + 1;
- if (!keep_non_patch_brackets_in_subject ||
- (7 <= remove &&
- memmem(subject->buf + at, remove, "PATCH", 5)))
- strbuf_remove(subject, at, remove);
- else
- at += remove;
- continue;
- }
- break;
- }
- strbuf_trim(subject);
-}
-
-static void cleanup_space(struct strbuf *sb)
-{
- size_t pos, cnt;
- for (pos = 0; pos < sb->len; pos++) {
- if (isspace(sb->buf[pos])) {
- sb->buf[pos] = ' ';
- for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
- strbuf_remove(sb, pos + 1, cnt);
- }
- }
-}
-
-static void decode_header(struct strbuf *line);
-static const char *header[MAX_HDR_PARSED] = {
- "From","Subject","Date",
-};
-
-static inline int cmp_header(const struct strbuf *line, const char *hdr)
-{
- int len = strlen(hdr);
- return !strncasecmp(line->buf, hdr, len) && line->len > len &&
- line->buf[len] == ':' && isspace(line->buf[len + 1]);
-}
-
-static int check_header(const struct strbuf *line,
- struct strbuf *hdr_data[], int overwrite)
-{
- int i, ret = 0, len;
- struct strbuf sb = STRBUF_INIT;
- /* search for the interesting parts */
- for (i = 0; header[i]; i++) {
- int len = strlen(header[i]);
- if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
- /* Unwrap inline B and Q encoding, and optionally
- * normalize the meta information to utf8.
- */
- strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
- decode_header(&sb);
- handle_header(&hdr_data[i], &sb);
- ret = 1;
- goto check_header_out;
- }
- }
-
- /* Content stuff */
- if (cmp_header(line, "Content-Type")) {
- len = strlen("Content-Type: ");
- strbuf_add(&sb, line->buf + len, line->len - len);
- decode_header(&sb);
- strbuf_insert(&sb, 0, "Content-Type: ", len);
- handle_content_type(&sb);
- ret = 1;
- goto check_header_out;
- }
- if (cmp_header(line, "Content-Transfer-Encoding")) {
- len = strlen("Content-Transfer-Encoding: ");
- strbuf_add(&sb, line->buf + len, line->len - len);
- decode_header(&sb);
- handle_content_transfer_encoding(&sb);
- ret = 1;
- goto check_header_out;
- }
-
- /* for inbody stuff */
- if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) {
- ret = 1; /* Should this return 0? */
- goto check_header_out;
- }
- if (!prefixcmp(line->buf, "[PATCH]") && isspace(line->buf[7])) {
- for (i = 0; header[i]; i++) {
- if (!memcmp("Subject", header[i], 7)) {
- handle_header(&hdr_data[i], line);
- ret = 1;
- goto check_header_out;
- }
- }
- }
-
-check_header_out:
- strbuf_release(&sb);
- return ret;
-}
-
-static int is_rfc2822_header(const struct strbuf *line)
-{
- /*
- * The section that defines the loosest possible
- * field name is "3.6.8 Optional fields".
- *
- * optional-field = field-name ":" unstructured CRLF
- * field-name = 1*ftext
- * ftext = %d33-57 / %59-126
- */
- int ch;
- char *cp = line->buf;
-
- /* Count mbox From headers as headers */
- if (!prefixcmp(cp, "From ") || !prefixcmp(cp, ">From "))
- return 1;
-
- while ((ch = *cp++)) {
- if (ch == ':')
- return 1;
- if ((33 <= ch && ch <= 57) ||
- (59 <= ch && ch <= 126))
- continue;
- break;
- }
- return 0;
-}
-
-static int read_one_header_line(struct strbuf *line, FILE *in)
-{
- /* Get the first part of the line. */
- if (strbuf_getline(line, in, '\n'))
- return 0;
-
- /*
- * Is it an empty line or not a valid rfc2822 header?
- * If so, stop here, and return false ("not a header")
- */
- strbuf_rtrim(line);
- if (!line->len || !is_rfc2822_header(line)) {
- /* Re-add the newline */
- strbuf_addch(line, '\n');
- return 0;
- }
-
- /*
- * Now we need to eat all the continuation lines..
- * Yuck, 2822 header "folding"
- */
- for (;;) {
- int peek;
- struct strbuf continuation = STRBUF_INIT;
-
- peek = fgetc(in); ungetc(peek, in);
- if (peek != ' ' && peek != '\t')
- break;
- if (strbuf_getline(&continuation, in, '\n'))
- break;
- continuation.buf[0] = '\n';
- strbuf_rtrim(&continuation);
- strbuf_addbuf(line, &continuation);
- }
-
- return 1;
-}
-
-static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
-{
- const char *in = q_seg->buf;
- int c;
- struct strbuf *out = xmalloc(sizeof(struct strbuf));
- strbuf_init(out, q_seg->len);
-
- while ((c = *in++) != 0) {
- if (c == '=') {
- int d = *in++;
- if (d == '\n' || !d)
- break; /* drop trailing newline */
- strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
- continue;
- }
- if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
- c = 0x20;
- strbuf_addch(out, c);
- }
- return out;
-}
-
-static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
-{
- /* Decode in..ep, possibly in-place to ot */
- int c, pos = 0, acc = 0;
- const char *in = b_seg->buf;
- struct strbuf *out = xmalloc(sizeof(struct strbuf));
- strbuf_init(out, b_seg->len);
-
- while ((c = *in++) != 0) {
- if (c == '+')
- c = 62;
- else if (c == '/')
- c = 63;
- else if ('A' <= c && c <= 'Z')
- c -= 'A';
- else if ('a' <= c && c <= 'z')
- c -= 'a' - 26;
- else if ('0' <= c && c <= '9')
- c -= '0' - 52;
- else
- continue; /* garbage */
- switch (pos++) {
- case 0:
- acc = (c << 2);
- break;
- case 1:
- strbuf_addch(out, (acc | (c >> 4)));
- acc = (c & 15) << 4;
- break;
- case 2:
- strbuf_addch(out, (acc | (c >> 2)));
- acc = (c & 3) << 6;
- break;
- case 3:
- strbuf_addch(out, (acc | c));
- acc = pos = 0;
- break;
- }
- }
- return out;
-}
-
-/*
- * 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 struct strbuf *line, const char *target_charset)
-{
- if (is_encoding_utf8(target_charset)) {
- if (is_utf8(line->buf))
- return NULL;
- }
- return "ISO8859-1";
-}
-
-static void convert_to_utf8(struct strbuf *line, const char *charset)
-{
- char *out;
-
- if (!charset || !*charset) {
- charset = guess_charset(line, metainfo_charset);
- if (!charset)
- return;
- }
-
- if (!strcasecmp(metainfo_charset, charset))
- return;
- out = reencode_string(line->buf, metainfo_charset, charset);
- if (!out)
- die("cannot convert from %s to %s",
- charset, metainfo_charset);
- strbuf_attach(line, out, strlen(out), strlen(out));
-}
-
-static int decode_header_bq(struct strbuf *it)
-{
- char *in, *ep, *cp;
- struct strbuf outbuf = STRBUF_INIT, *dec;
- struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
- int rfc2047 = 0;
-
- in = it->buf;
- while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
- int encoding;
- strbuf_reset(&charset_q);
- strbuf_reset(&piecebuf);
- rfc2047 = 1;
-
- if (in != ep) {
- /*
- * We are about to process an encoded-word
- * that begins at ep, but there is something
- * before the encoded word.
- */
- char *scan;
- for (scan = in; scan < ep; scan++)
- if (!isspace(*scan))
- break;
-
- if (scan != ep || in == it->buf) {
- /*
- * We should not lose that "something",
- * unless we have just processed an
- * encoded-word, and there is only LWS
- * before the one we are about to process.
- */
- strbuf_add(&outbuf, in, ep - in);
- }
- }
- /* E.g.
- * ep : "=?iso-2022-jp?B?GyR...?= foo"
- * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
- */
- ep += 2;
-
- if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
- goto decode_header_bq_out;
-
- if (cp + 3 - it->buf > it->len)
- goto decode_header_bq_out;
- strbuf_add(&charset_q, ep, cp - ep);
-
- encoding = cp[1];
- if (!encoding || cp[2] != '?')
- goto decode_header_bq_out;
- ep = strstr(cp + 3, "?=");
- if (!ep)
- goto decode_header_bq_out;
- strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
- switch (tolower(encoding)) {
- default:
- goto decode_header_bq_out;
- case 'b':
- dec = decode_b_segment(&piecebuf);
- break;
- case 'q':
- dec = decode_q_segment(&piecebuf, 1);
- break;
- }
- if (metainfo_charset)
- convert_to_utf8(dec, charset_q.buf);
-
- strbuf_addbuf(&outbuf, dec);
- strbuf_release(dec);
- free(dec);
- in = ep + 2;
- }
- strbuf_addstr(&outbuf, in);
- strbuf_reset(it);
- strbuf_addbuf(it, &outbuf);
-decode_header_bq_out:
- strbuf_release(&outbuf);
- strbuf_release(&charset_q);
- strbuf_release(&piecebuf);
- return rfc2047;
-}
-
-static void decode_header(struct strbuf *it)
-{
- if (decode_header_bq(it))
- return;
- /* otherwise "it" is a straight copy of the input.
- * This can be binary guck but there is no charset specified.
- */
- if (metainfo_charset)
- convert_to_utf8(it, "");
-}
-
-static void decode_transfer_encoding(struct strbuf *line)
-{
- struct strbuf *ret;
-
- switch (transfer_encoding) {
- case TE_QP:
- ret = decode_q_segment(line, 0);
- break;
- case TE_BASE64:
- ret = decode_b_segment(line);
- break;
- case TE_DONTCARE:
- default:
- return;
- }
- strbuf_reset(line);
- strbuf_addbuf(line, ret);
- strbuf_release(ret);
- free(ret);
-}
-
-static void handle_filter(struct strbuf *line);
-
-static int find_boundary(void)
-{
- while (!strbuf_getline(&line, fin, '\n')) {
- if (*content_top && is_multipart_boundary(&line))
- return 1;
- }
- return 0;
-}
-
-static int handle_boundary(void)
-{
- struct strbuf newline = STRBUF_INIT;
-
- strbuf_addch(&newline, '\n');
-again:
- if (line.len >= (*content_top)->len + 2 &&
- !memcmp(line.buf + (*content_top)->len, "--", 2)) {
- /* we hit an end boundary */
- /* pop the current boundary off the stack */
- strbuf_release(*content_top);
- free(*content_top);
- *content_top = NULL;
-
- /* technically won't happen as is_multipart_boundary()
- will fail first. But just in case..
- */
- if (--content_top < content) {
- fprintf(stderr, "Detected mismatched boundaries, "
- "can't recover\n");
- exit(1);
- }
- handle_filter(&newline);
- strbuf_release(&newline);
-
- /* skip to the next boundary */
- if (!find_boundary())
- return 0;
- goto again;
- }
-
- /* set some defaults */
- transfer_encoding = TE_DONTCARE;
- strbuf_reset(&charset);
- message_type = TYPE_TEXT;
-
- /* slurp in this section's info */
- while (read_one_header_line(&line, fin))
- check_header(&line, p_hdr_data, 0);
-
- strbuf_release(&newline);
- /* replenish line */
- if (strbuf_getline(&line, fin, '\n'))
- return 0;
- strbuf_addch(&line, '\n');
- return 1;
-}
-
-static inline int patchbreak(const struct strbuf *line)
-{
- size_t i;
-
- /* Beginning of a "diff -" header? */
- if (!prefixcmp(line->buf, "diff -"))
- return 1;
-
- /* CVS "Index: " line? */
- if (!prefixcmp(line->buf, "Index: "))
- return 1;
-
- /*
- * "--- <filename>" starts patches without headers
- * "---<sp>*" is a manual separator
- */
- if (line->len < 4)
- return 0;
-
- if (!prefixcmp(line->buf, "---")) {
- /* space followed by a filename? */
- if (line->buf[3] == ' ' && !isspace(line->buf[4]))
- return 1;
- /* Just whitespace? */
- for (i = 3; i < line->len; i++) {
- unsigned char c = line->buf[i];
- if (c == '\n')
- return 1;
- if (!isspace(c))
- break;
- }
- return 0;
- }
- return 0;
-}
-
-static int is_scissors_line(const struct strbuf *line)
-{
- size_t i, len = line->len;
- int scissors = 0, gap = 0;
- int first_nonblank = -1;
- int last_nonblank = 0, visible, perforation = 0, in_perforation = 0;
- const char *buf = line->buf;
-
- for (i = 0; i < len; i++) {
- if (isspace(buf[i])) {
- if (in_perforation) {
- perforation++;
- gap++;
- }
- continue;
- }
- last_nonblank = i;
- if (first_nonblank < 0)
- first_nonblank = i;
- if (buf[i] == '-') {
- in_perforation = 1;
- perforation++;
- continue;
- }
- if (i + 1 < len &&
- (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2))) {
- in_perforation = 1;
- perforation += 2;
- scissors += 2;
- i++;
- continue;
- }
- in_perforation = 0;
- }
-
- /*
- * The mark must be at least 8 bytes long (e.g. "-- >8 --").
- * Even though there can be arbitrary cruft on the same line
- * (e.g. "cut here"), in order to avoid misidentification, the
- * perforation must occupy more than a third of the visible
- * width of the line, and dashes and scissors must occupy more
- * than half of the perforation.
- */
-
- visible = last_nonblank - first_nonblank + 1;
- return (scissors && 8 <= visible &&
- visible < perforation * 3 &&
- gap * 2 < perforation);
-}
-
-static int handle_commit_msg(struct strbuf *line)
-{
- static int still_looking = 1;
-
- if (!cmitmsg)
- return 0;
-
- if (still_looking) {
- if (!line->len || (line->len == 1 && line->buf[0] == '\n'))
- return 0;
- }
-
- if (use_inbody_headers && still_looking) {
- still_looking = check_header(line, s_hdr_data, 0);
- if (still_looking)
- return 0;
- } else
- /* Only trim the first (blank) line of the commit message
- * when ignoring in-body headers.
- */
- still_looking = 0;
-
- /* normalize the log message to UTF-8. */
- if (metainfo_charset)
- convert_to_utf8(line, charset.buf);
-
- if (use_scissors && is_scissors_line(line)) {
- int i;
- if (fseek(cmitmsg, 0L, SEEK_SET))
- die_errno("Could not rewind output message file");
- if (ftruncate(fileno(cmitmsg), 0))
- die_errno("Could not truncate output message file at scissors");
- still_looking = 1;
-
- /*
- * We may have already read "secondary headers"; purge
- * them to give ourselves a clean restart.
- */
- for (i = 0; header[i]; i++) {
- if (s_hdr_data[i])
- strbuf_release(s_hdr_data[i]);
- s_hdr_data[i] = NULL;
- }
- return 0;
- }
-
- if (patchbreak(line)) {
- fclose(cmitmsg);
- cmitmsg = NULL;
- return 1;
- }
-
- fputs(line->buf, cmitmsg);
- return 0;
-}
-
-static void handle_patch(const struct strbuf *line)
-{
- fwrite(line->buf, 1, line->len, patchfile);
- patch_lines++;
-}
-
-static void handle_filter(struct strbuf *line)
-{
- static int filter = 0;
-
- /* filter tells us which part we left off on */
- switch (filter) {
- case 0:
- if (!handle_commit_msg(line))
- break;
- filter++;
- case 1:
- handle_patch(line);
- break;
- }
-}
-
-static void handle_body(void)
-{
- struct strbuf prev = STRBUF_INIT;
-
- /* Skip up to the first boundary */
- if (*content_top) {
- if (!find_boundary())
- goto handle_body_out;
- }
-
- do {
- /* process any boundary lines */
- if (*content_top && is_multipart_boundary(&line)) {
- /* flush any leftover */
- if (prev.len) {
- handle_filter(&prev);
- strbuf_reset(&prev);
- }
- if (!handle_boundary())
- goto handle_body_out;
- }
-
- /* Unwrap transfer encoding */
- decode_transfer_encoding(&line);
-
- switch (transfer_encoding) {
- case TE_BASE64:
- case TE_QP:
- {
- struct strbuf **lines, **it, *sb;
-
- /* Prepend any previous partial lines */
- strbuf_insert(&line, 0, prev.buf, prev.len);
- strbuf_reset(&prev);
-
- /* binary data most likely doesn't have newlines */
- if (message_type != TYPE_TEXT) {
- handle_filter(&line);
- break;
- }
- /*
- * This is a decoded line that may contain
- * multiple new lines. Pass only one chunk
- * at a time to handle_filter()
- */
- lines = strbuf_split(&line, '\n');
- for (it = lines; (sb = *it); it++) {
- if (*(it + 1) == NULL) /* The last line */
- if (sb->buf[sb->len - 1] != '\n') {
- /* Partial line, save it for later. */
- strbuf_addbuf(&prev, sb);
- break;
- }
- handle_filter(sb);
- }
- /*
- * The partial chunk is saved in "prev" and will be
- * appended by the next iteration of read_line_with_nul().
- */
- strbuf_list_free(lines);
- break;
- }
- default:
- handle_filter(&line);
- }
-
- } while (!strbuf_getwholeline(&line, fin, '\n'));
-
-handle_body_out:
- strbuf_release(&prev);
-}
-
-static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
-{
- const char *sp = data->buf;
- while (1) {
- char *ep = strchr(sp, '\n');
- int len;
- if (!ep)
- len = strlen(sp);
- else
- len = ep - sp;
- fprintf(fout, "%s: %.*s\n", hdr, len, sp);
- if (!ep)
- break;
- sp = ep + 1;
- }
-}
-
-static void handle_info(void)
-{
- struct strbuf *hdr;
- int i;
-
- for (i = 0; header[i]; i++) {
- /* only print inbody headers if we output a patch file */
- if (patch_lines && s_hdr_data[i])
- hdr = s_hdr_data[i];
- else if (p_hdr_data[i])
- hdr = p_hdr_data[i];
- else
- continue;
-
- if (!memcmp(header[i], "Subject", 7)) {
- if (!keep_subject) {
- cleanup_subject(hdr);
- cleanup_space(hdr);
- }
- output_header_lines(fout, "Subject", hdr);
- } else if (!memcmp(header[i], "From", 4)) {
- cleanup_space(hdr);
- handle_from(hdr);
- fprintf(fout, "Author: %s\n", name.buf);
- fprintf(fout, "Email: %s\n", email.buf);
- } else {
- cleanup_space(hdr);
- fprintf(fout, "%s: %s\n", header[i], hdr->buf);
- }
- }
- fprintf(fout, "\n");
-}
-
-static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch)
-{
- int peek;
- fin = in;
- fout = out;
-
- cmitmsg = fopen(msg, "w");
- if (!cmitmsg) {
- perror(msg);
- return -1;
- }
- patchfile = fopen(patch, "w");
- if (!patchfile) {
- perror(patch);
- fclose(cmitmsg);
- return -1;
- }
-
- p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data));
- s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data));
-
- do {
- peek = fgetc(in);
- } while (isspace(peek));
- ungetc(peek, in);
-
- /* process the email header */
- while (read_one_header_line(&line, fin))
- check_header(&line, p_hdr_data, 1);
-
- handle_body();
- handle_info();
-
- return 0;
-}
-
-static int git_mailinfo_config(const char *var, const char *value, void *unused)
-{
- if (prefixcmp(var, "mailinfo."))
- return git_default_config(var, value, unused);
- if (!strcmp(var, "mailinfo.scissors")) {
- use_scissors = git_config_bool(var, value);
- return 0;
- }
- /* perhaps others here */
- return 0;
-}
-
-static const char mailinfo_usage[] =
- "git mailinfo [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
-
-int cmd_mailinfo(int argc, const char **argv, const char *prefix)
-{
- const char *def_charset;
-
- /* NEEDSWORK: might want to do the optional .git/ directory
- * discovery
- */
- git_config(git_mailinfo_config, NULL);
-
- def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8");
- metainfo_charset = def_charset;
-
- while (1 < argc && argv[1][0] == '-') {
- if (!strcmp(argv[1], "-k"))
- keep_subject = 1;
- else if (!strcmp(argv[1], "-b"))
- keep_non_patch_brackets_in_subject = 1;
- else if (!strcmp(argv[1], "-u"))
- metainfo_charset = def_charset;
- else if (!strcmp(argv[1], "-n"))
- metainfo_charset = NULL;
- else if (!prefixcmp(argv[1], "--encoding="))
- metainfo_charset = argv[1] + 11;
- else if (!strcmp(argv[1], "--scissors"))
- use_scissors = 1;
- else if (!strcmp(argv[1], "--no-scissors"))
- use_scissors = 0;
- else if (!strcmp(argv[1], "--no-inbody-headers"))
- use_inbody_headers = 0;
- else
- usage(mailinfo_usage);
- argc--; argv++;
- }
-
- if (argc != 3)
- usage(mailinfo_usage);
-
- return !!mailinfo(stdin, stdout, argv[1], argv[2]);
-}
+++ /dev/null
-/*
- * Totally braindamaged mbox splitter program.
- *
- * It just splits a mbox into a list of files: "0001" "0002" ..
- * so you can process them further from there.
- */
-#include "cache.h"
-#include "builtin.h"
-#include "string-list.h"
-#include "strbuf.h"
-
-static const char git_mailsplit_usage[] =
-"git mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> [<mbox>|<Maildir>...]";
-
-static int is_from_line(const char *line, int len)
-{
- const char *colon;
-
- if (len < 20 || memcmp("From ", line, 5))
- return 0;
-
- colon = line + len - 2;
- line += 5;
- for (;;) {
- if (colon < line)
- return 0;
- if (*--colon == ':')
- break;
- }
-
- if (!isdigit(colon[-4]) ||
- !isdigit(colon[-2]) ||
- !isdigit(colon[-1]) ||
- !isdigit(colon[ 1]) ||
- !isdigit(colon[ 2]))
- return 0;
-
- /* year */
- if (strtol(colon+3, NULL, 10) <= 90)
- return 0;
-
- /* Ok, close enough */
- return 1;
-}
-
-static struct strbuf buf = STRBUF_INIT;
-static int keep_cr;
-
-/* Called with the first line (potentially partial)
- * already in buf[] -- normally that should begin with
- * the Unix "From " line. Write it into the specified
- * file.
- */
-static int split_one(FILE *mbox, const char *name, int allow_bare)
-{
- FILE *output = NULL;
- int fd;
- int status = 0;
- int is_bare = !is_from_line(buf.buf, buf.len);
-
- if (is_bare && !allow_bare)
- goto corrupt;
-
- fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
- if (fd < 0)
- die_errno("cannot open output file '%s'", name);
- output = xfdopen(fd, "w");
-
- /* Copy it out, while searching for a line that begins with
- * "From " and having something that looks like a date format.
- */
- for (;;) {
- if (!keep_cr && buf.len > 1 && buf.buf[buf.len-1] == '\n' &&
- buf.buf[buf.len-2] == '\r') {
- strbuf_setlen(&buf, buf.len-2);
- strbuf_addch(&buf, '\n');
- }
-
- if (fwrite(buf.buf, 1, buf.len, output) != buf.len)
- die_errno("cannot write output");
-
- if (strbuf_getwholeline(&buf, mbox, '\n')) {
- if (feof(mbox)) {
- status = 1;
- break;
- }
- die_errno("cannot read mbox");
- }
- if (!is_bare && is_from_line(buf.buf, buf.len))
- break; /* done with one message */
- }
- fclose(output);
- return status;
-
- corrupt:
- if (output)
- fclose(output);
- unlink(name);
- fprintf(stderr, "corrupt mailbox\n");
- exit(1);
-}
-
-static int populate_maildir_list(struct string_list *list, const char *path)
-{
- DIR *dir;
- struct dirent *dent;
- char name[PATH_MAX];
- char *subs[] = { "cur", "new", NULL };
- char **sub;
-
- for (sub = subs; *sub; ++sub) {
- snprintf(name, sizeof(name), "%s/%s", path, *sub);
- if ((dir = opendir(name)) == NULL) {
- if (errno == ENOENT)
- continue;
- error("cannot opendir %s (%s)", name, strerror(errno));
- return -1;
- }
-
- while ((dent = readdir(dir)) != NULL) {
- if (dent->d_name[0] == '.')
- continue;
- snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name);
- string_list_insert(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 name[PATH_MAX];
- int ret = -1;
- int i;
- struct string_list list = {NULL, 0, 0, 1};
-
- if (populate_maildir_list(&list, maildir) < 0)
- goto out;
-
- for (i = 0; i < list.nr; i++) {
- FILE *f;
- snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string);
- f = fopen(file, "r");
- if (!f) {
- error("cannot open mail %s (%s)", file, strerror(errno));
- goto out;
- }
-
- if (strbuf_getwholeline(&buf, f, '\n')) {
- error("cannot read mail %s (%s)", file, strerror(errno));
- goto out;
- }
-
- sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
- split_one(f, name, 1);
-
- fclose(f);
- }
-
- ret = skip;
-out:
- string_list_clear(&list, 1);
- 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;
- int peek;
-
- FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
- int file_done = 0;
-
- if (!f) {
- error("cannot open mbox %s", file);
- goto out;
- }
-
- do {
- peek = fgetc(f);
- } while (isspace(peek));
- ungetc(peek, f);
-
- if (strbuf_getwholeline(&buf, f, '\n')) {
- /* empty stdin is OK */
- if (f != stdin) {
- error("cannot read mbox %s", file);
- goto out;
- }
- file_done = 1;
- }
-
- 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:
- return ret;
-}
-
-int cmd_mailsplit(int argc, const char **argv, const char *prefix)
-{
- int nr = 0, nr_prec = 4, num = 0;
- int allow_bare = 0;
- const char *dir = NULL;
- const char **argp;
- static const char *stdin_only[] = { "-", NULL };
-
- for (argp = argv+1; *argp; argp++) {
- const char *arg = *argp;
-
- if (arg[0] != '-')
- break;
- /* do flags here */
- if ( arg[1] == 'd' ) {
- nr_prec = strtol(arg+2, NULL, 10);
- if (nr_prec < 3 || 10 <= nr_prec)
- usage(git_mailsplit_usage);
- continue;
- } else if ( arg[1] == 'f' ) {
- nr = strtol(arg+2, NULL, 10);
- } else if ( arg[1] == 'h' ) {
- usage(git_mailsplit_usage);
- } else if ( arg[1] == 'b' && !arg[2] ) {
- allow_bare = 1;
- } else if (!strcmp(arg, "--keep-cr")) {
- keep_cr = 1;
- } else if ( arg[1] == 'o' && arg[2] ) {
- dir = arg+2;
- } else if ( arg[1] == '-' && !arg[2] ) {
- argp++; /* -- marks end of options */
- break;
- } else {
- die("unknown option: %s", arg);
- }
- }
-
- if ( !dir ) {
- /* Backwards compatibility: if no -o specified, accept
- <mbox> <dir> or just <dir> */
- switch (argc - (argp-argv)) {
- case 1:
- dir = argp[0];
- argp = stdin_only;
- break;
- case 2:
- stdin_only[0] = argp[0];
- dir = argp[1];
- argp = stdin_only;
- break;
- default:
- usage(git_mailsplit_usage);
- }
- } else {
- /* New usage: if no more argument, parse stdin */
- if ( !*argp )
- argp = stdin_only;
- }
-
- 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 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "parse-options.h"
-
-static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
-{
- struct commit_list *result;
-
- result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0);
-
- if (!result)
- return 1;
-
- while (result) {
- printf("%s\n", sha1_to_hex(result->item->object.sha1));
- if (!show_all)
- return 0;
- result = result->next;
- }
-
- return 0;
-}
-
-static const char * const merge_base_usage[] = {
- "git merge-base [-a|--all] <commit> <commit>...",
- NULL
-};
-
-static struct commit *get_commit_reference(const char *arg)
-{
- unsigned char revkey[20];
- struct commit *r;
-
- if (get_sha1(arg, revkey))
- die("Not a valid object name %s", arg);
- r = lookup_commit_reference(revkey);
- if (!r)
- die("Not a valid commit name %s", arg);
-
- return r;
-}
-
-int cmd_merge_base(int argc, const char **argv, const char *prefix)
-{
- struct commit **rev;
- int rev_nr = 0;
- int show_all = 0;
-
- struct option options[] = {
- OPT_BOOLEAN('a', "all", &show_all, "outputs all common ancestors"),
- OPT_END()
- };
-
- git_config(git_default_config, NULL);
- argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
- if (argc < 2)
- usage_with_options(merge_base_usage, options);
- rev = xmalloc(argc * sizeof(*rev));
- while (argc-- > 0)
- rev[rev_nr++] = get_commit_reference(*argv++);
- return show_merge_base(rev, rev_nr, show_all);
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "xdiff/xdiff.h"
-#include "xdiff-interface.h"
-#include "parse-options.h"
-
-static const char *const merge_file_usage[] = {
- "git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2",
- NULL
-};
-
-static int label_cb(const struct option *opt, const char *arg, int unset)
-{
- static int label_count = 0;
- const char **names = (const char **)opt->value;
-
- if (label_count >= 3)
- return error("too many labels on the command line");
- names[label_count++] = arg;
- return 0;
-}
-
-int cmd_merge_file(int argc, const char **argv, const char *prefix)
-{
- const char *names[3] = { NULL, NULL, NULL };
- mmfile_t mmfs[3];
- mmbuffer_t result = {NULL, 0};
- xmparam_t xmp = {{XDF_NEED_MINIMAL}};
- int ret = 0, i = 0, to_stdout = 0;
- int level = XDL_MERGE_ZEALOUS_ALNUM;
- int style = 0, quiet = 0;
- int favor = 0;
- int nongit;
-
- struct option options[] = {
- OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"),
- OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3),
- OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version",
- XDL_MERGE_FAVOR_OURS),
- OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version",
- XDL_MERGE_FAVOR_THEIRS),
- OPT__QUIET(&quiet),
- OPT_CALLBACK('L', NULL, names, "name",
- "set labels for file1/orig_file/file2", &label_cb),
- OPT_END(),
- };
-
- prefix = setup_git_directory_gently(&nongit);
- if (!nongit) {
- /* Read the configuration file */
- git_config(git_xmerge_config, NULL);
- if (0 <= git_xmerge_style)
- style = git_xmerge_style;
- }
-
- argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0);
- if (argc != 3)
- usage_with_options(merge_file_usage, options);
- if (quiet) {
- if (!freopen("/dev/null", "w", stderr))
- return error("failed to redirect stderr to /dev/null: "
- "%s\n", strerror(errno));
- }
-
- for (i = 0; i < 3; i++) {
- if (!names[i])
- names[i] = argv[i];
- if (read_mmfile(mmfs + i, argv[i]))
- return -1;
- if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
- return error("Cannot merge binary files: %s\n",
- argv[i]);
- }
-
- ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
- &xmp, XDL_MERGE_FLAGS(level, style, favor), &result);
-
- for (i = 0; i < 3; i++)
- free(mmfs[i].ptr);
-
- if (ret >= 0) {
- const char *filename = argv[0];
- FILE *f = to_stdout ? stdout : fopen(filename, "wb");
-
- if (!f)
- ret = error("Could not open %s for writing", filename);
- else if (result.size &&
- fwrite(result.ptr, result.size, 1, f) != 1)
- ret = error("Could not write to %s", filename);
- else if (fclose(f))
- ret = error("Could not close %s", filename);
- free(result.ptr);
- }
-
- return ret;
-}
+++ /dev/null
-#include "cache.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-
-static const char *pgm;
-static int one_shot, quiet;
-static int err;
-
-static int merge_entry(int pos, const char *path)
-{
- int found;
- const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
- char hexbuf[4][60];
- char ownbuf[4][60];
-
- if (pos >= active_nr)
- die("git merge-index: %s not in the cache", path);
- found = 0;
- do {
- struct cache_entry *ce = active_cache[pos];
- int stage = ce_stage(ce);
-
- if (strcmp(ce->name, path))
- break;
- found++;
- strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
- sprintf(ownbuf[stage], "%o", ce->ce_mode);
- arguments[stage] = hexbuf[stage];
- arguments[stage + 4] = ownbuf[stage];
- } while (++pos < active_nr);
- if (!found)
- die("git merge-index: %s not in the cache", path);
-
- if (run_command_v_opt(arguments, 0)) {
- if (one_shot)
- err++;
- else {
- if (!quiet)
- die("merge program failed");
- exit(1);
- }
- }
- return found;
-}
-
-static void merge_file(const char *path)
-{
- int pos = cache_name_pos(path, strlen(path));
-
- /*
- * If it already exists in the cache as stage0, it's
- * already merged and there is nothing to do.
- */
- if (pos < 0)
- merge_entry(-pos-1, path);
-}
-
-static void merge_all(void)
-{
- int i;
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (!ce_stage(ce))
- continue;
- i += merge_entry(i, ce->name)-1;
- }
-}
-
-int cmd_merge_index(int argc, const char **argv, const char *prefix)
-{
- int i, force_file = 0;
-
- /* Without this we cannot rely on waitpid() to tell
- * what happened to our children.
- */
- signal(SIGCHLD, SIG_DFL);
-
- if (argc < 3)
- usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
-
- read_cache();
-
- i = 1;
- if (!strcmp(argv[i], "-o")) {
- one_shot = 1;
- i++;
- }
- if (!strcmp(argv[i], "-q")) {
- quiet = 1;
- i++;
- }
- pgm = argv[i++];
- for (; i < argc; i++) {
- const char *arg = argv[i];
- if (!force_file && *arg == '-') {
- if (!strcmp(arg, "--")) {
- force_file = 1;
- continue;
- }
- if (!strcmp(arg, "-a")) {
- merge_all();
- continue;
- }
- die("git merge-index: unknown option %s", arg);
- }
- merge_file(arg);
- }
- if (err && !quiet)
- die("merge program failed");
- return err;
-}
+++ /dev/null
-/*
- * Implementation of git-merge-ours.sh as builtin
- *
- * Copyright (c) 2007 Thomas Harning Jr
- * Original:
- * Original Copyright (c) 2005 Junio C Hamano
- *
- * Pretend we resolved the heads, but declare our tree trumps everybody else.
- */
-#include "git-compat-util.h"
-#include "builtin.h"
-
-static const char builtin_merge_ours_usage[] =
- "git merge-ours <base>... -- HEAD <remote>...";
-
-static const char *diff_index_args[] = {
- "diff-index", "--quiet", "--cached", "HEAD", "--", NULL
-};
-#define NARGS (ARRAY_SIZE(diff_index_args) - 1)
-
-int cmd_merge_ours(int argc, const char **argv, const char *prefix)
-{
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage(builtin_merge_ours_usage);
-
- /*
- * We need to exit with 2 if the index does not match our HEAD tree,
- * because the current index is what we will be committing as the
- * merge result.
- */
- if (cmd_diff_index(NARGS, diff_index_args, prefix))
- exit(2);
- exit(0);
-}
+++ /dev/null
-#include "cache.h"
-#include "commit.h"
-#include "tag.h"
-#include "merge-recursive.h"
-
-static const char *better_branch_name(const char *branch)
-{
- static char githead_env[8 + 40 + 1];
- char *name;
-
- if (strlen(branch) != 40)
- return branch;
- sprintf(githead_env, "GITHEAD_%s", branch);
- name = getenv(githead_env);
- return name ? name : branch;
-}
-
-int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
-{
- const unsigned char *bases[21];
- unsigned bases_count = 0;
- int i, failed;
- unsigned char h1[20], h2[20];
- struct merge_options o;
- struct commit *result;
-
- init_merge_options(&o);
- if (argv[0] && !suffixcmp(argv[0], "-subtree"))
- o.subtree_shift = "";
-
- if (argc < 4)
- usagef("%s <base>... -- <head> <remote> ...", argv[0]);
-
- for (i = 1; i < argc; ++i) {
- const char *arg = argv[i];
-
- if (!prefixcmp(arg, "--")) {
- if (!arg[2])
- break;
- if (!strcmp(arg+2, "ours"))
- o.recursive_variant = MERGE_RECURSIVE_OURS;
- else if (!strcmp(arg+2, "theirs"))
- o.recursive_variant = MERGE_RECURSIVE_THEIRS;
- else if (!strcmp(arg+2, "subtree"))
- o.subtree_shift = "";
- else if (!prefixcmp(arg+2, "subtree="))
- o.subtree_shift = arg + 10;
- else
- die("Unknown option %s", arg);
- continue;
- }
- if (bases_count < ARRAY_SIZE(bases)-1) {
- unsigned char *sha = xmalloc(20);
- if (get_sha1(argv[i], sha))
- die("Could not parse object '%s'", argv[i]);
- bases[bases_count++] = sha;
- }
- else
- warning("Cannot handle more than %d bases. "
- "Ignoring %s.",
- (int)ARRAY_SIZE(bases)-1, argv[i]);
- }
- if (argc - i != 3) /* "--" "<head>" "<remote>" */
- die("Not handling anything other than two heads merge.");
-
- o.branch1 = argv[++i];
- o.branch2 = argv[++i];
-
- if (get_sha1(o.branch1, h1))
- die("Could not resolve ref '%s'", o.branch1);
- if (get_sha1(o.branch2, h2))
- die("Could not resolve ref '%s'", o.branch2);
-
- o.branch1 = better_branch_name(o.branch1);
- o.branch2 = better_branch_name(o.branch2);
-
- if (o.verbosity >= 3)
- printf("Merging %s with %s\n", o.branch1, o.branch2);
-
- failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result);
- if (failed < 0)
- return 128; /* die() error code */
- return failed;
-}
+++ /dev/null
-#include "cache.h"
-#include "tree-walk.h"
-#include "xdiff-interface.h"
-#include "blob.h"
-#include "exec_cmd.h"
-
-static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
-static int resolve_directories = 1;
-
-struct merge_list {
- struct merge_list *next;
- struct merge_list *link; /* other stages for this object */
-
- unsigned int stage : 2,
- flags : 30;
- unsigned int mode;
- const char *path;
- struct blob *blob;
-};
-
-static struct merge_list *merge_result, **merge_result_end = &merge_result;
-
-static void add_merge_entry(struct merge_list *entry)
-{
- *merge_result_end = entry;
- merge_result_end = &entry->next;
-}
-
-static void merge_trees(struct tree_desc t[3], const char *base);
-
-static const char *explanation(struct merge_list *entry)
-{
- switch (entry->stage) {
- case 0:
- return "merged";
- case 3:
- return "added in remote";
- case 2:
- if (entry->link)
- return "added in both";
- return "added in local";
- }
-
- /* Existed in base */
- entry = entry->link;
- if (!entry)
- return "removed in both";
-
- if (entry->link)
- return "changed in both";
-
- if (entry->stage == 3)
- return "removed in local";
- return "removed in remote";
-}
-
-extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *);
-
-static void *result(struct merge_list *entry, unsigned long *size)
-{
- enum object_type type;
- struct blob *base, *our, *their;
-
- if (!entry->stage)
- return read_sha1_file(entry->blob->object.sha1, &type, size);
- base = NULL;
- if (entry->stage == 1) {
- base = entry->blob;
- entry = entry->link;
- }
- our = NULL;
- if (entry && entry->stage == 2) {
- our = entry->blob;
- entry = entry->link;
- }
- their = NULL;
- if (entry)
- their = entry->blob;
- return merge_file(entry->path, base, our, their, size);
-}
-
-static void *origin(struct merge_list *entry, unsigned long *size)
-{
- enum object_type type;
- while (entry) {
- if (entry->stage == 2)
- return read_sha1_file(entry->blob->object.sha1, &type, size);
- entry = entry->link;
- }
- return NULL;
-}
-
-static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf)
-{
- int i;
- for (i = 0; i < nbuf; i++)
- printf("%.*s", (int) mb[i].size, mb[i].ptr);
- return 0;
-}
-
-static void show_diff(struct merge_list *entry)
-{
- unsigned long size;
- mmfile_t src, dst;
- xpparam_t xpp;
- xdemitconf_t xecfg;
- xdemitcb_t ecb;
-
- xpp.flags = XDF_NEED_MINIMAL;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = 3;
- ecb.outf = show_outf;
- ecb.priv = NULL;
-
- src.ptr = origin(entry, &size);
- if (!src.ptr)
- size = 0;
- src.size = size;
- dst.ptr = result(entry, &size);
- if (!dst.ptr)
- size = 0;
- dst.size = size;
- xdi_diff(&src, &dst, &xpp, &xecfg, &ecb);
- free(src.ptr);
- free(dst.ptr);
-}
-
-static void show_result_list(struct merge_list *entry)
-{
- printf("%s\n", explanation(entry));
- do {
- struct merge_list *link = entry->link;
- static const char *desc[4] = { "result", "base", "our", "their" };
- printf(" %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path);
- entry = link;
- } while (entry);
-}
-
-static void show_result(void)
-{
- struct merge_list *walk;
-
- walk = merge_result;
- while (walk) {
- show_result_list(walk);
- show_diff(walk);
- walk = walk->next;
- }
-}
-
-/* An empty entry never compares same, not even to another empty entry */
-static int same_entry(struct name_entry *a, struct name_entry *b)
-{
- return a->sha1 &&
- b->sha1 &&
- !hashcmp(a->sha1, b->sha1) &&
- a->mode == b->mode;
-}
-
-static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path)
-{
- struct merge_list *res = xcalloc(1, sizeof(*res));
-
- res->stage = stage;
- res->path = path;
- res->mode = mode;
- res->blob = lookup_blob(sha1);
- return res;
-}
-
-static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
-{
- char *path = xmalloc(traverse_path_len(info, n) + 1);
- return make_traverse_path(path, info, n);
-}
-
-static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result)
-{
- struct merge_list *orig, *final;
- const char *path;
-
- /* If it's already branch1, don't bother showing it */
- if (!branch1)
- return;
-
- path = traverse_path(info, result);
- orig = create_entry(2, branch1->mode, branch1->sha1, path);
- final = create_entry(0, result->mode, result->sha1, path);
-
- final->link = orig;
-
- add_merge_entry(final);
-}
-
-static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3])
-{
- char *newbase;
- struct name_entry *p;
- struct tree_desc t[3];
- void *buf0, *buf1, *buf2;
-
- if (!resolve_directories)
- return 0;
- p = n;
- if (!p->mode) {
- p++;
- if (!p->mode)
- p++;
- }
- if (!S_ISDIR(p->mode))
- return 0;
- newbase = traverse_path(info, p);
- buf0 = fill_tree_descriptor(t+0, n[0].sha1);
- buf1 = fill_tree_descriptor(t+1, n[1].sha1);
- buf2 = fill_tree_descriptor(t+2, n[2].sha1);
- merge_trees(t, newbase);
-
- free(buf0);
- free(buf1);
- free(buf2);
- free(newbase);
- return 1;
-}
-
-
-static struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry)
-{
- const char *path;
- struct merge_list *link;
-
- if (!n->mode)
- return entry;
- if (entry)
- path = entry->path;
- else
- path = traverse_path(info, n);
- link = create_entry(stage, n->mode, n->sha1, path);
- link->link = entry;
- return link;
-}
-
-static void unresolved(const struct traverse_info *info, struct name_entry n[3])
-{
- struct merge_list *entry = NULL;
-
- if (unresolved_directory(info, n))
- return;
-
- /*
- * Do them in reverse order so that the resulting link
- * list has the stages in order - link_entry adds new
- * links at the front.
- */
- entry = link_entry(3, info, n + 2, entry);
- entry = link_entry(2, info, n + 1, entry);
- entry = link_entry(1, info, n + 0, entry);
-
- add_merge_entry(entry);
-}
-
-/*
- * Merge two trees together (t[1] and t[2]), using a common base (t[0])
- * as the origin.
- *
- * This walks the (sorted) trees in lock-step, checking every possible
- * name. Note that directories automatically sort differently from other
- * files (see "base_name_compare"), so you'll never see file/directory
- * conflicts, because they won't ever compare the same.
- *
- * IOW, if a directory changes to a filename, it will automatically be
- * seen as the directory going away, and the filename being created.
- *
- * Think of this as a three-way diff.
- *
- * The output will be either:
- * - successful merge
- * "0 mode sha1 filename"
- * NOTE NOTE NOTE! FIXME! We really really need to walk the index
- * in parallel with this too!
- *
- * - conflict:
- * "1 mode sha1 filename"
- * "2 mode sha1 filename"
- * "3 mode sha1 filename"
- * where not all of the 1/2/3 lines may exist, of course.
- *
- * The successful merge rules are the same as for the three-way merge
- * in git-read-tree.
- */
-static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info)
-{
- /* Same in both? */
- if (same_entry(entry+1, entry+2)) {
- if (entry[0].sha1) {
- resolve(info, NULL, entry+1);
- return mask;
- }
- }
-
- if (same_entry(entry+0, entry+1)) {
- if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
- resolve(info, entry+1, entry+2);
- return mask;
- }
- }
-
- if (same_entry(entry+0, entry+2)) {
- if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
- resolve(info, NULL, entry+1);
- return mask;
- }
- }
-
- unresolved(info, entry);
- return mask;
-}
-
-static void merge_trees(struct tree_desc t[3], const char *base)
-{
- struct traverse_info info;
-
- setup_traverse_info(&info, base);
- info.fn = threeway_callback;
- traverse_trees(3, t, &info);
-}
-
-static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
-{
- unsigned char sha1[20];
- void *buf;
-
- if (get_sha1(rev, sha1))
- die("unknown rev %s", rev);
- buf = fill_tree_descriptor(desc, sha1);
- if (!buf)
- die("%s is not a tree", rev);
- return buf;
-}
-
-int cmd_merge_tree(int argc, const char **argv, const char *prefix)
-{
- struct tree_desc t[3];
- void *buf1, *buf2, *buf3;
-
- if (argc != 4)
- usage(merge_tree_usage);
-
- buf1 = get_tree_descriptor(t+0, argv[1]);
- buf2 = get_tree_descriptor(t+1, argv[2]);
- buf3 = get_tree_descriptor(t+2, argv[3]);
- merge_trees(t, "");
- free(buf1);
- free(buf2);
- free(buf3);
-
- show_result();
- return 0;
-}
+++ /dev/null
-/*
- * Builtin "git merge"
- *
- * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
- *
- * Based on git-merge.sh by Junio C Hamano.
- */
-
-#include "cache.h"
-#include "parse-options.h"
-#include "builtin.h"
-#include "run-command.h"
-#include "diff.h"
-#include "refs.h"
-#include "commit.h"
-#include "diffcore.h"
-#include "revision.h"
-#include "unpack-trees.h"
-#include "cache-tree.h"
-#include "dir.h"
-#include "utf8.h"
-#include "log-tree.h"
-#include "color.h"
-#include "rerere.h"
-#include "help.h"
-#include "merge-recursive.h"
-#include "resolve-undo.h"
-
-#define DEFAULT_TWOHEAD (1<<0)
-#define DEFAULT_OCTOPUS (1<<1)
-#define NO_FAST_FORWARD (1<<2)
-#define NO_TRIVIAL (1<<3)
-
-struct strategy {
- const char *name;
- unsigned attr;
-};
-
-static const char * const builtin_merge_usage[] = {
- "git merge [options] <remote>...",
- "git merge [options] <msg> HEAD <remote>",
- NULL
-};
-
-static int show_diffstat = 1, option_log, squash;
-static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only;
-static int allow_trivial = 1, have_message;
-static struct strbuf merge_msg;
-static struct commit_list *remoteheads;
-static unsigned char head[20], stash[20];
-static struct strategy **use_strategies;
-static size_t use_strategies_nr, use_strategies_alloc;
-static const char **xopts;
-static size_t xopts_nr, xopts_alloc;
-static const char *branch;
-static int verbosity;
-static int allow_rerere_auto;
-
-static struct strategy all_strategy[] = {
- { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
- { "octopus", DEFAULT_OCTOPUS },
- { "resolve", 0 },
- { "ours", NO_FAST_FORWARD | NO_TRIVIAL },
- { "subtree", NO_FAST_FORWARD | NO_TRIVIAL },
-};
-
-static const char *pull_twohead, *pull_octopus;
-
-static int option_parse_message(const struct option *opt,
- const char *arg, int unset)
-{
- struct strbuf *buf = opt->value;
-
- if (unset)
- strbuf_setlen(buf, 0);
- else if (arg) {
- strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg);
- have_message = 1;
- } else
- return error("switch `m' requires a value");
- return 0;
-}
-
-static struct strategy *get_strategy(const char *name)
-{
- int i;
- struct strategy *ret;
- static struct cmdnames main_cmds, other_cmds;
- static int loaded;
-
- if (!name)
- return NULL;
-
- for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
- if (!strcmp(name, all_strategy[i].name))
- return &all_strategy[i];
-
- if (!loaded) {
- struct cmdnames not_strategies;
- loaded = 1;
-
- memset(¬_strategies, 0, sizeof(struct cmdnames));
- load_command_list("git-merge-", &main_cmds, &other_cmds);
- for (i = 0; i < main_cmds.cnt; i++) {
- int j, found = 0;
- struct cmdname *ent = main_cmds.names[i];
- for (j = 0; j < ARRAY_SIZE(all_strategy); j++)
- if (!strncmp(ent->name, all_strategy[j].name, ent->len)
- && !all_strategy[j].name[ent->len])
- found = 1;
- if (!found)
- add_cmdname(¬_strategies, ent->name, ent->len);
- }
- exclude_cmds(&main_cmds, ¬_strategies);
- }
- if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
- fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
- fprintf(stderr, "Available strategies are:");
- for (i = 0; i < main_cmds.cnt; i++)
- fprintf(stderr, " %s", main_cmds.names[i]->name);
- fprintf(stderr, ".\n");
- if (other_cmds.cnt) {
- fprintf(stderr, "Available custom strategies are:");
- for (i = 0; i < other_cmds.cnt; i++)
- fprintf(stderr, " %s", other_cmds.names[i]->name);
- fprintf(stderr, ".\n");
- }
- exit(1);
- }
-
- ret = xcalloc(1, sizeof(struct strategy));
- ret->name = xstrdup(name);
- return ret;
-}
-
-static void append_strategy(struct strategy *s)
-{
- ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
- use_strategies[use_strategies_nr++] = s;
-}
-
-static int option_parse_strategy(const struct option *opt,
- const char *name, int unset)
-{
- if (unset)
- return 0;
-
- append_strategy(get_strategy(name));
- return 0;
-}
-
-static int option_parse_x(const struct option *opt,
- const char *arg, int unset)
-{
- if (unset)
- return 0;
-
- ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
- xopts[xopts_nr++] = xstrdup(arg);
- return 0;
-}
-
-static int option_parse_n(const struct option *opt,
- const char *arg, int unset)
-{
- show_diffstat = unset;
- return 0;
-}
-
-static struct option builtin_merge_options[] = {
- { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
- "do not show a diffstat at the end of the merge",
- PARSE_OPT_NOARG, option_parse_n },
- OPT_BOOLEAN(0, "stat", &show_diffstat,
- "show a diffstat at the end of the merge"),
- OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
- OPT_BOOLEAN(0, "log", &option_log,
- "add list of one-line log to merge commit message"),
- OPT_BOOLEAN(0, "squash", &squash,
- "create a single commit instead of doing a merge"),
- OPT_BOOLEAN(0, "commit", &option_commit,
- "perform a commit if the merge succeeds (default)"),
- OPT_BOOLEAN(0, "ff", &allow_fast_forward,
- "allow fast-forward (default)"),
- OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
- "abort if fast-forward is not possible"),
- OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
- OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
- "merge strategy to use", option_parse_strategy),
- OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
- "option for selected merge strategy", option_parse_x),
- OPT_CALLBACK('m', "message", &merge_msg, "message",
- "message to be used for the merge commit (if any)",
- option_parse_message),
- OPT__VERBOSITY(&verbosity),
- OPT_END()
-};
-
-/* Cleans up metadata that is uninteresting after a succeeded merge. */
-static void drop_save(void)
-{
- unlink(git_path("MERGE_HEAD"));
- unlink(git_path("MERGE_MSG"));
- unlink(git_path("MERGE_MODE"));
-}
-
-static void save_state(void)
-{
- int len;
- struct child_process cp;
- struct strbuf buffer = STRBUF_INIT;
- const char *argv[] = {"stash", "create", NULL};
-
- memset(&cp, 0, sizeof(cp));
- cp.argv = argv;
- cp.out = -1;
- cp.git_cmd = 1;
-
- if (start_command(&cp))
- die("could not run stash.");
- len = strbuf_read(&buffer, cp.out, 1024);
- close(cp.out);
-
- if (finish_command(&cp) || len < 0)
- die("stash failed");
- else if (!len)
- return;
- strbuf_setlen(&buffer, buffer.len-1);
- if (get_sha1(buffer.buf, stash))
- die("not a valid object: %s", buffer.buf);
-}
-
-static void reset_hard(unsigned const char *sha1, int verbose)
-{
- int i = 0;
- const char *args[6];
-
- args[i++] = "read-tree";
- if (verbose)
- args[i++] = "-v";
- args[i++] = "--reset";
- args[i++] = "-u";
- args[i++] = sha1_to_hex(sha1);
- args[i] = NULL;
-
- if (run_command_v_opt(args, RUN_GIT_CMD))
- die("read-tree failed");
-}
-
-static void restore_state(void)
-{
- struct strbuf sb = STRBUF_INIT;
- const char *args[] = { "stash", "apply", NULL, NULL };
-
- if (is_null_sha1(stash))
- return;
-
- reset_hard(head, 1);
-
- args[2] = sha1_to_hex(stash);
-
- /*
- * It is OK to ignore error here, for example when there was
- * nothing to restore.
- */
- run_command_v_opt(args, RUN_GIT_CMD);
-
- strbuf_release(&sb);
- refresh_cache(REFRESH_QUIET);
-}
-
-/* This is called when no merge was necessary. */
-static void finish_up_to_date(const char *msg)
-{
- if (verbosity >= 0)
- printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
- drop_save();
-}
-
-static void squash_message(void)
-{
- struct rev_info rev;
- struct commit *commit;
- struct strbuf out = STRBUF_INIT;
- struct commit_list *j;
- int fd;
- struct pretty_print_context ctx = {0};
-
- printf("Squash commit -- not updating HEAD\n");
- fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno("Could not write to '%s'", git_path("SQUASH_MSG"));
-
- init_revisions(&rev, NULL);
- rev.ignore_merges = 1;
- rev.commit_format = CMIT_FMT_MEDIUM;
-
- commit = lookup_commit(head);
- commit->object.flags |= UNINTERESTING;
- add_pending_object(&rev, &commit->object, NULL);
-
- for (j = remoteheads; j; j = j->next)
- add_pending_object(&rev, &j->item->object, NULL);
-
- setup_revisions(0, NULL, &rev, NULL);
- if (prepare_revision_walk(&rev))
- die("revision walk setup failed");
-
- ctx.abbrev = rev.abbrev;
- ctx.date_mode = rev.date_mode;
-
- strbuf_addstr(&out, "Squashed commit of the following:\n");
- while ((commit = get_revision(&rev)) != NULL) {
- strbuf_addch(&out, '\n');
- strbuf_addf(&out, "commit %s\n",
- sha1_to_hex(commit->object.sha1));
- pretty_print_commit(rev.commit_format, commit, &out, &ctx);
- }
- if (write(fd, out.buf, out.len) < 0)
- die_errno("Writing SQUASH_MSG");
- if (close(fd))
- die_errno("Finishing SQUASH_MSG");
- strbuf_release(&out);
-}
-
-static void finish(const unsigned char *new_head, const char *msg)
-{
- struct strbuf reflog_message = STRBUF_INIT;
-
- if (!msg)
- strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
- else {
- if (verbosity >= 0)
- printf("%s\n", msg);
- strbuf_addf(&reflog_message, "%s: %s",
- getenv("GIT_REFLOG_ACTION"), msg);
- }
- if (squash) {
- squash_message();
- } else {
- if (verbosity >= 0 && !merge_msg.len)
- printf("No merge message -- not updating HEAD\n");
- else {
- const char *argv_gc_auto[] = { "gc", "--auto", NULL };
- update_ref(reflog_message.buf, "HEAD",
- new_head, head, 0,
- DIE_ON_ERR);
- /*
- * We ignore errors in 'gc --auto', since the
- * user should see them.
- */
- run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
- }
- }
- if (new_head && show_diffstat) {
- struct diff_options opts;
- diff_setup(&opts);
- opts.output_format |=
- DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
- opts.detect_rename = DIFF_DETECT_RENAME;
- if (diff_use_color_default > 0)
- DIFF_OPT_SET(&opts, COLOR_DIFF);
- if (diff_setup_done(&opts) < 0)
- die("diff_setup_done failed");
- diff_tree_sha1(head, new_head, "", &opts);
- diffcore_std(&opts);
- diff_flush(&opts);
- }
-
- /* Run a post-merge hook */
- run_hook(NULL, "post-merge", squash ? "1" : "0", NULL);
-
- strbuf_release(&reflog_message);
-}
-
-/* Get the name for the merge commit's message. */
-static void merge_name(const char *remote, struct strbuf *msg)
-{
- struct object *remote_head;
- unsigned char branch_head[20], buf_sha[20];
- struct strbuf buf = STRBUF_INIT;
- struct strbuf bname = STRBUF_INIT;
- const char *ptr;
- char *found_ref;
- int len, early;
-
- strbuf_branchname(&bname, remote);
- remote = bname.buf;
-
- memset(branch_head, 0, sizeof(branch_head));
- remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
- if (!remote_head)
- die("'%s' does not point to a commit", remote);
-
- if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) {
- if (!prefixcmp(found_ref, "refs/heads/")) {
- strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
- sha1_to_hex(branch_head), remote);
- goto cleanup;
- }
- if (!prefixcmp(found_ref, "refs/remotes/")) {
- strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n",
- sha1_to_hex(branch_head), remote);
- goto cleanup;
- }
- }
-
- /* See if remote matches <name>^^^.. or <name>~<number> */
- for (len = 0, ptr = remote + strlen(remote);
- remote < ptr && ptr[-1] == '^';
- ptr--)
- len++;
- if (len)
- early = 1;
- else {
- early = 0;
- ptr = strrchr(remote, '~');
- if (ptr) {
- int seen_nonzero = 0;
-
- len++; /* count ~ */
- while (*++ptr && isdigit(*ptr)) {
- seen_nonzero |= (*ptr != '0');
- len++;
- }
- if (*ptr)
- len = 0; /* not ...~<number> */
- else if (seen_nonzero)
- early = 1;
- else if (len == 1)
- early = 1; /* "name~" is "name~1"! */
- }
- }
- if (len) {
- struct strbuf truname = STRBUF_INIT;
- strbuf_addstr(&truname, "refs/heads/");
- strbuf_addstr(&truname, remote);
- strbuf_setlen(&truname, truname.len - len);
- if (resolve_ref(truname.buf, buf_sha, 0, NULL)) {
- strbuf_addf(msg,
- "%s\t\tbranch '%s'%s of .\n",
- sha1_to_hex(remote_head->sha1),
- truname.buf + 11,
- (early ? " (early part)" : ""));
- strbuf_release(&truname);
- goto cleanup;
- }
- }
-
- if (!strcmp(remote, "FETCH_HEAD") &&
- !access(git_path("FETCH_HEAD"), R_OK)) {
- FILE *fp;
- struct strbuf line = STRBUF_INIT;
- char *ptr;
-
- fp = fopen(git_path("FETCH_HEAD"), "r");
- if (!fp)
- die_errno("could not open '%s' for reading",
- git_path("FETCH_HEAD"));
- strbuf_getline(&line, fp, '\n');
- fclose(fp);
- ptr = strstr(line.buf, "\tnot-for-merge\t");
- if (ptr)
- strbuf_remove(&line, ptr-line.buf+1, 13);
- strbuf_addbuf(msg, &line);
- strbuf_release(&line);
- goto cleanup;
- }
- strbuf_addf(msg, "%s\t\tcommit '%s'\n",
- sha1_to_hex(remote_head->sha1), remote);
-cleanup:
- strbuf_release(&buf);
- strbuf_release(&bname);
-}
-
-static int git_merge_config(const char *k, const char *v, void *cb)
-{
- if (branch && !prefixcmp(k, "branch.") &&
- !prefixcmp(k + 7, branch) &&
- !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
- const char **argv;
- int argc;
- char *buf;
-
- buf = xstrdup(v);
- argc = split_cmdline(buf, &argv);
- if (argc < 0)
- die("Bad branch.%s.mergeoptions string", branch);
- argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
- memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
- argc++;
- parse_options(argc, argv, NULL, builtin_merge_options,
- builtin_merge_usage, 0);
- free(buf);
- }
-
- if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
- show_diffstat = git_config_bool(k, v);
- else if (!strcmp(k, "pull.twohead"))
- return git_config_string(&pull_twohead, k, v);
- else if (!strcmp(k, "pull.octopus"))
- return git_config_string(&pull_octopus, k, v);
- else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
- option_log = git_config_bool(k, v);
- return git_diff_ui_config(k, v, cb);
-}
-
-static int read_tree_trivial(unsigned char *common, unsigned char *head,
- unsigned char *one)
-{
- int i, nr_trees = 0;
- struct tree *trees[MAX_UNPACK_TREES];
- struct tree_desc t[MAX_UNPACK_TREES];
- struct unpack_trees_options opts;
-
- memset(&opts, 0, sizeof(opts));
- opts.head_idx = 2;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
- opts.update = 1;
- opts.verbose_update = 1;
- opts.trivial_merges_only = 1;
- opts.merge = 1;
- trees[nr_trees] = parse_tree_indirect(common);
- if (!trees[nr_trees++])
- return -1;
- trees[nr_trees] = parse_tree_indirect(head);
- if (!trees[nr_trees++])
- return -1;
- trees[nr_trees] = parse_tree_indirect(one);
- if (!trees[nr_trees++])
- return -1;
- opts.fn = threeway_merge;
- cache_tree_free(&active_cache_tree);
- for (i = 0; i < nr_trees; i++) {
- parse_tree(trees[i]);
- init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
- }
- if (unpack_trees(nr_trees, t, &opts))
- return -1;
- return 0;
-}
-
-static void write_tree_trivial(unsigned char *sha1)
-{
- if (write_cache_as_tree(sha1, 0, NULL))
- die("git write-tree failed to write a tree");
-}
-
-static int try_merge_strategy(const char *strategy, struct commit_list *common,
- const char *head_arg)
-{
- const char **args;
- int i = 0, x = 0, ret;
- struct commit_list *j;
- struct strbuf buf = STRBUF_INIT;
- int index_fd;
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-
- index_fd = hold_locked_index(lock, 1);
- refresh_cache(REFRESH_QUIET);
- if (active_cache_changed &&
- (write_cache(index_fd, active_cache, active_nr) ||
- commit_locked_index(lock)))
- return error("Unable to write index.");
- rollback_lock_file(lock);
-
- if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
- int clean;
- struct commit *result;
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
- int index_fd;
- struct commit_list *reversed = NULL;
- struct merge_options o;
-
- if (remoteheads->next) {
- error("Not handling anything other than two heads merge.");
- return 2;
- }
-
- init_merge_options(&o);
- if (!strcmp(strategy, "subtree"))
- o.subtree_shift = "";
-
- for (x = 0; x < xopts_nr; x++) {
- if (!strcmp(xopts[x], "ours"))
- o.recursive_variant = MERGE_RECURSIVE_OURS;
- else if (!strcmp(xopts[x], "theirs"))
- o.recursive_variant = MERGE_RECURSIVE_THEIRS;
- else if (!strcmp(xopts[x], "subtree"))
- o.subtree_shift = "";
- else if (!prefixcmp(xopts[x], "subtree="))
- o.subtree_shift = xopts[x]+8;
- else
- die("Unknown option for merge-recursive: -X%s", xopts[x]);
- }
-
- o.branch1 = head_arg;
- o.branch2 = remoteheads->item->util;
-
- for (j = common; j; j = j->next)
- commit_list_insert(j->item, &reversed);
-
- index_fd = hold_locked_index(lock, 1);
- clean = merge_recursive(&o, lookup_commit(head),
- remoteheads->item, reversed, &result);
- if (active_cache_changed &&
- (write_cache(index_fd, active_cache, active_nr) ||
- commit_locked_index(lock)))
- die ("unable to write %s", get_index_file());
- rollback_lock_file(lock);
- return clean ? 0 : 1;
- } else {
- args = xmalloc((4 + xopts_nr + commit_list_count(common) +
- commit_list_count(remoteheads)) * sizeof(char *));
- strbuf_addf(&buf, "merge-%s", strategy);
- args[i++] = buf.buf;
- for (x = 0; x < xopts_nr; x++) {
- char *s = xmalloc(strlen(xopts[x])+2+1);
- strcpy(s, "--");
- strcpy(s+2, xopts[x]);
- args[i++] = s;
- }
- for (j = common; j; j = j->next)
- args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
- args[i++] = "--";
- args[i++] = head_arg;
- for (j = remoteheads; j; j = j->next)
- args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
- args[i] = NULL;
- ret = run_command_v_opt(args, RUN_GIT_CMD);
- strbuf_release(&buf);
- i = 1;
- for (x = 0; x < xopts_nr; x++)
- free((void *)args[i++]);
- for (j = common; j; j = j->next)
- free((void *)args[i++]);
- i += 2;
- for (j = remoteheads; j; j = j->next)
- free((void *)args[i++]);
- free(args);
- discard_cache();
- if (read_cache() < 0)
- die("failed to read the cache");
- resolve_undo_clear();
- return ret;
- }
-}
-
-static void count_diff_files(struct diff_queue_struct *q,
- struct diff_options *opt, void *data)
-{
- int *count = data;
-
- (*count) += q->nr;
-}
-
-static int count_unmerged_entries(void)
-{
- int i, ret = 0;
-
- for (i = 0; i < active_nr; i++)
- if (ce_stage(active_cache[i]))
- ret++;
-
- return ret;
-}
-
-static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
-{
- struct tree *trees[MAX_UNPACK_TREES];
- struct unpack_trees_options opts;
- struct tree_desc t[MAX_UNPACK_TREES];
- int i, fd, nr_trees = 0;
- struct dir_struct dir;
- struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-
- refresh_cache(REFRESH_QUIET);
-
- fd = hold_locked_index(lock_file, 1);
-
- memset(&trees, 0, sizeof(trees));
- memset(&opts, 0, sizeof(opts));
- memset(&t, 0, sizeof(t));
- memset(&dir, 0, sizeof(dir));
- dir.flags |= DIR_SHOW_IGNORED;
- dir.exclude_per_dir = ".gitignore";
- opts.dir = &dir;
-
- opts.head_idx = 1;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
- opts.update = 1;
- opts.verbose_update = 1;
- opts.merge = 1;
- opts.fn = twoway_merge;
- opts.msgs = get_porcelain_error_msgs();
-
- trees[nr_trees] = parse_tree_indirect(head);
- if (!trees[nr_trees++])
- return -1;
- trees[nr_trees] = parse_tree_indirect(remote);
- if (!trees[nr_trees++])
- return -1;
- for (i = 0; i < nr_trees; i++) {
- parse_tree(trees[i]);
- init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
- }
- if (unpack_trees(nr_trees, t, &opts))
- return -1;
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
- die("unable to write new index file");
- return 0;
-}
-
-static void split_merge_strategies(const char *string, struct strategy **list,
- int *nr, int *alloc)
-{
- char *p, *q, *buf;
-
- if (!string)
- return;
-
- buf = xstrdup(string);
- q = buf;
- for (;;) {
- p = strchr(q, ' ');
- if (!p) {
- ALLOC_GROW(*list, *nr + 1, *alloc);
- (*list)[(*nr)++].name = xstrdup(q);
- free(buf);
- return;
- } else {
- *p = '\0';
- ALLOC_GROW(*list, *nr + 1, *alloc);
- (*list)[(*nr)++].name = xstrdup(q);
- q = ++p;
- }
- }
-}
-
-static void add_strategies(const char *string, unsigned attr)
-{
- struct strategy *list = NULL;
- int list_alloc = 0, list_nr = 0, i;
-
- memset(&list, 0, sizeof(list));
- split_merge_strategies(string, &list, &list_nr, &list_alloc);
- if (list) {
- for (i = 0; i < list_nr; i++)
- append_strategy(get_strategy(list[i].name));
- return;
- }
- for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
- if (all_strategy[i].attr & attr)
- append_strategy(&all_strategy[i]);
-
-}
-
-static int merge_trivial(void)
-{
- unsigned char result_tree[20], result_commit[20];
- struct commit_list *parent = xmalloc(sizeof(*parent));
-
- write_tree_trivial(result_tree);
- printf("Wonderful.\n");
- parent->item = lookup_commit(head);
- parent->next = xmalloc(sizeof(*parent->next));
- parent->next->item = remoteheads->item;
- parent->next->next = NULL;
- commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
- finish(result_commit, "In-index merge");
- drop_save();
- return 0;
-}
-
-static int finish_automerge(struct commit_list *common,
- unsigned char *result_tree,
- const char *wt_strategy)
-{
- struct commit_list *parents = NULL, *j;
- struct strbuf buf = STRBUF_INIT;
- unsigned char result_commit[20];
-
- free_commit_list(common);
- if (allow_fast_forward) {
- parents = remoteheads;
- commit_list_insert(lookup_commit(head), &parents);
- parents = reduce_heads(parents);
- } else {
- struct commit_list **pptr = &parents;
-
- pptr = &commit_list_insert(lookup_commit(head),
- pptr)->next;
- for (j = remoteheads; j; j = j->next)
- pptr = &commit_list_insert(j->item, pptr)->next;
- }
- free_commit_list(remoteheads);
- strbuf_addch(&merge_msg, '\n');
- commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
- strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
- finish(result_commit, buf.buf);
- strbuf_release(&buf);
- drop_save();
- return 0;
-}
-
-static int suggest_conflicts(void)
-{
- FILE *fp;
- int pos;
-
- fp = fopen(git_path("MERGE_MSG"), "a");
- if (!fp)
- die_errno("Could not open '%s' for writing",
- git_path("MERGE_MSG"));
- fprintf(fp, "\nConflicts:\n");
- for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
-
- if (ce_stage(ce)) {
- fprintf(fp, "\t%s\n", ce->name);
- while (pos + 1 < active_nr &&
- !strcmp(ce->name,
- active_cache[pos + 1]->name))
- pos++;
- }
- }
- fclose(fp);
- rerere(allow_rerere_auto);
- printf("Automatic merge failed; "
- "fix conflicts and then commit the result.\n");
- return 1;
-}
-
-static struct commit *is_old_style_invocation(int argc, const char **argv)
-{
- struct commit *second_token = NULL;
- if (argc > 2) {
- unsigned char second_sha1[20];
-
- if (get_sha1(argv[1], second_sha1))
- return NULL;
- second_token = lookup_commit_reference_gently(second_sha1, 0);
- if (!second_token)
- die("'%s' is not a commit", argv[1]);
- if (hashcmp(second_token->object.sha1, head))
- return NULL;
- }
- return second_token;
-}
-
-static int evaluate_result(void)
-{
- int cnt = 0;
- struct rev_info rev;
-
- /* Check how many files differ. */
- init_revisions(&rev, "");
- setup_revisions(0, NULL, &rev, NULL);
- rev.diffopt.output_format |=
- DIFF_FORMAT_CALLBACK;
- rev.diffopt.format_callback = count_diff_files;
- rev.diffopt.format_callback_data = &cnt;
- run_diff_files(&rev, 0);
-
- /*
- * Check how many unmerged entries are
- * there.
- */
- cnt += count_unmerged_entries();
-
- return cnt;
-}
-
-int cmd_merge(int argc, const char **argv, const char *prefix)
-{
- unsigned char result_tree[20];
- struct strbuf buf = STRBUF_INIT;
- const char *head_arg;
- int flag, head_invalid = 0, i;
- int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
- struct commit_list *common = NULL;
- const char *best_strategy = NULL, *wt_strategy = NULL;
- struct commit_list **remotes = &remoteheads;
-
- if (read_cache_unmerged()) {
- die_resolve_conflict("merge");
- }
- if (file_exists(git_path("MERGE_HEAD"))) {
- /*
- * There is no unmerged entry, don't advise 'git
- * add/rm <file>', just 'git commit'.
- */
- if (advice_resolve_conflict)
- die("You have not concluded your merge (MERGE_HEAD exists).\n"
- "Please, commit your changes before you can merge.");
- else
- die("You have not concluded your merge (MERGE_HEAD exists).");
- }
-
- resolve_undo_clear();
- /*
- * Check if we are _not_ on a detached HEAD, i.e. if there is a
- * current branch.
- */
- branch = resolve_ref("HEAD", head, 0, &flag);
- if (branch && !prefixcmp(branch, "refs/heads/"))
- branch += 11;
- if (is_null_sha1(head))
- head_invalid = 1;
-
- git_config(git_merge_config, NULL);
-
- /* for color.ui */
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
- argc = parse_options(argc, argv, prefix, builtin_merge_options,
- builtin_merge_usage, 0);
- if (verbosity < 0)
- show_diffstat = 0;
-
- if (squash) {
- if (!allow_fast_forward)
- die("You cannot combine --squash with --no-ff.");
- option_commit = 0;
- }
-
- if (!allow_fast_forward && fast_forward_only)
- die("You cannot combine --no-ff with --ff-only.");
-
- if (!argc)
- usage_with_options(builtin_merge_usage,
- builtin_merge_options);
-
- /*
- * 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 is the same as HEAD there instead.
- * Traditional format never would have "-m" so it is an
- * additional safety measure to check for it.
- */
-
- if (!have_message && is_old_style_invocation(argc, argv)) {
- strbuf_addstr(&merge_msg, argv[0]);
- head_arg = argv[1];
- argv += 2;
- argc -= 2;
- } else if (head_invalid) {
- struct object *remote_head;
- /*
- * 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 the same for "git pull".
- */
- if (argc != 1)
- die("Can merge only exactly one commit into "
- "empty head");
- if (squash)
- die("Squash commit into empty head not supported yet");
- if (!allow_fast_forward)
- die("Non-fast-forward commit does not make sense into "
- "an empty head");
- remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
- if (!remote_head)
- die("%s - not something we can merge", argv[0]);
- update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
- DIE_ON_ERR);
- reset_hard(remote_head->sha1, 0);
- return 0;
- } else {
- struct strbuf msg = STRBUF_INIT;
-
- /* We are invoked directly as the first-class UI. */
- head_arg = "HEAD";
-
- /*
- * All the rest are the commits being merged;
- * prepare the standard merge summary message to
- * be appended to the given message. If remote
- * is invalid we will die later in the common
- * codepath so we discard the error in this
- * loop.
- */
- if (!have_message) {
- for (i = 0; i < argc; i++)
- merge_name(argv[i], &msg);
- fmt_merge_msg(option_log, &msg, &merge_msg);
- if (merge_msg.len)
- strbuf_setlen(&merge_msg, merge_msg.len-1);
- }
- }
-
- if (head_invalid || !argc)
- usage_with_options(builtin_merge_usage,
- builtin_merge_options);
-
- strbuf_addstr(&buf, "merge");
- for (i = 0; i < argc; i++)
- strbuf_addf(&buf, " %s", argv[i]);
- setenv("GIT_REFLOG_ACTION", buf.buf, 0);
- strbuf_reset(&buf);
-
- for (i = 0; i < argc; i++) {
- struct object *o;
- struct commit *commit;
-
- o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
- if (!o)
- die("%s - not something we can merge", argv[i]);
- commit = lookup_commit(o->sha1);
- commit->util = (void *)argv[i];
- remotes = &commit_list_insert(commit, remotes)->next;
-
- strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
- setenv(buf.buf, argv[i], 1);
- strbuf_reset(&buf);
- }
-
- if (!use_strategies) {
- if (!remoteheads->next)
- add_strategies(pull_twohead, DEFAULT_TWOHEAD);
- else
- add_strategies(pull_octopus, DEFAULT_OCTOPUS);
- }
-
- for (i = 0; i < use_strategies_nr; i++) {
- if (use_strategies[i]->attr & NO_FAST_FORWARD)
- allow_fast_forward = 0;
- if (use_strategies[i]->attr & NO_TRIVIAL)
- allow_trivial = 0;
- }
-
- if (!remoteheads->next)
- common = get_merge_bases(lookup_commit(head),
- remoteheads->item, 1);
- else {
- struct commit_list *list = remoteheads;
- commit_list_insert(lookup_commit(head), &list);
- common = get_octopus_merge_bases(list);
- free(list);
- }
-
- update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
- DIE_ON_ERR);
-
- if (!common)
- ; /* No common ancestors found. We need a real merge. */
- else if (!remoteheads->next && !common->next &&
- common->item == remoteheads->item) {
- /*
- * If head can reach all the merge then we are up to date.
- * but first the most common case of merging one remote.
- */
- finish_up_to_date("Already up-to-date.");
- return 0;
- } else if (allow_fast_forward && !remoteheads->next &&
- !common->next &&
- !hashcmp(common->item->object.sha1, head)) {
- /* Again the most common case of merging one remote. */
- struct strbuf msg = STRBUF_INIT;
- struct object *o;
- char hex[41];
-
- strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
-
- if (verbosity >= 0)
- printf("Updating %s..%s\n",
- hex,
- find_unique_abbrev(remoteheads->item->object.sha1,
- DEFAULT_ABBREV));
- strbuf_addstr(&msg, "Fast-forward");
- if (have_message)
- strbuf_addstr(&msg,
- " (no commit created; -m option ignored)");
- o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
- 0, NULL, OBJ_COMMIT);
- if (!o)
- return 1;
-
- if (checkout_fast_forward(head, remoteheads->item->object.sha1))
- return 1;
-
- finish(o->sha1, msg.buf);
- drop_save();
- return 0;
- } else if (!remoteheads->next && common->next)
- ;
- /*
- * We are not doing octopus and not fast-forward. Need
- * a real merge.
- */
- else if (!remoteheads->next && !common->next && option_commit) {
- /*
- * We are not doing octopus, not fast-forward, and have
- * only one common.
- */
- refresh_cache(REFRESH_QUIET);
- if (allow_trivial && !fast_forward_only) {
- /* See if it is really trivial. */
- git_committer_info(IDENT_ERROR_ON_NO_NAME);
- printf("Trying really trivial in-index merge...\n");
- if (!read_tree_trivial(common->item->object.sha1,
- head, remoteheads->item->object.sha1))
- return merge_trivial();
- printf("Nope.\n");
- }
- } else {
- /*
- * An octopus. If we can reach all the remote we are up
- * to date.
- */
- int up_to_date = 1;
- struct commit_list *j;
-
- for (j = remoteheads; j; j = j->next) {
- struct commit_list *common_one;
-
- /*
- * Here we *have* to calculate the individual
- * merge_bases again, otherwise "git merge HEAD^
- * HEAD^^" would be missed.
- */
- common_one = get_merge_bases(lookup_commit(head),
- j->item, 1);
- if (hashcmp(common_one->item->object.sha1,
- j->item->object.sha1)) {
- up_to_date = 0;
- break;
- }
- }
- if (up_to_date) {
- finish_up_to_date("Already up-to-date. Yeeah!");
- return 0;
- }
- }
-
- if (fast_forward_only)
- die("Not possible to fast-forward, aborting.");
-
- /* We are going to make a new commit. */
- git_committer_info(IDENT_ERROR_ON_NO_NAME);
-
- /*
- * At this point, we need a real merge. No matter what strategy
- * we use, it would operate on the index, possibly affecting the
- * working tree, and when resolved cleanly, have the desired
- * tree in the index -- this means that the index must be in
- * sync with the head commit. The strategies are responsible
- * to ensure this.
- */
- if (use_strategies_nr != 1) {
- /*
- * Stash away the local changes so that we can try more
- * than one.
- */
- save_state();
- } else {
- memcpy(stash, null_sha1, 20);
- }
-
- for (i = 0; i < use_strategies_nr; i++) {
- int ret;
- if (i) {
- printf("Rewinding the tree to pristine...\n");
- restore_state();
- }
- if (use_strategies_nr != 1)
- printf("Trying merge strategy %s...\n",
- use_strategies[i]->name);
- /*
- * Remember which strategy left the state in the working
- * tree.
- */
- wt_strategy = use_strategies[i]->name;
-
- ret = try_merge_strategy(use_strategies[i]->name,
- common, head_arg);
- if (!option_commit && !ret) {
- merge_was_ok = 1;
- /*
- * This is necessary here just to avoid writing
- * the tree, but later we will *not* exit with
- * status code 1 because merge_was_ok is set.
- */
- ret = 1;
- }
-
- if (ret) {
- /*
- * The backend exits with 1 when conflicts are
- * left to be resolved, with 2 when it does not
- * handle the given merge at all.
- */
- if (ret == 1) {
- int cnt = evaluate_result();
-
- if (best_cnt <= 0 || cnt <= best_cnt) {
- best_strategy = use_strategies[i]->name;
- best_cnt = cnt;
- }
- }
- if (merge_was_ok)
- break;
- else
- continue;
- }
-
- /* Automerge succeeded. */
- write_tree_trivial(result_tree);
- automerge_was_ok = 1;
- break;
- }
-
- /*
- * If we have a resulting tree, that means the strategy module
- * auto resolved the merge cleanly.
- */
- if (automerge_was_ok)
- return finish_automerge(common, result_tree, wt_strategy);
-
- /*
- * Pick the result from the best strategy and have the user fix
- * it up.
- */
- if (!best_strategy) {
- restore_state();
- if (use_strategies_nr > 1)
- fprintf(stderr,
- "No merge strategy handled the merge.\n");
- else
- fprintf(stderr, "Merge with strategy %s failed.\n",
- use_strategies[0]->name);
- return 2;
- } else if (best_strategy == wt_strategy)
- ; /* We already have its result in the working tree. */
- else {
- printf("Rewinding the tree to pristine...\n");
- restore_state();
- printf("Using the %s to prepare resolving by hand.\n",
- best_strategy);
- try_merge_strategy(best_strategy, common, head_arg);
- }
-
- if (squash)
- finish(NULL, NULL);
- else {
- int fd;
- struct commit_list *j;
-
- for (j = remoteheads; j; j = j->next)
- strbuf_addf(&buf, "%s\n",
- sha1_to_hex(j->item->object.sha1));
- fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno("Could not open '%s' for writing",
- git_path("MERGE_HEAD"));
- if (write_in_full(fd, buf.buf, buf.len) != buf.len)
- die_errno("Could not write to '%s'", git_path("MERGE_HEAD"));
- close(fd);
- strbuf_addch(&merge_msg, '\n');
- fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno("Could not open '%s' for writing",
- git_path("MERGE_MSG"));
- if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
- merge_msg.len)
- die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
- close(fd);
- fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
- if (fd < 0)
- die_errno("Could not open '%s' for writing",
- git_path("MERGE_MODE"));
- strbuf_reset(&buf);
- if (!allow_fast_forward)
- strbuf_addf(&buf, "no-ff");
- if (write_in_full(fd, buf.buf, buf.len) != buf.len)
- die_errno("Could not write to '%s'", git_path("MERGE_MODE"));
- close(fd);
- }
-
- if (merge_was_ok) {
- fprintf(stderr, "Automatic merge went well; "
- "stopped before committing as requested\n");
- return 0;
- } else
- return suggest_conflicts();
-}
+++ /dev/null
-#include "cache.h"
-#include "tag.h"
-#include "exec_cmd.h"
-
-/*
- * A signature file has a very simple fixed format: four lines
- * of "object <sha1>" + "type <typename>" + "tag <tagname>" +
- * "tagger <committer>", followed by a blank line, a free-form tag
- * message and a signature block that git itself doesn't care about,
- * but that can be verified with gpg or similar.
- *
- * The first four lines are guaranteed to be at least 83 bytes:
- * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
- * shortest possible type-line, "tag .\n" at 6 bytes is the shortest
- * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is
- * the shortest possible tagger-line.
- */
-
-/*
- * We refuse to tag something we can't verify. Just because.
- */
-static int verify_object(const unsigned char *sha1, const char *expected_type)
-{
- int ret = -1;
- enum object_type type;
- unsigned long size;
- const unsigned char *repl;
- void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl);
-
- if (buffer) {
- if (type == type_from_string(expected_type))
- ret = check_sha1_signature(repl, buffer, size, expected_type);
- free(buffer);
- }
- return ret;
-}
-
-#ifdef NO_C99_FORMAT
-#define PD_FMT "%d"
-#else
-#define PD_FMT "%td"
-#endif
-
-static int verify_tag(char *buffer, unsigned long size)
-{
- int typelen;
- char type[20];
- unsigned char sha1[20];
- const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb;
- size_t len;
-
- if (size < 84)
- return error("wanna fool me ? you obviously got the size wrong !");
-
- buffer[size] = 0;
-
- /* Verify object line */
- object = buffer;
- if (memcmp(object, "object ", 7))
- return error("char%d: does not start with \"object \"", 0);
-
- if (get_sha1_hex(object + 7, sha1))
- return error("char%d: could not get SHA1 hash", 7);
-
- /* Verify type line */
- type_line = object + 48;
- if (memcmp(type_line - 1, "\ntype ", 6))
- return error("char%d: could not find \"\\ntype \"", 47);
-
- /* Verify tag-line */
- tag_line = strchr(type_line, '\n');
- if (!tag_line)
- return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer);
- tag_line++;
- if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
- return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer);
-
- /* Get the actual type */
- typelen = tag_line - type_line - strlen("type \n");
- if (typelen >= sizeof(type))
- return error("char" PD_FMT ": type too long", type_line+5 - buffer);
-
- memcpy(type, type_line+5, typelen);
- type[typelen] = 0;
-
- /* Verify that the object matches */
- if (verify_object(sha1, type))
- return error("char%d: could not verify object %s", 7, sha1_to_hex(sha1));
-
- /* Verify the tag-name: we don't allow control characters or spaces in it */
- tag_line += 4;
- for (;;) {
- unsigned char c = *tag_line++;
- if (c == '\n')
- break;
- if (c > ' ')
- continue;
- return error("char" PD_FMT ": could not verify tag name", tag_line - buffer);
- }
-
- /* Verify the tagger line */
- tagger_line = tag_line;
-
- if (memcmp(tagger_line, "tagger ", 7))
- return error("char" PD_FMT ": could not find \"tagger \"",
- tagger_line - buffer);
-
- /*
- * Check for correct form for name and email
- * i.e. " <" followed by "> " on _this_ line
- * No angle brackets within the name or email address fields.
- * No spaces within the email address field.
- */
- tagger_line += 7;
- if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) ||
- strpbrk(tagger_line, "<>\n") != lb+1 ||
- strpbrk(lb+2, "><\n ") != rb)
- return error("char" PD_FMT ": malformed tagger field",
- tagger_line - buffer);
-
- /* Check for author name, at least one character, space is acceptable */
- if (lb == tagger_line)
- return error("char" PD_FMT ": missing tagger name",
- tagger_line - buffer);
-
- /* timestamp, 1 or more digits followed by space */
- tagger_line = rb + 2;
- if (!(len = strspn(tagger_line, "0123456789")))
- return error("char" PD_FMT ": missing tag timestamp",
- tagger_line - buffer);
- tagger_line += len;
- if (*tagger_line != ' ')
- return error("char" PD_FMT ": malformed tag timestamp",
- tagger_line - buffer);
- tagger_line++;
-
- /* timezone, 5 digits [+-]hhmm, max. 1400 */
- if (!((tagger_line[0] == '+' || tagger_line[0] == '-') &&
- strspn(tagger_line+1, "0123456789") == 4 &&
- tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400))
- return error("char" PD_FMT ": malformed tag timezone",
- tagger_line - buffer);
- tagger_line += 6;
-
- /* Verify the blank line separating the header from the body */
- if (*tagger_line != '\n')
- return error("char" PD_FMT ": trailing garbage in tag header",
- tagger_line - buffer);
-
- /* The actual stuff afterwards we don't care about.. */
- return 0;
-}
-
-#undef PD_FMT
-
-int cmd_mktag(int argc, const char **argv, const char *prefix)
-{
- struct strbuf buf = STRBUF_INIT;
- unsigned char result_sha1[20];
-
- if (argc != 1)
- usage("git mktag < signaturefile");
-
- if (strbuf_read(&buf, 0, 4096) < 0) {
- die_errno("could not read from stdin");
- }
-
- /* Verify it for some basic sanity: it needs to start with
- "object <sha1>\ntype\ntagger " */
- if (verify_tag(buf.buf, buf.len) < 0)
- die("invalid tag signature file");
-
- if (write_sha1_file(buf.buf, buf.len, tag_type, result_sha1) < 0)
- die("unable to write tag file");
-
- strbuf_release(&buf);
- printf("%s\n", sha1_to_hex(result_sha1));
- return 0;
-}
+++ /dev/null
-/*
- * GIT - the stupid content tracker
- *
- * Copyright (c) Junio C Hamano, 2006, 2009
- */
-#include "builtin.h"
-#include "quote.h"
-#include "tree.h"
-#include "parse-options.h"
-
-static struct treeent {
- unsigned mode;
- unsigned char sha1[20];
- int len;
- char name[FLEX_ARRAY];
-} **entries;
-static int alloc, used;
-
-static void append_to_tree(unsigned mode, unsigned char *sha1, char *path)
-{
- struct treeent *ent;
- int len = strlen(path);
- if (strchr(path, '/'))
- die("path %s contains slash", path);
-
- if (alloc <= used) {
- alloc = alloc_nr(used);
- entries = xrealloc(entries, sizeof(*entries) * alloc);
- }
- ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1);
- ent->mode = mode;
- ent->len = len;
- hashcpy(ent->sha1, sha1);
- memcpy(ent->name, path, len+1);
-}
-
-static int ent_compare(const void *a_, const void *b_)
-{
- struct treeent *a = *(struct treeent **)a_;
- struct treeent *b = *(struct treeent **)b_;
- return base_name_compare(a->name, a->len, a->mode,
- b->name, b->len, b->mode);
-}
-
-static void write_tree(unsigned char *sha1)
-{
- struct strbuf buf;
- size_t size;
- int i;
-
- qsort(entries, used, sizeof(*entries), ent_compare);
- for (size = i = 0; i < used; i++)
- size += 32 + entries[i]->len;
-
- strbuf_init(&buf, size);
- for (i = 0; i < used; i++) {
- struct treeent *ent = entries[i];
- strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0');
- strbuf_add(&buf, ent->sha1, 20);
- }
-
- write_sha1_file(buf.buf, buf.len, tree_type, sha1);
-}
-
-static const char *mktree_usage[] = {
- "git mktree [-z] [--missing] [--batch]",
- NULL
-};
-
-static void mktree_line(char *buf, size_t len, int line_termination, int allow_missing)
-{
- char *ptr, *ntr;
- unsigned mode;
- enum object_type mode_type; /* object type derived from mode */
- enum object_type obj_type; /* object type derived from sha */
- char *path;
- unsigned char sha1[20];
-
- ptr = buf;
- /*
- * Read non-recursive ls-tree output format:
- * mode SP type SP sha1 TAB name
- */
- mode = strtoul(ptr, &ntr, 8);
- if (ptr == ntr || !ntr || *ntr != ' ')
- die("input format error: %s", buf);
- ptr = ntr + 1; /* type */
- ntr = strchr(ptr, ' ');
- if (!ntr || buf + len <= ntr + 40 ||
- ntr[41] != '\t' ||
- get_sha1_hex(ntr + 1, sha1))
- die("input format error: %s", buf);
-
- /* It is perfectly normal if we do not have a commit from a submodule */
- if (S_ISGITLINK(mode))
- allow_missing = 1;
-
-
- *ntr++ = 0; /* now at the beginning of SHA1 */
-
- path = ntr + 41; /* at the beginning of name */
- if (line_termination && path[0] == '"') {
- struct strbuf p_uq = STRBUF_INIT;
- if (unquote_c_style(&p_uq, path, NULL))
- die("invalid quoting");
- path = strbuf_detach(&p_uq, NULL);
- }
-
- /*
- * Object type is redundantly derivable three ways.
- * These should all agree.
- */
- mode_type = object_type(mode);
- if (mode_type != type_from_string(ptr)) {
- die("entry '%s' object type (%s) doesn't match mode type (%s)",
- path, ptr, typename(mode_type));
- }
-
- /* Check the type of object identified by sha1 */
- obj_type = sha1_object_info(sha1, NULL);
- if (obj_type < 0) {
- if (allow_missing) {
- ; /* no problem - missing objects are presumed to be of the right type */
- } else {
- die("entry '%s' object %s is unavailable", path, sha1_to_hex(sha1));
- }
- } else {
- if (obj_type != mode_type) {
- /*
- * The object exists but is of the wrong type.
- * This is a problem regardless of allow_missing
- * because the new tree entry will never be correct.
- */
- die("entry '%s' object %s is a %s but specified type was (%s)",
- path, sha1_to_hex(sha1), typename(obj_type), typename(mode_type));
- }
- }
-
- append_to_tree(mode, sha1, path);
-}
-
-int cmd_mktree(int ac, const char **av, const char *prefix)
-{
- struct strbuf sb = STRBUF_INIT;
- unsigned char sha1[20];
- int line_termination = '\n';
- int allow_missing = 0;
- int is_batch_mode = 0;
- int got_eof = 0;
-
- const struct option option[] = {
- OPT_SET_INT('z', NULL, &line_termination, "input is NUL terminated", '\0'),
- OPT_SET_INT( 0 , "missing", &allow_missing, "allow missing objects", 1),
- OPT_SET_INT( 0 , "batch", &is_batch_mode, "allow creation of more than one tree", 1),
- OPT_END()
- };
-
- ac = parse_options(ac, av, prefix, option, mktree_usage, 0);
-
- while (!got_eof) {
- while (1) {
- if (strbuf_getline(&sb, stdin, line_termination) == EOF) {
- got_eof = 1;
- break;
- }
- if (sb.buf[0] == '\0') {
- /* empty lines denote tree boundaries in batch mode */
- if (is_batch_mode)
- break;
- die("input format error: (blank line only valid in batch mode)");
- }
- mktree_line(sb.buf, sb.len, line_termination, allow_missing);
- }
- if (is_batch_mode && got_eof && used < 1) {
- /*
- * Execution gets here if the last tree entry is terminated with a
- * new-line. The final new-line has been made optional to be
- * consistent with the original non-batch behaviour of mktree.
- */
- ; /* skip creating an empty tree */
- } else {
- write_tree(sha1);
- puts(sha1_to_hex(sha1));
- fflush(stdout);
- }
- used=0; /* reset tree entry buffer for re-use in batch mode */
- }
- strbuf_release(&sb);
- exit(0);
-}
+++ /dev/null
-/*
- * "git mv" builtin command
- *
- * Copyright (C) 2006 Johannes Schindelin
- */
-#include "cache.h"
-#include "builtin.h"
-#include "dir.h"
-#include "cache-tree.h"
-#include "string-list.h"
-#include "parse-options.h"
-
-static const char * const builtin_mv_usage[] = {
- "git mv [options] <source>... <destination>",
- NULL
-};
-
-static const char **copy_pathspec(const char *prefix, const char **pathspec,
- int count, int base_name)
-{
- int i;
- const char **result = xmalloc((count + 1) * sizeof(const char *));
- memcpy(result, pathspec, count * sizeof(const char *));
- result[count] = NULL;
- for (i = 0; i < count; i++) {
- int length = strlen(result[i]);
- int to_copy = length;
- while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
- to_copy--;
- if (to_copy != length || base_name) {
- char *it = xmemdupz(result[i], to_copy);
- result[i] = base_name ? strdup(basename(it)) : it;
- }
- }
- return get_pathspec(prefix, result);
-}
-
-static const char *add_slash(const char *path)
-{
- int len = strlen(path);
- if (path[len - 1] != '/') {
- char *with_slash = xmalloc(len + 2);
- memcpy(with_slash, path, len);
- with_slash[len++] = '/';
- with_slash[len] = 0;
- return with_slash;
- }
- return path;
-}
-
-static struct lock_file lock_file;
-
-int cmd_mv(int argc, const char **argv, const char *prefix)
-{
- int i, newfd;
- int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
- struct option builtin_mv_options[] = {
- OPT__DRY_RUN(&show_only),
- OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"),
- OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
- OPT_END(),
- };
- const char **source, **destination, **dest_path;
- enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
- struct stat st;
- struct string_list src_for_dst = {NULL, 0, 0, 0};
-
- git_config(git_default_config, NULL);
-
- argc = parse_options(argc, argv, prefix, builtin_mv_options,
- builtin_mv_usage, 0);
- if (--argc < 1)
- usage_with_options(builtin_mv_usage, builtin_mv_options);
-
- newfd = hold_locked_index(&lock_file, 1);
- if (read_cache() < 0)
- die("index file corrupt");
-
- source = copy_pathspec(prefix, argv, argc, 0);
- modes = xcalloc(argc, sizeof(enum update_mode));
- dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
-
- if (dest_path[0][0] == '\0')
- /* special case: "." was normalized to "" */
- destination = copy_pathspec(dest_path[0], argv, argc, 1);
- else if (!lstat(dest_path[0], &st) &&
- S_ISDIR(st.st_mode)) {
- dest_path[0] = add_slash(dest_path[0]);
- destination = copy_pathspec(dest_path[0], argv, argc, 1);
- } else {
- if (argc != 1)
- usage_with_options(builtin_mv_usage, builtin_mv_options);
- destination = dest_path;
- }
-
- /* Checking */
- for (i = 0; i < argc; i++) {
- const char *src = source[i], *dst = destination[i];
- int length, src_is_dir;
- const char *bad = NULL;
-
- if (show_only)
- printf("Checking rename of '%s' to '%s'\n", src, dst);
-
- length = strlen(src);
- if (lstat(src, &st) < 0)
- bad = "bad source";
- else if (!strncmp(src, dst, length) &&
- (dst[length] == 0 || dst[length] == '/')) {
- bad = "can not move directory into itself";
- } else if ((src_is_dir = S_ISDIR(st.st_mode))
- && lstat(dst, &st) == 0)
- bad = "cannot move directory over file";
- else if (src_is_dir) {
- const char *src_w_slash = add_slash(src);
- int len_w_slash = length + 1;
- int first, last;
-
- modes[i] = WORKING_DIRECTORY;
-
- first = cache_name_pos(src_w_slash, len_w_slash);
- if (first >= 0)
- die ("Huh? %.*s is in index?",
- len_w_slash, src_w_slash);
-
- first = -1 - first;
- for (last = first; last < active_nr; last++) {
- const char *path = active_cache[last]->name;
- if (strncmp(path, src_w_slash, len_w_slash))
- break;
- }
- free((char *)src_w_slash);
-
- if (last - first < 1)
- bad = "source directory is empty";
- else {
- int j, dst_len;
-
- if (last - first > 0) {
- source = xrealloc(source,
- (argc + last - first)
- * sizeof(char *));
- destination = xrealloc(destination,
- (argc + last - first)
- * sizeof(char *));
- modes = xrealloc(modes,
- (argc + last - first)
- * sizeof(enum update_mode));
- }
-
- dst = add_slash(dst);
- dst_len = strlen(dst);
-
- for (j = 0; j < last - first; j++) {
- const char *path =
- active_cache[first + j]->name;
- source[argc + j] = path;
- destination[argc + j] =
- prefix_path(dst, dst_len,
- path + length + 1);
- modes[argc + j] = INDEX;
- }
- argc += last - first;
- }
- } else if (cache_name_pos(src, length) < 0)
- bad = "not under version control";
- else if (lstat(dst, &st) == 0) {
- bad = "destination exists";
- if (force) {
- /*
- * only files can overwrite each other:
- * check both source and destination
- */
- if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
- warning("%s; will overwrite!", bad);
- bad = NULL;
- } else
- bad = "Cannot overwrite";
- }
- } else if (string_list_has_string(&src_for_dst, dst))
- bad = "multiple sources for the same target";
- else
- string_list_insert(dst, &src_for_dst);
-
- if (bad) {
- if (ignore_errors) {
- if (--argc > 0) {
- memmove(source + i, source + i + 1,
- (argc - i) * sizeof(char *));
- memmove(destination + i,
- destination + i + 1,
- (argc - i) * sizeof(char *));
- i--;
- }
- } else
- die ("%s, source=%s, destination=%s",
- bad, src, dst);
- }
- }
-
- for (i = 0; i < argc; i++) {
- const char *src = source[i], *dst = destination[i];
- enum update_mode mode = modes[i];
- int pos;
- if (show_only || verbose)
- printf("Renaming %s to %s\n", src, dst);
- if (!show_only && mode != INDEX &&
- rename(src, dst) < 0 && !ignore_errors)
- die_errno ("renaming '%s' failed", src);
-
- if (mode == WORKING_DIRECTORY)
- continue;
-
- pos = cache_name_pos(src, strlen(src));
- assert(pos >= 0);
- if (!show_only)
- rename_cache_entry_at(pos, dst);
- }
-
- if (active_cache_changed) {
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
- die("Unable to write new index file");
- }
-
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "tag.h"
-#include "refs.h"
-#include "parse-options.h"
-
-#define CUTOFF_DATE_SLOP 86400 /* one day */
-
-typedef struct rev_name {
- const char *tip_name;
- int generation;
- int distance;
-} rev_name;
-
-static long cutoff = LONG_MAX;
-
-/* How many generations are maximally preferred over _one_ merge traversal? */
-#define MERGE_TRAVERSAL_WEIGHT 65535
-
-static void name_rev(struct commit *commit,
- const char *tip_name, int generation, int distance,
- int deref)
-{
- struct rev_name *name = (struct rev_name *)commit->util;
- struct commit_list *parents;
- int parent_number = 1;
-
- if (!commit->object.parsed)
- parse_commit(commit);
-
- if (commit->date < cutoff)
- return;
-
- if (deref) {
- char *new_name = xmalloc(strlen(tip_name)+3);
- strcpy(new_name, tip_name);
- strcat(new_name, "^0");
- tip_name = new_name;
-
- if (generation)
- die("generation: %d, but deref?", generation);
- }
-
- if (name == NULL) {
- name = xmalloc(sizeof(rev_name));
- commit->util = name;
- goto copy_data;
- } else if (name->distance > distance) {
-copy_data:
- name->tip_name = tip_name;
- name->generation = generation;
- name->distance = distance;
- } else
- return;
-
- for (parents = commit->parents;
- parents;
- parents = parents->next, parent_number++) {
- if (parent_number > 1) {
- int len = strlen(tip_name);
- char *new_name = xmalloc(len +
- 1 + decimal_length(generation) + /* ~<n> */
- 1 + 2 + /* ^NN */
- 1);
-
- if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
- len -= 2;
- if (generation > 0)
- sprintf(new_name, "%.*s~%d^%d", len, tip_name,
- generation, parent_number);
- else
- sprintf(new_name, "%.*s^%d", len, tip_name,
- parent_number);
-
- name_rev(parents->item, new_name, 0,
- distance + MERGE_TRAVERSAL_WEIGHT, 0);
- } else {
- name_rev(parents->item, tip_name, generation + 1,
- distance + 1, 0);
- }
- }
-}
-
-struct name_ref_data {
- int tags_only;
- int name_only;
- const char *ref_filter;
-};
-
-static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
-{
- struct object *o = parse_object(sha1);
- struct name_ref_data *data = cb_data;
- int deref = 0;
-
- if (data->tags_only && prefixcmp(path, "refs/tags/"))
- return 0;
-
- if (data->ref_filter && fnmatch(data->ref_filter, path, 0))
- return 0;
-
- while (o && o->type == OBJ_TAG) {
- struct tag *t = (struct tag *) o;
- if (!t->tagged)
- break; /* broken repository */
- o = parse_object(t->tagged->sha1);
- deref = 1;
- }
- if (o && o->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *)o;
-
- if (!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;
-
- name_rev(commit, xstrdup(path), 0, 0, deref);
- }
- return 0;
-}
-
-/* returns a static buffer */
-static const char *get_rev_name(const struct object *o)
-{
- static char buffer[1024];
- struct rev_name *n;
- struct commit *c;
-
- if (o->type != OBJ_COMMIT)
- return NULL;
- c = (struct commit *) o;
- n = c->util;
- if (!n)
- return NULL;
-
- if (!n->generation)
- return n->tip_name;
- else {
- int len = strlen(n->tip_name);
- if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
- len -= 2;
- snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name,
- n->generation);
-
- return buffer;
- }
-}
-
-static void show_name(const struct object *obj,
- const char *caller_name,
- int always, int allow_undefined, int name_only)
-{
- const char *name;
- const unsigned char *sha1 = obj->sha1;
-
- if (!name_only)
- printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1));
- name = get_rev_name(obj);
- if (name)
- printf("%s\n", name);
- else if (allow_undefined)
- printf("undefined\n");
- else if (always)
- printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV));
- else
- die("cannot describe '%s'", sha1_to_hex(sha1));
-}
-
-static char const * const name_rev_usage[] = {
- "git name-rev [options] ( --all | --stdin | <commit>... )",
- NULL
-};
-
-static void name_rev_line(char *p, struct name_ref_data *data)
-{
- int forty = 0;
- char *p_start;
- for (p_start = p; *p; p++) {
-#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
- if (!ishex(*p))
- forty = 0;
- else if (++forty == 40 &&
- !ishex(*(p+1))) {
- unsigned char sha1[40];
- const char *name = NULL;
- char c = *(p+1);
- int p_len = p - p_start + 1;
-
- forty = 0;
-
- *(p+1) = 0;
- if (!get_sha1(p - 39, sha1)) {
- struct object *o =
- lookup_object(sha1);
- if (o)
- name = get_rev_name(o);
- }
- *(p+1) = c;
-
- if (!name)
- continue;
-
- if (data->name_only)
- printf("%.*s%s", p_len - 40, p_start, name);
- else
- printf("%.*s (%s)", p_len, p_start, name);
- p_start = p + 1;
- }
- }
-
- /* flush */
- if (p_start != p)
- fwrite(p_start, p - p_start, 1, stdout);
-}
-
-int cmd_name_rev(int argc, const char **argv, const char *prefix)
-{
- struct object_array revs = { 0, 0, NULL };
- int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0;
- struct name_ref_data data = { 0, 0, NULL };
- struct option opts[] = {
- OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"),
- OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"),
- OPT_STRING(0, "refs", &data.ref_filter, "pattern",
- "only use refs matching <pattern>"),
- OPT_GROUP(""),
- OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"),
- OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"),
- OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"),
- OPT_BOOLEAN(0, "always", &always,
- "show abbreviated commit object as fallback"),
- OPT_END(),
- };
-
- git_config(git_default_config, NULL);
- argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0);
- if (!!all + !!transform_stdin + !!argc > 1) {
- error("Specify either a list, or --all, not both!");
- usage_with_options(name_rev_usage, opts);
- }
- if (all || transform_stdin)
- cutoff = 0;
-
- for (; argc; argc--, argv++) {
- unsigned char sha1[20];
- struct object *o;
- struct commit *commit;
-
- if (get_sha1(*argv, sha1)) {
- fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
- *argv);
- continue;
- }
-
- o = deref_tag(parse_object(sha1), *argv, 0);
- if (!o || o->type != OBJ_COMMIT) {
- fprintf(stderr, "Could not get commit for %s. Skipping.\n",
- *argv);
- continue;
- }
-
- commit = (struct commit *)o;
- if (cutoff > commit->date)
- cutoff = commit->date;
- add_object_array((struct object *)commit, *argv, &revs);
- }
-
- if (cutoff)
- cutoff = cutoff - CUTOFF_DATE_SLOP;
- for_each_ref(name_ref, &data);
-
- if (transform_stdin) {
- char buffer[2048];
-
- while (!feof(stdin)) {
- char *p = fgets(buffer, sizeof(buffer), stdin);
- if (!p)
- break;
- name_rev_line(p, &data);
- }
- } else if (all) {
- int i, max;
-
- max = get_max_object_index();
- for (i = 0; i < max; i++) {
- struct object *obj = get_indexed_object(i);
- if (!obj)
- continue;
- show_name(obj, NULL,
- always, allow_undefined, data.name_only);
- }
- } else {
- int i;
- for (i = 0; i < revs.nr; i++)
- show_name(revs.objects[i].item, revs.objects[i].name,
- always, allow_undefined, data.name_only);
- }
-
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "attr.h"
-#include "object.h"
-#include "blob.h"
-#include "commit.h"
-#include "tag.h"
-#include "tree.h"
-#include "delta.h"
-#include "pack.h"
-#include "pack-revindex.h"
-#include "csum-file.h"
-#include "tree-walk.h"
-#include "diff.h"
-#include "revision.h"
-#include "list-objects.h"
-#include "progress.h"
-#include "refs.h"
-
-#ifndef NO_PTHREADS
-#include "thread-utils.h"
-#include <pthread.h>
-#endif
-
-static const char pack_usage[] =
- "git pack-objects [{ -q | --progress | --all-progress }]\n"
- " [--all-progress-implied]\n"
- " [--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"
- " [--threads=N] [--non-empty] [--revs [--unpacked | --all]*]\n"
- " [--reflog] [--stdout | base-name] [--include-tag]\n"
- " [--keep-unreachable | --unpack-unreachable \n"
- " [<ref-list | <object-list]";
-
-struct object_entry {
- struct pack_idx_entry idx;
- unsigned long size; /* uncompressed size */
- struct packed_git *in_pack; /* already in pack */
- off_t in_pack_offset;
- struct object_entry *delta; /* delta base object */
- struct object_entry *delta_child; /* deltified objects who bases me */
- struct object_entry *delta_sibling; /* other deltified objects who
- * uses the same base as me
- */
- void *delta_data; /* cached delta (uncompressed) */
- unsigned long delta_size; /* delta data size (uncompressed) */
- unsigned long z_delta_size; /* delta data size (compressed) */
- 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 object to delta
- * objects against.
- */
- unsigned char no_try_delta;
-};
-
-/*
- * Objects we are going to pack are collected in objects array (dynamically
- * expanded). nr_objects & nr_alloc controls this array. They are stored
- * in the order we see -- typically rev-list --objects order that gives us
- * nice "minimum seek" order.
- */
-static struct object_entry *objects;
-static struct pack_idx_entry **written_list;
-static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
-
-static int non_empty;
-static int reuse_delta = 1, reuse_object = 1;
-static int keep_unreachable, unpack_unreachable, include_tag;
-static int local;
-static int incremental;
-static int ignore_packed_keep;
-static int allow_ofs_delta;
-static const char *base_name;
-static int progress = 1;
-static int window = 10;
-static unsigned long pack_size_limit, pack_size_limit_cfg;
-static int depth = 50;
-static int delta_search_threads;
-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 = 256 * 1024 * 1024;
-static unsigned long cache_max_small_delta_size = 1000;
-
-static unsigned long window_memory_limit = 0;
-
-/*
- * The object names in objects array are hashed with this hashtable,
- * to help looking up the entry by object name.
- * This hashtable is built after all the objects are seen.
- */
-static int *object_ix;
-static int object_ix_hashsz;
-
-/*
- * stats
- */
-static uint32_t written, written_delta;
-static uint32_t reused, reused_delta;
-
-
-static void *get_delta(struct object_entry *entry)
-{
- unsigned long size, base_size, delta_size;
- void *buf, *base_buf, *delta_buf;
- enum object_type type;
-
- buf = read_sha1_file(entry->idx.sha1, &type, &size);
- if (!buf)
- die("unable to read %s", sha1_to_hex(entry->idx.sha1));
- base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size);
- if (!base_buf)
- die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1));
- delta_buf = diff_delta(base_buf, base_size,
- buf, size, &delta_size, 0);
- if (!delta_buf || delta_size != entry->delta_size)
- die("delta size changed");
- free(buf);
- free(base_buf);
- return delta_buf;
-}
-
-static unsigned long do_compress(void **pptr, unsigned long size)
-{
- z_stream stream;
- void *in, *out;
- unsigned long maxsize;
-
- memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, pack_compression_level);
- maxsize = deflateBound(&stream, size);
-
- in = *pptr;
- out = xmalloc(maxsize);
- *pptr = out;
-
- 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);
-
- free(in);
- return stream.total_out;
-}
-
-/*
- * The per-object header is a pretty dense thing, which is
- * - first byte: low four bits are "size", then three bits of "type",
- * and the high bit is "size continues".
- * - each byte afterwards: low seven bits are size continuation,
- * with the high bit being "size continues"
- */
-static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr)
-{
- int n = 1;
- unsigned char c;
-
- if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
- die("bad type %d", type);
-
- c = (type << 4) | (size & 15);
- size >>= 4;
- while (size) {
- *hdr++ = c | 0x80;
- c = size & 0x7f;
- size >>= 7;
- n++;
- }
- *hdr = c;
- return n;
-}
-
-/*
- * we are going to reuse the existing object data as is. make
- * sure it is not corrupt.
- */
-static int check_pack_inflate(struct packed_git *p,
- struct pack_window **w_curs,
- off_t offset,
- off_t len,
- unsigned long expect)
-{
- z_stream stream;
- unsigned char fakebuf[4096], *in;
- int st;
-
- memset(&stream, 0, sizeof(stream));
- git_inflate_init(&stream);
- do {
- in = use_pack(p, w_curs, offset, &stream.avail_in);
- stream.next_in = in;
- stream.next_out = fakebuf;
- stream.avail_out = sizeof(fakebuf);
- st = git_inflate(&stream, Z_FINISH);
- offset += stream.next_in - in;
- } while (st == Z_OK || st == Z_BUF_ERROR);
- git_inflate_end(&stream);
- return (st == Z_STREAM_END &&
- stream.total_out == expect &&
- stream.total_in == len) ? 0 : -1;
-}
-
-static void copy_pack_data(struct sha1file *f,
- struct packed_git *p,
- struct pack_window **w_curs,
- off_t offset,
- off_t len)
-{
- unsigned char *in;
- unsigned int avail;
-
- while (len) {
- in = use_pack(p, w_curs, offset, &avail);
- if (avail > len)
- avail = (unsigned int)len;
- sha1write(f, in, avail);
- offset += avail;
- len -= avail;
- }
-}
-
-static unsigned long write_object(struct sha1file *f,
- struct object_entry *entry,
- off_t write_offset)
-{
- unsigned long size, limit, datalen;
- void *buf;
- unsigned char header[10], dheader[10];
- unsigned hdrlen;
- enum object_type type;
- int usable_delta, to_reuse;
-
- if (!pack_to_stdout)
- crc32_begin(f);
-
- type = entry->type;
-
- /* apply size limit if limited packsize and not first object */
- if (!pack_size_limit || !nr_written)
- limit = 0;
- else if (pack_size_limit <= write_offset)
- /*
- * the earlier object did not fit the limit; avoid
- * mistaking this with unlimited (i.e. limit = 0).
- */
- limit = 1;
- else
- limit = pack_size_limit - write_offset;
-
- if (!entry->delta)
- usable_delta = 0; /* no delta */
- else if (!pack_size_limit)
- usable_delta = 1; /* unlimited packfile */
- else if (entry->delta->idx.offset == (off_t)-1)
- usable_delta = 0; /* base was written to another pack */
- else if (entry->delta->idx.offset)
- usable_delta = 1; /* base already exists in this pack */
- else
- usable_delta = 0; /* base could end up in another pack */
-
- if (!reuse_object)
- to_reuse = 0; /* explicit */
- else if (!entry->in_pack)
- to_reuse = 0; /* can't reuse what we don't have */
- else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA)
- /* check_object() decided it for us ... */
- to_reuse = usable_delta;
- /* ... but pack split may override that */
- else if (type != entry->in_pack_type)
- to_reuse = 0; /* pack has delta which is unusable */
- else if (entry->delta)
- to_reuse = 0; /* we want to pack afresh */
- else
- to_reuse = 1; /* we have it in-pack undeltified,
- * and we do not need to deltify it.
- */
-
- if (!to_reuse) {
- no_reuse:
- if (!usable_delta) {
- buf = read_sha1_file(entry->idx.sha1, &type, &size);
- if (!buf)
- die("unable to read %s", sha1_to_hex(entry->idx.sha1));
- /*
- * make sure no cached delta data remains from a
- * previous attempt before a pack split occurred.
- */
- free(entry->delta_data);
- entry->delta_data = NULL;
- entry->z_delta_size = 0;
- } else if (entry->delta_data) {
- size = entry->delta_size;
- buf = entry->delta_data;
- entry->delta_data = NULL;
- type = (allow_ofs_delta && entry->delta->idx.offset) ?
- OBJ_OFS_DELTA : OBJ_REF_DELTA;
- } else {
- buf = get_delta(entry);
- size = entry->delta_size;
- type = (allow_ofs_delta && entry->delta->idx.offset) ?
- OBJ_OFS_DELTA : OBJ_REF_DELTA;
- }
-
- if (entry->z_delta_size)
- datalen = entry->z_delta_size;
- else
- datalen = do_compress(&buf, size);
-
- /*
- * The object header is a byte of 'type' followed by zero or
- * more bytes of length.
- */
- hdrlen = encode_header(type, size, header);
-
- if (type == OBJ_OFS_DELTA) {
- /*
- * Deltas with relative base contain an additional
- * encoding of the relative offset for the delta
- * base from this object's position in the pack.
- */
- 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) {
- free(buf);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, dheader + pos, sizeof(dheader) - pos);
- hdrlen += sizeof(dheader) - pos;
- } else if (type == OBJ_REF_DELTA) {
- /*
- * Deltas with a base reference contain
- * an additional 20 bytes for the base sha1.
- */
- if (limit && hdrlen + 20 + datalen + 20 >= limit) {
- 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(buf);
- return 0;
- }
- sha1write(f, header, hdrlen);
- }
- sha1write(f, buf, datalen);
- free(buf);
- }
- else {
- struct packed_git *p = entry->in_pack;
- struct pack_window *w_curs = NULL;
- struct revindex_entry *revidx;
- off_t offset;
-
- if (entry->delta)
- type = (allow_ofs_delta && entry->delta->idx.offset) ?
- OBJ_OFS_DELTA : OBJ_REF_DELTA;
- hdrlen = encode_header(type, entry->size, header);
-
- offset = entry->in_pack_offset;
- revidx = find_pack_revindex(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)) {
- error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
- unuse_pack(&w_curs);
- goto no_reuse;
- }
-
- offset += entry->in_pack_header_size;
- datalen -= entry->in_pack_header_size;
- if (!pack_to_stdout && p->index_version == 1 &&
- check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
- error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
- unuse_pack(&w_curs);
- goto no_reuse;
- }
-
- if (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) {
- unuse_pack(&w_curs);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, dheader + pos, sizeof(dheader) - pos);
- hdrlen += sizeof(dheader) - pos;
- reused_delta++;
- } else if (type == OBJ_REF_DELTA) {
- if (limit && hdrlen + 20 + datalen + 20 >= limit) {
- unuse_pack(&w_curs);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, entry->delta->idx.sha1, 20);
- hdrlen += 20;
- reused_delta++;
- } else {
- if (limit && hdrlen + datalen + 20 >= limit) {
- unuse_pack(&w_curs);
- return 0;
- }
- sha1write(f, header, hdrlen);
- }
- copy_pack_data(f, p, &w_curs, offset, datalen);
- unuse_pack(&w_curs);
- reused++;
- }
- if (usable_delta)
- written_delta++;
- written++;
- if (!pack_to_stdout)
- entry->idx.crc32 = crc32_end(f);
- return hdrlen + datalen;
-}
-
-static int write_one(struct sha1file *f,
- struct object_entry *e,
- off_t *offset)
-{
- unsigned long size;
-
- /* offset is non zero if object is written already. */
- if (e->idx.offset || e->preferred_base)
- return -1;
-
- /* if we are deltified, write out base object first. */
- if (e->delta && !write_one(f, e->delta, offset))
- return 0;
-
- e->idx.offset = *offset;
- size = write_object(f, e, *offset);
- if (!size) {
- e->idx.offset = 0;
- return 0;
- }
- written_list[nr_written++] = &e->idx;
-
- /* make sure off_t is sufficiently large not to wrap */
- if (*offset > *offset + size)
- die("pack too large for current definition of off_t");
- *offset += size;
- return 1;
-}
-
-static void write_pack_file(void)
-{
- uint32_t i = 0, j;
- struct sha1file *f;
- off_t offset;
- struct pack_header hdr;
- uint32_t nr_remaining = nr_result;
- time_t last_mtime = 0;
-
- if (progress > pack_to_stdout)
- progress_state = start_progress("Writing objects", nr_result);
- written_list = xmalloc(nr_objects * sizeof(*written_list));
-
- do {
- unsigned char sha1[20];
- char *pack_tmp_name = NULL;
-
- if (pack_to_stdout) {
- f = sha1fd_throughput(1, "<stdout>", progress_state);
- } else {
- char tmpname[PATH_MAX];
- int fd;
- fd = odb_mkstemp(tmpname, sizeof(tmpname),
- "pack/tmp_pack_XXXXXX");
- pack_tmp_name = xstrdup(tmpname);
- f = sha1fd(fd, pack_tmp_name);
- }
-
- hdr.hdr_signature = htonl(PACK_SIGNATURE);
- hdr.hdr_version = htonl(PACK_VERSION);
- hdr.hdr_entries = htonl(nr_remaining);
- sha1write(f, &hdr, sizeof(hdr));
- offset = sizeof(hdr);
- nr_written = 0;
- for (; i < nr_objects; i++) {
- if (!write_one(f, objects + i, &offset))
- break;
- display_progress(progress_state, written);
- }
-
- /*
- * Did we write the wrong # entries in the header?
- * If so, rewrite it like in fast-import
- */
- if (pack_to_stdout) {
- sha1close(f, sha1, CSUM_CLOSE);
- } else if (nr_written == nr_remaining) {
- sha1close(f, sha1, CSUM_FSYNC);
- } else {
- int fd = sha1close(f, sha1, 0);
- fixup_pack_header_footer(fd, sha1, pack_tmp_name,
- nr_written, sha1, offset);
- close(fd);
- }
-
- if (!pack_to_stdout) {
- struct stat st;
- const char *idx_tmp_name;
- char tmpname[PATH_MAX];
-
- idx_tmp_name = write_idx_file(NULL, written_list,
- nr_written, sha1);
-
- snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
- base_name, sha1_to_hex(sha1));
- free_pack_by_name(tmpname);
- if (adjust_shared_perm(pack_tmp_name))
- die_errno("unable to make temporary pack file readable");
- if (rename(pack_tmp_name, tmpname))
- die_errno("unable to rename temporary pack file");
-
- /*
- * Packs are runtime accessed in their mtime
- * order since newer packs are more likely to contain
- * younger objects. So if we are creating multiple
- * packs then we should modify the mtime of later ones
- * to preserve this property.
- */
- if (stat(tmpname, &st) < 0) {
- warning("failed to stat %s: %s",
- tmpname, strerror(errno));
- } else if (!last_mtime) {
- last_mtime = st.st_mtime;
- } else {
- struct utimbuf utb;
- utb.actime = st.st_atime;
- utb.modtime = --last_mtime;
- if (utime(tmpname, &utb) < 0)
- warning("failed utime() on %s: %s",
- tmpname, strerror(errno));
- }
-
- snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
- base_name, sha1_to_hex(sha1));
- if (adjust_shared_perm(idx_tmp_name))
- die_errno("unable to make temporary index file readable");
- if (rename(idx_tmp_name, tmpname))
- die_errno("unable to rename temporary index file");
-
- free((void *) idx_tmp_name);
- free(pack_tmp_name);
- puts(sha1_to_hex(sha1));
- }
-
- /* mark written objects as written to previous pack */
- for (j = 0; j < nr_written; j++) {
- written_list[j]->offset = (off_t)-1;
- }
- nr_remaining -= nr_written;
- } while (nr_remaining && i < nr_objects);
-
- free(written_list);
- stop_progress(&progress_state);
- if (written != nr_result)
- die("wrote %"PRIu32" objects while expecting %"PRIu32,
- written, nr_result);
-}
-
-static int locate_object_entry_hash(const unsigned char *sha1)
-{
- int i;
- unsigned int ui;
- memcpy(&ui, sha1, sizeof(unsigned int));
- i = ui % object_ix_hashsz;
- while (0 < object_ix[i]) {
- if (!hashcmp(sha1, objects[object_ix[i] - 1].idx.sha1))
- return i;
- if (++i == object_ix_hashsz)
- i = 0;
- }
- return -1 - i;
-}
-
-static struct object_entry *locate_object_entry(const unsigned char *sha1)
-{
- int i;
-
- if (!object_ix_hashsz)
- return NULL;
-
- i = locate_object_entry_hash(sha1);
- if (0 <= i)
- return &objects[object_ix[i]-1];
- return NULL;
-}
-
-static void rehash_objects(void)
-{
- uint32_t i;
- struct object_entry *oe;
-
- object_ix_hashsz = nr_objects * 3;
- if (object_ix_hashsz < 1024)
- object_ix_hashsz = 1024;
- object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz);
- memset(object_ix, 0, sizeof(int) * object_ix_hashsz);
- for (i = 0, oe = objects; i < nr_objects; i++, oe++) {
- int ix = locate_object_entry_hash(oe->idx.sha1);
- if (0 <= ix)
- continue;
- ix = -1 - ix;
- object_ix[ix] = i + 1;
- }
-}
-
-static unsigned name_hash(const char *name)
-{
- unsigned c, hash = 0;
-
- if (!name)
- return 0;
-
- /*
- * This effectively just creates a sortable number from the
- * last sixteen non-whitespace characters. Last characters
- * count "most", so things that end in ".c" sort together.
- */
- while ((c = *name++) != 0) {
- if (isspace(c))
- continue;
- hash = (hash >> 2) + (c << 24);
- }
- return hash;
-}
-
-static void setup_delta_attr_check(struct git_attr_check *check)
-{
- static struct git_attr *attr_delta;
-
- if (!attr_delta)
- attr_delta = git_attr("delta");
-
- 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,
- 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) {
- if (exclude) {
- entry = objects + object_ix[ix] - 1;
- if (!entry->preferred_base)
- nr_result--;
- entry->preferred_base = 1;
- }
- return 0;
- }
-
- if (!exclude && local && has_loose_object_nonlocal(sha1))
- return 0;
-
- for (p = packed_git; p; p = p->next) {
- off_t offset = find_pack_entry_one(sha1, p);
- if (offset) {
- if (!found_pack) {
- found_offset = offset;
- found_pack = p;
- }
- if (exclude)
- break;
- if (incremental)
- return 0;
- if (local && !p->pack_local)
- return 0;
- if (ignore_packed_keep && p->pack_local && p->pack_keep)
- return 0;
- }
- }
-
- if (nr_objects >= nr_alloc) {
- nr_alloc = (nr_alloc + 1024) * 3 / 2;
- objects = xrealloc(objects, nr_alloc * sizeof(*entry));
- }
-
- entry = objects + nr_objects++;
- memset(entry, 0, sizeof(*entry));
- hashcpy(entry->idx.sha1, sha1);
- entry->hash = hash;
- if (type)
- entry->type = type;
- if (exclude)
- entry->preferred_base = 1;
- else
- nr_result++;
- if (found_pack) {
- entry->in_pack = found_pack;
- entry->in_pack_offset = found_offset;
- }
-
- if (object_ix_hashsz * 3 <= nr_objects * 4)
- rehash_objects();
- else
- object_ix[-1 - ix] = nr_objects;
-
- display_progress(progress_state, nr_objects);
-
- if (name && no_try_delta(name))
- entry->no_try_delta = 1;
-
- return 1;
-}
-
-struct pbase_tree_cache {
- unsigned char sha1[20];
- int ref;
- int temporary;
- void *tree_data;
- unsigned long tree_size;
-};
-
-static struct pbase_tree_cache *(pbase_tree_cache[256]);
-static int pbase_tree_cache_ix(const unsigned char *sha1)
-{
- return sha1[0] % ARRAY_SIZE(pbase_tree_cache);
-}
-static int pbase_tree_cache_ix_incr(int ix)
-{
- return (ix+1) % ARRAY_SIZE(pbase_tree_cache);
-}
-
-static struct pbase_tree {
- struct pbase_tree *next;
- /* This is a phony "cache" entry; we are not
- * going to evict it nor find it through _get()
- * mechanism -- this is for the toplevel node that
- * would almost always change with any commit.
- */
- struct pbase_tree_cache pcache;
-} *pbase_tree;
-
-static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1)
-{
- struct pbase_tree_cache *ent, *nent;
- void *data;
- unsigned long size;
- enum object_type type;
- int neigh;
- int my_ix = pbase_tree_cache_ix(sha1);
- int available_ix = -1;
-
- /* pbase-tree-cache acts as a limited hashtable.
- * your object will be found at your index or within a few
- * slots after that slot if it is cached.
- */
- for (neigh = 0; neigh < 8; neigh++) {
- ent = pbase_tree_cache[my_ix];
- if (ent && !hashcmp(ent->sha1, sha1)) {
- ent->ref++;
- return ent;
- }
- else if (((available_ix < 0) && (!ent || !ent->ref)) ||
- ((0 <= available_ix) &&
- (!ent && pbase_tree_cache[available_ix])))
- available_ix = my_ix;
- if (!ent)
- break;
- my_ix = pbase_tree_cache_ix_incr(my_ix);
- }
-
- /* Did not find one. Either we got a bogus request or
- * we need to read and perhaps cache.
- */
- data = read_sha1_file(sha1, &type, &size);
- if (!data)
- return NULL;
- if (type != OBJ_TREE) {
- free(data);
- return NULL;
- }
-
- /* We need to either cache or return a throwaway copy */
-
- if (available_ix < 0)
- ent = NULL;
- else {
- ent = pbase_tree_cache[available_ix];
- my_ix = available_ix;
- }
-
- if (!ent) {
- nent = xmalloc(sizeof(*nent));
- nent->temporary = (available_ix < 0);
- }
- else {
- /* evict and reuse */
- free(ent->tree_data);
- nent = ent;
- }
- hashcpy(nent->sha1, sha1);
- nent->tree_data = data;
- nent->tree_size = size;
- nent->ref = 1;
- if (!nent->temporary)
- pbase_tree_cache[my_ix] = nent;
- return nent;
-}
-
-static void pbase_tree_put(struct pbase_tree_cache *cache)
-{
- if (!cache->temporary) {
- cache->ref--;
- return;
- }
- free(cache->tree_data);
- free(cache);
-}
-
-static int name_cmp_len(const char *name)
-{
- int i;
- for (i = 0; name[i] && name[i] != '\n' && name[i] != '/'; i++)
- ;
- return i;
-}
-
-static void add_pbase_object(struct tree_desc *tree,
- const char *name,
- int cmplen,
- const char *fullname)
-{
- struct name_entry entry;
- int cmp;
-
- while (tree_entry(tree,&entry)) {
- if (S_ISGITLINK(entry.mode))
- continue;
- cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
- memcmp(name, entry.path, cmplen);
- if (cmp > 0)
- continue;
- if (cmp < 0)
- return;
- if (name[cmplen] != '/') {
- add_object_entry(entry.sha1,
- object_type(entry.mode),
- fullname, 1);
- return;
- }
- if (S_ISDIR(entry.mode)) {
- struct tree_desc sub;
- struct pbase_tree_cache *tree;
- const char *down = name+cmplen+1;
- int downlen = name_cmp_len(down);
-
- tree = pbase_tree_get(entry.sha1);
- if (!tree)
- return;
- init_tree_desc(&sub, tree->tree_data, tree->tree_size);
-
- add_pbase_object(&sub, down, downlen, fullname);
- pbase_tree_put(tree);
- }
- }
-}
-
-static unsigned *done_pbase_paths;
-static int done_pbase_paths_num;
-static int done_pbase_paths_alloc;
-static int done_pbase_path_pos(unsigned hash)
-{
- int lo = 0;
- int hi = done_pbase_paths_num;
- while (lo < hi) {
- int mi = (hi + lo) / 2;
- if (done_pbase_paths[mi] == hash)
- return mi;
- if (done_pbase_paths[mi] < hash)
- hi = mi;
- else
- lo = mi + 1;
- }
- return -lo-1;
-}
-
-static int check_pbase_path(unsigned hash)
-{
- int pos = (!done_pbase_paths) ? -1 : done_pbase_path_pos(hash);
- if (0 <= pos)
- return 1;
- pos = -pos - 1;
- if (done_pbase_paths_alloc <= done_pbase_paths_num) {
- done_pbase_paths_alloc = alloc_nr(done_pbase_paths_alloc);
- done_pbase_paths = xrealloc(done_pbase_paths,
- done_pbase_paths_alloc *
- sizeof(unsigned));
- }
- done_pbase_paths_num++;
- if (pos < done_pbase_paths_num)
- memmove(done_pbase_paths + pos + 1,
- done_pbase_paths + pos,
- (done_pbase_paths_num - pos - 1) * sizeof(unsigned));
- done_pbase_paths[pos] = hash;
- return 0;
-}
-
-static void add_preferred_base_object(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, NULL, 1);
- }
- else {
- struct tree_desc tree;
- init_tree_desc(&tree, it->pcache.tree_data, it->pcache.tree_size);
- add_pbase_object(&tree, name, cmplen, name);
- }
- }
-}
-
-static void add_preferred_base(unsigned char *sha1)
-{
- struct pbase_tree *it;
- void *data;
- unsigned long size;
- unsigned char tree_sha1[20];
-
- if (window <= num_preferred_base++)
- return;
-
- data = read_object_with_reference(sha1, tree_type, &size, tree_sha1);
- if (!data)
- return;
-
- for (it = pbase_tree; it; it = it->next) {
- if (!hashcmp(it->pcache.sha1, tree_sha1)) {
- free(data);
- return;
- }
- }
-
- it = xcalloc(1, sizeof(*it));
- it->next = pbase_tree;
- pbase_tree = it;
-
- hashcpy(it->pcache.sha1, tree_sha1);
- it->pcache.tree_data = data;
- it->pcache.tree_size = size;
-}
-
-static void cleanup_preferred_base(void)
-{
- struct pbase_tree *it;
- unsigned i;
-
- it = pbase_tree;
- pbase_tree = NULL;
- while (it) {
- struct pbase_tree *this = it;
- it = this->next;
- free(this->pcache.tree_data);
- free(this);
- }
-
- for (i = 0; i < ARRAY_SIZE(pbase_tree_cache); i++) {
- if (!pbase_tree_cache[i])
- continue;
- free(pbase_tree_cache[i]->tree_data);
- free(pbase_tree_cache[i]);
- pbase_tree_cache[i] = NULL;
- }
-
- free(done_pbase_paths);
- done_pbase_paths = NULL;
- done_pbase_paths_num = done_pbase_paths_alloc = 0;
-}
-
-static void check_object(struct object_entry *entry)
-{
- if (entry->in_pack) {
- struct packed_git *p = entry->in_pack;
- struct pack_window *w_curs = NULL;
- const unsigned char *base_ref = NULL;
- struct object_entry *base_entry;
- unsigned long used, used_0;
- unsigned int avail;
- off_t ofs;
- unsigned char *buf, c;
-
- buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
-
- /*
- * We want in_pack_type even if we do not reuse delta
- * since non-delta representations could still be reused.
- */
- used = unpack_object_header_buffer(buf, avail,
- &entry->in_pack_type,
- &entry->size);
- if (used == 0)
- goto give_up;
-
- /*
- * Determine if this is a delta and if so whether we can
- * reuse it or not. Otherwise let's find out as cheaply as
- * possible what the actual type and size for this object is.
- */
- switch (entry->in_pack_type) {
- default:
- /* Not a delta hence we've already got all we need. */
- entry->type = entry->in_pack_type;
- entry->in_pack_header_size = used;
- if (entry->type < OBJ_COMMIT || entry->type > OBJ_BLOB)
- goto give_up;
- unuse_pack(&w_curs);
- return;
- case OBJ_REF_DELTA:
- if (reuse_delta && !entry->preferred_base)
- base_ref = use_pack(p, &w_curs,
- entry->in_pack_offset + used, NULL);
- entry->in_pack_header_size = used + 20;
- break;
- case OBJ_OFS_DELTA:
- buf = use_pack(p, &w_curs,
- entry->in_pack_offset + used, NULL);
- used_0 = 0;
- c = buf[used_0++];
- ofs = c & 127;
- while (c & 128) {
- ofs += 1;
- if (!ofs || MSB(ofs, 7)) {
- error("delta base offset overflow in pack for %s",
- sha1_to_hex(entry->idx.sha1));
- goto give_up;
- }
- c = buf[used_0++];
- ofs = (ofs << 7) + (c & 127);
- }
- ofs = entry->in_pack_offset - ofs;
- if (ofs <= 0 || ofs >= entry->in_pack_offset) {
- error("delta base offset out of bound for %s",
- sha1_to_hex(entry->idx.sha1));
- goto give_up;
- }
- if (reuse_delta && !entry->preferred_base) {
- struct revindex_entry *revidx;
- revidx = find_pack_revindex(p, ofs);
- if (!revidx)
- goto give_up;
- base_ref = nth_packed_object_sha1(p, revidx->nr);
- }
- entry->in_pack_header_size = used + used_0;
- break;
- }
-
- if (base_ref && (base_entry = locate_object_entry(base_ref))) {
- /*
- * If base_ref was set above that means we wish to
- * reuse delta data, and we even found that base
- * in the list of objects we want to pack. Goodie!
- *
- * Depth value does not matter - find_deltas() will
- * never consider reused delta as the base object to
- * deltify other objects against, in order to avoid
- * circular deltas.
- */
- entry->type = entry->in_pack_type;
- entry->delta = base_entry;
- entry->delta_size = entry->size;
- entry->delta_sibling = base_entry->delta_child;
- base_entry->delta_child = entry;
- unuse_pack(&w_curs);
- return;
- }
-
- if (entry->type) {
- /*
- * This must be a delta and we already know what the
- * final object type is. Let's extract the actual
- * object size from the delta header.
- */
- entry->size = get_size_from_delta(p, &w_curs,
- entry->in_pack_offset + entry->in_pack_header_size);
- if (entry->size == 0)
- goto give_up;
- unuse_pack(&w_curs);
- return;
- }
-
- /*
- * No choice but to fall back to the recursive delta walk
- * with sha1_object_info() to find about the object type
- * at this point...
- */
- give_up:
- unuse_pack(&w_curs);
- }
-
- entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
- /*
- * The error condition is checked in prepare_pack(). This is
- * to permit a missing preferred base object to be ignored
- * as a preferred base. Doing so can result in a larger
- * pack file, but the transfer will still take place.
- */
-}
-
-static int pack_offset_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;
-
- /* avoid filesystem trashing with loose objects */
- if (!a->in_pack && !b->in_pack)
- return hashcmp(a->idx.sha1, b->idx.sha1);
-
- if (a->in_pack < b->in_pack)
- return -1;
- if (a->in_pack > b->in_pack)
- return 1;
- return a->in_pack_offset < b->in_pack_offset ? -1 :
- (a->in_pack_offset > b->in_pack_offset);
-}
-
-static void get_object_details(void)
-{
- uint32_t i;
- struct object_entry **sorted_by_offset;
-
- sorted_by_offset = xcalloc(nr_objects, sizeof(struct object_entry *));
- for (i = 0; i < nr_objects; i++)
- sorted_by_offset[i] = objects + i;
- qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort);
-
- for (i = 0; i < nr_objects; i++)
- check_object(sorted_by_offset[i]);
-
- free(sorted_by_offset);
-}
-
-/*
- * We search for deltas in a list sorted by type, by filename hash, and then
- * by size, so that we see progressively smaller and smaller files.
- * That's because we prefer deltas to be from the bigger file
- * to the smaller -- deletes are potentially cheaper, but perhaps
- * more importantly, the bigger file is likely the more recent
- * one. The deepest deltas are therefore the oldest objects which are
- * less susceptible to be accessed often.
- */
-static int type_size_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;
-
- if (a->type > b->type)
- return -1;
- if (a->type < b->type)
- return 1;
- if (a->hash > b->hash)
- return -1;
- if (a->hash < b->hash)
- return 1;
- if (a->preferred_base > b->preferred_base)
- return -1;
- if (a->preferred_base < b->preferred_base)
- return 1;
- if (a->size > b->size)
- return -1;
- if (a->size < b->size)
- return 1;
- return a < b ? -1 : (a > b); /* newest first */
-}
-
-struct unpacked {
- struct object_entry *entry;
- void *data;
- struct delta_index *index;
- unsigned depth;
-};
-
-static int delta_cacheable(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;
-}
-
-#ifndef NO_PTHREADS
-
-static pthread_mutex_t read_mutex;
-#define read_lock() pthread_mutex_lock(&read_mutex)
-#define read_unlock() pthread_mutex_unlock(&read_mutex)
-
-static pthread_mutex_t cache_mutex;
-#define cache_lock() pthread_mutex_lock(&cache_mutex)
-#define cache_unlock() pthread_mutex_unlock(&cache_mutex)
-
-static pthread_mutex_t progress_mutex;
-#define progress_lock() pthread_mutex_lock(&progress_mutex)
-#define progress_unlock() pthread_mutex_unlock(&progress_mutex)
-
-#else
-
-#define read_lock() (void)0
-#define read_unlock() (void)0
-#define cache_lock() (void)0
-#define cache_unlock() (void)0
-#define progress_lock() (void)0
-#define progress_unlock() (void)0
-
-#endif
-
-static int try_delta(struct unpacked *trg, struct unpacked *src,
- unsigned max_depth, unsigned long *mem_usage)
-{
- 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;
-
- /* Don't bother doing diffs between different types */
- if (trg_entry->type != src_entry->type)
- return -1;
-
- /*
- * We do not bother to try a delta that we discarded
- * on an earlier try, but only when reusing delta data.
- */
- if (reuse_delta && trg_entry->in_pack &&
- trg_entry->in_pack == src_entry->in_pack &&
- trg_entry->in_pack_type != OBJ_REF_DELTA &&
- trg_entry->in_pack_type != OBJ_OFS_DELTA)
- return 0;
-
- /* Let's not bust the allowed depth. */
- if (src->depth >= max_depth)
- return 0;
-
- /* Now some size filtering heuristics. */
- trg_size = trg_entry->size;
- 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 = (uint64_t)max_size * (max_depth - src->depth) /
- (max_depth - ref_depth + 1);
- if (max_size == 0)
- return 0;
- 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) {
- read_lock();
- trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
- read_unlock();
- if (!trg->data)
- die("object %s cannot be read",
- sha1_to_hex(trg_entry->idx.sha1));
- if (sz != trg_size)
- die("object %s inconsistent object length (%lu vs %lu)",
- sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
- *mem_usage += sz;
- }
- if (!src->data) {
- read_lock();
- src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
- read_unlock();
- if (!src->data)
- die("object %s cannot be read",
- sha1_to_hex(src_entry->idx.sha1));
- if (sz != src_size)
- die("object %s inconsistent object length (%lu vs %lu)",
- sha1_to_hex(src_entry->idx.sha1), sz, src_size);
- *mem_usage += sz;
- }
- if (!src->index) {
- src->index = create_delta_index(src->data, src_size);
- if (!src->index) {
- static int warned = 0;
- if (!warned++)
- warning("suboptimal pack - out of memory");
- return 0;
- }
- *mem_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) {
- /* Prefer only shallower same-sized deltas. */
- if (delta_size == trg_entry->delta_size &&
- src->depth + 1 >= trg->depth) {
- free(delta_buf);
- return 0;
- }
- }
-
- /*
- * Handle memory allocation outside of the cache
- * accounting lock. Compiler will optimize the strangeness
- * away when NO_PTHREADS is defined.
- */
- free(trg_entry->delta_data);
- cache_lock();
- if (trg_entry->delta_data) {
- delta_cache_size -= trg_entry->delta_size;
- trg_entry->delta_data = NULL;
- }
- if (delta_cacheable(src_size, trg_size, delta_size)) {
- delta_cache_size += delta_size;
- cache_unlock();
- trg_entry->delta_data = xrealloc(delta_buf, delta_size);
- } else {
- cache_unlock();
- free(delta_buf);
- }
-
- trg_entry->delta = src_entry;
- trg_entry->delta_size = delta_size;
- trg->depth = src->depth + 1;
-
- return 1;
-}
-
-static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
-{
- struct object_entry *child = me->delta_child;
- unsigned int m = n;
- while (child) {
- unsigned int c = check_delta_limit(child, n + 1);
- if (m < c)
- m = c;
- child = child->delta_sibling;
- }
- return m;
-}
-
-static unsigned long free_unpacked(struct unpacked *n)
-{
- unsigned long freed_mem = sizeof_delta_index(n->index);
- free_delta_index(n->index);
- n->index = NULL;
- if (n->data) {
- freed_mem += n->entry->size;
- free(n->data);
- n->data = NULL;
- }
- n->entry = NULL;
- n->depth = 0;
- return freed_mem;
-}
-
-static void find_deltas(struct object_entry **list, unsigned *list_size,
- int window, int depth, unsigned *processed)
-{
- uint32_t i, idx = 0, count = 0;
- struct unpacked *array;
- unsigned long mem_usage = 0;
-
- array = xcalloc(window, sizeof(struct unpacked));
-
- for (;;) {
- struct object_entry *entry;
- struct unpacked *n = array + idx;
- int j, max_depth, best_base = -1;
-
- progress_lock();
- if (!*list_size) {
- progress_unlock();
- break;
- }
- entry = *list++;
- (*list_size)--;
- if (!entry->preferred_base) {
- (*processed)++;
- display_progress(progress_state, *processed);
- }
- progress_unlock();
-
- mem_usage -= free_unpacked(n);
- n->entry = entry;
-
- while (window_memory_limit &&
- mem_usage > window_memory_limit &&
- count > 1) {
- uint32_t tail = (idx + window - count) % window;
- mem_usage -= free_unpacked(array + tail);
- count--;
- }
-
- /* We do not compute delta to *create* objects we are not
- * going to pack.
- */
- if (entry->preferred_base)
- goto next;
-
- /*
- * If the current object is at pack edge, take the depth the
- * objects that depend on the current object into account
- * otherwise they would become too deep.
- */
- max_depth = depth;
- if (entry->delta_child) {
- max_depth -= check_delta_limit(entry, 0);
- if (max_depth <= 0)
- goto next;
- }
-
- j = window;
- while (--j > 0) {
- int ret;
- uint32_t other_idx = idx + j;
- struct unpacked *m;
- if (other_idx >= window)
- other_idx -= window;
- m = array + other_idx;
- if (!m->entry)
- break;
- ret = try_delta(n, m, max_depth, &mem_usage);
- if (ret < 0)
- break;
- else if (ret > 0)
- best_base = other_idx;
- }
-
- /*
- * If we decided to cache the delta data, then it is best
- * to compress it right away. First because we have to do
- * it anyway, and doing it here while we're threaded will
- * save a lot of time in the non threaded write phase,
- * as well as allow for caching more deltas within
- * the same cache size limit.
- * ...
- * But only if not writing to stdout, since in that case
- * the network is most likely throttling writes anyway,
- * and therefore it is best to go to the write phase ASAP
- * instead, as we can afford spending more time compressing
- * between writes at that moment.
- */
- if (entry->delta_data && !pack_to_stdout) {
- entry->z_delta_size = do_compress(&entry->delta_data,
- entry->delta_size);
- cache_lock();
- delta_cache_size -= entry->delta_size;
- delta_cache_size += entry->z_delta_size;
- cache_unlock();
- }
-
- /* if we made n a delta, and if n is already at max
- * depth, leaving it in the window is pointless. we
- * should evict it first.
- */
- if (entry->delta && max_depth <= n->depth)
- continue;
-
- /*
- * Move the best delta base up in the window, after the
- * currently deltified object, to keep it longer. It will
- * be the first base object to be attempted next.
- */
- if (entry->delta) {
- struct unpacked swap = array[best_base];
- int dist = (window + idx - best_base) % window;
- int dst = best_base;
- while (dist--) {
- int src = (dst + 1) % window;
- array[dst] = array[src];
- dst = src;
- }
- array[dst] = swap;
- }
-
- next:
- idx++;
- if (count + 1 < window)
- count++;
- if (idx >= window)
- idx = 0;
- }
-
- for (i = 0; i < window; ++i) {
- free_delta_index(array[i].index);
- free(array[i].data);
- }
- free(array);
-}
-
-#ifndef NO_PTHREADS
-
-/*
- * The main thread waits on the condition that (at least) one of the workers
- * has stopped working (which is indicated in the .working member of
- * struct thread_params).
- * When a work thread has completed its work, it sets .working to 0 and
- * signals the main thread and waits on the condition that .data_ready
- * becomes 1.
- */
-
-struct thread_params {
- pthread_t thread;
- struct object_entry **list;
- unsigned list_size;
- unsigned remaining;
- int window;
- int depth;
- int working;
- int data_ready;
- pthread_mutex_t mutex;
- pthread_cond_t cond;
- unsigned *processed;
-};
-
-static pthread_cond_t progress_cond;
-
-/*
- * Mutex and conditional variable can't be statically-initialized on Windows.
- */
-static void init_threaded_search(void)
-{
- pthread_mutex_init(&read_mutex, NULL);
- pthread_mutex_init(&cache_mutex, NULL);
- pthread_mutex_init(&progress_mutex, NULL);
- pthread_cond_init(&progress_cond, NULL);
-}
-
-static void cleanup_threaded_search(void)
-{
- pthread_cond_destroy(&progress_cond);
- pthread_mutex_destroy(&read_mutex);
- pthread_mutex_destroy(&cache_mutex);
- pthread_mutex_destroy(&progress_mutex);
-}
-
-static void *threaded_find_deltas(void *arg)
-{
- struct thread_params *me = arg;
-
- while (me->remaining) {
- find_deltas(me->list, &me->remaining,
- me->window, me->depth, me->processed);
-
- progress_lock();
- me->working = 0;
- pthread_cond_signal(&progress_cond);
- progress_unlock();
-
- /*
- * We must not set ->data_ready before we wait on the
- * condition because the main thread may have set it to 1
- * before we get here. In order to be sure that new
- * work is available if we see 1 in ->data_ready, it
- * was initialized to 0 before this thread was spawned
- * and we reset it to 0 right away.
- */
- pthread_mutex_lock(&me->mutex);
- while (!me->data_ready)
- pthread_cond_wait(&me->cond, &me->mutex);
- me->data_ready = 0;
- pthread_mutex_unlock(&me->mutex);
- }
- /* leave ->working 1 so that this doesn't get more work assigned */
- return NULL;
-}
-
-static void ll_find_deltas(struct object_entry **list, unsigned list_size,
- int window, int depth, unsigned *processed)
-{
- struct thread_params *p;
- int i, ret, active_threads = 0;
-
- init_threaded_search();
-
- if (!delta_search_threads) /* --threads=0 means autodetect */
- delta_search_threads = online_cpus();
- if (delta_search_threads <= 1) {
- find_deltas(list, &list_size, window, depth, processed);
- cleanup_threaded_search();
- return;
- }
- if (progress > pack_to_stdout)
- fprintf(stderr, "Delta compression using up to %d threads.\n",
- delta_search_threads);
- p = xcalloc(delta_search_threads, sizeof(*p));
-
- /* Partition the work amongst work threads. */
- for (i = 0; i < delta_search_threads; i++) {
- unsigned sub_size = list_size / (delta_search_threads - i);
-
- /* don't use too small segments or no deltas will be found */
- if (sub_size < 2*window && i+1 < delta_search_threads)
- sub_size = 0;
-
- p[i].window = window;
- p[i].depth = depth;
- p[i].processed = processed;
- p[i].working = 1;
- p[i].data_ready = 0;
-
- /* try to split chunks on "path" boundaries */
- while (sub_size && sub_size < list_size &&
- list[sub_size]->hash &&
- list[sub_size]->hash == list[sub_size-1]->hash)
- sub_size++;
-
- p[i].list = list;
- p[i].list_size = sub_size;
- p[i].remaining = sub_size;
-
- list += sub_size;
- list_size -= sub_size;
- }
-
- /* Start work threads. */
- for (i = 0; i < delta_search_threads; i++) {
- if (!p[i].list_size)
- continue;
- pthread_mutex_init(&p[i].mutex, NULL);
- pthread_cond_init(&p[i].cond, NULL);
- ret = pthread_create(&p[i].thread, NULL,
- threaded_find_deltas, &p[i]);
- if (ret)
- die("unable to create thread: %s", strerror(ret));
- active_threads++;
- }
-
- /*
- * Now let's wait for work completion. Each time a thread is done
- * with its work, we steal half of the remaining work from the
- * thread with the largest number of unprocessed objects and give
- * it to that newly idle thread. This ensure good load balancing
- * until the remaining object list segments are simply too short
- * to be worth splitting anymore.
- */
- while (active_threads) {
- struct thread_params *target = NULL;
- struct thread_params *victim = NULL;
- unsigned sub_size = 0;
-
- progress_lock();
- for (;;) {
- for (i = 0; !target && i < delta_search_threads; i++)
- if (!p[i].working)
- target = &p[i];
- if (target)
- break;
- pthread_cond_wait(&progress_cond, &progress_mutex);
- }
-
- for (i = 0; i < delta_search_threads; i++)
- if (p[i].remaining > 2*window &&
- (!victim || victim->remaining < p[i].remaining))
- victim = &p[i];
- if (victim) {
- sub_size = victim->remaining / 2;
- list = victim->list + victim->list_size - sub_size;
- while (sub_size && list[0]->hash &&
- list[0]->hash == list[-1]->hash) {
- list++;
- sub_size--;
- }
- if (!sub_size) {
- /*
- * It is possible for some "paths" to have
- * so many objects that no hash boundary
- * might be found. Let's just steal the
- * exact half in that case.
- */
- sub_size = victim->remaining / 2;
- list -= sub_size;
- }
- target->list = list;
- victim->list_size -= sub_size;
- victim->remaining -= sub_size;
- }
- target->list_size = sub_size;
- target->remaining = sub_size;
- target->working = 1;
- progress_unlock();
-
- pthread_mutex_lock(&target->mutex);
- target->data_ready = 1;
- pthread_cond_signal(&target->cond);
- pthread_mutex_unlock(&target->mutex);
-
- if (!sub_size) {
- pthread_join(target->thread, NULL);
- pthread_cond_destroy(&target->cond);
- pthread_mutex_destroy(&target->mutex);
- active_threads--;
- }
- }
- cleanup_threaded_search();
- free(p);
-}
-
-#else
-#define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p)
-#endif
-
-static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
- unsigned char peeled[20];
-
- if (!prefixcmp(path, "refs/tags/") && /* is a tag? */
- !peel_ref(path, peeled) && /* peelable? */
- !is_null_sha1(peeled) && /* annotated tag? */
- locate_object_entry(peeled)) /* object packed? */
- add_object_entry(sha1, OBJ_TAG, NULL, 0);
- return 0;
-}
-
-static void prepare_pack(int window, int depth)
-{
- struct object_entry **delta_list;
- uint32_t i, nr_deltas;
- unsigned n;
-
- get_object_details();
-
- /*
- * If we're locally repacking then we need to be doubly careful
- * from now on in order to make sure no stealth corruption gets
- * propagated to the new pack. Clients receiving streamed packs
- * should validate everything they get anyway so no need to incur
- * the additional cost here in that case.
- */
- if (!pack_to_stdout)
- do_check_packed_object_crc = 1;
-
- if (!nr_objects || !window || !depth)
- return;
-
- delta_list = xmalloc(nr_objects * sizeof(*delta_list));
- nr_deltas = n = 0;
-
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *entry = objects + i;
-
- if (entry->delta)
- /* This happens if we decided to reuse existing
- * delta from a pack. "reuse_delta &&" is implied.
- */
- continue;
-
- if (entry->size < 50)
- continue;
-
- if (entry->no_try_delta)
- continue;
-
- if (!entry->preferred_base) {
- nr_deltas++;
- if (entry->type < 0)
- die("unable to get type of object %s",
- sha1_to_hex(entry->idx.sha1));
- } else {
- if (entry->type < 0) {
- /*
- * This object is not found, but we
- * don't have to include it anyway.
- */
- continue;
- }
- }
-
- delta_list[n++] = entry;
- }
-
- if (nr_deltas && n > 1) {
- unsigned nr_done = 0;
- if (progress)
- progress_state = start_progress("Compressing objects",
- nr_deltas);
- qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
- ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
- stop_progress(&progress_state);
- if (nr_done != nr_deltas)
- die("inconsistency with delta count");
- }
- free(delta_list);
-}
-
-static int git_pack_config(const char *k, const char *v, void *cb)
-{
- if (!strcmp(k, "pack.window")) {
- window = git_config_int(k, v);
- return 0;
- }
- 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;
- }
- if (!strcmp(k, "pack.threads")) {
- delta_search_threads = git_config_int(k, v);
- if (delta_search_threads < 0)
- die("invalid number of threads specified (%d)",
- delta_search_threads);
-#ifdef NO_PTHREADS
- if (delta_search_threads != 1)
- warning("no threads support, ignoring %s", k);
-#endif
- return 0;
- }
- if (!strcmp(k, "pack.indexversion")) {
- pack_idx_default_version = git_config_int(k, v);
- if (pack_idx_default_version > 2)
- die("bad pack.indexversion=%"PRIu32,
- pack_idx_default_version);
- return 0;
- }
- if (!strcmp(k, "pack.packsizelimit")) {
- pack_size_limit_cfg = git_config_ulong(k, v);
- return 0;
- }
- return git_default_config(k, v, cb);
-}
-
-static void read_object_list_from_stdin(void)
-{
- char line[40 + 1 + PATH_MAX + 2];
- unsigned char sha1[20];
-
- for (;;) {
- if (!fgets(line, sizeof(line), stdin)) {
- if (feof(stdin))
- break;
- if (!ferror(stdin))
- die("fgets returned NULL, not EOF, not error!");
- if (errno != EINTR)
- die_errno("fgets");
- clearerr(stdin);
- continue;
- }
- if (line[0] == '-') {
- if (get_sha1_hex(line+1, sha1))
- die("expected edge sha1, got garbage:\n %s",
- line);
- add_preferred_base(sha1);
- continue;
- }
- if (get_sha1_hex(line, sha1))
- die("expected sha1, got garbage:\n %s", line);
-
- add_preferred_base_object(line+41);
- add_object_entry(sha1, 0, line+41, 0);
- }
-}
-
-#define OBJECT_ADDED (1u<<20)
-
-static void show_commit(struct commit *commit, void *data)
-{
- add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
- commit->object.flags |= OBJECT_ADDED;
-}
-
-static void show_object(struct object *obj, const struct name_path *path, const char *last)
-{
- char *name = path_name(path, last);
-
- add_preferred_base_object(name);
- add_object_entry(obj->sha1, obj->type, name, 0);
- obj->flags |= OBJECT_ADDED;
-
- /*
- * We will have generated the hash from the name,
- * but not saved a pointer to it - we can free it
- */
- free((char *)name);
-}
-
-static void show_edge(struct commit *commit)
-{
- add_preferred_base(commit->object.sha1);
-}
-
-struct in_pack_object {
- off_t offset;
- struct object *object;
-};
-
-struct in_pack {
- int alloc;
- int nr;
- struct in_pack_object *array;
-};
-
-static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack)
-{
- in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p);
- in_pack->array[in_pack->nr].object = object;
- in_pack->nr++;
-}
-
-/*
- * Compare the objects in the offset order, in order to emulate the
- * "git rev-list --objects" output that produced the pack originally.
- */
-static int ofscmp(const void *a_, const void *b_)
-{
- struct in_pack_object *a = (struct in_pack_object *)a_;
- struct in_pack_object *b = (struct in_pack_object *)b_;
-
- if (a->offset < b->offset)
- return -1;
- else if (a->offset > b->offset)
- return 1;
- else
- return hashcmp(a->object->sha1, b->object->sha1);
-}
-
-static void add_objects_in_unpacked_packs(struct rev_info *revs)
-{
- struct packed_git *p;
- struct in_pack in_pack;
- uint32_t i;
-
- memset(&in_pack, 0, sizeof(in_pack));
-
- for (p = packed_git; p; p = p->next) {
- const unsigned char *sha1;
- struct object *o;
-
- if (!p->pack_local || p->pack_keep)
- continue;
- if (open_pack_index(p))
- die("cannot open pack index");
-
- ALLOC_GROW(in_pack.array,
- in_pack.nr + p->num_objects,
- in_pack.alloc);
-
- for (i = 0; i < p->num_objects; i++) {
- sha1 = nth_packed_object_sha1(p, i);
- o = lookup_unknown_object(sha1);
- if (!(o->flags & OBJECT_ADDED))
- mark_in_pack_object(o, p, &in_pack);
- o->flags |= OBJECT_ADDED;
- }
- }
-
- if (in_pack.nr) {
- qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]),
- ofscmp);
- for (i = 0; i < in_pack.nr; i++) {
- struct object *o = in_pack.array[i].object;
- add_object_entry(o->sha1, o->type, "", 0);
- }
- }
- free(in_pack.array);
-}
-
-static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1)
-{
- static struct packed_git *last_found = (void *)1;
- struct packed_git *p;
-
- p = (last_found != (void *)1) ? last_found : packed_git;
-
- while (p) {
- if ((!p->pack_local || p->pack_keep) &&
- find_pack_entry_one(sha1, p)) {
- last_found = p;
- return 1;
- }
- if (p == last_found)
- p = packed_git;
- else
- p = p->next;
- if (p == last_found)
- p = p->next;
- }
- return 0;
-}
-
-static void loosen_unused_packed_objects(struct rev_info *revs)
-{
- struct packed_git *p;
- uint32_t i;
- const unsigned char *sha1;
-
- for (p = packed_git; p; p = p->next) {
- if (!p->pack_local || p->pack_keep)
- continue;
-
- if (open_pack_index(p))
- die("cannot open pack index");
-
- for (i = 0; i < p->num_objects; i++) {
- sha1 = nth_packed_object_sha1(p, i);
- if (!locate_object_entry(sha1) &&
- !has_sha1_pack_kept_or_nonlocal(sha1))
- if (force_object_loose(sha1, p->mtime))
- die("unable to force loose object");
- }
- }
-}
-
-static void get_object_list(int ac, const char **av)
-{
- struct rev_info revs;
- char line[1000];
- int flags = 0;
-
- init_revisions(&revs, NULL);
- save_commit_buffer = 0;
- setup_revisions(ac, av, &revs, NULL);
-
- while (fgets(line, sizeof(line), stdin) != NULL) {
- int len = strlen(line);
- if (len && line[len - 1] == '\n')
- line[--len] = 0;
- if (!len)
- break;
- if (*line == '-') {
- if (!strcmp(line, "--not")) {
- flags ^= UNINTERESTING;
- continue;
- }
- die("not a rev '%s'", line);
- }
- if (handle_revision_arg(line, &revs, flags, 1))
- die("bad revision '%s'", line);
- }
-
- if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
- mark_edges_uninteresting(revs.commits, &revs, show_edge);
- traverse_commit_list(&revs, show_commit, show_object, NULL);
-
- if (keep_unreachable)
- add_objects_in_unpacked_packs(&revs);
- if (unpack_unreachable)
- loosen_unused_packed_objects(&revs);
-}
-
-int cmd_pack_objects(int argc, const char **argv, const char *prefix)
-{
- int use_internal_rev_list = 0;
- int thin = 0;
- int all_progress_implied = 0;
- uint32_t i;
- const char **rp_av;
- int rp_ac_alloc = 64;
- int rp_ac;
-
- read_replace_refs = 0;
-
- rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av));
-
- rp_av[0] = "pack-objects";
- rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
- rp_ac = 2;
-
- git_config(git_pack_config, NULL);
- if (!pack_compression_seen && core_compression_seen)
- pack_compression_level = core_compression_level;
-
- progress = isatty(2);
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (*arg != '-')
- break;
-
- if (!strcmp("--non-empty", arg)) {
- non_empty = 1;
- continue;
- }
- if (!strcmp("--local", arg)) {
- local = 1;
- continue;
- }
- if (!strcmp("--incremental", arg)) {
- incremental = 1;
- continue;
- }
- if (!strcmp("--honor-pack-keep", arg)) {
- ignore_packed_keep = 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=")) {
- pack_size_limit_cfg = 0;
- if (!git_parse_ulong(arg+16, &pack_size_limit))
- usage(pack_usage);
- continue;
- }
- if (!prefixcmp(arg, "--window=")) {
- char *end;
- window = strtoul(arg+9, &end, 0);
- if (!arg[9] || *end)
- 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, "--threads=")) {
- char *end;
- delta_search_threads = strtoul(arg+10, &end, 0);
- if (!arg[10] || *end || delta_search_threads < 0)
- usage(pack_usage);
-#ifdef NO_PTHREADS
- if (delta_search_threads != 1)
- warning("no threads support, "
- "ignoring %s", arg);
-#endif
- continue;
- }
- if (!prefixcmp(arg, "--depth=")) {
- char *end;
- depth = strtoul(arg+8, &end, 0);
- if (!arg[8] || *end)
- usage(pack_usage);
- continue;
- }
- if (!strcmp("--progress", arg)) {
- progress = 1;
- continue;
- }
- if (!strcmp("--all-progress", arg)) {
- progress = 2;
- continue;
- }
- if (!strcmp("--all-progress-implied", arg)) {
- all_progress_implied = 1;
- continue;
- }
- if (!strcmp("-q", arg)) {
- progress = 0;
- continue;
- }
- if (!strcmp("--no-reuse-delta", arg)) {
- reuse_delta = 0;
- continue;
- }
- if (!strcmp("--no-reuse-object", arg)) {
- reuse_object = reuse_delta = 0;
- continue;
- }
- if (!strcmp("--delta-base-offset", arg)) {
- allow_ofs_delta = 1;
- continue;
- }
- if (!strcmp("--stdout", arg)) {
- pack_to_stdout = 1;
- continue;
- }
- if (!strcmp("--revs", arg)) {
- use_internal_rev_list = 1;
- continue;
- }
- if (!strcmp("--keep-unreachable", arg)) {
- keep_unreachable = 1;
- continue;
- }
- if (!strcmp("--unpack-unreachable", arg)) {
- unpack_unreachable = 1;
- continue;
- }
- if (!strcmp("--include-tag", arg)) {
- include_tag = 1;
- continue;
- }
- if (!strcmp("--unpacked", arg) ||
- !strcmp("--reflog", arg) ||
- !strcmp("--all", arg)) {
- use_internal_rev_list = 1;
- if (rp_ac >= rp_ac_alloc - 1) {
- rp_ac_alloc = alloc_nr(rp_ac_alloc);
- rp_av = xrealloc(rp_av,
- rp_ac_alloc * sizeof(*rp_av));
- }
- rp_av[rp_ac++] = arg;
- continue;
- }
- if (!strcmp("--thin", arg)) {
- use_internal_rev_list = 1;
- thin = 1;
- rp_av[1] = "--objects-edge";
- continue;
- }
- if (!prefixcmp(arg, "--index-version=")) {
- char *c;
- pack_idx_default_version = strtoul(arg + 16, &c, 10);
- if (pack_idx_default_version > 2)
- die("bad %s", arg);
- if (*c == ',')
- pack_idx_off32_limit = strtoul(c+1, &c, 0);
- if (*c || pack_idx_off32_limit & 0x80000000)
- die("bad %s", arg);
- continue;
- }
- if (!strcmp(arg, "--keep-true-parents")) {
- grafts_replace_parents = 0;
- continue;
- }
- usage(pack_usage);
- }
-
- /* Traditionally "pack-objects [options] base extra" failed;
- * we would however want to take refs parameter that would
- * have been given to upstream rev-list ourselves, which means
- * we somehow want to say what the base name is. So the
- * syntax would be:
- *
- * pack-objects [options] base <refs...>
- *
- * in other words, we would treat the first non-option as the
- * base_name and send everything else to the internal revision
- * walker.
- */
-
- if (!pack_to_stdout)
- base_name = argv[i++];
-
- if (pack_to_stdout != !base_name)
- usage(pack_usage);
-
- if (!pack_to_stdout && !pack_size_limit)
- pack_size_limit = pack_size_limit_cfg;
- if (pack_to_stdout && pack_size_limit)
- die("--max-pack-size cannot be used to build a pack for transfer.");
- if (pack_size_limit && pack_size_limit < 1024*1024) {
- warning("minimum pack size limit is 1 MiB");
- pack_size_limit = 1024*1024;
- }
-
- if (!pack_to_stdout && thin)
- die("--thin cannot be used to build an indexable pack.");
-
- if (keep_unreachable && unpack_unreachable)
- die("--keep-unreachable and --unpack-unreachable are incompatible.");
-
- if (progress && all_progress_implied)
- progress = 2;
-
- prepare_packed_git();
-
- if (progress)
- progress_state = start_progress("Counting objects", 0);
- if (!use_internal_rev_list)
- read_object_list_from_stdin();
- else {
- rp_av[rp_ac] = NULL;
- get_object_list(rp_ac, rp_av);
- }
- cleanup_preferred_base();
- if (include_tag && nr_result)
- for_each_ref(add_ref_tag, NULL);
- stop_progress(&progress_state);
-
- if (non_empty && !nr_result)
- return 0;
- if (nr_result)
- prepare_pack(window, depth);
- write_pack_file();
- if (progress)
- fprintf(stderr, "Total %"PRIu32" (delta %"PRIu32"),"
- " reused %"PRIu32" (delta %"PRIu32")\n",
- written, written_delta, reused, reused_delta);
- return 0;
-}
+++ /dev/null
-/*
-*
-* Copyright 2005, Lukas Sandstrom <lukass@etek.chalmers.se>
-*
-* This file is licensed under the GPL v2.
-*
-*/
-
-#include "cache.h"
-#include "exec_cmd.h"
-
-#define BLKSIZE 512
-
-static const char pack_redundant_usage[] =
-"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
-
-static int load_all_packs, verbose, alt_odb;
-
-struct llist_item {
- struct llist_item *next;
- const unsigned char *sha1;
-};
-static struct llist {
- struct llist_item *front;
- struct llist_item *back;
- size_t size;
-} *all_objects; /* all objects which must be present in local packfiles */
-
-static struct pack_list {
- struct pack_list *next;
- struct packed_git *pack;
- struct llist *unique_objects;
- struct llist *all_objects;
-} *local_packs = NULL, *altodb_packs = NULL;
-
-struct pll {
- struct pll *next;
- struct pack_list *pl;
-};
-
-static struct llist_item *free_nodes;
-
-static inline void llist_item_put(struct llist_item *item)
-{
- item->next = free_nodes;
- free_nodes = item;
-}
-
-static inline struct llist_item *llist_item_get(void)
-{
- struct llist_item *new;
- if ( free_nodes ) {
- new = free_nodes;
- free_nodes = free_nodes->next;
- } else {
- int i = 1;
- new = xmalloc(sizeof(struct llist_item) * BLKSIZE);
- for (; i < BLKSIZE; i++)
- llist_item_put(&new[i]);
- }
- return new;
-}
-
-static void llist_free(struct llist *list)
-{
- while ((list->back = list->front)) {
- list->front = list->front->next;
- llist_item_put(list->back);
- }
- free(list);
-}
-
-static inline void llist_init(struct llist **list)
-{
- *list = xmalloc(sizeof(struct llist));
- (*list)->front = (*list)->back = NULL;
- (*list)->size = 0;
-}
-
-static struct llist * llist_copy(struct llist *list)
-{
- struct llist *ret;
- struct llist_item *new, *old, *prev;
-
- llist_init(&ret);
-
- if ((ret->size = list->size) == 0)
- return ret;
-
- new = ret->front = llist_item_get();
- new->sha1 = list->front->sha1;
-
- old = list->front->next;
- while (old) {
- prev = new;
- new = llist_item_get();
- prev->next = new;
- new->sha1 = old->sha1;
- old = old->next;
- }
- new->next = NULL;
- ret->back = new;
-
- return ret;
-}
-
-static inline struct llist_item *llist_insert(struct llist *list,
- struct llist_item *after,
- const unsigned char *sha1)
-{
- struct llist_item *new = llist_item_get();
- new->sha1 = sha1;
- new->next = NULL;
-
- if (after != NULL) {
- new->next = after->next;
- after->next = new;
- if (after == list->back)
- list->back = new;
- } else {/* insert in front */
- if (list->size == 0)
- list->back = new;
- else
- new->next = list->front;
- list->front = new;
- }
- list->size++;
- return new;
-}
-
-static inline struct llist_item *llist_insert_back(struct llist *list,
- const unsigned char *sha1)
-{
- return llist_insert(list, list->back, sha1);
-}
-
-static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
- const unsigned char *sha1, struct llist_item *hint)
-{
- struct llist_item *prev = NULL, *l;
-
- l = (hint == NULL) ? list->front : hint;
- while (l) {
- int cmp = hashcmp(l->sha1, sha1);
- if (cmp > 0) { /* we insert before this entry */
- return llist_insert(list, prev, sha1);
- }
- if (!cmp) { /* already exists */
- return l;
- }
- prev = l;
- l = l->next;
- }
- /* insert at the end */
- return llist_insert_back(list, sha1);
-}
-
-/* returns a pointer to an item in front of sha1 */
-static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint)
-{
- struct llist_item *prev, *l;
-
-redo_from_start:
- l = (hint == NULL) ? list->front : hint;
- prev = NULL;
- while (l) {
- int cmp = hashcmp(l->sha1, sha1);
- if (cmp > 0) /* not in list, since sorted */
- return prev;
- if (!cmp) { /* found */
- if (prev == NULL) {
- if (hint != NULL && hint != list->front) {
- /* we don't know the previous element */
- hint = NULL;
- goto redo_from_start;
- }
- list->front = l->next;
- } else
- prev->next = l->next;
- if (l == list->back)
- list->back = prev;
- llist_item_put(l);
- list->size--;
- return prev;
- }
- prev = l;
- l = l->next;
- }
- return prev;
-}
-
-/* computes A\B */
-static void llist_sorted_difference_inplace(struct llist *A,
- struct llist *B)
-{
- struct llist_item *hint, *b;
-
- hint = NULL;
- b = B->front;
-
- while (b) {
- hint = llist_sorted_remove(A, b->sha1, hint);
- b = b->next;
- }
-}
-
-static inline struct pack_list * pack_list_insert(struct pack_list **pl,
- struct pack_list *entry)
-{
- struct pack_list *p = xmalloc(sizeof(struct pack_list));
- memcpy(p, entry, sizeof(struct pack_list));
- p->next = *pl;
- *pl = p;
- return p;
-}
-
-static inline size_t pack_list_size(struct pack_list *pl)
-{
- size_t ret = 0;
- while (pl) {
- ret++;
- pl = pl->next;
- }
- return ret;
-}
-
-static struct pack_list * pack_list_difference(const struct pack_list *A,
- const struct pack_list *B)
-{
- struct pack_list *ret;
- const struct pack_list *pl;
-
- if (A == NULL)
- return NULL;
-
- pl = B;
- while (pl != NULL) {
- if (A->pack == pl->pack)
- return pack_list_difference(A->next, B);
- pl = pl->next;
- }
- ret = xmalloc(sizeof(struct pack_list));
- memcpy(ret, A, sizeof(struct pack_list));
- ret->next = pack_list_difference(A->next, B);
- return ret;
-}
-
-static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
-{
- unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
- const unsigned char *p1_base, *p2_base;
- struct llist_item *p1_hint = NULL, *p2_hint = NULL;
-
- p1_base = p1->pack->index_data;
- p2_base = p2->pack->index_data;
- p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8);
- p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8);
- p1_step = (p1->pack->index_version < 2) ? 24 : 20;
- p2_step = (p2->pack->index_version < 2) ? 24 : 20;
-
- while (p1_off < p1->pack->num_objects * p1_step &&
- p2_off < p2->pack->num_objects * p2_step)
- {
- int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
- /* cmp ~ p1 - p2 */
- if (cmp == 0) {
- p1_hint = llist_sorted_remove(p1->unique_objects,
- p1_base + p1_off, p1_hint);
- p2_hint = llist_sorted_remove(p2->unique_objects,
- p1_base + p1_off, p2_hint);
- p1_off += p1_step;
- p2_off += p2_step;
- continue;
- }
- if (cmp < 0) { /* p1 has the object, p2 doesn't */
- p1_off += p1_step;
- } else { /* p2 has the object, p1 doesn't */
- p2_off += p2_step;
- }
- }
-}
-
-static void pll_free(struct pll *l)
-{
- struct pll *old;
- struct pack_list *opl;
-
- while (l) {
- old = l;
- while (l->pl) {
- opl = l->pl;
- l->pl = opl->next;
- free(opl);
- }
- l = l->next;
- free(old);
- }
-}
-
-/* all the permutations have to be free()d at the same time,
- * since they refer to each other
- */
-static struct pll * get_permutations(struct pack_list *list, int n)
-{
- struct pll *subset, *ret = NULL, *new_pll = NULL, *pll;
-
- if (list == NULL || pack_list_size(list) < n || n == 0)
- return NULL;
-
- if (n == 1) {
- while (list) {
- new_pll = xmalloc(sizeof(pll));
- new_pll->pl = NULL;
- pack_list_insert(&new_pll->pl, list);
- new_pll->next = ret;
- ret = new_pll;
- list = list->next;
- }
- return ret;
- }
-
- while (list->next) {
- subset = get_permutations(list->next, n - 1);
- while (subset) {
- new_pll = xmalloc(sizeof(pll));
- new_pll->pl = subset->pl;
- pack_list_insert(&new_pll->pl, list);
- new_pll->next = ret;
- ret = new_pll;
- subset = subset->next;
- }
- list = list->next;
- }
- return ret;
-}
-
-static int is_superset(struct pack_list *pl, struct llist *list)
-{
- struct llist *diff;
-
- diff = llist_copy(list);
-
- while (pl) {
- llist_sorted_difference_inplace(diff, pl->all_objects);
- if (diff->size == 0) { /* we're done */
- llist_free(diff);
- return 1;
- }
- pl = pl->next;
- }
- llist_free(diff);
- return 0;
-}
-
-static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
-{
- size_t ret = 0;
- unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
- const unsigned char *p1_base, *p2_base;
-
- p1_base = p1->index_data;
- p2_base = p2->index_data;
- p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8);
- p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8);
- p1_step = (p1->index_version < 2) ? 24 : 20;
- p2_step = (p2->index_version < 2) ? 24 : 20;
-
- while (p1_off < p1->num_objects * p1_step &&
- p2_off < p2->num_objects * p2_step)
- {
- int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
- /* cmp ~ p1 - p2 */
- if (cmp == 0) {
- ret++;
- p1_off += p1_step;
- p2_off += p2_step;
- continue;
- }
- if (cmp < 0) { /* p1 has the object, p2 doesn't */
- p1_off += p1_step;
- } else { /* p2 has the object, p1 doesn't */
- p2_off += p2_step;
- }
- }
- return ret;
-}
-
-/* another O(n^2) function ... */
-static size_t get_pack_redundancy(struct pack_list *pl)
-{
- struct pack_list *subset;
- size_t ret = 0;
-
- if (pl == NULL)
- return 0;
-
- while ((subset = pl->next)) {
- while (subset) {
- ret += sizeof_union(pl->pack, subset->pack);
- subset = subset->next;
- }
- pl = pl->next;
- }
- return ret;
-}
-
-static inline off_t pack_set_bytecount(struct pack_list *pl)
-{
- off_t ret = 0;
- while (pl) {
- ret += pl->pack->pack_size;
- ret += pl->pack->index_size;
- pl = pl->next;
- }
- return ret;
-}
-
-static void minimize(struct pack_list **min)
-{
- struct pack_list *pl, *unique = NULL,
- *non_unique = NULL, *min_perm = NULL;
- struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm;
- struct llist *missing;
- off_t min_perm_size = 0, perm_size;
- int n;
-
- pl = local_packs;
- while (pl) {
- if (pl->unique_objects->size)
- pack_list_insert(&unique, pl);
- else
- pack_list_insert(&non_unique, pl);
- pl = pl->next;
- }
- /* find out which objects are missing from the set of unique packs */
- missing = llist_copy(all_objects);
- pl = unique;
- while (pl) {
- llist_sorted_difference_inplace(missing, pl->all_objects);
- pl = pl->next;
- }
-
- /* return if there are no objects missing from the unique set */
- if (missing->size == 0) {
- *min = unique;
- return;
- }
-
- /* find the permutations which contain all missing objects */
- for (n = 1; n <= pack_list_size(non_unique) && !perm_ok; n++) {
- perm_all = perm = get_permutations(non_unique, n);
- while (perm) {
- if (is_superset(perm->pl, missing)) {
- new_perm = xmalloc(sizeof(struct pll));
- memcpy(new_perm, perm, sizeof(struct pll));
- new_perm->next = perm_ok;
- perm_ok = new_perm;
- }
- perm = perm->next;
- }
- if (perm_ok)
- break;
- pll_free(perm_all);
- }
- if (perm_ok == NULL)
- die("Internal error: No complete sets found!");
-
- /* find the permutation with the smallest size */
- perm = perm_ok;
- while (perm) {
- perm_size = pack_set_bytecount(perm->pl);
- if (!min_perm_size || min_perm_size > perm_size) {
- min_perm_size = perm_size;
- min_perm = perm->pl;
- }
- perm = perm->next;
- }
- *min = min_perm;
- /* add the unique packs to the list */
- pl = unique;
- while (pl) {
- pack_list_insert(min, pl);
- pl = pl->next;
- }
-}
-
-static void load_all_objects(void)
-{
- struct pack_list *pl = local_packs;
- struct llist_item *hint, *l;
-
- llist_init(&all_objects);
-
- while (pl) {
- hint = NULL;
- l = pl->all_objects->front;
- while (l) {
- hint = llist_insert_sorted_unique(all_objects,
- l->sha1, hint);
- l = l->next;
- }
- pl = pl->next;
- }
- /* remove objects present in remote packs */
- pl = altodb_packs;
- while (pl) {
- llist_sorted_difference_inplace(all_objects, pl->all_objects);
- pl = pl->next;
- }
-}
-
-/* this scales like O(n^2) */
-static void cmp_local_packs(void)
-{
- struct pack_list *subset, *pl = local_packs;
-
- while ((subset = pl)) {
- while ((subset = subset->next))
- cmp_two_packs(pl, subset);
- pl = pl->next;
- }
-}
-
-static void scan_alt_odb_packs(void)
-{
- struct pack_list *local, *alt;
-
- alt = altodb_packs;
- while (alt) {
- local = local_packs;
- while (local) {
- llist_sorted_difference_inplace(local->unique_objects,
- alt->all_objects);
- local = local->next;
- }
- llist_sorted_difference_inplace(all_objects, alt->all_objects);
- alt = alt->next;
- }
-}
-
-static struct pack_list * add_pack(struct packed_git *p)
-{
- struct pack_list l;
- unsigned long off = 0, step;
- const unsigned char *base;
-
- if (!p->pack_local && !(alt_odb || verbose))
- return NULL;
-
- 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;
- while (off < p->num_objects * step) {
- llist_insert_back(l.all_objects, base + off);
- off += step;
- }
- /* this list will be pruned in cmp_two_packs later */
- l.unique_objects = llist_copy(l.all_objects);
- if (p->pack_local)
- return pack_list_insert(&local_packs, &l);
- else
- return pack_list_insert(&altodb_packs, &l);
-}
-
-static struct pack_list * add_pack_file(const char *filename)
-{
- struct packed_git *p = packed_git;
-
- if (strlen(filename) < 40)
- die("Bad pack filename: %s", filename);
-
- while (p) {
- if (strstr(p->pack_name, filename))
- return add_pack(p);
- p = p->next;
- }
- die("Filename %s not found in packed_git", filename);
-}
-
-static void load_all(void)
-{
- struct packed_git *p = packed_git;
-
- while (p) {
- add_pack(p);
- p = p->next;
- }
-}
-
-int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
-{
- int i;
- struct pack_list *min, *red, *pl;
- struct llist *ignore;
- unsigned char *sha1;
- char buf[42]; /* 40 byte sha1 + \n + \0 */
-
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage(pack_redundant_usage);
-
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (!strcmp(arg, "--")) {
- i++;
- break;
- }
- if (!strcmp(arg, "--all")) {
- load_all_packs = 1;
- continue;
- }
- if (!strcmp(arg, "--verbose")) {
- verbose = 1;
- continue;
- }
- if (!strcmp(arg, "--alt-odb")) {
- alt_odb = 1;
- continue;
- }
- if (*arg == '-')
- usage(pack_redundant_usage);
- else
- break;
- }
-
- prepare_packed_git();
-
- if (load_all_packs)
- load_all();
- else
- while (*(argv + i) != NULL)
- add_pack_file(*(argv + i++));
-
- if (local_packs == NULL)
- die("Zero packs found!");
-
- load_all_objects();
-
- cmp_local_packs();
- if (alt_odb)
- scan_alt_odb_packs();
-
- /* ignore objects given on stdin */
- llist_init(&ignore);
- if (!isatty(0)) {
- while (fgets(buf, sizeof(buf), stdin)) {
- sha1 = xmalloc(20);
- if (get_sha1_hex(buf, sha1))
- die("Bad sha1 on stdin: %s", buf);
- llist_insert_sorted_unique(ignore, sha1, NULL);
- }
- }
- llist_sorted_difference_inplace(all_objects, ignore);
- pl = local_packs;
- while (pl) {
- llist_sorted_difference_inplace(pl->unique_objects, ignore);
- pl = pl->next;
- }
-
- minimize(&min);
-
- if (verbose) {
- fprintf(stderr, "There are %lu packs available in alt-odbs.\n",
- (unsigned long)pack_list_size(altodb_packs));
- fprintf(stderr, "The smallest (bytewise) set of packs is:\n");
- pl = min;
- while (pl) {
- fprintf(stderr, "\t%s\n", pl->pack->pack_name);
- pl = pl->next;
- }
- fprintf(stderr, "containing %lu duplicate objects "
- "with a total size of %lukb.\n",
- (unsigned long)get_pack_redundancy(min),
- (unsigned long)pack_set_bytecount(min)/1024);
- fprintf(stderr, "A total of %lu unique objects were considered.\n",
- (unsigned long)all_objects->size);
- fprintf(stderr, "Redundant packs (with indexes):\n");
- }
- pl = red = pack_list_difference(local_packs, min);
- while (pl) {
- printf("%s\n%s\n",
- sha1_pack_index_name(pl->pack->sha1),
- pl->pack->pack_name);
- pl = pl->next;
- }
- if (verbose)
- fprintf(stderr, "%luMB of redundant packs in total.\n",
- (unsigned long)pack_set_bytecount(red)/(1024*1024));
-
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "parse-options.h"
-#include "pack-refs.h"
-
-static char const * const pack_refs_usage[] = {
- "git pack-refs [options]",
- NULL
-};
-
-int cmd_pack_refs(int argc, const char **argv, const char *prefix)
-{
- unsigned int flags = PACK_REFS_PRUNE;
- struct option opts[] = {
- OPT_BIT(0, "all", &flags, "pack everything", PACK_REFS_ALL),
- OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE),
- OPT_END(),
- };
- if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0))
- usage_with_options(pack_refs_usage, opts);
- return pack_refs(flags);
-}
+++ /dev/null
-#include "cache.h"
-#include "exec_cmd.h"
-
-static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
-{
- unsigned char result[20];
- char name[50];
-
- if (!patchlen)
- return;
-
- git_SHA1_Final(result, c);
- memcpy(name, sha1_to_hex(id), 41);
- printf("%s %s\n", sha1_to_hex(result), name);
- git_SHA1_Init(c);
-}
-
-static int remove_space(char *line)
-{
- char *src = line;
- char *dst = line;
- unsigned char c;
-
- while ((c = *src++) != '\0') {
- if (!isspace(c))
- *dst++ = c;
- }
- return dst - line;
-}
-
-static void generate_id_list(void)
-{
- static unsigned char sha1[20];
- static char line[1000];
- git_SHA_CTX ctx;
- int patchlen = 0;
-
- git_SHA1_Init(&ctx);
- while (fgets(line, sizeof(line), stdin) != NULL) {
- unsigned char n[20];
- char *p = line;
- int len;
-
- if (!memcmp(line, "diff-tree ", 10))
- p += 10;
- else if (!memcmp(line, "commit ", 7))
- p += 7;
-
- if (!get_sha1_hex(p, n)) {
- flush_current_id(patchlen, sha1, &ctx);
- hashcpy(sha1, n);
- patchlen = 0;
- continue;
- }
-
- /* Ignore commit comments */
- if (!patchlen && memcmp(line, "diff ", 5))
- continue;
-
- /* Ignore git-diff index header */
- if (!memcmp(line, "index ", 6))
- continue;
-
- /* Ignore line numbers when computing the SHA1 of the patch */
- if (!memcmp(line, "@@ -", 4))
- continue;
-
- /* Compute the sha without whitespace */
- len = remove_space(line);
- patchlen += len;
- git_SHA1_Update(&ctx, line, len);
- }
- flush_current_id(patchlen, sha1, &ctx);
-}
-
-static const char patch_id_usage[] = "git patch-id < patch";
-
-int cmd_patch_id(int argc, const char **argv, const char *prefix)
-{
- if (argc != 1)
- usage(patch_id_usage);
-
- generate_id_list();
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "progress.h"
-#include "parse-options.h"
-
-static const char * const prune_packed_usage[] = {
- "git prune-packed [-n|--dry-run] [-q|--quiet]",
- NULL
-};
-
-#define DRY_RUN 01
-#define VERBOSE 02
-
-static struct progress *progress;
-
-static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
-{
- struct dirent *de;
- char hex[40];
-
- sprintf(hex, "%02x", i);
- while ((de = readdir(dir)) != NULL) {
- unsigned char sha1[20];
- if (strlen(de->d_name) != 38)
- continue;
- memcpy(hex+2, de->d_name, 38);
- if (get_sha1_hex(hex, sha1))
- continue;
- if (!has_sha1_pack(sha1))
- continue;
- memcpy(pathname + len, de->d_name, 38);
- if (opts & DRY_RUN)
- printf("rm -f %s\n", pathname);
- else
- unlink_or_warn(pathname);
- display_progress(progress, i + 1);
- }
- pathname[len] = 0;
- rmdir(pathname);
-}
-
-void prune_packed_objects(int opts)
-{
- int i;
- static char pathname[PATH_MAX];
- const char *dir = get_object_directory();
- int len = strlen(dir);
-
- if (opts == VERBOSE)
- progress = start_progress_delay("Removing duplicate objects",
- 256, 95, 2);
-
- if (len > PATH_MAX - 42)
- die("impossible object directory");
- memcpy(pathname, dir, len);
- if (len && pathname[len-1] != '/')
- pathname[len++] = '/';
- for (i = 0; i < 256; i++) {
- DIR *d;
-
- display_progress(progress, i + 1);
- sprintf(pathname + len, "%02x/", i);
- d = opendir(pathname);
- if (!d)
- continue;
- prune_dir(i, d, pathname, len + 3, opts);
- closedir(d);
- }
- stop_progress(&progress);
-}
-
-int cmd_prune_packed(int argc, const char **argv, const char *prefix)
-{
- int opts = isatty(2) ? VERBOSE : 0;
- const struct option prune_packed_options[] = {
- OPT_BIT('n', "dry-run", &opts, "dry run", DRY_RUN),
- OPT_NEGBIT('q', "quiet", &opts, "be quiet", VERBOSE),
- OPT_END()
- };
-
- argc = parse_options(argc, argv, prefix, prune_packed_options,
- prune_packed_usage, 0);
-
- prune_packed_objects(opts);
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "builtin.h"
-#include "reachable.h"
-#include "parse-options.h"
-#include "dir.h"
-
-static const char * const prune_usage[] = {
- "git prune [-n] [-v] [--expire <time>] [--] [<head>...]",
- NULL
-};
-static int show_only;
-static int verbose;
-static unsigned long expire;
-
-static int prune_tmp_object(const char *path, const char *filename)
-{
- const char *fullpath = mkpath("%s/%s", path, filename);
- struct stat st;
- if (lstat(fullpath, &st))
- return error("Could not stat '%s'", fullpath);
- if (st.st_mtime > expire)
- return 0;
- printf("Removing stale temporary file %s\n", fullpath);
- if (!show_only)
- unlink_or_warn(fullpath);
- return 0;
-}
-
-static int prune_object(char *path, const char *filename, const unsigned char *sha1)
-{
- const char *fullpath = mkpath("%s/%s", path, filename);
- struct stat st;
- if (lstat(fullpath, &st))
- return error("Could not stat '%s'", fullpath);
- if (st.st_mtime > expire)
- return 0;
- if (show_only || verbose) {
- enum object_type type = sha1_object_info(sha1, NULL);
- printf("%s %s\n", sha1_to_hex(sha1),
- (type > 0) ? typename(type) : "unknown");
- }
- if (!show_only)
- unlink_or_warn(fullpath);
- return 0;
-}
-
-static int prune_dir(int i, char *path)
-{
- DIR *dir = opendir(path);
- struct dirent *de;
-
- if (!dir)
- return 0;
-
- while ((de = readdir(dir)) != NULL) {
- char name[100];
- unsigned char sha1[20];
-
- if (is_dot_or_dotdot(de->d_name))
- continue;
- if (strlen(de->d_name) == 38) {
- sprintf(name, "%02x", i);
- memcpy(name+2, de->d_name, 39);
- if (get_sha1_hex(name, sha1) < 0)
- break;
-
- /*
- * Do we know about this object?
- * It must have been reachable
- */
- if (lookup_object(sha1))
- continue;
-
- prune_object(path, de->d_name, sha1);
- continue;
- }
- if (!prefixcmp(de->d_name, "tmp_obj_")) {
- prune_tmp_object(path, de->d_name);
- continue;
- }
- fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
- }
- if (!show_only)
- rmdir(path);
- closedir(dir);
- return 0;
-}
-
-static void prune_object_dir(const char *path)
-{
- int i;
- for (i = 0; i < 256; i++) {
- static char dir[4096];
- sprintf(dir, "%s/%02x", path, i);
- prune_dir(i, dir);
- }
-}
-
-/*
- * Write errors (particularly out of space) can result in
- * failed temporary packs (and more rarely indexes and other
- * files beginning with "tmp_") accumulating in the object
- * and the pack directories.
- */
-static void remove_temporary_files(const char *path)
-{
- DIR *dir;
- struct dirent *de;
-
- dir = opendir(path);
- if (!dir) {
- fprintf(stderr, "Unable to open directory %s\n", path);
- return;
- }
- while ((de = readdir(dir)) != NULL)
- if (!prefixcmp(de->d_name, "tmp_"))
- prune_tmp_object(path, de->d_name);
- closedir(dir);
-}
-
-int cmd_prune(int argc, const char **argv, const char *prefix)
-{
- struct rev_info revs;
- const struct option options[] = {
- OPT_BOOLEAN('n', NULL, &show_only,
- "do not remove, show only"),
- OPT_BOOLEAN('v', NULL, &verbose,
- "report pruned objects"),
- OPT_DATE(0, "expire", &expire,
- "expire objects older than <time>"),
- OPT_END()
- };
- char *s;
-
- expire = ULONG_MAX;
- save_commit_buffer = 0;
- read_replace_refs = 0;
- init_revisions(&revs, prefix);
-
- argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
- while (argc--) {
- unsigned char sha1[20];
- const char *name = *argv++;
-
- if (!get_sha1(name, sha1)) {
- struct object *object = parse_object(sha1);
- if (!object)
- die("bad object: %s", name);
- add_pending_object(&revs, object, "");
- }
- else
- die("unrecognized argument: %s", name);
- }
- mark_reachable_objects(&revs, 1);
- prune_object_dir(get_object_directory());
-
- prune_packed_objects(show_only);
- remove_temporary_files(get_object_directory());
- s = xstrdup(mkpath("%s/pack", get_object_directory()));
- remove_temporary_files(s);
- free(s);
- return 0;
-}
+++ /dev/null
-/*
- * "git push"
- */
-#include "cache.h"
-#include "refs.h"
-#include "run-command.h"
-#include "builtin.h"
-#include "remote.h"
-#include "transport.h"
-#include "parse-options.h"
-
-static const char * const push_usage[] = {
- "git push [<options>] [<repository> <refspec>...]",
- NULL,
-};
-
-static int thin;
-static int deleterefs;
-static const char *receivepack;
-
-static const char **refspec;
-static int refspec_nr;
-
-static void add_refspec(const char *ref)
-{
- int nr = refspec_nr + 1;
- refspec = xrealloc(refspec, nr * sizeof(char *));
- refspec[nr-1] = ref;
- refspec_nr = nr;
-}
-
-static void set_refspecs(const char **refs, int 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;
- if (deleterefs) {
- tag = xmalloc(len+1);
- strcpy(tag, ":refs/tags/");
- } else {
- tag = xmalloc(len);
- strcpy(tag, "refs/tags/");
- }
- strcat(tag, refs[i]);
- ref = tag;
- } else if (deleterefs && !strchr(ref, ':')) {
- char *delref;
- int len = strlen(ref)+1;
- delref = xmalloc(len+1);
- strcpy(delref, ":");
- strcat(delref, ref);
- ref = delref;
- } else if (deleterefs)
- die("--delete only accepts plain target ref names");
- add_refspec(ref);
- }
-}
-
-static void setup_push_tracking(void)
-{
- struct strbuf refspec = STRBUF_INIT;
- struct branch *branch = branch_get(NULL);
- if (!branch)
- die("You are not currently on a branch.");
- if (!branch->merge_nr || !branch->merge)
- die("The current branch %s is not tracking anything.",
- branch->name);
- if (branch->merge_nr != 1)
- die("The current branch %s is tracking multiple branches, "
- "refusing to push.", branch->name);
- strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
- add_refspec(refspec.buf);
-}
-
-static void setup_default_push_refspecs(void)
-{
- switch (push_default) {
- default:
- case PUSH_DEFAULT_MATCHING:
- add_refspec(":");
- break;
-
- case PUSH_DEFAULT_TRACKING:
- setup_push_tracking();
- break;
-
- case PUSH_DEFAULT_CURRENT:
- add_refspec("HEAD");
- break;
-
- case PUSH_DEFAULT_NOTHING:
- die("You didn't specify any refspecs to push, and "
- "push.default is \"nothing\".");
- break;
- }
-}
-
-static int push_with_options(struct transport *transport, int flags)
-{
- int err;
- int nonfastforward;
- if (receivepack)
- transport_set_option(transport,
- TRANS_OPT_RECEIVEPACK, receivepack);
- if (thin)
- transport_set_option(transport, TRANS_OPT_THIN, "yes");
-
- if (flags & TRANSPORT_PUSH_VERBOSE)
- fprintf(stderr, "Pushing to %s\n", transport->url);
- err = transport_push(transport, refspec_nr, refspec, flags,
- &nonfastforward);
- if (err != 0)
- error("failed to push some refs to '%s'", transport->url);
-
- err |= transport_disconnect(transport);
-
- if (!err)
- return 0;
-
- if (nonfastforward && advice_push_nonfastforward) {
- printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
- "Merge the remote changes before pushing again. See the 'Note about\n"
- "fast-forwards' section of 'git push --help' for details.\n");
- }
-
- return 1;
-}
-
-static int do_push(const char *repo, int flags)
-{
- int i, errs;
- struct remote *remote = remote_get(repo);
- const char **url;
- int url_nr;
-
- if (!remote) {
- if (repo)
- die("bad repository '%s'", repo);
- die("No destination configured to push to.");
- }
-
- if (remote->mirror)
- flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
-
- if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
- if (!strcmp(*refspec, "refs/tags/*"))
- return error("--all and --tags are incompatible");
- return error("--all can't be combined with refspecs");
- }
-
- if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) {
- if (!strcmp(*refspec, "refs/tags/*"))
- return error("--mirror and --tags are incompatible");
- return error("--mirror can't be combined with refspecs");
- }
-
- if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
- (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
- return error("--all and --mirror are incompatible");
- }
-
- if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) {
- if (remote->push_refspec_nr) {
- refspec = remote->push_refspec;
- refspec_nr = remote->push_refspec_nr;
- } else if (!(flags & TRANSPORT_PUSH_MIRROR))
- setup_default_push_refspecs();
- }
- errs = 0;
- if (remote->pushurl_nr) {
- url = remote->pushurl;
- url_nr = remote->pushurl_nr;
- } else {
- url = remote->url;
- url_nr = remote->url_nr;
- }
- if (url_nr) {
- for (i = 0; i < url_nr; i++) {
- struct transport *transport =
- transport_get(remote, url[i]);
- if (push_with_options(transport, flags))
- errs++;
- }
- } else {
- struct transport *transport =
- transport_get(remote, NULL);
-
- if (push_with_options(transport, flags))
- errs++;
- }
- return !!errs;
-}
-
-int cmd_push(int argc, const char **argv, const char *prefix)
-{
- int flags = 0;
- int tags = 0;
- int rc;
- const char *repo = NULL; /* default repository */
- struct option options[] = {
- OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET),
- OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE),
- OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
- OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
- OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
- (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
- OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"),
- OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"),
- OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
- OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
- OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
- OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
- OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
- OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
- OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
- TRANSPORT_PUSH_SET_UPSTREAM),
- OPT_END()
- };
-
- git_config(git_default_config, NULL);
- argc = parse_options(argc, argv, prefix, options, push_usage, 0);
-
- if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
- die("--delete is incompatible with --all, --mirror and --tags");
- if (deleterefs && argc < 2)
- die("--delete doesn't make sense without any refs");
-
- if (tags)
- add_refspec("refs/tags/*");
-
- if (argc > 0) {
- repo = argv[0];
- set_refspecs(argv + 1, argc - 1);
- }
-
- rc = do_push(repo, flags);
- if (rc == -1)
- usage_with_options(push_usage, options);
- else
- return rc;
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-
-#include "cache.h"
-#include "object.h"
-#include "tree.h"
-#include "tree-walk.h"
-#include "cache-tree.h"
-#include "unpack-trees.h"
-#include "dir.h"
-#include "builtin.h"
-#include "parse-options.h"
-#include "resolve-undo.h"
-
-static int nr_trees;
-static struct tree *trees[MAX_UNPACK_TREES];
-
-static int list_tree(unsigned char *sha1)
-{
- struct tree *tree;
-
- if (nr_trees >= MAX_UNPACK_TREES)
- die("I cannot read more than %d trees", MAX_UNPACK_TREES);
- tree = parse_tree_indirect(sha1);
- if (!tree)
- return -1;
- trees[nr_trees++] = tree;
- return 0;
-}
-
-static const char * const read_tree_usage[] = {
- "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
- NULL
-};
-
-static int index_output_cb(const struct option *opt, const char *arg,
- int unset)
-{
- set_alternate_index_output(arg);
- return 0;
-}
-
-static int exclude_per_directory_cb(const struct option *opt, const char *arg,
- int unset)
-{
- struct dir_struct *dir;
- struct unpack_trees_options *opts;
-
- opts = (struct unpack_trees_options *)opt->value;
-
- if (opts->dir)
- die("more than one --exclude-per-directory given.");
-
- dir = xcalloc(1, sizeof(*opts->dir));
- dir->flags |= DIR_SHOW_IGNORED;
- dir->exclude_per_dir = arg;
- opts->dir = dir;
- /* We do not need to nor want to do read-directory
- * here; we are merely interested in reusing the
- * per directory ignore stack mechanism.
- */
- return 0;
-}
-
-static void debug_stage(const char *label, struct cache_entry *ce,
- struct unpack_trees_options *o)
-{
- printf("%s ", label);
- if (!ce)
- printf("(missing)\n");
- else if (ce == o->df_conflict_entry)
- printf("(conflict)\n");
- else
- printf("%06o #%d %s %.8s\n",
- ce->ce_mode, ce_stage(ce), ce->name,
- sha1_to_hex(ce->sha1));
-}
-
-static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o)
-{
- int i;
-
- printf("* %d-way merge\n", o->merge_size);
- debug_stage("index", stages[0], o);
- for (i = 1; i <= o->merge_size; i++) {
- char buf[24];
- sprintf(buf, "ent#%d", i);
- debug_stage(buf, stages[i], o);
- }
- return 0;
-}
-
-static struct lock_file lock_file;
-
-int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
-{
- int i, newfd, stage = 0;
- unsigned char sha1[20];
- struct tree_desc t[MAX_UNPACK_TREES];
- struct unpack_trees_options opts;
- int prefix_set = 0;
- const struct option read_tree_options[] = {
- { OPTION_CALLBACK, 0, "index-output", NULL, "FILE",
- "write resulting index to <FILE>",
- PARSE_OPT_NONEG, index_output_cb },
- OPT__VERBOSE(&opts.verbose_update),
- OPT_GROUP("Merging"),
- OPT_SET_INT('m', NULL, &opts.merge,
- "perform a merge in addition to a read", 1),
- OPT_SET_INT(0, "trivial", &opts.trivial_merges_only,
- "3-way merge if no file level merging required", 1),
- OPT_SET_INT(0, "aggressive", &opts.aggressive,
- "3-way merge in presence of adds and removes", 1),
- OPT_SET_INT(0, "reset", &opts.reset,
- "same as -m, but discard unmerged entries", 1),
- { OPTION_STRING, 0, "prefix", &opts.prefix, "<subdirectory>/",
- "read the tree into the index under <subdirectory>/",
- PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP },
- OPT_SET_INT('u', NULL, &opts.update,
- "update working tree with merge result", 1),
- { OPTION_CALLBACK, 0, "exclude-per-directory", &opts,
- "gitignore",
- "allow explicitly ignored files to be overwritten",
- PARSE_OPT_NONEG, exclude_per_directory_cb },
- OPT_SET_INT('i', NULL, &opts.index_only,
- "don't check the working tree after merging", 1),
- OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
- "skip applying sparse checkout filter", 1),
- OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack,
- "debug unpack-trees", 1),
- OPT_END()
- };
-
- memset(&opts, 0, sizeof(opts));
- opts.head_idx = -1;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
-
- git_config(git_default_config, NULL);
-
- argc = parse_options(argc, argv, unused_prefix, read_tree_options,
- read_tree_usage, 0);
-
- newfd = hold_locked_index(&lock_file, 1);
-
- prefix_set = opts.prefix ? 1 : 0;
- if (1 < opts.merge + opts.reset + prefix_set)
- die("Which one? -m, --reset, or --prefix?");
-
- if (opts.reset || opts.merge || opts.prefix) {
- if (read_cache_unmerged() && (opts.prefix || opts.merge))
- die("You need to resolve your current index first");
- stage = opts.merge = 1;
- }
- resolve_undo_clear();
-
- for (i = 0; i < argc; i++) {
- const char *arg = argv[i];
-
- if (get_sha1(arg, sha1))
- die("Not a valid object name %s", arg);
- if (list_tree(sha1) < 0)
- die("failed to unpack tree object %s", arg);
- stage++;
- }
- if (1 < opts.index_only + opts.update)
- die("-u and -i at the same time makes no sense");
- if ((opts.update||opts.index_only) && !opts.merge)
- die("%s is meaningless without -m, --reset, or --prefix",
- opts.update ? "-u" : "-i");
- if ((opts.dir && !opts.update))
- die("--exclude-per-directory is meaningless unless -u");
- if (opts.merge && !opts.index_only)
- setup_work_tree();
-
- if (opts.merge) {
- if (stage < 2)
- die("just how do you expect me to merge %d trees?", stage-1);
- switch (stage - 1) {
- case 1:
- opts.fn = opts.prefix ? bind_merge : oneway_merge;
- break;
- case 2:
- opts.fn = twoway_merge;
- opts.initial_checkout = is_cache_unborn();
- break;
- case 3:
- default:
- opts.fn = threeway_merge;
- break;
- }
-
- if (stage - 1 >= 3)
- opts.head_idx = stage - 2;
- else
- opts.head_idx = 1;
- }
-
- if (opts.debug_unpack)
- opts.fn = debug_merge;
-
- cache_tree_free(&active_cache_tree);
- for (i = 0; i < nr_trees; i++) {
- struct tree *tree = trees[i];
- parse_tree(tree);
- init_tree_desc(t+i, tree->buffer, tree->size);
- }
- if (unpack_trees(nr_trees, t, &opts))
- return 128;
-
- if (opts.debug_unpack)
- return 0; /* do not write the index out */
-
- /*
- * When reading only one tree (either the most basic form,
- * "-m ent" or "--reset ent" form), we can obtain a fully
- * valid cache-tree because the index must match exactly
- * what came from the tree.
- *
- * The same holds true if we are switching between two trees
- * using read-tree -m A B. The index must match B after that.
- */
- if (nr_trees == 1 && !opts.prefix)
- prime_cache_tree(&active_cache_tree, trees[0]);
- else if (nr_trees == 2 && opts.merge)
- prime_cache_tree(&active_cache_tree, trees[1]);
-
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
- die("unable to write new index file");
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "pack.h"
-#include "refs.h"
-#include "pkt-line.h"
-#include "sideband.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-#include "commit.h"
-#include "object.h"
-#include "remote.h"
-#include "transport.h"
-
-static const char receive_pack_usage[] = "git receive-pack <git-dir>";
-
-enum deny_action {
- DENY_UNCONFIGURED,
- DENY_IGNORE,
- DENY_WARN,
- DENY_REFUSE,
-};
-
-static int deny_deletes;
-static int deny_non_fast_forwards;
-static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
-static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
-static int receive_fsck_objects;
-static int receive_unpack_limit = -1;
-static int transfer_unpack_limit = -1;
-static int unpack_limit = 100;
-static int report_status;
-static int use_sideband;
-static int prefer_ofs_delta = 1;
-static int auto_update_server_info;
-static int auto_gc = 1;
-static const char *head_name;
-static int sent_capabilities;
-
-static enum deny_action parse_deny_action(const char *var, const char *value)
-{
- if (value) {
- if (!strcasecmp(value, "ignore"))
- return DENY_IGNORE;
- if (!strcasecmp(value, "warn"))
- return DENY_WARN;
- if (!strcasecmp(value, "refuse"))
- return DENY_REFUSE;
- }
- if (git_config_bool(var, value))
- return DENY_REFUSE;
- return DENY_IGNORE;
-}
-
-static int receive_pack_config(const char *var, const char *value, void *cb)
-{
- if (strcmp(var, "receive.denydeletes") == 0) {
- deny_deletes = git_config_bool(var, value);
- return 0;
- }
-
- if (strcmp(var, "receive.denynonfastforwards") == 0) {
- deny_non_fast_forwards = git_config_bool(var, value);
- return 0;
- }
-
- if (strcmp(var, "receive.unpacklimit") == 0) {
- receive_unpack_limit = git_config_int(var, value);
- return 0;
- }
-
- if (strcmp(var, "transfer.unpacklimit") == 0) {
- transfer_unpack_limit = git_config_int(var, value);
- return 0;
- }
-
- if (strcmp(var, "receive.fsckobjects") == 0) {
- receive_fsck_objects = git_config_bool(var, value);
- return 0;
- }
-
- if (!strcmp(var, "receive.denycurrentbranch")) {
- deny_current_branch = parse_deny_action(var, value);
- return 0;
- }
-
- if (strcmp(var, "receive.denydeletecurrent") == 0) {
- deny_delete_current = parse_deny_action(var, value);
- return 0;
- }
-
- if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
- prefer_ofs_delta = git_config_bool(var, value);
- return 0;
- }
-
- if (strcmp(var, "receive.updateserverinfo") == 0) {
- auto_update_server_info = git_config_bool(var, value);
- return 0;
- }
-
- if (strcmp(var, "receive.autogc") == 0) {
- auto_gc = git_config_bool(var, value);
- return 0;
- }
-
- return git_default_config(var, value, cb);
-}
-
-static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
- if (sent_capabilities)
- packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
- else
- packet_write(1, "%s %s%c%s%s\n",
- sha1_to_hex(sha1), path, 0,
- " report-status delete-refs side-band-64k",
- prefer_ofs_delta ? " ofs-delta" : "");
- sent_capabilities = 1;
- return 0;
-}
-
-static void write_head_info(void)
-{
- for_each_ref(show_ref, NULL);
- if (!sent_capabilities)
- show_ref("capabilities^{}", null_sha1, 0, NULL);
-
-}
-
-struct command {
- struct command *next;
- const char *error_string;
- unsigned char old_sha1[20];
- unsigned char new_sha1[20];
- char ref_name[FLEX_ARRAY]; /* more */
-};
-
-static struct command *commands;
-
-static const char pre_receive_hook[] = "hooks/pre-receive";
-static const char post_receive_hook[] = "hooks/post-receive";
-
-static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
-static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
-
-static void report_message(const char *prefix, const char *err, va_list params)
-{
- int sz = strlen(prefix);
- char msg[4096];
-
- strncpy(msg, prefix, sz);
- sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params);
- if (sz > (sizeof(msg) - 1))
- sz = sizeof(msg) - 1;
- msg[sz++] = '\n';
-
- if (use_sideband)
- send_sideband(1, 2, msg, sz, use_sideband);
- else
- xwrite(2, msg, sz);
-}
-
-static void rp_warning(const char *err, ...)
-{
- va_list params;
- va_start(params, err);
- report_message("warning: ", err, params);
- va_end(params);
-}
-
-static void rp_error(const char *err, ...)
-{
- va_list params;
- va_start(params, err);
- report_message("error: ", err, params);
- va_end(params);
-}
-
-static int copy_to_sideband(int in, int out, void *arg)
-{
- char data[128];
- while (1) {
- ssize_t sz = xread(in, data, sizeof(data));
- if (sz <= 0)
- break;
- send_sideband(1, 2, data, sz, use_sideband);
- }
- close(in);
- return 0;
-}
-
-static int run_receive_hook(const char *hook_name)
-{
- static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
- struct command *cmd;
- struct child_process proc;
- struct async muxer;
- const char *argv[2];
- int have_input = 0, code;
-
- for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
- if (!cmd->error_string)
- have_input = 1;
- }
-
- if (!have_input || access(hook_name, X_OK) < 0)
- return 0;
-
- argv[0] = hook_name;
- argv[1] = NULL;
-
- memset(&proc, 0, sizeof(proc));
- proc.argv = argv;
- proc.in = -1;
- proc.stdout_to_stderr = 1;
-
- if (use_sideband) {
- memset(&muxer, 0, sizeof(muxer));
- muxer.proc = copy_to_sideband;
- muxer.in = -1;
- code = start_async(&muxer);
- if (code)
- return code;
- proc.err = muxer.in;
- }
-
- code = start_command(&proc);
- if (code) {
- if (use_sideband)
- finish_async(&muxer);
- return code;
- }
-
- for (cmd = commands; cmd; cmd = cmd->next) {
- if (!cmd->error_string) {
- size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
- sha1_to_hex(cmd->old_sha1),
- sha1_to_hex(cmd->new_sha1),
- cmd->ref_name);
- if (write_in_full(proc.in, buf, n) != n)
- break;
- }
- }
- close(proc.in);
- if (use_sideband)
- finish_async(&muxer);
- return finish_command(&proc);
-}
-
-static int run_update_hook(struct command *cmd)
-{
- static const char update_hook[] = "hooks/update";
- const char *argv[5];
- struct child_process proc;
- int code;
-
- if (access(update_hook, X_OK) < 0)
- return 0;
-
- argv[0] = update_hook;
- argv[1] = cmd->ref_name;
- argv[2] = sha1_to_hex(cmd->old_sha1);
- argv[3] = sha1_to_hex(cmd->new_sha1);
- argv[4] = NULL;
-
- memset(&proc, 0, sizeof(proc));
- proc.no_stdin = 1;
- proc.stdout_to_stderr = 1;
- proc.err = use_sideband ? -1 : 0;
- proc.argv = argv;
-
- code = start_command(&proc);
- if (code)
- return code;
- if (use_sideband)
- copy_to_sideband(proc.err, -1, NULL);
- return finish_command(&proc);
-}
-
-static int is_ref_checked_out(const char *ref)
-{
- if (is_bare_repository())
- return 0;
-
- if (!head_name)
- return 0;
- return !strcmp(head_name, ref);
-}
-
-static char *refuse_unconfigured_deny_msg[] = {
- "By default, updating the current branch in a non-bare repository",
- "is denied, because it will make the index and work tree inconsistent",
- "with what you pushed, and will require 'git reset --hard' to match",
- "the work tree to HEAD.",
- "",
- "You can set 'receive.denyCurrentBranch' configuration variable to",
- "'ignore' or 'warn' in the remote repository to allow pushing into",
- "its current branch; however, this is not recommended unless you",
- "arranged to update its work tree to match what you pushed in some",
- "other way.",
- "",
- "To squelch this message and still keep the default behaviour, set",
- "'receive.denyCurrentBranch' configuration variable to 'refuse'."
-};
-
-static void refuse_unconfigured_deny(void)
-{
- int i;
- for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++)
- rp_error("%s", refuse_unconfigured_deny_msg[i]);
-}
-
-static char *refuse_unconfigured_deny_delete_current_msg[] = {
- "By default, deleting the current branch is denied, because the next",
- "'git clone' won't result in any file checked out, causing confusion.",
- "",
- "You can set 'receive.denyDeleteCurrent' configuration variable to",
- "'warn' or 'ignore' in the remote repository to allow deleting the",
- "current branch, with or without a warning message.",
- "",
- "To squelch this message, you can set it to 'refuse'."
-};
-
-static void refuse_unconfigured_deny_delete_current(void)
-{
- int i;
- for (i = 0;
- i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg);
- i++)
- rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
-}
-
-static const char *update(struct command *cmd)
-{
- const char *name = cmd->ref_name;
- unsigned char *old_sha1 = cmd->old_sha1;
- unsigned char *new_sha1 = cmd->new_sha1;
- struct ref_lock *lock;
-
- /* only refs/... are allowed */
- if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
- rp_error("refusing to create funny ref '%s' remotely", name);
- return "funny refname";
- }
-
- if (is_ref_checked_out(name)) {
- switch (deny_current_branch) {
- case DENY_IGNORE:
- break;
- case DENY_WARN:
- rp_warning("updating the current branch");
- break;
- case DENY_REFUSE:
- case DENY_UNCONFIGURED:
- rp_error("refusing to update checked out branch: %s", name);
- if (deny_current_branch == DENY_UNCONFIGURED)
- refuse_unconfigured_deny();
- return "branch is currently checked out";
- }
- }
-
- if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
- error("unpack should have generated %s, "
- "but I can't find it!", sha1_to_hex(new_sha1));
- return "bad pack";
- }
-
- if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
- if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
- rp_error("denying ref deletion for %s", name);
- return "deletion prohibited";
- }
-
- if (!strcmp(name, head_name)) {
- switch (deny_delete_current) {
- case DENY_IGNORE:
- break;
- case DENY_WARN:
- rp_warning("deleting the current branch");
- break;
- case DENY_REFUSE:
- case DENY_UNCONFIGURED:
- if (deny_delete_current == DENY_UNCONFIGURED)
- refuse_unconfigured_deny_delete_current();
- rp_error("refusing to delete the current branch: %s", name);
- return "deletion of the current branch prohibited";
- }
- }
- }
-
- if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
- !is_null_sha1(old_sha1) &&
- !prefixcmp(name, "refs/heads/")) {
- struct object *old_object, *new_object;
- struct commit *old_commit, *new_commit;
- struct commit_list *bases, *ent;
-
- old_object = parse_object(old_sha1);
- new_object = parse_object(new_sha1);
-
- if (!old_object || !new_object ||
- old_object->type != OBJ_COMMIT ||
- new_object->type != OBJ_COMMIT) {
- error("bad sha1 objects for %s", name);
- return "bad ref";
- }
- old_commit = (struct commit *)old_object;
- new_commit = (struct commit *)new_object;
- bases = get_merge_bases(old_commit, new_commit, 1);
- for (ent = bases; ent; ent = ent->next)
- if (!hashcmp(old_sha1, ent->item->object.sha1))
- break;
- free_commit_list(bases);
- if (!ent) {
- rp_error("denying non-fast-forward %s"
- " (you should pull first)", name);
- return "non-fast-forward";
- }
- }
- if (run_update_hook(cmd)) {
- rp_error("hook declined to update %s", name);
- return "hook declined";
- }
-
- if (is_null_sha1(new_sha1)) {
- if (!parse_object(old_sha1)) {
- rp_warning("Allowing deletion of corrupt ref.");
- old_sha1 = NULL;
- }
- if (delete_ref(name, old_sha1, 0)) {
- rp_error("failed to delete %s", name);
- return "failed to delete";
- }
- return NULL; /* good */
- }
- else {
- lock = lock_any_ref_for_update(name, old_sha1, 0);
- if (!lock) {
- rp_error("failed to lock %s", name);
- return "failed to lock";
- }
- if (write_ref_sha1(lock, new_sha1, "push")) {
- return "failed to write"; /* error() already called */
- }
- return NULL; /* good */
- }
-}
-
-static char update_post_hook[] = "hooks/post-update";
-
-static void run_update_post_hook(struct command *cmd)
-{
- struct command *cmd_p;
- int argc;
- const char **argv;
- struct child_process proc;
-
- for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
- if (cmd_p->error_string)
- continue;
- argc++;
- }
- if (!argc || access(update_post_hook, X_OK) < 0)
- return;
- argv = xmalloc(sizeof(*argv) * (2 + argc));
- argv[0] = update_post_hook;
-
- for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
- char *p;
- if (cmd_p->error_string)
- continue;
- p = xmalloc(strlen(cmd_p->ref_name) + 1);
- strcpy(p, cmd_p->ref_name);
- argv[argc] = p;
- argc++;
- }
- argv[argc] = NULL;
-
- memset(&proc, 0, sizeof(proc));
- proc.no_stdin = 1;
- proc.stdout_to_stderr = 1;
- proc.err = use_sideband ? -1 : 0;
- proc.argv = argv;
-
- if (!start_command(&proc)) {
- if (use_sideband)
- copy_to_sideband(proc.err, -1, NULL);
- finish_command(&proc);
- }
-}
-
-static void execute_commands(const char *unpacker_error)
-{
- struct command *cmd = commands;
- unsigned char sha1[20];
-
- if (unpacker_error) {
- while (cmd) {
- cmd->error_string = "n/a (unpacker error)";
- cmd = cmd->next;
- }
- return;
- }
-
- if (run_receive_hook(pre_receive_hook)) {
- while (cmd) {
- cmd->error_string = "pre-receive hook declined";
- cmd = cmd->next;
- }
- return;
- }
-
- head_name = resolve_ref("HEAD", sha1, 0, NULL);
-
- while (cmd) {
- cmd->error_string = update(cmd);
- cmd = cmd->next;
- }
-}
-
-static void read_head_info(void)
-{
- struct command **p = &commands;
- for (;;) {
- static char line[1000];
- unsigned char old_sha1[20], new_sha1[20];
- struct command *cmd;
- char *refname;
- int len, reflen;
-
- len = packet_read_line(0, line, sizeof(line));
- if (!len)
- break;
- if (line[len-1] == '\n')
- line[--len] = 0;
- if (len < 83 ||
- line[40] != ' ' ||
- line[81] != ' ' ||
- get_sha1_hex(line, old_sha1) ||
- get_sha1_hex(line + 41, new_sha1))
- die("protocol error: expected old/new/ref, got '%s'",
- line);
-
- refname = line + 82;
- reflen = strlen(refname);
- if (reflen + 82 < len) {
- if (strstr(refname + reflen + 1, "report-status"))
- report_status = 1;
- if (strstr(refname + reflen + 1, "side-band-64k"))
- use_sideband = LARGE_PACKET_MAX;
- }
- cmd = xmalloc(sizeof(struct command) + len - 80);
- hashcpy(cmd->old_sha1, old_sha1);
- hashcpy(cmd->new_sha1, new_sha1);
- memcpy(cmd->ref_name, line + 82, len - 81);
- cmd->error_string = NULL;
- cmd->next = NULL;
- *p = cmd;
- p = &cmd->next;
- }
-}
-
-static const char *parse_pack_header(struct pack_header *hdr)
-{
- switch (read_pack_header(0, hdr)) {
- case PH_ERROR_EOF:
- return "eof before pack header was fully read";
-
- case PH_ERROR_PACK_SIGNATURE:
- return "protocol error (pack signature mismatch detected)";
-
- case PH_ERROR_PROTOCOL:
- return "protocol error (pack version unsupported)";
-
- default:
- return "unknown error in parse_pack_header";
-
- case 0:
- return NULL;
- }
-}
-
-static const char *pack_lockfile;
-
-static const char *unpack(void)
-{
- struct pack_header hdr;
- const char *hdr_err;
- char hdr_arg[38];
-
- hdr_err = parse_pack_header(&hdr);
- if (hdr_err)
- return hdr_err;
- snprintf(hdr_arg, sizeof(hdr_arg),
- "--pack_header=%"PRIu32",%"PRIu32,
- ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
-
- if (ntohl(hdr.hdr_entries) < unpack_limit) {
- int code, i = 0;
- const char *unpacker[4];
- unpacker[i++] = "unpack-objects";
- if (receive_fsck_objects)
- unpacker[i++] = "--strict";
- unpacker[i++] = hdr_arg;
- unpacker[i++] = NULL;
- code = run_command_v_opt(unpacker, RUN_GIT_CMD);
- if (!code)
- return NULL;
- return "unpack-objects abnormal exit";
- } else {
- const char *keeper[7];
- int s, status, i = 0;
- char keep_arg[256];
- struct child_process ip;
-
- s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
- if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
- strcpy(keep_arg + s, "localhost");
-
- keeper[i++] = "index-pack";
- keeper[i++] = "--stdin";
- if (receive_fsck_objects)
- keeper[i++] = "--strict";
- keeper[i++] = "--fix-thin";
- keeper[i++] = hdr_arg;
- keeper[i++] = keep_arg;
- keeper[i++] = NULL;
- memset(&ip, 0, sizeof(ip));
- ip.argv = keeper;
- ip.out = -1;
- ip.git_cmd = 1;
- status = start_command(&ip);
- if (status) {
- return "index-pack fork failed";
- }
- pack_lockfile = index_pack_lockfile(ip.out);
- close(ip.out);
- status = finish_command(&ip);
- if (!status) {
- reprepare_packed_git();
- return NULL;
- }
- return "index-pack abnormal exit";
- }
-}
-
-static void report(const char *unpack_status)
-{
- struct command *cmd;
- struct strbuf buf = STRBUF_INIT;
-
- packet_buf_write(&buf, "unpack %s\n",
- unpack_status ? unpack_status : "ok");
- for (cmd = commands; cmd; cmd = cmd->next) {
- if (!cmd->error_string)
- packet_buf_write(&buf, "ok %s\n",
- cmd->ref_name);
- else
- packet_buf_write(&buf, "ng %s %s\n",
- cmd->ref_name, cmd->error_string);
- }
- packet_buf_flush(&buf);
-
- if (use_sideband)
- send_sideband(1, 1, buf.buf, buf.len, use_sideband);
- else
- safe_write(1, buf.buf, buf.len);
- strbuf_release(&buf);
-}
-
-static int delete_only(struct command *cmd)
-{
- while (cmd) {
- if (!is_null_sha1(cmd->new_sha1))
- return 0;
- cmd = cmd->next;
- }
- return 1;
-}
-
-static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
-{
- char *other;
- size_t len;
- struct remote *remote;
- struct transport *transport;
- const struct ref *extra;
-
- e->name[-1] = '\0';
- other = xstrdup(make_absolute_path(e->base));
- e->name[-1] = '/';
- len = strlen(other);
-
- while (other[len-1] == '/')
- other[--len] = '\0';
- if (len < 8 || memcmp(other + len - 8, "/objects", 8))
- return 0;
- /* Is this a git repository with refs? */
- memcpy(other + len - 8, "/refs", 6);
- if (!is_directory(other))
- return 0;
- other[len - 8] = '\0';
- remote = remote_get(other);
- transport = transport_get(remote, other);
- for (extra = transport_get_remote_refs(transport);
- extra;
- extra = extra->next) {
- add_extra_ref(".have", extra->old_sha1, 0);
- }
- transport_disconnect(transport);
- free(other);
- return 0;
-}
-
-static void add_alternate_refs(void)
-{
- foreach_alt_odb(add_refs_from_alternate, NULL);
-}
-
-int cmd_receive_pack(int argc, const char **argv, const char *prefix)
-{
- int advertise_refs = 0;
- int stateless_rpc = 0;
- int i;
- char *dir = NULL;
-
- argv++;
- for (i = 1; i < argc; i++) {
- const char *arg = *argv++;
-
- if (*arg == '-') {
- if (!strcmp(arg, "--advertise-refs")) {
- advertise_refs = 1;
- continue;
- }
- if (!strcmp(arg, "--stateless-rpc")) {
- stateless_rpc = 1;
- continue;
- }
-
- usage(receive_pack_usage);
- }
- if (dir)
- usage(receive_pack_usage);
- dir = xstrdup(arg);
- }
- if (!dir)
- usage(receive_pack_usage);
-
- setup_path();
-
- if (!enter_repo(dir, 0))
- die("'%s' does not appear to be a git repository", dir);
-
- if (is_repository_shallow())
- die("attempt to push into a shallow repository");
-
- git_config(receive_pack_config, NULL);
-
- if (0 <= transfer_unpack_limit)
- unpack_limit = transfer_unpack_limit;
- else if (0 <= receive_unpack_limit)
- unpack_limit = receive_unpack_limit;
-
- if (advertise_refs || !stateless_rpc) {
- add_alternate_refs();
- write_head_info();
- clear_extra_refs();
-
- /* EOF */
- packet_flush(1);
- }
- if (advertise_refs)
- return 0;
-
- read_head_info();
- if (commands) {
- const char *unpack_status = NULL;
-
- if (!delete_only(commands))
- unpack_status = unpack();
- execute_commands(unpack_status);
- if (pack_lockfile)
- unlink_or_warn(pack_lockfile);
- if (report_status)
- report(unpack_status);
- run_receive_hook(post_receive_hook);
- run_update_post_hook(commands);
- if (auto_gc) {
- const char *argv_gc_auto[] = {
- "gc", "--auto", "--quiet", NULL,
- };
- run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
- }
- if (auto_update_server_info)
- update_server_info(0);
- }
- if (use_sideband)
- packet_flush(1);
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "builtin.h"
-#include "commit.h"
-#include "refs.h"
-#include "dir.h"
-#include "tree-walk.h"
-#include "diff.h"
-#include "revision.h"
-#include "reachable.h"
-
-/*
- * reflog expire
- */
-
-static const char reflog_expire_usage[] =
-"git reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
-static const char reflog_delete_usage[] =
-"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
-
-static unsigned long default_reflog_expire;
-static unsigned long default_reflog_expire_unreachable;
-
-struct cmd_reflog_expire_cb {
- struct rev_info revs;
- int dry_run;
- int stalefix;
- int rewrite;
- int updateref;
- int verbose;
- unsigned long expire_total;
- unsigned long expire_unreachable;
- int recno;
-};
-
-struct expire_reflog_cb {
- FILE *newlog;
- const char *ref;
- struct commit *ref_commit;
- struct cmd_reflog_expire_cb *cmd;
- unsigned char last_kept_sha1[20];
-};
-
-struct collected_reflog {
- unsigned char sha1[20];
- char reflog[FLEX_ARRAY];
-};
-struct collect_reflog_cb {
- struct collected_reflog **e;
- int alloc;
- int nr;
-};
-
-#define INCOMPLETE (1u<<10)
-#define STUDYING (1u<<11)
-#define REACHABLE (1u<<12)
-
-static int tree_is_complete(const unsigned char *sha1)
-{
- struct tree_desc desc;
- struct name_entry entry;
- int complete;
- struct tree *tree;
-
- tree = lookup_tree(sha1);
- if (!tree)
- return 0;
- if (tree->object.flags & SEEN)
- return 1;
- if (tree->object.flags & INCOMPLETE)
- return 0;
-
- if (!tree->buffer) {
- enum object_type type;
- unsigned long size;
- void *data = read_sha1_file(sha1, &type, &size);
- if (!data) {
- tree->object.flags |= INCOMPLETE;
- return 0;
- }
- tree->buffer = data;
- tree->size = size;
- }
- init_tree_desc(&desc, tree->buffer, tree->size);
- complete = 1;
- while (tree_entry(&desc, &entry)) {
- if (!has_sha1_file(entry.sha1) ||
- (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
- tree->object.flags |= INCOMPLETE;
- complete = 0;
- }
- }
- free(tree->buffer);
- tree->buffer = NULL;
-
- if (complete)
- tree->object.flags |= SEEN;
- return complete;
-}
-
-static int commit_is_complete(struct commit *commit)
-{
- struct object_array study;
- struct object_array found;
- int is_incomplete = 0;
- int i;
-
- /* early return */
- if (commit->object.flags & SEEN)
- return 1;
- if (commit->object.flags & INCOMPLETE)
- return 0;
- /*
- * Find all commits that are reachable and are not marked as
- * SEEN. Then make sure the trees and blobs contained are
- * complete. After that, mark these commits also as SEEN.
- * If some of the objects that are needed to complete this
- * commit are missing, mark this commit as INCOMPLETE.
- */
- memset(&study, 0, sizeof(study));
- memset(&found, 0, sizeof(found));
- add_object_array(&commit->object, NULL, &study);
- add_object_array(&commit->object, NULL, &found);
- commit->object.flags |= STUDYING;
- while (study.nr) {
- struct commit *c;
- struct commit_list *parent;
-
- c = (struct commit *)study.objects[--study.nr].item;
- if (!c->object.parsed && !parse_object(c->object.sha1))
- c->object.flags |= INCOMPLETE;
-
- if (c->object.flags & INCOMPLETE) {
- is_incomplete = 1;
- break;
- }
- else if (c->object.flags & SEEN)
- continue;
- for (parent = c->parents; parent; parent = parent->next) {
- struct commit *p = parent->item;
- if (p->object.flags & STUDYING)
- continue;
- p->object.flags |= STUDYING;
- add_object_array(&p->object, NULL, &study);
- add_object_array(&p->object, NULL, &found);
- }
- }
- if (!is_incomplete) {
- /*
- * make sure all commits in "found" array have all the
- * necessary objects.
- */
- for (i = 0; i < found.nr; i++) {
- struct commit *c =
- (struct commit *)found.objects[i].item;
- if (!tree_is_complete(c->tree->object.sha1)) {
- is_incomplete = 1;
- c->object.flags |= INCOMPLETE;
- }
- }
- if (!is_incomplete) {
- /* mark all found commits as complete, iow SEEN */
- for (i = 0; i < found.nr; i++)
- found.objects[i].item->flags |= SEEN;
- }
- }
- /* clear flags from the objects we traversed */
- for (i = 0; i < found.nr; i++)
- found.objects[i].item->flags &= ~STUDYING;
- if (is_incomplete)
- commit->object.flags |= INCOMPLETE;
- else {
- /*
- * If we come here, we have (1) traversed the ancestry chain
- * from the "commit" until we reach SEEN commits (which are
- * known to be complete), and (2) made sure that the commits
- * encountered during the above traversal refer to trees that
- * are complete. Which means that we know *all* the commits
- * we have seen during this process are complete.
- */
- for (i = 0; i < found.nr; i++)
- found.objects[i].item->flags |= SEEN;
- }
- /* free object arrays */
- free(study.objects);
- free(found.objects);
- return !is_incomplete;
-}
-
-static int keep_entry(struct commit **it, unsigned char *sha1)
-{
- struct commit *commit;
-
- if (is_null_sha1(sha1))
- return 1;
- commit = lookup_commit_reference_gently(sha1, 1);
- if (!commit)
- return 0;
-
- /*
- * Make sure everything in this commit exists.
- *
- * We have walked all the objects reachable from the refs
- * and cache earlier. The commits reachable by this commit
- * must meet SEEN commits -- and then we should mark them as
- * SEEN as well.
- */
- if (!commit_is_complete(commit))
- return 0;
- *it = commit;
- return 1;
-}
-
-static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
-{
- /*
- * We may or may not have the commit yet - if not, look it
- * up using the supplied sha1.
- */
- if (!commit) {
- if (is_null_sha1(sha1))
- return 0;
-
- commit = lookup_commit_reference_gently(sha1, 1);
-
- /* Not a commit -- keep it */
- if (!commit)
- return 0;
- }
-
- /* Reachable from the current ref? Don't prune. */
- if (commit->object.flags & REACHABLE)
- return 0;
- if (in_merge_bases(commit, &cb->ref_commit, 1))
- return 0;
-
- /* We can't reach it - prune it. */
- return 1;
-}
-
-static void mark_reachable(struct commit *commit, unsigned long expire_limit)
-{
- /*
- * We need to compute whether the commit on either side of a reflog
- * entry is reachable from the tip of the ref for all entries.
- * Mark commits that are reachable from the tip down to the
- * time threshold first; we know a commit marked thusly is
- * reachable from the tip without running in_merge_bases()
- * at all.
- */
- struct commit_list *pending = NULL;
-
- commit_list_insert(commit, &pending);
- while (pending) {
- struct commit_list *entry = pending;
- struct commit_list *parent;
- pending = entry->next;
- commit = entry->item;
- free(entry);
- if (commit->object.flags & REACHABLE)
- continue;
- if (parse_commit(commit))
- continue;
- commit->object.flags |= REACHABLE;
- if (commit->date < expire_limit)
- continue;
- parent = commit->parents;
- while (parent) {
- commit = parent->item;
- parent = parent->next;
- if (commit->object.flags & REACHABLE)
- continue;
- commit_list_insert(commit, &pending);
- }
- }
-}
-
-static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
- const char *email, unsigned long timestamp, int tz,
- const char *message, void *cb_data)
-{
- struct expire_reflog_cb *cb = cb_data;
- struct commit *old, *new;
-
- if (timestamp < cb->cmd->expire_total)
- goto prune;
-
- if (cb->cmd->rewrite)
- osha1 = cb->last_kept_sha1;
-
- old = new = NULL;
- if (cb->cmd->stalefix &&
- (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
- goto prune;
-
- if (timestamp < cb->cmd->expire_unreachable) {
- if (!cb->ref_commit)
- goto prune;
- if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1))
- goto prune;
- }
-
- if (cb->cmd->recno && --(cb->cmd->recno) == 0)
- goto prune;
-
- if (cb->newlog) {
- char sign = (tz < 0) ? '-' : '+';
- int zone = (tz < 0) ? (-tz) : tz;
- fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s",
- sha1_to_hex(osha1), sha1_to_hex(nsha1),
- email, timestamp, sign, zone,
- message);
- hashcpy(cb->last_kept_sha1, nsha1);
- }
- if (cb->cmd->verbose)
- printf("keep %s", message);
- return 0;
- prune:
- if (!cb->newlog || cb->cmd->verbose)
- printf("%sprune %s", cb->newlog ? "" : "would ", message);
- return 0;
-}
-
-static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
-{
- struct cmd_reflog_expire_cb *cmd = cb_data;
- struct expire_reflog_cb cb;
- struct ref_lock *lock;
- char *log_file, *newlog_path = NULL;
- int status = 0;
-
- memset(&cb, 0, sizeof(cb));
-
- /*
- * we take the lock for the ref itself to prevent it from
- * getting updated.
- */
- lock = lock_any_ref_for_update(ref, sha1, 0);
- if (!lock)
- return error("cannot lock ref '%s'", ref);
- log_file = git_pathdup("logs/%s", ref);
- if (!file_exists(log_file))
- goto finish;
- if (!cmd->dry_run) {
- newlog_path = git_pathdup("logs/%s.lock", ref);
- cb.newlog = fopen(newlog_path, "w");
- }
-
- cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
- cb.ref = ref;
- cb.cmd = cmd;
- if (cb.ref_commit)
- mark_reachable(cb.ref_commit, cmd->expire_total);
- for_each_reflog_ent(ref, expire_reflog_ent, &cb);
- if (cb.ref_commit)
- clear_commit_marks(cb.ref_commit, REACHABLE);
- finish:
- if (cb.newlog) {
- if (fclose(cb.newlog)) {
- status |= error("%s: %s", strerror(errno),
- newlog_path);
- unlink(newlog_path);
- } else if (cmd->updateref &&
- (write_in_full(lock->lock_fd,
- sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
- write_str_in_full(lock->lock_fd, "\n") != 1 ||
- close_ref(lock) < 0)) {
- status |= error("Couldn't write %s",
- lock->lk->filename);
- unlink(newlog_path);
- } else if (rename(newlog_path, log_file)) {
- status |= error("cannot rename %s to %s",
- newlog_path, log_file);
- unlink(newlog_path);
- } else if (cmd->updateref && commit_ref(lock)) {
- status |= error("Couldn't set %s", lock->ref_name);
- } else {
- adjust_shared_perm(log_file);
- }
- }
- free(newlog_path);
- free(log_file);
- unlock_ref(lock);
- return status;
-}
-
-static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
-{
- struct collected_reflog *e;
- struct collect_reflog_cb *cb = cb_data;
- size_t namelen = strlen(ref);
-
- e = xmalloc(sizeof(*e) + namelen + 1);
- hashcpy(e->sha1, sha1);
- memcpy(e->reflog, ref, namelen + 1);
- ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
- cb->e[cb->nr++] = e;
- return 0;
-}
-
-static struct reflog_expire_cfg {
- struct reflog_expire_cfg *next;
- unsigned long expire_total;
- unsigned long expire_unreachable;
- size_t len;
- char pattern[FLEX_ARRAY];
-} *reflog_expire_cfg, **reflog_expire_cfg_tail;
-
-static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
-{
- struct reflog_expire_cfg *ent;
-
- if (!reflog_expire_cfg_tail)
- reflog_expire_cfg_tail = &reflog_expire_cfg;
-
- for (ent = reflog_expire_cfg; ent; ent = ent->next)
- if (ent->len == len &&
- !memcmp(ent->pattern, pattern, len))
- return ent;
-
- ent = xcalloc(1, (sizeof(*ent) + len));
- memcpy(ent->pattern, pattern, len);
- ent->len = len;
- *reflog_expire_cfg_tail = ent;
- reflog_expire_cfg_tail = &(ent->next);
- return ent;
-}
-
-static int parse_expire_cfg_value(const char *var, const char *value, unsigned long *expire)
-{
- if (!value)
- return config_error_nonbool(var);
- if (!strcmp(value, "never") || !strcmp(value, "false")) {
- *expire = 0;
- return 0;
- }
- *expire = approxidate(value);
- return 0;
-}
-
-/* expiry timer slot */
-#define EXPIRE_TOTAL 01
-#define EXPIRE_UNREACH 02
-
-static int reflog_expire_config(const char *var, const char *value, void *cb)
-{
- const char *lastdot = strrchr(var, '.');
- unsigned long expire;
- int slot;
- struct reflog_expire_cfg *ent;
-
- if (!lastdot || prefixcmp(var, "gc."))
- return git_default_config(var, value, cb);
-
- if (!strcmp(lastdot, ".reflogexpire")) {
- slot = EXPIRE_TOTAL;
- if (parse_expire_cfg_value(var, value, &expire))
- return -1;
- } else if (!strcmp(lastdot, ".reflogexpireunreachable")) {
- slot = EXPIRE_UNREACH;
- if (parse_expire_cfg_value(var, value, &expire))
- return -1;
- } else
- return git_default_config(var, value, cb);
-
- if (lastdot == var + 2) {
- switch (slot) {
- case EXPIRE_TOTAL:
- default_reflog_expire = expire;
- break;
- case EXPIRE_UNREACH:
- default_reflog_expire_unreachable = expire;
- break;
- }
- return 0;
- }
-
- ent = find_cfg_ent(var + 3, lastdot - (var+3));
- if (!ent)
- return -1;
- switch (slot) {
- case EXPIRE_TOTAL:
- ent->expire_total = expire;
- break;
- case EXPIRE_UNREACH:
- ent->expire_unreachable = expire;
- break;
- }
- return 0;
-}
-
-static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
-{
- struct reflog_expire_cfg *ent;
-
- if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
- return; /* both given explicitly -- nothing to tweak */
-
- for (ent = reflog_expire_cfg; ent; ent = ent->next) {
- if (!fnmatch(ent->pattern, ref, 0)) {
- if (!(slot & EXPIRE_TOTAL))
- cb->expire_total = ent->expire_total;
- if (!(slot & EXPIRE_UNREACH))
- cb->expire_unreachable = ent->expire_unreachable;
- return;
- }
- }
-
- /*
- * If unconfigured, make stash never expire
- */
- if (!strcmp(ref, "refs/stash")) {
- if (!(slot & EXPIRE_TOTAL))
- cb->expire_total = 0;
- if (!(slot & EXPIRE_UNREACH))
- cb->expire_unreachable = 0;
- return;
- }
-
- /* Nothing matched -- use the default value */
- if (!(slot & EXPIRE_TOTAL))
- cb->expire_total = default_reflog_expire;
- if (!(slot & EXPIRE_UNREACH))
- cb->expire_unreachable = default_reflog_expire_unreachable;
-}
-
-static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
-{
- struct cmd_reflog_expire_cb cb;
- unsigned long now = time(NULL);
- int i, status, do_all;
- int explicit_expiry = 0;
-
- default_reflog_expire_unreachable = now - 30 * 24 * 3600;
- default_reflog_expire = now - 90 * 24 * 3600;
- git_config(reflog_expire_config, NULL);
-
- save_commit_buffer = 0;
- do_all = status = 0;
- memset(&cb, 0, sizeof(cb));
-
- cb.expire_total = default_reflog_expire;
- cb.expire_unreachable = default_reflog_expire_unreachable;
-
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
- cb.dry_run = 1;
- else if (!prefixcmp(arg, "--expire=")) {
- cb.expire_total = approxidate(arg + 9);
- explicit_expiry |= EXPIRE_TOTAL;
- }
- else if (!prefixcmp(arg, "--expire-unreachable=")) {
- cb.expire_unreachable = approxidate(arg + 21);
- explicit_expiry |= EXPIRE_UNREACH;
- }
- else if (!strcmp(arg, "--stale-fix"))
- cb.stalefix = 1;
- else if (!strcmp(arg, "--rewrite"))
- cb.rewrite = 1;
- else if (!strcmp(arg, "--updateref"))
- cb.updateref = 1;
- else if (!strcmp(arg, "--all"))
- do_all = 1;
- else if (!strcmp(arg, "--verbose"))
- cb.verbose = 1;
- else if (!strcmp(arg, "--")) {
- i++;
- break;
- }
- else if (arg[0] == '-')
- usage(reflog_expire_usage);
- else
- break;
- }
-
- /*
- * We can trust the commits and objects reachable from refs
- * even in older repository. We cannot trust what's reachable
- * from reflog if the repository was pruned with older git.
- */
- if (cb.stalefix) {
- init_revisions(&cb.revs, prefix);
- if (cb.verbose)
- printf("Marking reachable objects...");
- mark_reachable_objects(&cb.revs, 0);
- if (cb.verbose)
- putchar('\n');
- }
-
- if (do_all) {
- struct collect_reflog_cb collected;
- int i;
-
- memset(&collected, 0, sizeof(collected));
- for_each_reflog(collect_reflog, &collected);
- for (i = 0; i < collected.nr; i++) {
- struct collected_reflog *e = collected.e[i];
- set_reflog_expiry_param(&cb, explicit_expiry, e->reflog);
- status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
- free(e);
- }
- free(collected.e);
- }
-
- for (; i < argc; i++) {
- char *ref;
- unsigned char sha1[20];
- if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) {
- status |= error("%s points nowhere!", argv[i]);
- continue;
- }
- set_reflog_expiry_param(&cb, explicit_expiry, ref);
- status |= expire_reflog(ref, sha1, 0, &cb);
- }
- return status;
-}
-
-static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
- const char *email, unsigned long timestamp, int tz,
- const char *message, void *cb_data)
-{
- struct cmd_reflog_expire_cb *cb = cb_data;
- if (!cb->expire_total || timestamp < cb->expire_total)
- cb->recno++;
- return 0;
-}
-
-static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
-{
- struct cmd_reflog_expire_cb cb;
- int i, status = 0;
-
- memset(&cb, 0, sizeof(cb));
-
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
- cb.dry_run = 1;
- else if (!strcmp(arg, "--rewrite"))
- cb.rewrite = 1;
- else if (!strcmp(arg, "--updateref"))
- cb.updateref = 1;
- else if (!strcmp(arg, "--verbose"))
- cb.verbose = 1;
- else if (!strcmp(arg, "--")) {
- i++;
- break;
- }
- else if (arg[0] == '-')
- usage(reflog_delete_usage);
- else
- break;
- }
-
- if (argc - i < 1)
- return error("Nothing to delete?");
-
- for ( ; i < argc; i++) {
- const char *spec = strstr(argv[i], "@{");
- unsigned char sha1[20];
- char *ep, *ref;
- int recno;
-
- if (!spec) {
- status |= error("Not a reflog: %s", argv[i]);
- continue;
- }
-
- if (!dwim_log(argv[i], spec - argv[i], sha1, &ref)) {
- status |= error("no reflog for '%s'", argv[i]);
- continue;
- }
-
- recno = strtoul(spec + 2, &ep, 10);
- if (*ep == '}') {
- cb.recno = -recno;
- for_each_reflog_ent(ref, count_reflog_ent, &cb);
- } else {
- cb.expire_total = approxidate(spec + 2);
- for_each_reflog_ent(ref, count_reflog_ent, &cb);
- cb.expire_total = 0;
- }
-
- status |= expire_reflog(ref, sha1, 0, &cb);
- free(ref);
- }
- return status;
-}
-
-/*
- * main "reflog"
- */
-
-static const char reflog_usage[] =
-"git reflog [ show | expire | delete ]";
-
-int cmd_reflog(int argc, const char **argv, const char *prefix)
-{
- if (argc > 1 && !strcmp(argv[1], "-h"))
- usage(reflog_usage);
-
- /* With no command, we default to showing it. */
- if (argc < 2 || *argv[1] == '-')
- return cmd_log_reflog(argc, argv, prefix);
-
- if (!strcmp(argv[1], "show"))
- return cmd_log_reflog(argc - 1, argv + 1, prefix);
-
- if (!strcmp(argv[1], "expire"))
- return cmd_reflog_expire(argc - 1, argv + 1, prefix);
-
- if (!strcmp(argv[1], "delete"))
- return cmd_reflog_delete(argc - 1, argv + 1, prefix);
-
- /* Not a recognized reflog command..*/
- usage(reflog_usage);
-}
+++ /dev/null
-#include "cache.h"
-#include "parse-options.h"
-#include "transport.h"
-#include "remote.h"
-#include "string-list.h"
-#include "strbuf.h"
-#include "run-command.h"
-#include "refs.h"
-
-static const char * const builtin_remote_usage[] = {
- "git remote [-v | --verbose]",
- "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
- "git remote rename <old> <new>",
- "git remote rm <name>",
- "git remote set-head <name> (-a | -d | <branch>)",
- "git remote [-v | --verbose] show [-n] <name>",
- "git remote prune [-n | --dry-run] <name>",
- "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
- "git remote set-url <name> <newurl> [<oldurl>]",
- "git remote set-url --add <name> <newurl>",
- "git remote set-url --delete <name> <url>",
- NULL
-};
-
-static const char * const builtin_remote_add_usage[] = {
- "git remote add [<options>] <name> <url>",
- NULL
-};
-
-static const char * const builtin_remote_rename_usage[] = {
- "git remote rename <old> <new>",
- NULL
-};
-
-static const char * const builtin_remote_rm_usage[] = {
- "git remote rm <name>",
- NULL
-};
-
-static const char * const builtin_remote_sethead_usage[] = {
- "git remote set-head <name> (-a | -d | <branch>])",
- NULL
-};
-
-static const char * const builtin_remote_show_usage[] = {
- "git remote show [<options>] <name>",
- NULL
-};
-
-static const char * const builtin_remote_prune_usage[] = {
- "git remote prune [<options>] <name>",
- NULL
-};
-
-static const char * const builtin_remote_update_usage[] = {
- "git remote update [<options>] [<group> | <remote>]...",
- NULL
-};
-
-static const char * const builtin_remote_seturl_usage[] = {
- "git remote set-url [--push] <name> <newurl> [<oldurl>]",
- "git remote set-url --add <name> <newurl>",
- "git remote set-url --delete <name> <url>",
- NULL
-};
-
-#define GET_REF_STATES (1<<0)
-#define GET_HEAD_NAMES (1<<1)
-#define GET_PUSH_REF_STATES (1<<2)
-
-static int verbose;
-
-static int show_all(void);
-static int prune_remote(const char *remote, int dry_run);
-
-static inline int postfixcmp(const char *string, const char *postfix)
-{
- int len1 = strlen(string), len2 = strlen(postfix);
- if (len1 < len2)
- return 1;
- return strcmp(string + len1 - len2, postfix);
-}
-
-static int opt_parse_track(const struct option *opt, const char *arg, int not)
-{
- struct string_list *list = opt->value;
- if (not)
- string_list_clear(list, 0);
- else
- string_list_append(arg, list);
- return 0;
-}
-
-static int fetch_remote(const char *name)
-{
- const char *argv[] = { "fetch", name, NULL, NULL };
- if (verbose) {
- argv[1] = "-v";
- argv[2] = name;
- }
- printf("Updating %s\n", name);
- if (run_command_v_opt(argv, RUN_GIT_CMD))
- return error("Could not fetch %s", name);
- return 0;
-}
-
-static int add(int argc, const char **argv)
-{
- int fetch = 0, mirror = 0;
- struct string_list track = { NULL, 0, 0 };
- const char *master = NULL;
- struct remote *remote;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
- const char *name, *url;
- int i;
-
- struct option options[] = {
- OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
- OPT_CALLBACK('t', "track", &track, "branch",
- "branch(es) to track", opt_parse_track),
- OPT_STRING('m', "master", &master, "branch", "master branch"),
- OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"),
- OPT_END()
- };
-
- argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage,
- 0);
-
- if (argc < 2)
- usage_with_options(builtin_remote_add_usage, options);
-
- name = argv[0];
- url = argv[1];
-
- remote = remote_get(name);
- if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
- remote->fetch_refspec_nr))
- die("remote %s already exists.", name);
-
- strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
- if (!valid_fetch_refspec(buf2.buf))
- die("'%s' is not a valid remote name", name);
-
- strbuf_addf(&buf, "remote.%s.url", name);
- if (git_config_set(buf.buf, url))
- return 1;
-
- strbuf_reset(&buf);
- strbuf_addf(&buf, "remote.%s.fetch", name);
-
- if (track.nr == 0)
- string_list_append("*", &track);
- for (i = 0; i < track.nr; i++) {
- struct string_list_item *item = track.items + i;
-
- strbuf_reset(&buf2);
- strbuf_addch(&buf2, '+');
- if (mirror)
- strbuf_addf(&buf2, "refs/%s:refs/%s",
- item->string, item->string);
- else
- strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s",
- item->string, name, item->string);
- if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
- return 1;
- }
-
- if (mirror) {
- strbuf_reset(&buf);
- strbuf_addf(&buf, "remote.%s.mirror", name);
- if (git_config_set(buf.buf, "true"))
- return 1;
- }
-
- if (fetch && fetch_remote(name))
- return 1;
-
- if (master) {
- strbuf_reset(&buf);
- strbuf_addf(&buf, "refs/remotes/%s/HEAD", name);
-
- strbuf_reset(&buf2);
- strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
-
- if (create_symref(buf.buf, buf2.buf, "remote add"))
- return error("Could not setup master '%s'", master);
- }
-
- strbuf_release(&buf);
- strbuf_release(&buf2);
- string_list_clear(&track, 0);
-
- return 0;
-}
-
-struct branch_info {
- char *remote_name;
- struct string_list merge;
- int rebase;
-};
-
-static struct string_list branch_list;
-
-static const char *abbrev_ref(const char *name, const char *prefix)
-{
- const char *abbrev = skip_prefix(name, prefix);
- if (abbrev)
- return abbrev;
- return name;
-}
-#define abbrev_branch(name) abbrev_ref((name), "refs/heads/")
-
-static int config_read_branches(const char *key, const char *value, void *cb)
-{
- if (!prefixcmp(key, "branch.")) {
- const char *orig_key = key;
- char *name;
- struct string_list_item *item;
- struct branch_info *info;
- enum { REMOTE, MERGE, REBASE } type;
-
- key += 7;
- if (!postfixcmp(key, ".remote")) {
- name = xstrndup(key, strlen(key) - 7);
- type = REMOTE;
- } else if (!postfixcmp(key, ".merge")) {
- name = xstrndup(key, strlen(key) - 6);
- type = MERGE;
- } else if (!postfixcmp(key, ".rebase")) {
- name = xstrndup(key, strlen(key) - 7);
- type = REBASE;
- } else
- return 0;
-
- item = string_list_insert(name, &branch_list);
-
- if (!item->util)
- item->util = xcalloc(sizeof(struct branch_info), 1);
- info = item->util;
- if (type == REMOTE) {
- if (info->remote_name)
- warning("more than one %s", orig_key);
- info->remote_name = xstrdup(value);
- } else if (type == MERGE) {
- char *space = strchr(value, ' ');
- value = abbrev_branch(value);
- while (space) {
- char *merge;
- merge = xstrndup(value, space - value);
- string_list_append(merge, &info->merge);
- value = abbrev_branch(space + 1);
- space = strchr(value, ' ');
- }
- string_list_append(xstrdup(value), &info->merge);
- } else
- info->rebase = git_config_bool(orig_key, value);
- }
- return 0;
-}
-
-static void read_branches(void)
-{
- if (branch_list.nr)
- return;
- git_config(config_read_branches, NULL);
-}
-
-struct ref_states {
- struct remote *remote;
- struct string_list new, stale, tracked, heads, push;
- int queried;
-};
-
-static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
-{
- struct ref *fetch_map = NULL, **tail = &fetch_map;
- struct ref *ref, *stale_refs;
- int i;
-
- for (i = 0; i < states->remote->fetch_refspec_nr; i++)
- if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
- die("Could not get fetch map for refspec %s",
- states->remote->fetch_refspec[i]);
-
- states->new.strdup_strings = 1;
- states->tracked.strdup_strings = 1;
- states->stale.strdup_strings = 1;
- for (ref = fetch_map; ref; ref = ref->next) {
- unsigned char sha1[20];
- if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
- string_list_append(abbrev_branch(ref->name), &states->new);
- else
- string_list_append(abbrev_branch(ref->name), &states->tracked);
- }
- stale_refs = get_stale_heads(states->remote, fetch_map);
- for (ref = stale_refs; ref; ref = ref->next) {
- struct string_list_item *item =
- string_list_append(abbrev_branch(ref->name), &states->stale);
- item->util = xstrdup(ref->name);
- }
- free_refs(stale_refs);
- free_refs(fetch_map);
-
- sort_string_list(&states->new);
- sort_string_list(&states->tracked);
- sort_string_list(&states->stale);
-
- return 0;
-}
-
-struct push_info {
- char *dest;
- int forced;
- enum {
- PUSH_STATUS_CREATE = 0,
- PUSH_STATUS_DELETE,
- PUSH_STATUS_UPTODATE,
- PUSH_STATUS_FASTFORWARD,
- PUSH_STATUS_OUTOFDATE,
- PUSH_STATUS_NOTQUERIED,
- } status;
-};
-
-static int get_push_ref_states(const struct ref *remote_refs,
- struct ref_states *states)
-{
- struct remote *remote = states->remote;
- struct ref *ref, *local_refs, *push_map;
- if (remote->mirror)
- return 0;
-
- local_refs = get_local_heads();
- push_map = copy_ref_list(remote_refs);
-
- match_refs(local_refs, &push_map, remote->push_refspec_nr,
- remote->push_refspec, MATCH_REFS_NONE);
-
- states->push.strdup_strings = 1;
- for (ref = push_map; ref; ref = ref->next) {
- struct string_list_item *item;
- struct push_info *info;
-
- if (!ref->peer_ref)
- continue;
- hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-
- item = string_list_append(abbrev_branch(ref->peer_ref->name),
- &states->push);
- item->util = xcalloc(sizeof(struct push_info), 1);
- info = item->util;
- info->forced = ref->force;
- info->dest = xstrdup(abbrev_branch(ref->name));
-
- if (is_null_sha1(ref->new_sha1)) {
- info->status = PUSH_STATUS_DELETE;
- } else if (!hashcmp(ref->old_sha1, ref->new_sha1))
- info->status = PUSH_STATUS_UPTODATE;
- else if (is_null_sha1(ref->old_sha1))
- info->status = PUSH_STATUS_CREATE;
- else if (has_sha1_file(ref->old_sha1) &&
- ref_newer(ref->new_sha1, ref->old_sha1))
- info->status = PUSH_STATUS_FASTFORWARD;
- else
- info->status = PUSH_STATUS_OUTOFDATE;
- }
- free_refs(local_refs);
- free_refs(push_map);
- return 0;
-}
-
-static int get_push_ref_states_noquery(struct ref_states *states)
-{
- int i;
- struct remote *remote = states->remote;
- struct string_list_item *item;
- struct push_info *info;
-
- if (remote->mirror)
- return 0;
-
- states->push.strdup_strings = 1;
- if (!remote->push_refspec_nr) {
- item = string_list_append("(matching)", &states->push);
- info = item->util = xcalloc(sizeof(struct push_info), 1);
- info->status = PUSH_STATUS_NOTQUERIED;
- info->dest = xstrdup(item->string);
- }
- for (i = 0; i < remote->push_refspec_nr; i++) {
- struct refspec *spec = remote->push + i;
- if (spec->matching)
- item = string_list_append("(matching)", &states->push);
- else if (strlen(spec->src))
- item = string_list_append(spec->src, &states->push);
- else
- item = string_list_append("(delete)", &states->push);
-
- info = item->util = xcalloc(sizeof(struct push_info), 1);
- info->forced = spec->force;
- info->status = PUSH_STATUS_NOTQUERIED;
- info->dest = xstrdup(spec->dst ? spec->dst : item->string);
- }
- return 0;
-}
-
-static int get_head_names(const struct ref *remote_refs, struct ref_states *states)
-{
- struct ref *ref, *matches;
- struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
- struct refspec refspec;
-
- refspec.force = 0;
- refspec.pattern = 1;
- refspec.src = refspec.dst = "refs/heads/*";
- states->heads.strdup_strings = 1;
- get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
- matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
- fetch_map, 1);
- for (ref = matches; ref; ref = ref->next)
- string_list_append(abbrev_branch(ref->name), &states->heads);
-
- free_refs(fetch_map);
- free_refs(matches);
-
- return 0;
-}
-
-struct known_remote {
- struct known_remote *next;
- struct remote *remote;
-};
-
-struct known_remotes {
- struct remote *to_delete;
- struct known_remote *list;
-};
-
-static int add_known_remote(struct remote *remote, void *cb_data)
-{
- struct known_remotes *all = cb_data;
- struct known_remote *r;
-
- if (!strcmp(all->to_delete->name, remote->name))
- return 0;
-
- r = xmalloc(sizeof(*r));
- r->remote = remote;
- r->next = all->list;
- all->list = r;
- return 0;
-}
-
-struct branches_for_remote {
- struct remote *remote;
- struct string_list *branches, *skipped;
- struct known_remotes *keep;
-};
-
-static int add_branch_for_removal(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
-{
- struct branches_for_remote *branches = cb_data;
- struct refspec refspec;
- struct string_list_item *item;
- struct known_remote *kr;
-
- memset(&refspec, 0, sizeof(refspec));
- refspec.dst = (char *)refname;
- if (remote_find_tracking(branches->remote, &refspec))
- return 0;
-
- /* don't delete a branch if another remote also uses it */
- for (kr = branches->keep->list; kr; kr = kr->next) {
- memset(&refspec, 0, sizeof(refspec));
- refspec.dst = (char *)refname;
- if (!remote_find_tracking(kr->remote, &refspec))
- return 0;
- }
-
- /* don't delete non-remote refs */
- if (prefixcmp(refname, "refs/remotes")) {
- /* advise user how to delete local branches */
- if (!prefixcmp(refname, "refs/heads/"))
- string_list_append(abbrev_branch(refname),
- branches->skipped);
- /* silently skip over other non-remote refs */
- return 0;
- }
-
- /* make sure that symrefs are deleted */
- if (flags & REF_ISSYMREF)
- return unlink(git_path("%s", refname));
-
- item = string_list_append(refname, branches->branches);
- item->util = xmalloc(20);
- hashcpy(item->util, sha1);
-
- return 0;
-}
-
-struct rename_info {
- const char *old;
- const char *new;
- struct string_list *remote_branches;
-};
-
-static int read_remote_branches(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
-{
- struct rename_info *rename = cb_data;
- struct strbuf buf = STRBUF_INIT;
- struct string_list_item *item;
- int flag;
- unsigned char orig_sha1[20];
- const char *symref;
-
- strbuf_addf(&buf, "refs/remotes/%s", rename->old);
- if (!prefixcmp(refname, buf.buf)) {
- item = string_list_append(xstrdup(refname), rename->remote_branches);
- symref = resolve_ref(refname, orig_sha1, 1, &flag);
- if (flag & REF_ISSYMREF)
- item->util = xstrdup(symref);
- else
- item->util = NULL;
- }
-
- return 0;
-}
-
-static int migrate_file(struct remote *remote)
-{
- struct strbuf buf = STRBUF_INIT;
- int i;
- char *path = NULL;
-
- strbuf_addf(&buf, "remote.%s.url", remote->name);
- for (i = 0; i < remote->url_nr; i++)
- if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
- remote->url[i], buf.buf);
- strbuf_reset(&buf);
- strbuf_addf(&buf, "remote.%s.push", remote->name);
- for (i = 0; i < remote->push_refspec_nr; i++)
- if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
- remote->push_refspec[i], buf.buf);
- strbuf_reset(&buf);
- strbuf_addf(&buf, "remote.%s.fetch", remote->name);
- for (i = 0; i < remote->fetch_refspec_nr; i++)
- if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
- remote->fetch_refspec[i], buf.buf);
- if (remote->origin == REMOTE_REMOTES)
- path = git_path("remotes/%s", remote->name);
- else if (remote->origin == REMOTE_BRANCHES)
- path = git_path("branches/%s", remote->name);
- if (path)
- unlink_or_warn(path);
- return 0;
-}
-
-static int mv(int argc, const char **argv)
-{
- struct option options[] = {
- OPT_END()
- };
- struct remote *oldremote, *newremote;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
- struct string_list remote_branches = { NULL, 0, 0, 0 };
- struct rename_info rename;
- int i;
-
- if (argc != 3)
- usage_with_options(builtin_remote_rename_usage, options);
-
- rename.old = argv[1];
- rename.new = argv[2];
- rename.remote_branches = &remote_branches;
-
- oldremote = remote_get(rename.old);
- if (!oldremote)
- die("No such remote: %s", rename.old);
-
- if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG)
- return migrate_file(oldremote);
-
- newremote = remote_get(rename.new);
- if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr))
- die("remote %s already exists.", rename.new);
-
- strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
- if (!valid_fetch_refspec(buf.buf))
- die("'%s' is not a valid remote name", rename.new);
-
- strbuf_reset(&buf);
- strbuf_addf(&buf, "remote.%s", rename.old);
- strbuf_addf(&buf2, "remote.%s", rename.new);
- if (git_config_rename_section(buf.buf, buf2.buf) < 1)
- return error("Could not rename config section '%s' to '%s'",
- buf.buf, buf2.buf);
-
- strbuf_reset(&buf);
- strbuf_addf(&buf, "remote.%s.fetch", rename.new);
- if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
- return error("Could not remove config section '%s'", buf.buf);
- for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
- char *ptr;
-
- strbuf_reset(&buf2);
- strbuf_addstr(&buf2, oldremote->fetch_refspec[i]);
- ptr = strstr(buf2.buf, rename.old);
- if (ptr)
- strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old),
- rename.new, strlen(rename.new));
- if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
- return error("Could not append '%s'", buf.buf);
- }
-
- read_branches();
- for (i = 0; i < branch_list.nr; i++) {
- struct string_list_item *item = branch_list.items + i;
- struct branch_info *info = item->util;
- if (info->remote_name && !strcmp(info->remote_name, rename.old)) {
- strbuf_reset(&buf);
- strbuf_addf(&buf, "branch.%s.remote", item->string);
- if (git_config_set(buf.buf, rename.new)) {
- return error("Could not set '%s'", buf.buf);
- }
- }
- }
-
- /*
- * First remove symrefs, then rename the rest, finally create
- * the new symrefs.
- */
- for_each_ref(read_remote_branches, &rename);
- for (i = 0; i < remote_branches.nr; i++) {
- struct string_list_item *item = remote_branches.items + i;
- int flag = 0;
- unsigned char sha1[20];
-
- resolve_ref(item->string, sha1, 1, &flag);
- if (!(flag & REF_ISSYMREF))
- continue;
- if (delete_ref(item->string, NULL, REF_NODEREF))
- die("deleting '%s' failed", item->string);
- }
- for (i = 0; i < remote_branches.nr; i++) {
- struct string_list_item *item = remote_branches.items + i;
-
- if (item->util)
- continue;
- strbuf_reset(&buf);
- strbuf_addstr(&buf, item->string);
- strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
- rename.new, strlen(rename.new));
- strbuf_reset(&buf2);
- strbuf_addf(&buf2, "remote: renamed %s to %s",
- item->string, buf.buf);
- if (rename_ref(item->string, buf.buf, buf2.buf))
- die("renaming '%s' failed", item->string);
- }
- for (i = 0; i < remote_branches.nr; i++) {
- struct string_list_item *item = remote_branches.items + i;
-
- if (!item->util)
- continue;
- strbuf_reset(&buf);
- strbuf_addstr(&buf, item->string);
- strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
- rename.new, strlen(rename.new));
- strbuf_reset(&buf2);
- strbuf_addstr(&buf2, item->util);
- strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old),
- rename.new, strlen(rename.new));
- strbuf_reset(&buf3);
- strbuf_addf(&buf3, "remote: renamed %s to %s",
- item->string, buf.buf);
- if (create_symref(buf.buf, buf2.buf, buf3.buf))
- die("creating '%s' failed", buf.buf);
- }
- return 0;
-}
-
-static int remove_branches(struct string_list *branches)
-{
- int i, result = 0;
- for (i = 0; i < branches->nr; i++) {
- struct string_list_item *item = branches->items + i;
- const char *refname = item->string;
- unsigned char *sha1 = item->util;
-
- if (delete_ref(refname, sha1, 0))
- result |= error("Could not remove branch %s", refname);
- }
- return result;
-}
-
-static int rm(int argc, const char **argv)
-{
- struct option options[] = {
- OPT_END()
- };
- struct remote *remote;
- struct strbuf buf = STRBUF_INIT;
- struct known_remotes known_remotes = { NULL, NULL };
- struct string_list branches = { NULL, 0, 0, 1 };
- struct string_list skipped = { NULL, 0, 0, 1 };
- struct branches_for_remote cb_data = {
- NULL, &branches, &skipped, &known_remotes
- };
- int i, result;
-
- if (argc != 2)
- usage_with_options(builtin_remote_rm_usage, options);
-
- remote = remote_get(argv[1]);
- if (!remote)
- die("No such remote: %s", argv[1]);
-
- known_remotes.to_delete = remote;
- for_each_remote(add_known_remote, &known_remotes);
-
- strbuf_addf(&buf, "remote.%s", remote->name);
- if (git_config_rename_section(buf.buf, NULL) < 1)
- return error("Could not remove config section '%s'", buf.buf);
-
- read_branches();
- for (i = 0; i < branch_list.nr; i++) {
- struct string_list_item *item = branch_list.items + i;
- struct branch_info *info = item->util;
- if (info->remote_name && !strcmp(info->remote_name, remote->name)) {
- const char *keys[] = { "remote", "merge", NULL }, **k;
- for (k = keys; *k; k++) {
- strbuf_reset(&buf);
- strbuf_addf(&buf, "branch.%s.%s",
- item->string, *k);
- if (git_config_set(buf.buf, NULL)) {
- strbuf_release(&buf);
- return -1;
- }
- }
- }
- }
-
- /*
- * We cannot just pass a function to for_each_ref() which deletes
- * the branches one by one, since for_each_ref() relies on cached
- * refs, which are invalidated when deleting a branch.
- */
- cb_data.remote = remote;
- result = for_each_ref(add_branch_for_removal, &cb_data);
- strbuf_release(&buf);
-
- if (!result)
- result = remove_branches(&branches);
- string_list_clear(&branches, 1);
-
- if (skipped.nr) {
- fprintf(stderr, skipped.nr == 1 ?
- "Note: A non-remote branch was not removed; "
- "to delete it, use:\n" :
- "Note: Non-remote branches were not removed; "
- "to delete them, use:\n");
- for (i = 0; i < skipped.nr; i++)
- fprintf(stderr, " git branch -d %s\n",
- skipped.items[i].string);
- }
- string_list_clear(&skipped, 0);
-
- return result;
-}
-
-static void clear_push_info(void *util, const char *string)
-{
- struct push_info *info = util;
- free(info->dest);
- free(info);
-}
-
-static void free_remote_ref_states(struct ref_states *states)
-{
- string_list_clear(&states->new, 0);
- string_list_clear(&states->stale, 1);
- string_list_clear(&states->tracked, 0);
- string_list_clear(&states->heads, 0);
- string_list_clear_func(&states->push, clear_push_info);
-}
-
-static int append_ref_to_tracked_list(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
-{
- struct ref_states *states = cb_data;
- struct refspec refspec;
-
- if (flags & REF_ISSYMREF)
- return 0;
-
- memset(&refspec, 0, sizeof(refspec));
- refspec.dst = (char *)refname;
- if (!remote_find_tracking(states->remote, &refspec))
- string_list_append(abbrev_branch(refspec.src), &states->tracked);
-
- return 0;
-}
-
-static int get_remote_ref_states(const char *name,
- struct ref_states *states,
- int query)
-{
- struct transport *transport;
- const struct ref *remote_refs;
-
- states->remote = remote_get(name);
- if (!states->remote)
- return error("No such remote: %s", name);
-
- read_branches();
-
- if (query) {
- transport = transport_get(states->remote, states->remote->url_nr > 0 ?
- states->remote->url[0] : NULL);
- remote_refs = transport_get_remote_refs(transport);
- transport_disconnect(transport);
-
- states->queried = 1;
- if (query & GET_REF_STATES)
- get_ref_states(remote_refs, states);
- if (query & GET_HEAD_NAMES)
- get_head_names(remote_refs, states);
- if (query & GET_PUSH_REF_STATES)
- get_push_ref_states(remote_refs, states);
- } else {
- for_each_ref(append_ref_to_tracked_list, states);
- sort_string_list(&states->tracked);
- get_push_ref_states_noquery(states);
- }
-
- return 0;
-}
-
-struct show_info {
- struct string_list *list;
- struct ref_states *states;
- int width, width2;
- int any_rebase;
-};
-
-static int add_remote_to_show_info(struct string_list_item *item, void *cb_data)
-{
- struct show_info *info = cb_data;
- int n = strlen(item->string);
- if (n > info->width)
- info->width = n;
- string_list_insert(item->string, info->list);
- return 0;
-}
-
-static int show_remote_info_item(struct string_list_item *item, void *cb_data)
-{
- struct show_info *info = cb_data;
- struct ref_states *states = info->states;
- const char *name = item->string;
-
- if (states->queried) {
- const char *fmt = "%s";
- const char *arg = "";
- if (string_list_has_string(&states->new, name)) {
- fmt = " new (next fetch will store in remotes/%s)";
- arg = states->remote->name;
- } else if (string_list_has_string(&states->tracked, name))
- arg = " tracked";
- else if (string_list_has_string(&states->stale, name))
- arg = " stale (use 'git remote prune' to remove)";
- else
- arg = " ???";
- printf(" %-*s", info->width, name);
- printf(fmt, arg);
- printf("\n");
- } else
- printf(" %s\n", name);
-
- return 0;
-}
-
-static int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data)
-{
- struct show_info *show_info = cb_data;
- struct ref_states *states = show_info->states;
- struct branch_info *branch_info = branch_item->util;
- struct string_list_item *item;
- int n;
-
- if (!branch_info->merge.nr || !branch_info->remote_name ||
- strcmp(states->remote->name, branch_info->remote_name))
- return 0;
- if ((n = strlen(branch_item->string)) > show_info->width)
- show_info->width = n;
- if (branch_info->rebase)
- show_info->any_rebase = 1;
-
- item = string_list_insert(branch_item->string, show_info->list);
- item->util = branch_info;
-
- return 0;
-}
-
-static int show_local_info_item(struct string_list_item *item, void *cb_data)
-{
- struct show_info *show_info = cb_data;
- struct branch_info *branch_info = item->util;
- struct string_list *merge = &branch_info->merge;
- const char *also;
- int i;
-
- if (branch_info->rebase && branch_info->merge.nr > 1) {
- error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
- item->string);
- return 0;
- }
-
- printf(" %-*s ", show_info->width, item->string);
- if (branch_info->rebase) {
- printf("rebases onto remote %s\n", merge->items[0].string);
- return 0;
- } else if (show_info->any_rebase) {
- printf(" merges with remote %s\n", merge->items[0].string);
- also = " and with remote";
- } else {
- printf("merges with remote %s\n", merge->items[0].string);
- also = " and with remote";
- }
- for (i = 1; i < merge->nr; i++)
- printf(" %-*s %s %s\n", show_info->width, "", also,
- merge->items[i].string);
-
- return 0;
-}
-
-static int add_push_to_show_info(struct string_list_item *push_item, void *cb_data)
-{
- struct show_info *show_info = cb_data;
- struct push_info *push_info = push_item->util;
- struct string_list_item *item;
- int n;
- if ((n = strlen(push_item->string)) > show_info->width)
- show_info->width = n;
- if ((n = strlen(push_info->dest)) > show_info->width2)
- show_info->width2 = n;
- item = string_list_append(push_item->string, show_info->list);
- item->util = push_item->util;
- return 0;
-}
-
-/*
- * Sorting comparison for a string list that has push_info
- * structs in its util field
- */
-static int cmp_string_with_push(const void *va, const void *vb)
-{
- const struct string_list_item *a = va;
- const struct string_list_item *b = vb;
- const struct push_info *a_push = a->util;
- const struct push_info *b_push = b->util;
- int cmp = strcmp(a->string, b->string);
- return cmp ? cmp : strcmp(a_push->dest, b_push->dest);
-}
-
-static int show_push_info_item(struct string_list_item *item, void *cb_data)
-{
- struct show_info *show_info = cb_data;
- struct push_info *push_info = item->util;
- char *src = item->string, *status = NULL;
-
- switch (push_info->status) {
- case PUSH_STATUS_CREATE:
- status = "create";
- break;
- case PUSH_STATUS_DELETE:
- status = "delete";
- src = "(none)";
- break;
- case PUSH_STATUS_UPTODATE:
- status = "up to date";
- break;
- case PUSH_STATUS_FASTFORWARD:
- status = "fast-forwardable";
- break;
- case PUSH_STATUS_OUTOFDATE:
- status = "local out of date";
- break;
- case PUSH_STATUS_NOTQUERIED:
- break;
- }
- if (status)
- printf(" %-*s %s to %-*s (%s)\n", show_info->width, src,
- push_info->forced ? "forces" : "pushes",
- show_info->width2, push_info->dest, status);
- else
- printf(" %-*s %s to %s\n", show_info->width, src,
- push_info->forced ? "forces" : "pushes",
- push_info->dest);
- return 0;
-}
-
-static int show(int argc, const char **argv)
-{
- int no_query = 0, result = 0, query_flag = 0;
- struct option options[] = {
- OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"),
- OPT_END()
- };
- struct ref_states states;
- struct string_list info_list = { NULL, 0, 0, 0 };
- struct show_info info;
-
- argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage,
- 0);
-
- if (argc < 1)
- return show_all();
-
- if (!no_query)
- query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES);
-
- memset(&states, 0, sizeof(states));
- memset(&info, 0, sizeof(info));
- info.states = &states;
- info.list = &info_list;
- for (; argc; argc--, argv++) {
- int i;
- const char **url;
- int url_nr;
-
- get_remote_ref_states(*argv, &states, query_flag);
-
- printf("* remote %s\n", *argv);
- printf(" Fetch URL: %s\n", states.remote->url_nr > 0 ?
- states.remote->url[0] : "(no URL)");
- if (states.remote->pushurl_nr) {
- url = states.remote->pushurl;
- url_nr = states.remote->pushurl_nr;
- } else {
- url = states.remote->url;
- url_nr = states.remote->url_nr;
- }
- for (i=0; i < url_nr; i++)
- printf(" Push URL: %s\n", url[i]);
- if (!i)
- printf(" Push URL: %s\n", "(no URL)");
- if (no_query)
- printf(" HEAD branch: (not queried)\n");
- else if (!states.heads.nr)
- printf(" HEAD branch: (unknown)\n");
- else if (states.heads.nr == 1)
- printf(" HEAD branch: %s\n", states.heads.items[0].string);
- else {
- printf(" HEAD branch (remote HEAD is ambiguous,"
- " may be one of the following):\n");
- for (i = 0; i < states.heads.nr; i++)
- printf(" %s\n", states.heads.items[i].string);
- }
-
- /* remote branch info */
- info.width = 0;
- for_each_string_list(add_remote_to_show_info, &states.new, &info);
- for_each_string_list(add_remote_to_show_info, &states.tracked, &info);
- for_each_string_list(add_remote_to_show_info, &states.stale, &info);
- if (info.list->nr)
- printf(" Remote branch%s:%s\n",
- info.list->nr > 1 ? "es" : "",
- no_query ? " (status not queried)" : "");
- for_each_string_list(show_remote_info_item, info.list, &info);
- string_list_clear(info.list, 0);
-
- /* git pull info */
- info.width = 0;
- info.any_rebase = 0;
- for_each_string_list(add_local_to_show_info, &branch_list, &info);
- if (info.list->nr)
- printf(" Local branch%s configured for 'git pull':\n",
- info.list->nr > 1 ? "es" : "");
- for_each_string_list(show_local_info_item, info.list, &info);
- string_list_clear(info.list, 0);
-
- /* git push info */
- if (states.remote->mirror)
- printf(" Local refs will be mirrored by 'git push'\n");
-
- info.width = info.width2 = 0;
- for_each_string_list(add_push_to_show_info, &states.push, &info);
- qsort(info.list->items, info.list->nr,
- sizeof(*info.list->items), cmp_string_with_push);
- if (info.list->nr)
- printf(" Local ref%s configured for 'git push'%s:\n",
- info.list->nr > 1 ? "s" : "",
- no_query ? " (status not queried)" : "");
- for_each_string_list(show_push_info_item, info.list, &info);
- string_list_clear(info.list, 0);
-
- free_remote_ref_states(&states);
- }
-
- return result;
-}
-
-static int set_head(int argc, const char **argv)
-{
- int i, opt_a = 0, opt_d = 0, result = 0;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
- char *head_name = NULL;
-
- struct option options[] = {
- OPT_BOOLEAN('a', "auto", &opt_a,
- "set refs/remotes/<name>/HEAD according to remote"),
- OPT_BOOLEAN('d', "delete", &opt_d,
- "delete refs/remotes/<name>/HEAD"),
- OPT_END()
- };
- argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage,
- 0);
- if (argc)
- strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
-
- if (!opt_a && !opt_d && argc == 2) {
- head_name = xstrdup(argv[1]);
- } else if (opt_a && !opt_d && argc == 1) {
- struct ref_states states;
- memset(&states, 0, sizeof(states));
- get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
- if (!states.heads.nr)
- result |= error("Cannot determine remote HEAD");
- else if (states.heads.nr > 1) {
- result |= error("Multiple remote HEAD branches. "
- "Please choose one explicitly with:");
- for (i = 0; i < states.heads.nr; i++)
- fprintf(stderr, " git remote set-head %s %s\n",
- argv[0], states.heads.items[i].string);
- } else
- head_name = xstrdup(states.heads.items[0].string);
- free_remote_ref_states(&states);
- } else if (opt_d && !opt_a && argc == 1) {
- if (delete_ref(buf.buf, NULL, REF_NODEREF))
- result |= error("Could not delete %s", buf.buf);
- } else
- usage_with_options(builtin_remote_sethead_usage, options);
-
- if (head_name) {
- unsigned char sha1[20];
- strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
- /* make sure it's valid */
- if (!resolve_ref(buf2.buf, sha1, 1, NULL))
- result |= error("Not a valid ref: %s", buf2.buf);
- else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
- result |= error("Could not setup %s", buf.buf);
- if (opt_a)
- printf("%s/HEAD set to %s\n", argv[0], head_name);
- free(head_name);
- }
-
- strbuf_release(&buf);
- strbuf_release(&buf2);
- return result;
-}
-
-static int prune(int argc, const char **argv)
-{
- int dry_run = 0, result = 0;
- struct option options[] = {
- OPT__DRY_RUN(&dry_run),
- OPT_END()
- };
-
- argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage,
- 0);
-
- if (argc < 1)
- usage_with_options(builtin_remote_prune_usage, options);
-
- for (; argc; argc--, argv++)
- result |= prune_remote(*argv, dry_run);
-
- return result;
-}
-
-static int prune_remote(const char *remote, int dry_run)
-{
- int result = 0, i;
- struct ref_states states;
- const char *dangling_msg = dry_run
- ? " %s will become dangling!\n"
- : " %s has become dangling!\n";
-
- memset(&states, 0, sizeof(states));
- get_remote_ref_states(remote, &states, GET_REF_STATES);
-
- if (states.stale.nr) {
- printf("Pruning %s\n", remote);
- printf("URL: %s\n",
- states.remote->url_nr
- ? states.remote->url[0]
- : "(no URL)");
- }
-
- for (i = 0; i < states.stale.nr; i++) {
- const char *refname = states.stale.items[i].util;
-
- if (!dry_run)
- result |= delete_ref(refname, NULL, 0);
-
- printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
- abbrev_ref(refname, "refs/remotes/"));
- warn_dangling_symref(stdout, dangling_msg, refname);
- }
-
- free_remote_ref_states(&states);
- return result;
-}
-
-static int get_remote_default(const char *key, const char *value, void *priv)
-{
- if (strcmp(key, "remotes.default") == 0) {
- int *found = priv;
- *found = 1;
- }
- return 0;
-}
-
-static int update(int argc, const char **argv)
-{
- int i, prune = 0;
- struct option options[] = {
- OPT_BOOLEAN('p', "prune", &prune,
- "prune remotes after fetching"),
- OPT_END()
- };
- const char **fetch_argv;
- int fetch_argc = 0;
- int default_defined = 0;
-
- fetch_argv = xmalloc(sizeof(char *) * (argc+5));
-
- argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
- PARSE_OPT_KEEP_ARGV0);
-
- fetch_argv[fetch_argc++] = "fetch";
-
- if (prune)
- fetch_argv[fetch_argc++] = "--prune";
- if (verbose)
- fetch_argv[fetch_argc++] = "-v";
- fetch_argv[fetch_argc++] = "--multiple";
- if (argc < 2)
- fetch_argv[fetch_argc++] = "default";
- for (i = 1; i < argc; i++)
- fetch_argv[fetch_argc++] = argv[i];
-
- if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) {
- git_config(get_remote_default, &default_defined);
- if (!default_defined)
- fetch_argv[fetch_argc-1] = "--all";
- }
-
- fetch_argv[fetch_argc] = NULL;
-
- return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
-}
-
-static int set_url(int argc, const char **argv)
-{
- int i, push_mode = 0, add_mode = 0, delete_mode = 0;
- int matches = 0, negative_matches = 0;
- const char *remotename = NULL;
- const char *newurl = NULL;
- const char *oldurl = NULL;
- struct remote *remote;
- regex_t old_regex;
- const char **urlset;
- int urlset_nr;
- struct strbuf name_buf = STRBUF_INIT;
- struct option options[] = {
- OPT_BOOLEAN('\0', "push", &push_mode,
- "manipulate push URLs"),
- OPT_BOOLEAN('\0', "add", &add_mode,
- "add URL"),
- OPT_BOOLEAN('\0', "delete", &delete_mode,
- "delete URLs"),
- OPT_END()
- };
- argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
- PARSE_OPT_KEEP_ARGV0);
-
- if (add_mode && delete_mode)
- die("--add --delete doesn't make sense");
-
- if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3))
- usage_with_options(builtin_remote_seturl_usage, options);
-
- remotename = argv[1];
- newurl = argv[2];
- if (argc > 3)
- oldurl = argv[3];
-
- if (delete_mode)
- oldurl = newurl;
-
- if (!remote_is_configured(remotename))
- die("No such remote '%s'", remotename);
- remote = remote_get(remotename);
-
- if (push_mode) {
- strbuf_addf(&name_buf, "remote.%s.pushurl", remotename);
- urlset = remote->pushurl;
- urlset_nr = remote->pushurl_nr;
- } else {
- strbuf_addf(&name_buf, "remote.%s.url", remotename);
- urlset = remote->url;
- urlset_nr = remote->url_nr;
- }
-
- /* Special cases that add new entry. */
- if ((!oldurl && !delete_mode) || add_mode) {
- if (add_mode)
- git_config_set_multivar(name_buf.buf, newurl,
- "^$", 0);
- else
- git_config_set(name_buf.buf, newurl);
- strbuf_release(&name_buf);
- return 0;
- }
-
- /* Old URL specified. Demand that one matches. */
- if (regcomp(&old_regex, oldurl, REG_EXTENDED))
- die("Invalid old URL pattern: %s", oldurl);
-
- for (i = 0; i < urlset_nr; i++)
- if (!regexec(&old_regex, urlset[i], 0, NULL, 0))
- matches++;
- else
- negative_matches++;
- if (!delete_mode && !matches)
- die("No such URL found: %s", oldurl);
- if (delete_mode && !negative_matches && !push_mode)
- die("Will not delete all non-push URLs");
-
- regfree(&old_regex);
-
- if (!delete_mode)
- git_config_set_multivar(name_buf.buf, newurl, oldurl, 0);
- else
- git_config_set_multivar(name_buf.buf, NULL, oldurl, 1);
- return 0;
-}
-
-static int get_one_entry(struct remote *remote, void *priv)
-{
- struct string_list *list = priv;
- struct strbuf url_buf = STRBUF_INIT;
- const char **url;
- int i, url_nr;
-
- if (remote->url_nr > 0) {
- strbuf_addf(&url_buf, "%s (fetch)", remote->url[0]);
- string_list_append(remote->name, list)->util =
- strbuf_detach(&url_buf, NULL);
- } else
- string_list_append(remote->name, list)->util = NULL;
- if (remote->pushurl_nr) {
- url = remote->pushurl;
- url_nr = remote->pushurl_nr;
- } else {
- url = remote->url;
- url_nr = remote->url_nr;
- }
- for (i = 0; i < url_nr; i++)
- {
- strbuf_addf(&url_buf, "%s (push)", url[i]);
- string_list_append(remote->name, list)->util =
- strbuf_detach(&url_buf, NULL);
- }
-
- return 0;
-}
-
-static int show_all(void)
-{
- struct string_list list = { NULL, 0, 0 };
- int result;
-
- list.strdup_strings = 1;
- result = for_each_remote(get_one_entry, &list);
-
- if (!result) {
- int i;
-
- sort_string_list(&list);
- for (i = 0; i < list.nr; i++) {
- struct string_list_item *item = list.items + i;
- if (verbose)
- printf("%s\t%s\n", item->string,
- item->util ? (const char *)item->util : "");
- else {
- if (i && !strcmp((item - 1)->string, item->string))
- continue;
- printf("%s\n", item->string);
- }
- }
- }
- string_list_clear(&list, 1);
- return result;
-}
-
-int cmd_remote(int argc, const char **argv, const char *prefix)
-{
- struct option options[] = {
- OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"),
- OPT_END()
- };
- int result;
-
- argc = parse_options(argc, argv, prefix, options, builtin_remote_usage,
- PARSE_OPT_STOP_AT_NON_OPTION);
-
- if (argc < 1)
- result = show_all();
- else if (!strcmp(argv[0], "add"))
- result = add(argc, argv);
- else if (!strcmp(argv[0], "rename"))
- result = mv(argc, argv);
- else if (!strcmp(argv[0], "rm"))
- result = rm(argc, argv);
- else if (!strcmp(argv[0], "set-head"))
- result = set_head(argc, argv);
- else if (!strcmp(argv[0], "set-url"))
- result = set_url(argc, argv);
- else if (!strcmp(argv[0], "show"))
- result = show(argc, argv);
- else if (!strcmp(argv[0], "prune"))
- result = prune(argc, argv);
- else if (!strcmp(argv[0], "update"))
- result = update(argc, argv);
- else {
- error("Unknown subcommand: %s", argv[0]);
- usage_with_options(builtin_remote_usage, options);
- }
-
- return result ? 1 : 0;
-}
+++ /dev/null
-/*
- * Builtin "git replace"
- *
- * Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org>
- *
- * Based on builtin-tag.c by Kristian Høgsberg <krh@redhat.com>
- * and Carlos Rica <jasampler@gmail.com> that was itself based on
- * git-tag.sh and mktag.c by Linus Torvalds.
- */
-
-#include "cache.h"
-#include "builtin.h"
-#include "refs.h"
-#include "parse-options.h"
-
-static const char * const git_replace_usage[] = {
- "git replace [-f] <object> <replacement>",
- "git replace -d <object>...",
- "git replace -l [<pattern>]",
- NULL
-};
-
-static int show_reference(const char *refname, const unsigned char *sha1,
- int flag, void *cb_data)
-{
- const char *pattern = cb_data;
-
- if (!fnmatch(pattern, refname, 0))
- printf("%s\n", refname);
-
- return 0;
-}
-
-static int list_replace_refs(const char *pattern)
-{
- if (pattern == NULL)
- pattern = "*";
-
- for_each_replace_ref(show_reference, (void *) pattern);
-
- return 0;
-}
-
-typedef int (*each_replace_name_fn)(const char *name, const char *ref,
- const unsigned char *sha1);
-
-static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
-{
- const char **p;
- char ref[PATH_MAX];
- int had_error = 0;
- unsigned char sha1[20];
-
- for (p = argv; *p; p++) {
- if (snprintf(ref, sizeof(ref), "refs/replace/%s", *p)
- >= sizeof(ref)) {
- error("replace ref name too long: %.*s...", 50, *p);
- had_error = 1;
- continue;
- }
- if (!resolve_ref(ref, sha1, 1, NULL)) {
- error("replace ref '%s' not found.", *p);
- had_error = 1;
- continue;
- }
- if (fn(*p, ref, sha1))
- had_error = 1;
- }
- return had_error;
-}
-
-static int delete_replace_ref(const char *name, const char *ref,
- const unsigned char *sha1)
-{
- if (delete_ref(ref, sha1, 0))
- return 1;
- printf("Deleted replace ref '%s'\n", name);
- return 0;
-}
-
-static int replace_object(const char *object_ref, const char *replace_ref,
- int force)
-{
- unsigned char object[20], prev[20], repl[20];
- char ref[PATH_MAX];
- struct ref_lock *lock;
-
- if (get_sha1(object_ref, object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
- if (get_sha1(replace_ref, repl))
- die("Failed to resolve '%s' as a valid ref.", replace_ref);
-
- if (snprintf(ref, sizeof(ref),
- "refs/replace/%s",
- sha1_to_hex(object)) > sizeof(ref) - 1)
- die("replace ref name too long: %.*s...", 50, ref);
- if (check_ref_format(ref))
- die("'%s' is not a valid ref name.", ref);
-
- if (!resolve_ref(ref, prev, 1, NULL))
- hashclr(prev);
- else if (!force)
- die("replace ref '%s' already exists", ref);
-
- lock = lock_any_ref_for_update(ref, prev, 0);
- if (!lock)
- die("%s: cannot lock the ref", ref);
- if (write_ref_sha1(lock, repl, NULL) < 0)
- die("%s: cannot update the ref", ref);
-
- return 0;
-}
-
-int cmd_replace(int argc, const char **argv, const char *prefix)
-{
- int list = 0, delete = 0, force = 0;
- struct option options[] = {
- OPT_BOOLEAN('l', NULL, &list, "list replace refs"),
- OPT_BOOLEAN('d', NULL, &delete, "delete replace refs"),
- OPT_BOOLEAN('f', NULL, &force, "replace the ref if it exists"),
- OPT_END()
- };
-
- argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
-
- if (list && delete)
- usage_msg_opt("-l and -d cannot be used together",
- git_replace_usage, options);
-
- if (force && (list || delete))
- usage_msg_opt("-f cannot be used with -d or -l",
- git_replace_usage, options);
-
- /* Delete refs */
- if (delete) {
- if (argc < 1)
- usage_msg_opt("-d needs at least one argument",
- git_replace_usage, options);
- return for_each_replace_name(argv, delete_replace_ref);
- }
-
- /* Replace object */
- if (!list && argc) {
- if (argc != 2)
- usage_msg_opt("bad number of arguments",
- git_replace_usage, options);
- return replace_object(argv[0], argv[1], force);
- }
-
- /* List refs, even if "list" is not set */
- if (argc > 1)
- usage_msg_opt("only one pattern can be given with -l",
- git_replace_usage, options);
- if (force)
- usage_msg_opt("-f needs some arguments",
- git_replace_usage, options);
-
- return list_replace_refs(argv[0]);
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "dir.h"
-#include "string-list.h"
-#include "rerere.h"
-#include "xdiff/xdiff.h"
-#include "xdiff-interface.h"
-
-static const char git_rerere_usage[] =
-"git rerere [clear | status | diff | gc]";
-
-/* these values are days */
-static int cutoff_noresolve = 15;
-static int cutoff_resolve = 60;
-
-static time_t rerere_created_at(const char *name)
-{
- struct stat st;
- return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
-}
-
-static void unlink_rr_item(const char *name)
-{
- unlink(rerere_path(name, "thisimage"));
- unlink(rerere_path(name, "preimage"));
- unlink(rerere_path(name, "postimage"));
- rmdir(git_path("rr-cache/%s", name));
-}
-
-static int git_rerere_gc_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "gc.rerereresolved"))
- cutoff_resolve = git_config_int(var, value);
- else if (!strcmp(var, "gc.rerereunresolved"))
- cutoff_noresolve = git_config_int(var, value);
- else
- return git_default_config(var, value, cb);
- return 0;
-}
-
-static void garbage_collect(struct string_list *rr)
-{
- struct string_list to_remove = { NULL, 0, 0, 1 };
- DIR *dir;
- struct dirent *e;
- int i, cutoff;
- time_t now = time(NULL), then;
-
- git_config(git_rerere_gc_config, NULL);
- dir = opendir(git_path("rr-cache"));
- if (!dir)
- die_errno("unable to open rr-cache directory");
- while ((e = readdir(dir))) {
- if (is_dot_or_dotdot(e->d_name))
- continue;
- then = rerere_created_at(e->d_name);
- if (!then)
- continue;
- cutoff = (has_rerere_resolution(e->d_name)
- ? cutoff_resolve : cutoff_noresolve);
- if (then < now - cutoff * 86400)
- string_list_append(e->d_name, &to_remove);
- }
- for (i = 0; i < to_remove.nr; i++)
- unlink_rr_item(to_remove.items[i].string);
- string_list_clear(&to_remove, 0);
-}
-
-static int outf(void *dummy, mmbuffer_t *ptr, int nbuf)
-{
- int i;
- for (i = 0; i < nbuf; i++)
- if (write_in_full(1, ptr[i].ptr, ptr[i].size) != ptr[i].size)
- return -1;
- return 0;
-}
-
-static int diff_two(const char *file1, const char *label1,
- const char *file2, const char *label2)
-{
- xpparam_t xpp;
- xdemitconf_t xecfg;
- xdemitcb_t ecb;
- mmfile_t minus, plus;
-
- if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
- return 1;
-
- printf("--- a/%s\n+++ b/%s\n", label1, label2);
- fflush(stdout);
- memset(&xpp, 0, sizeof(xpp));
- xpp.flags = XDF_NEED_MINIMAL;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = 3;
- ecb.outf = outf;
- xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
-
- free(minus.ptr);
- free(plus.ptr);
- return 0;
-}
-
-int cmd_rerere(int argc, const char **argv, const char *prefix)
-{
- struct string_list merge_rr = { NULL, 0, 0, 1 };
- int i, fd, flags = 0;
-
- if (2 < argc) {
- if (!strcmp(argv[1], "-h"))
- usage(git_rerere_usage);
- if (!strcmp(argv[1], "--rerere-autoupdate"))
- flags = RERERE_AUTOUPDATE;
- else if (!strcmp(argv[1], "--no-rerere-autoupdate"))
- flags = RERERE_NOAUTOUPDATE;
- if (flags) {
- argc--;
- argv++;
- }
- }
- if (argc < 2)
- return rerere(flags);
-
- if (!strcmp(argv[1], "forget")) {
- const char **pathspec = get_pathspec(prefix, argv + 2);
- return rerere_forget(pathspec);
- }
-
- fd = setup_rerere(&merge_rr, flags);
- if (fd < 0)
- return 0;
-
- if (!strcmp(argv[1], "clear")) {
- for (i = 0; i < merge_rr.nr; i++) {
- const char *name = (const char *)merge_rr.items[i].util;
- if (!has_rerere_resolution(name))
- unlink_rr_item(name);
- }
- unlink_or_warn(git_path("rr-cache/MERGE_RR"));
- } else if (!strcmp(argv[1], "gc"))
- garbage_collect(&merge_rr);
- else if (!strcmp(argv[1], "status"))
- for (i = 0; i < merge_rr.nr; i++)
- printf("%s\n", merge_rr.items[i].string);
- else if (!strcmp(argv[1], "diff"))
- for (i = 0; i < merge_rr.nr; i++) {
- const char *path = merge_rr.items[i].string;
- const char *name = (const char *)merge_rr.items[i].util;
- diff_two(rerere_path(name, "preimage"), path, path, path);
- }
- else
- usage(git_rerere_usage);
-
- string_list_clear(&merge_rr, 1);
- return 0;
-}
+++ /dev/null
-/*
- * "git reset" builtin command
- *
- * Copyright (c) 2007 Carlos Rica
- *
- * Based on git-reset.sh, which is
- *
- * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
- */
-#include "cache.h"
-#include "tag.h"
-#include "object.h"
-#include "commit.h"
-#include "run-command.h"
-#include "refs.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "tree.h"
-#include "branch.h"
-#include "parse-options.h"
-#include "unpack-trees.h"
-#include "cache-tree.h"
-
-static const char * const git_reset_usage[] = {
- "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
- "git reset [--mixed] <commit> [--] <paths>...",
- NULL
-};
-
-enum reset_type { MIXED, SOFT, HARD, MERGE, NONE };
-static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL };
-
-static char *args_to_str(const char **argv)
-{
- char *buf = NULL;
- unsigned long len, space = 0, nr = 0;
-
- for (; *argv; argv++) {
- len = strlen(*argv);
- ALLOC_GROW(buf, nr + 1 + len, space);
- if (nr)
- buf[nr++] = ' ';
- memcpy(buf + nr, *argv, len);
- nr += len;
- }
- ALLOC_GROW(buf, nr + 1, space);
- buf[nr] = '\0';
-
- return buf;
-}
-
-static inline int is_merge(void)
-{
- return !access(git_path("MERGE_HEAD"), F_OK);
-}
-
-static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
-{
- int nr = 1;
- int newfd;
- struct tree_desc desc[2];
- struct unpack_trees_options opts;
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-
- memset(&opts, 0, sizeof(opts));
- opts.head_idx = 1;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
- opts.fn = oneway_merge;
- opts.merge = 1;
- if (!quiet)
- opts.verbose_update = 1;
- switch (reset_type) {
- case MERGE:
- opts.update = 1;
- break;
- case HARD:
- opts.update = 1;
- /* fallthrough */
- default:
- opts.reset = 1;
- }
-
- newfd = hold_locked_index(lock, 1);
-
- read_cache_unmerged();
-
- if (!fill_tree_descriptor(desc + nr - 1, sha1))
- return error("Failed to find tree of %s.", sha1_to_hex(sha1));
- if (unpack_trees(nr, desc, &opts))
- return -1;
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(lock))
- return error("Could not write new index file.");
-
- return 0;
-}
-
-static void print_new_head_line(struct commit *commit)
-{
- const char *hex, *body;
-
- hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
- printf("HEAD is now at %s", hex);
- body = strstr(commit->buffer, "\n\n");
- if (body) {
- const char *eol;
- size_t len;
- body += 2;
- eol = strchr(body, '\n');
- len = eol ? eol - body : strlen(body);
- printf(" %.*s\n", (int) len, body);
- }
- else
- printf("\n");
-}
-
-static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
-{
- int result;
-
- if (!index_lock) {
- index_lock = xcalloc(1, sizeof(struct lock_file));
- fd = hold_locked_index(index_lock, 1);
- }
-
- if (read_cache() < 0)
- return error("Could not read index");
-
- result = refresh_index(&the_index, (flags), NULL, NULL,
- "Unstaged changes after reset:") ? 1 : 0;
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(index_lock))
- return error ("Could not refresh index");
- return result;
-}
-
-static void update_index_from_diff(struct diff_queue_struct *q,
- struct diff_options *opt, void *data)
-{
- int i;
- int *discard_flag = data;
-
- /* do_diff_cache() mangled the index */
- discard_cache();
- *discard_flag = 1;
- read_cache();
-
- for (i = 0; i < q->nr; i++) {
- struct diff_filespec *one = q->queue[i]->one;
- if (one->mode) {
- struct cache_entry *ce;
- ce = make_cache_entry(one->mode, one->sha1, one->path,
- 0, 0);
- if (!ce)
- die("make_cache_entry failed for path '%s'",
- one->path);
- add_cache_entry(ce, ADD_CACHE_OK_TO_ADD |
- ADD_CACHE_OK_TO_REPLACE);
- } else
- remove_file_from_cache(one->path);
- }
-}
-
-static int interactive_reset(const char *revision, const char **argv,
- const char *prefix)
-{
- const char **pathspec = NULL;
-
- if (*argv)
- pathspec = get_pathspec(prefix, argv);
-
- return run_add_interactive(revision, "--patch=reset", pathspec);
-}
-
-static int read_from_tree(const char *prefix, const char **argv,
- unsigned char *tree_sha1, int refresh_flags)
-{
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
- int index_fd, index_was_discarded = 0;
- struct diff_options opt;
-
- memset(&opt, 0, sizeof(opt));
- diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
- opt.output_format = DIFF_FORMAT_CALLBACK;
- opt.format_callback = update_index_from_diff;
- opt.format_callback_data = &index_was_discarded;
-
- index_fd = hold_locked_index(lock, 1);
- index_was_discarded = 0;
- read_cache();
- if (do_diff_cache(tree_sha1, &opt))
- return 1;
- diffcore_std(&opt);
- diff_flush(&opt);
- diff_tree_release_paths(&opt);
-
- if (!index_was_discarded)
- /* The index is still clobbered from do_diff_cache() */
- discard_cache();
- return update_index_refresh(index_fd, lock, refresh_flags);
-}
-
-static void prepend_reflog_action(const char *action, char *buf, size_t size)
-{
- const char *sep = ": ";
- const char *rla = getenv("GIT_REFLOG_ACTION");
- if (!rla)
- rla = sep = "";
- if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size)
- warning("Reflog action message too long: %.*s...", 50, buf);
-}
-
-int cmd_reset(int argc, const char **argv, const char *prefix)
-{
- int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
- int patch_mode = 0;
- const char *rev = "HEAD";
- unsigned char sha1[20], *orig = NULL, sha1_orig[20],
- *old_orig = NULL, sha1_old_orig[20];
- struct commit *commit;
- char *reflog_action, msg[1024];
- const struct option options[] = {
- OPT__QUIET(&quiet),
- OPT_SET_INT(0, "mixed", &reset_type,
- "reset HEAD and index", MIXED),
- OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
- OPT_SET_INT(0, "hard", &reset_type,
- "reset HEAD, index and working tree", HARD),
- OPT_SET_INT(0, "merge", &reset_type,
- "reset HEAD, index and working tree", MERGE),
- OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
- OPT_END()
- };
-
- git_config(git_default_config, NULL);
-
- argc = parse_options(argc, argv, prefix, options, git_reset_usage,
- PARSE_OPT_KEEP_DASHDASH);
- reflog_action = args_to_str(argv);
- setenv("GIT_REFLOG_ACTION", reflog_action, 0);
-
- /*
- * Possible arguments are:
- *
- * git reset [-opts] <rev> <paths>...
- * git reset [-opts] <rev> -- <paths>...
- * git reset [-opts] -- <paths>...
- * git reset [-opts] <paths>...
- *
- * At this point, argv[i] points immediately after [-opts].
- */
-
- if (i < argc) {
- if (!strcmp(argv[i], "--")) {
- i++; /* reset to HEAD, possibly with paths */
- } else if (i + 1 < argc && !strcmp(argv[i+1], "--")) {
- rev = argv[i];
- i += 2;
- }
- /*
- * Otherwise, argv[i] could be either <rev> or <paths> and
- * has to be unambiguous.
- */
- else if (!get_sha1(argv[i], sha1)) {
- /*
- * Ok, argv[i] looks like a rev; it should not
- * be a filename.
- */
- verify_non_filename(prefix, argv[i]);
- rev = argv[i++];
- } else {
- /* Otherwise we treat this as a filename */
- verify_filename(prefix, argv[i]);
- }
- }
-
- if (get_sha1(rev, sha1))
- die("Failed to resolve '%s' as a valid ref.", rev);
-
- commit = lookup_commit_reference(sha1);
- if (!commit)
- die("Could not parse object '%s'.", rev);
- hashcpy(sha1, commit->object.sha1);
-
- if (patch_mode) {
- if (reset_type != NONE)
- die("--patch is incompatible with --{hard,mixed,soft}");
- return interactive_reset(rev, argv + i, prefix);
- }
-
- /* git reset tree [--] paths... can be used to
- * load chosen paths from the tree into the index without
- * affecting the working tree nor HEAD. */
- if (i < argc) {
- if (reset_type == MIXED)
- warning("--mixed option is deprecated with paths.");
- else if (reset_type != NONE)
- die("Cannot do %s reset with paths.",
- reset_type_names[reset_type]);
- return read_from_tree(prefix, argv + i, sha1,
- quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
- }
- if (reset_type == NONE)
- reset_type = MIXED; /* by default */
-
- if (reset_type == HARD || reset_type == MERGE)
- setup_work_tree();
-
- if (reset_type == MIXED && is_bare_repository())
- die("%s reset is not allowed in a bare repository",
- reset_type_names[reset_type]);
-
- /* Soft reset does not touch the index file nor the working tree
- * at all, but requires them in a good order. Other resets reset
- * the index file to the tree object we are switching to. */
- if (reset_type == SOFT) {
- if (is_merge() || read_cache() < 0 || unmerged_cache())
- die("Cannot do a soft reset in the middle of a merge.");
- }
- else if (reset_index_file(sha1, reset_type, quiet))
- die("Could not reset index file to revision '%s'.", rev);
-
- /* Any resets update HEAD to the head being switched to,
- * saving the previous head in ORIG_HEAD before. */
- if (!get_sha1("ORIG_HEAD", sha1_old_orig))
- old_orig = sha1_old_orig;
- if (!get_sha1("HEAD", sha1_orig)) {
- orig = sha1_orig;
- prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg));
- update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
- }
- else if (old_orig)
- delete_ref("ORIG_HEAD", old_orig, 0);
- prepend_reflog_action("updating HEAD", msg, sizeof(msg));
- update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR);
-
- switch (reset_type) {
- case HARD:
- if (!update_ref_status && !quiet)
- print_new_head_line(commit);
- break;
- case SOFT: /* Nothing else to do. */
- break;
- case MIXED: /* Report what has not been updated. */
- update_index_refresh(0, NULL,
- quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
- break;
- }
-
- remove_branch_state();
-
- free(reflog_action);
-
- return update_ref_status;
-}
+++ /dev/null
-#include "cache.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "list-objects.h"
-#include "builtin.h"
-#include "log-tree.h"
-#include "graph.h"
-#include "bisect.h"
-
-static const char rev_list_usage[] =
-"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
-" limiting output:\n"
-" --max-count=nr\n"
-" --max-age=epoch\n"
-" --min-age=epoch\n"
-" --sparse\n"
-" --no-merges\n"
-" --remove-empty\n"
-" --all\n"
-" --branches\n"
-" --tags\n"
-" --remotes\n"
-" --stdin\n"
-" --quiet\n"
-" ordering output:\n"
-" --topo-order\n"
-" --date-order\n"
-" --reverse\n"
-" formatting output:\n"
-" --parents\n"
-" --children\n"
-" --objects | --objects-edge\n"
-" --unpacked\n"
-" --header | --pretty\n"
-" --abbrev=nr | --no-abbrev\n"
-" --abbrev-commit\n"
-" --left-right\n"
-" special purpose:\n"
-" --bisect\n"
-" --bisect-vars\n"
-" --bisect-all"
-;
-
-static void finish_commit(struct commit *commit, void *data);
-static void show_commit(struct commit *commit, void *data)
-{
- struct rev_list_info *info = data;
- struct rev_info *revs = info->revs;
-
- graph_show_commit(revs->graph);
-
- if (info->show_timestamp)
- printf("%lu ", commit->date);
- if (info->header_prefix)
- fputs(info->header_prefix, stdout);
-
- if (!revs->graph) {
- if (commit->object.flags & BOUNDARY)
- putchar('-');
- else if (commit->object.flags & UNINTERESTING)
- putchar('^');
- else if (revs->left_right) {
- if (commit->object.flags & SYMMETRIC_LEFT)
- putchar('<');
- else
- putchar('>');
- }
- }
- if (revs->abbrev_commit && revs->abbrev)
- fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev),
- stdout);
- else
- fputs(sha1_to_hex(commit->object.sha1), stdout);
- if (revs->print_parents) {
- struct commit_list *parents = commit->parents;
- while (parents) {
- printf(" %s", sha1_to_hex(parents->item->object.sha1));
- parents = parents->next;
- }
- }
- if (revs->children.name) {
- struct commit_list *children;
-
- children = lookup_decoration(&revs->children, &commit->object);
- while (children) {
- printf(" %s", sha1_to_hex(children->item->object.sha1));
- children = children->next;
- }
- }
- show_decorations(revs, commit);
- if (revs->commit_format == CMIT_FMT_ONELINE)
- putchar(' ');
- else
- putchar('\n');
-
- if (revs->verbose_header && commit->buffer) {
- struct strbuf buf = STRBUF_INIT;
- struct pretty_print_context ctx = {0};
- ctx.abbrev = revs->abbrev;
- ctx.date_mode = revs->date_mode;
- pretty_print_commit(revs->commit_format, commit, &buf, &ctx);
- if (revs->graph) {
- if (buf.len) {
- if (revs->commit_format != CMIT_FMT_ONELINE)
- graph_show_oneline(revs->graph);
-
- graph_show_commit_msg(revs->graph, &buf);
-
- /*
- * Add a newline after the commit message.
- *
- * Usually, this newline produces a blank
- * padding line between entries, in which case
- * we need to add graph padding on this line.
- *
- * However, the commit message may not end in a
- * newline. In this case the newline simply
- * ends the last line of the commit message,
- * and we don't need any graph output. (This
- * always happens with CMIT_FMT_ONELINE, and it
- * happens with CMIT_FMT_USERFORMAT when the
- * format doesn't explicitly end in a newline.)
- */
- if (buf.len && buf.buf[buf.len - 1] == '\n')
- graph_show_padding(revs->graph);
- putchar('\n');
- } else {
- /*
- * If the message buffer is empty, just show
- * the rest of the graph output for this
- * commit.
- */
- if (graph_show_remainder(revs->graph))
- putchar('\n');
- }
- } else {
- if (buf.len)
- printf("%s%c", buf.buf, info->hdr_termination);
- }
- strbuf_release(&buf);
- } else {
- if (graph_show_remainder(revs->graph))
- putchar('\n');
- }
- maybe_flush_or_die(stdout, "stdout");
- finish_commit(commit, data);
-}
-
-static void finish_commit(struct commit *commit, void *data)
-{
- if (commit->parents) {
- free_commit_list(commit->parents);
- commit->parents = NULL;
- }
- free(commit->buffer);
- commit->buffer = NULL;
-}
-
-static void finish_object(struct object *obj, const struct name_path *path, const char *name)
-{
- if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
- die("missing blob object '%s'", sha1_to_hex(obj->sha1));
-}
-
-static void show_object(struct object *obj, const struct name_path *path, const char *component)
-{
- char *name = path_name(path, component);
- /* An object with name "foo\n0000000..." can be used to
- * confuse downstream "git pack-objects" very badly.
- */
- const char *ep = strchr(name, '\n');
-
- finish_object(obj, path, name);
- if (ep) {
- printf("%s %.*s\n", sha1_to_hex(obj->sha1),
- (int) (ep - name),
- name);
- }
- else
- printf("%s %s\n", sha1_to_hex(obj->sha1), name);
- free(name);
-}
-
-static void show_edge(struct commit *commit)
-{
- printf("-%s\n", sha1_to_hex(commit->object.sha1));
-}
-
-static inline int log2i(int n)
-{
- int log2 = 0;
-
- for (; n > 1; n >>= 1)
- log2++;
-
- return log2;
-}
-
-static inline int exp2i(int n)
-{
- return 1 << n;
-}
-
-/*
- * Estimate the number of bisect steps left (after the current step)
- *
- * For any x between 0 included and 2^n excluded, the probability for
- * n - 1 steps left looks like:
- *
- * P(2^n + x) == (2^n - x) / (2^n + x)
- *
- * and P(2^n + x) < 0.5 means 2^n < 3x
- */
-int estimate_bisect_steps(int all)
-{
- int n, x, e;
-
- if (all < 3)
- return 0;
-
- n = log2i(all);
- e = exp2i(n);
- x = all - e;
-
- return (e < 3 * x) ? n : n - 1;
-}
-
-void print_commit_list(struct commit_list *list,
- const char *format_cur,
- const char *format_last)
-{
- for ( ; list; list = list->next) {
- const char *format = list->next ? format_cur : format_last;
- printf(format, sha1_to_hex(list->item->object.sha1));
- }
-}
-
-static void show_tried_revs(struct commit_list *tried)
-{
- printf("bisect_tried='");
- print_commit_list(tried, "%s|", "%s");
- printf("'\n");
-}
-
-static void print_var_str(const char *var, const char *val)
-{
- printf("%s='%s'\n", var, val);
-}
-
-static void print_var_int(const char *var, int val)
-{
- printf("%s=%d\n", var, val);
-}
-
-static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
-{
- int cnt, flags = info->bisect_show_flags;
- char hex[41] = "";
- struct commit_list *tried;
- struct rev_info *revs = info->revs;
-
- if (!revs->commits && !(flags & BISECT_SHOW_TRIED))
- return 1;
-
- revs->commits = filter_skipped(revs->commits, &tried,
- flags & BISECT_SHOW_ALL,
- NULL, NULL);
-
- /*
- * revs->commits can reach "reaches" commits among
- * "all" commits. If it is good, then there are
- * (all-reaches) commits left to be bisected.
- * On the other hand, if it is bad, then the set
- * to bisect is "reaches".
- * A bisect set of size N has (N-1) commits further
- * to test, as we already know one bad one.
- */
- cnt = all - reaches;
- if (cnt < reaches)
- cnt = reaches;
-
- if (revs->commits)
- strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1));
-
- if (flags & BISECT_SHOW_ALL) {
- traverse_commit_list(revs, show_commit, show_object, info);
- printf("------\n");
- }
-
- if (flags & BISECT_SHOW_TRIED)
- show_tried_revs(tried);
-
- print_var_str("bisect_rev", hex);
- print_var_int("bisect_nr", cnt - 1);
- print_var_int("bisect_good", all - reaches - 1);
- print_var_int("bisect_bad", reaches - 1);
- print_var_int("bisect_all", all);
- print_var_int("bisect_steps", estimate_bisect_steps(all));
-
- return 0;
-}
-
-int cmd_rev_list(int argc, const char **argv, const char *prefix)
-{
- struct rev_info revs;
- struct rev_list_info info;
- int i;
- int bisect_list = 0;
- int bisect_show_vars = 0;
- int bisect_find_all = 0;
- int quiet = 0;
-
- git_config(git_default_config, NULL);
- init_revisions(&revs, prefix);
- revs.abbrev = 0;
- revs.commit_format = CMIT_FMT_UNSPECIFIED;
- argc = setup_revisions(argc, argv, &revs, NULL);
-
- memset(&info, 0, sizeof(info));
- info.revs = &revs;
- if (revs.bisect)
- bisect_list = 1;
-
- quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
- for (i = 1 ; i < argc; i++) {
- const char *arg = argv[i];
-
- if (!strcmp(arg, "--header")) {
- revs.verbose_header = 1;
- continue;
- }
- if (!strcmp(arg, "--timestamp")) {
- info.show_timestamp = 1;
- continue;
- }
- if (!strcmp(arg, "--bisect")) {
- bisect_list = 1;
- continue;
- }
- if (!strcmp(arg, "--bisect-all")) {
- bisect_list = 1;
- bisect_find_all = 1;
- info.bisect_show_flags = BISECT_SHOW_ALL;
- revs.show_decorations = 1;
- continue;
- }
- if (!strcmp(arg, "--bisect-vars")) {
- bisect_list = 1;
- bisect_show_vars = 1;
- continue;
- }
- usage(rev_list_usage);
-
- }
- if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
- /* The command line has a --pretty */
- info.hdr_termination = '\n';
- if (revs.commit_format == CMIT_FMT_ONELINE)
- info.header_prefix = "";
- else
- info.header_prefix = "commit ";
- }
- else if (revs.verbose_header)
- /* Only --header was specified */
- revs.commit_format = CMIT_FMT_RAW;
-
- if ((!revs.commits &&
- (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
- !revs.pending.nr)) ||
- revs.diff)
- usage(rev_list_usage);
-
- save_commit_buffer = (revs.verbose_header ||
- revs.grep_filter.pattern_list ||
- revs.grep_filter.header_list);
- if (bisect_list)
- revs.limited = 1;
-
- if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
- if (revs.tree_objects)
- mark_edges_uninteresting(revs.commits, &revs, show_edge);
-
- if (bisect_list) {
- int reaches = reaches, all = all;
-
- revs.commits = find_bisection(revs.commits, &reaches, &all,
- bisect_find_all);
-
- if (bisect_show_vars)
- return show_bisect_vars(&info, reaches, all);
- }
-
- traverse_commit_list(&revs,
- quiet ? finish_commit : show_commit,
- quiet ? finish_object : show_object,
- &info);
-
- return 0;
-}
+++ /dev/null
-/*
- * rev-parse.c
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "commit.h"
-#include "refs.h"
-#include "quote.h"
-#include "builtin.h"
-#include "parse-options.h"
-
-#define DO_REVS 1
-#define DO_NOREV 2
-#define DO_FLAGS 4
-#define DO_NONFLAGS 8
-static int filter = ~0;
-
-static const char *def;
-
-#define NORMAL 0
-#define REVERSED 1
-static int show_type = NORMAL;
-
-#define SHOW_SYMBOLIC_ASIS 1
-#define SHOW_SYMBOLIC_FULL 2
-static int symbolic;
-static int abbrev;
-static int abbrev_ref;
-static int abbrev_ref_strict;
-static int output_sq;
-
-/*
- * Some arguments are relevant "revision" arguments,
- * others are about output format or other details.
- * This sorts it all out.
- */
-static int is_rev_argument(const char *arg)
-{
- static const char *rev_args[] = {
- "--all",
- "--bisect",
- "--dense",
- "--branches=",
- "--branches",
- "--header",
- "--max-age=",
- "--max-count=",
- "--min-age=",
- "--no-merges",
- "--objects",
- "--objects-edge",
- "--parents",
- "--pretty",
- "--remotes=",
- "--remotes",
- "--glob=",
- "--sparse",
- "--tags=",
- "--tags",
- "--topo-order",
- "--date-order",
- "--unpacked",
- NULL
- };
- const char **p = rev_args;
-
- /* accept -<digit>, like traditional "head" */
- if ((*arg == '-') && isdigit(arg[1]))
- return 1;
-
- for (;;) {
- const char *str = *p++;
- int len;
- if (!str)
- return 0;
- len = strlen(str);
- if (!strcmp(arg, str) ||
- (str[len-1] == '=' && !strncmp(arg, str, len)))
- return 1;
- }
-}
-
-/* Output argument as a string, either SQ or normal */
-static void show(const char *arg)
-{
- if (output_sq) {
- int sq = '\'', ch;
-
- putchar(sq);
- while ((ch = *arg++)) {
- if (ch == sq)
- fputs("'\\'", stdout);
- putchar(ch);
- }
- putchar(sq);
- putchar(' ');
- }
- else
- puts(arg);
-}
-
-/* Like show(), but with a negation prefix according to type */
-static void show_with_type(int type, const char *arg)
-{
- if (type != show_type)
- putchar('^');
- show(arg);
-}
-
-/* Output a revision, only if filter allows it */
-static void show_rev(int type, const unsigned char *sha1, const char *name)
-{
- if (!(filter & DO_REVS))
- return;
- def = NULL;
-
- if ((symbolic || abbrev_ref) && name) {
- if (symbolic == SHOW_SYMBOLIC_FULL || abbrev_ref) {
- unsigned char discard[20];
- char *full;
-
- switch (dwim_ref(name, strlen(name), discard, &full)) {
- case 0:
- /*
- * Not found -- not a ref. We could
- * emit "name" here, but symbolic-full
- * users are interested in finding the
- * refs spelled in full, and they would
- * need to filter non-refs if we did so.
- */
- break;
- case 1: /* happy */
- if (abbrev_ref)
- full = shorten_unambiguous_ref(full,
- abbrev_ref_strict);
- show_with_type(type, full);
- break;
- default: /* ambiguous */
- error("refname '%s' is ambiguous", name);
- break;
- }
- } else {
- show_with_type(type, name);
- }
- }
- else if (abbrev)
- show_with_type(type, find_unique_abbrev(sha1, abbrev));
- else
- show_with_type(type, sha1_to_hex(sha1));
-}
-
-/* Output a flag, only if filter allows it. */
-static int show_flag(const char *arg)
-{
- if (!(filter & DO_FLAGS))
- return 0;
- if (filter & (is_rev_argument(arg) ? DO_REVS : DO_NOREV)) {
- show(arg);
- return 1;
- }
- return 0;
-}
-
-static int show_default(void)
-{
- const char *s = def;
-
- if (s) {
- unsigned char sha1[20];
-
- def = NULL;
- if (!get_sha1(s, sha1)) {
- show_rev(NORMAL, sha1, s);
- return 1;
- }
- }
- return 0;
-}
-
-static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- show_rev(NORMAL, sha1, refname);
- return 0;
-}
-
-static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- show_rev(REVERSED, sha1, refname);
- return 0;
-}
-
-static void show_datestring(const char *flag, const char *datestr)
-{
- static char buffer[100];
-
- /* date handling requires both flags and revs */
- if ((filter & (DO_FLAGS | DO_REVS)) != (DO_FLAGS | DO_REVS))
- return;
- snprintf(buffer, sizeof(buffer), "%s%lu", flag, approxidate(datestr));
- show(buffer);
-}
-
-static int show_file(const char *arg)
-{
- show_default();
- if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) {
- show(arg);
- return 1;
- }
- return 0;
-}
-
-static int try_difference(const char *arg)
-{
- char *dotdot;
- unsigned char sha1[20];
- unsigned char end[20];
- const char *next;
- const char *this;
- int symmetric;
-
- if (!(dotdot = strstr(arg, "..")))
- return 0;
- next = dotdot + 2;
- this = arg;
- symmetric = (*next == '.');
-
- *dotdot = 0;
- next += symmetric;
-
- if (!*next)
- next = "HEAD";
- if (dotdot == arg)
- this = "HEAD";
- if (!get_sha1(this, sha1) && !get_sha1(next, end)) {
- show_rev(NORMAL, end, next);
- show_rev(symmetric ? NORMAL : REVERSED, sha1, this);
- if (symmetric) {
- struct commit_list *exclude;
- struct commit *a, *b;
- a = lookup_commit_reference(sha1);
- b = lookup_commit_reference(end);
- exclude = get_merge_bases(a, b, 1);
- while (exclude) {
- struct commit_list *n = exclude->next;
- show_rev(REVERSED,
- exclude->item->object.sha1,NULL);
- free(exclude);
- exclude = n;
- }
- }
- return 1;
- }
- *dotdot = '.';
- return 0;
-}
-
-static int try_parent_shorthands(const char *arg)
-{
- char *dotdot;
- unsigned char sha1[20];
- struct commit *commit;
- struct commit_list *parents;
- int parents_only;
-
- if ((dotdot = strstr(arg, "^!")))
- parents_only = 0;
- else if ((dotdot = strstr(arg, "^@")))
- parents_only = 1;
-
- if (!dotdot || dotdot[2])
- return 0;
-
- *dotdot = 0;
- if (get_sha1(arg, sha1))
- return 0;
-
- if (!parents_only)
- show_rev(NORMAL, sha1, arg);
- commit = lookup_commit_reference(sha1);
- for (parents = commit->parents; parents; parents = parents->next)
- show_rev(parents_only ? NORMAL : REVERSED,
- parents->item->object.sha1, arg);
-
- return 1;
-}
-
-static int parseopt_dump(const struct option *o, const char *arg, int unset)
-{
- struct strbuf *parsed = o->value;
- if (unset)
- strbuf_addf(parsed, " --no-%s", o->long_name);
- else if (o->short_name)
- strbuf_addf(parsed, " -%c", o->short_name);
- else
- strbuf_addf(parsed, " --%s", o->long_name);
- if (arg) {
- strbuf_addch(parsed, ' ');
- sq_quote_buf(parsed, arg);
- }
- return 0;
-}
-
-static const char *skipspaces(const char *s)
-{
- while (isspace(*s))
- s++;
- return s;
-}
-
-static int cmd_parseopt(int argc, const char **argv, const char *prefix)
-{
- static int keep_dashdash = 0, stop_at_non_option = 0;
- static char const * const parseopt_usage[] = {
- "git rev-parse --parseopt [options] -- [<args>...]",
- NULL
- };
- static struct option parseopt_opts[] = {
- OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash,
- "keep the `--` passed as an arg"),
- OPT_BOOLEAN(0, "stop-at-non-option", &stop_at_non_option,
- "stop parsing after the "
- "first non-option argument"),
- OPT_END(),
- };
-
- struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
- const char **usage = NULL;
- struct option *opts = NULL;
- int onb = 0, osz = 0, unb = 0, usz = 0;
-
- strbuf_addstr(&parsed, "set --");
- argc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage,
- PARSE_OPT_KEEP_DASHDASH);
- if (argc < 1 || strcmp(argv[0], "--"))
- usage_with_options(parseopt_usage, parseopt_opts);
-
- /* get the usage up to the first line with a -- on it */
- for (;;) {
- if (strbuf_getline(&sb, stdin, '\n') == EOF)
- die("premature end of input");
- ALLOC_GROW(usage, unb + 1, usz);
- if (!strcmp("--", sb.buf)) {
- if (unb < 1)
- die("no usage string given before the `--' separator");
- usage[unb] = NULL;
- break;
- }
- usage[unb++] = strbuf_detach(&sb, NULL);
- }
-
- /* parse: (<short>|<short>,<long>|<long>)[=?]? SP+ <help> */
- while (strbuf_getline(&sb, stdin, '\n') != EOF) {
- const char *s;
- struct option *o;
-
- if (!sb.len)
- continue;
-
- ALLOC_GROW(opts, onb + 1, osz);
- memset(opts + onb, 0, sizeof(opts[onb]));
-
- o = &opts[onb++];
- s = strchr(sb.buf, ' ');
- if (!s || *sb.buf == ' ') {
- o->type = OPTION_GROUP;
- o->help = xstrdup(skipspaces(sb.buf));
- continue;
- }
-
- o->type = OPTION_CALLBACK;
- o->help = xstrdup(skipspaces(s));
- o->value = &parsed;
- o->flags = PARSE_OPT_NOARG;
- o->callback = &parseopt_dump;
- while (s > sb.buf && strchr("*=?!", s[-1])) {
- switch (*--s) {
- case '=':
- o->flags &= ~PARSE_OPT_NOARG;
- break;
- case '?':
- o->flags &= ~PARSE_OPT_NOARG;
- o->flags |= PARSE_OPT_OPTARG;
- break;
- case '!':
- o->flags |= PARSE_OPT_NONEG;
- break;
- case '*':
- o->flags |= PARSE_OPT_HIDDEN;
- break;
- }
- }
-
- if (s - sb.buf == 1) /* short option only */
- o->short_name = *sb.buf;
- else if (sb.buf[1] != ',') /* long option only */
- o->long_name = xmemdupz(sb.buf, s - sb.buf);
- else {
- o->short_name = *sb.buf;
- o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
- }
- }
- strbuf_release(&sb);
-
- /* put an OPT_END() */
- ALLOC_GROW(opts, onb + 1, osz);
- memset(opts + onb, 0, sizeof(opts[onb]));
- argc = parse_options(argc, argv, prefix, opts, usage,
- keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0 |
- stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0);
-
- strbuf_addf(&parsed, " --");
- sq_quote_argv(&parsed, argv, 0);
- puts(parsed.buf);
- return 0;
-}
-
-static int cmd_sq_quote(int argc, const char **argv)
-{
- struct strbuf buf = STRBUF_INIT;
-
- if (argc)
- sq_quote_argv(&buf, argv, 0);
- printf("%s\n", buf.buf);
- strbuf_release(&buf);
-
- return 0;
-}
-
-static void die_no_single_rev(int quiet)
-{
- if (quiet)
- exit(1);
- else
- die("Needed a single revision");
-}
-
-static const char builtin_rev_parse_usage[] =
-"git rev-parse --parseopt [options] -- [<args>...]\n"
-" or: git rev-parse --sq-quote [<arg>...]\n"
-" or: git rev-parse [options] [<arg>...]\n"
-"\n"
-"Run \"git rev-parse --parseopt -h\" for more information on the first usage.";
-
-int cmd_rev_parse(int argc, const char **argv, const char *prefix)
-{
- int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
- unsigned char sha1[20];
- const char *name = NULL;
-
- if (argc > 1 && !strcmp("--parseopt", argv[1]))
- return cmd_parseopt(argc - 1, argv + 1, prefix);
-
- if (argc > 1 && !strcmp("--sq-quote", argv[1]))
- return cmd_sq_quote(argc - 2, argv + 2);
-
- if (argc == 2 && !strcmp("--local-env-vars", argv[1])) {
- int i;
- for (i = 0; local_repo_env[i]; i++)
- printf("%s\n", local_repo_env[i]);
- return 0;
- }
-
- if (argc > 1 && !strcmp("-h", argv[1]))
- usage(builtin_rev_parse_usage);
-
- prefix = setup_git_directory();
- git_config(git_default_config, NULL);
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (as_is) {
- if (show_file(arg) && as_is < 2)
- verify_filename(prefix, arg);
- continue;
- }
- if (!strcmp(arg,"-n")) {
- if (++i >= argc)
- die("-n requires an argument");
- if ((filter & DO_FLAGS) && (filter & DO_REVS)) {
- show(arg);
- show(argv[i]);
- }
- continue;
- }
- if (!prefixcmp(arg, "-n")) {
- if ((filter & DO_FLAGS) && (filter & DO_REVS))
- show(arg);
- continue;
- }
-
- if (*arg == '-') {
- if (!strcmp(arg, "--")) {
- as_is = 2;
- /* Pass on the "--" if we show anything but files.. */
- if (filter & (DO_FLAGS | DO_REVS))
- show_file(arg);
- continue;
- }
- if (!strcmp(arg, "--default")) {
- def = argv[i+1];
- i++;
- continue;
- }
- if (!strcmp(arg, "--revs-only")) {
- filter &= ~DO_NOREV;
- continue;
- }
- if (!strcmp(arg, "--no-revs")) {
- filter &= ~DO_REVS;
- continue;
- }
- if (!strcmp(arg, "--flags")) {
- filter &= ~DO_NONFLAGS;
- continue;
- }
- if (!strcmp(arg, "--no-flags")) {
- filter &= ~DO_FLAGS;
- continue;
- }
- if (!strcmp(arg, "--verify")) {
- filter &= ~(DO_FLAGS|DO_NOREV);
- verify = 1;
- continue;
- }
- if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) {
- quiet = 1;
- continue;
- }
- if (!strcmp(arg, "--short") ||
- !prefixcmp(arg, "--short=")) {
- filter &= ~(DO_FLAGS|DO_NOREV);
- verify = 1;
- abbrev = DEFAULT_ABBREV;
- if (arg[7] == '=')
- abbrev = strtoul(arg + 8, NULL, 10);
- if (abbrev < MINIMUM_ABBREV)
- abbrev = MINIMUM_ABBREV;
- else if (40 <= abbrev)
- abbrev = 40;
- continue;
- }
- if (!strcmp(arg, "--sq")) {
- output_sq = 1;
- continue;
- }
- if (!strcmp(arg, "--not")) {
- show_type ^= REVERSED;
- continue;
- }
- if (!strcmp(arg, "--symbolic")) {
- symbolic = SHOW_SYMBOLIC_ASIS;
- continue;
- }
- if (!strcmp(arg, "--symbolic-full-name")) {
- symbolic = SHOW_SYMBOLIC_FULL;
- continue;
- }
- if (!prefixcmp(arg, "--abbrev-ref") &&
- (!arg[12] || arg[12] == '=')) {
- abbrev_ref = 1;
- abbrev_ref_strict = warn_ambiguous_refs;
- if (arg[12] == '=') {
- if (!strcmp(arg + 13, "strict"))
- abbrev_ref_strict = 1;
- else if (!strcmp(arg + 13, "loose"))
- abbrev_ref_strict = 0;
- else
- die("unknown mode for %s", arg);
- }
- continue;
- }
- if (!strcmp(arg, "--all")) {
- for_each_ref(show_reference, NULL);
- continue;
- }
- if (!strcmp(arg, "--bisect")) {
- for_each_ref_in("refs/bisect/bad", show_reference, NULL);
- for_each_ref_in("refs/bisect/good", anti_reference, NULL);
- continue;
- }
- if (!prefixcmp(arg, "--branches=")) {
- for_each_glob_ref_in(show_reference, arg + 11,
- "refs/heads/", NULL);
- continue;
- }
- if (!strcmp(arg, "--branches")) {
- for_each_branch_ref(show_reference, NULL);
- continue;
- }
- if (!prefixcmp(arg, "--tags=")) {
- for_each_glob_ref_in(show_reference, arg + 7,
- "refs/tags/", NULL);
- continue;
- }
- if (!strcmp(arg, "--tags")) {
- for_each_tag_ref(show_reference, NULL);
- continue;
- }
- if (!prefixcmp(arg, "--glob=")) {
- for_each_glob_ref(show_reference, arg + 7, NULL);
- continue;
- }
- if (!prefixcmp(arg, "--remotes=")) {
- for_each_glob_ref_in(show_reference, arg + 10,
- "refs/remotes/", NULL);
- continue;
- }
- if (!strcmp(arg, "--remotes")) {
- for_each_remote_ref(show_reference, NULL);
- continue;
- }
- if (!strcmp(arg, "--show-toplevel")) {
- const char *work_tree = get_git_work_tree();
- if (work_tree)
- puts(work_tree);
- continue;
- }
- if (!strcmp(arg, "--show-prefix")) {
- if (prefix)
- puts(prefix);
- continue;
- }
- 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) {
- pfx++;
- printf("../");
- }
- }
- putchar('\n');
- continue;
- }
- if (!strcmp(arg, "--git-dir")) {
- const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
- static char cwd[PATH_MAX];
- if (gitdir) {
- puts(gitdir);
- continue;
- }
- if (!prefix) {
- puts(".git");
- continue;
- }
- if (!getcwd(cwd, PATH_MAX))
- die_errno("unable to get current working directory");
- printf("%s/.git\n", cwd);
- continue;
- }
- if (!strcmp(arg, "--is-inside-git-dir")) {
- printf("%s\n", is_inside_git_dir() ? "true"
- : "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;
- }
- if (!prefixcmp(arg, "--after=")) {
- show_datestring("--max-age=", arg+8);
- continue;
- }
- if (!prefixcmp(arg, "--before=")) {
- show_datestring("--min-age=", arg+9);
- continue;
- }
- if (!prefixcmp(arg, "--until=")) {
- show_datestring("--min-age=", arg+8);
- continue;
- }
- if (show_flag(arg) && verify)
- die_no_single_rev(quiet);
- continue;
- }
-
- /* Not a flag argument */
- if (try_difference(arg))
- continue;
- if (try_parent_shorthands(arg))
- continue;
- name = arg;
- type = NORMAL;
- if (*arg == '^') {
- name++;
- type = REVERSED;
- }
- if (!get_sha1(name, sha1)) {
- if (verify)
- revs_count++;
- else
- show_rev(type, sha1, name);
- continue;
- }
- if (verify)
- die_no_single_rev(quiet);
- as_is = 1;
- if (!show_file(arg))
- continue;
- verify_filename(prefix, arg);
- }
- if (verify) {
- if (revs_count == 1) {
- show_rev(type, sha1, name);
- return 0;
- } else if (revs_count == 0 && show_default())
- return 0;
- die_no_single_rev(quiet);
- } else
- show_default();
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "builtin.h"
-#include "object.h"
-#include "commit.h"
-#include "tag.h"
-#include "wt-status.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-#include "utf8.h"
-#include "parse-options.h"
-#include "cache-tree.h"
-#include "diff.h"
-#include "revision.h"
-#include "rerere.h"
-#include "merge-recursive.h"
-
-/*
- * This implements the builtins revert and cherry-pick.
- *
- * Copyright (c) 2007 Johannes E. Schindelin
- *
- * Based on git-revert.sh, which is
- *
- * Copyright (c) 2005 Linus Torvalds
- * Copyright (c) 2005 Junio C Hamano
- */
-
-static const char * const revert_usage[] = {
- "git revert [options] <commit-ish>",
- NULL
-};
-
-static const char * const cherry_pick_usage[] = {
- "git cherry-pick [options] <commit-ish>",
- NULL
-};
-
-static int edit, no_replay, no_commit, mainline, signoff;
-static enum { REVERT, CHERRY_PICK } action;
-static struct commit *commit;
-static const char *commit_name;
-static int allow_rerere_auto;
-
-static const char *me;
-
-#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
-
-static void parse_args(int argc, const char **argv)
-{
- const char * const * usage_str =
- action == REVERT ? revert_usage : cherry_pick_usage;
- unsigned char sha1[20];
- int noop;
- struct option options[] = {
- OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
- OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
- OPT_BOOLEAN('x', NULL, &no_replay, "append commit name when cherry-picking"),
- OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
- OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
- OPT_INTEGER('m', "mainline", &mainline, "parent number"),
- OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
- OPT_END(),
- };
-
- if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1)
- usage_with_options(usage_str, options);
-
- commit_name = argv[0];
- if (get_sha1(commit_name, sha1))
- die ("Cannot find '%s'", commit_name);
- commit = lookup_commit_reference(sha1);
- if (!commit)
- exit(1);
-}
-
-static char *get_oneline(const char *message)
-{
- char *result;
- const char *p = message, *abbrev, *eol;
- int abbrev_len, oneline_len;
-
- if (!p)
- die ("Could not read commit message of %s",
- sha1_to_hex(commit->object.sha1));
- while (*p && (*p != '\n' || p[1] != '\n'))
- p++;
-
- if (*p) {
- p += 2;
- for (eol = p + 1; *eol && *eol != '\n'; eol++)
- ; /* do nothing */
- } else
- eol = p;
- abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
- abbrev_len = strlen(abbrev);
- oneline_len = eol - p;
- result = xmalloc(abbrev_len + 5 + oneline_len);
- memcpy(result, abbrev, abbrev_len);
- memcpy(result + abbrev_len, "... ", 4);
- memcpy(result + abbrev_len + 4, p, oneline_len);
- result[abbrev_len + 4 + oneline_len] = '\0';
- return result;
-}
-
-static char *get_encoding(const char *message)
-{
- const char *p = message, *eol;
-
- if (!p)
- die ("Could not read commit message of %s",
- sha1_to_hex(commit->object.sha1));
- while (*p && *p != '\n') {
- for (eol = p + 1; *eol && *eol != '\n'; eol++)
- ; /* do nothing */
- if (!prefixcmp(p, "encoding ")) {
- char *result = xmalloc(eol - 8 - p);
- strlcpy(result, p + 9, eol - 8 - p);
- return result;
- }
- p = eol;
- if (*p == '\n')
- p++;
- }
- return NULL;
-}
-
-static struct lock_file msg_file;
-static int msg_fd;
-
-static void add_to_msg(const char *string)
-{
- int len = strlen(string);
- if (write_in_full(msg_fd, string, len) < 0)
- die_errno ("Could not write to MERGE_MSG");
-}
-
-static void add_message_to_msg(const char *message)
-{
- const char *p = message;
- while (*p && (*p != '\n' || p[1] != '\n'))
- p++;
-
- if (!*p)
- add_to_msg(sha1_to_hex(commit->object.sha1));
-
- p += 2;
- add_to_msg(p);
- return;
-}
-
-static void set_author_ident_env(const char *message)
-{
- const char *p = message;
- if (!p)
- die ("Could not read commit message of %s",
- sha1_to_hex(commit->object.sha1));
- while (*p && *p != '\n') {
- const char *eol;
-
- for (eol = p; *eol && *eol != '\n'; eol++)
- ; /* do nothing */
- if (!prefixcmp(p, "author ")) {
- char *line, *pend, *email, *timestamp;
-
- p += 7;
- line = xmemdupz(p, eol - p);
- email = strchr(line, '<');
- if (!email)
- die ("Could not extract author email from %s",
- sha1_to_hex(commit->object.sha1));
- if (email == line)
- pend = line;
- else
- for (pend = email; pend != line + 1 &&
- isspace(pend[-1]); pend--);
- ; /* do nothing */
- *pend = '\0';
- email++;
- timestamp = strchr(email, '>');
- if (!timestamp)
- die ("Could not extract author time from %s",
- sha1_to_hex(commit->object.sha1));
- *timestamp = '\0';
- for (timestamp++; *timestamp && isspace(*timestamp);
- timestamp++)
- ; /* do nothing */
- setenv("GIT_AUTHOR_NAME", line, 1);
- setenv("GIT_AUTHOR_EMAIL", email, 1);
- setenv("GIT_AUTHOR_DATE", timestamp, 1);
- free(line);
- return;
- }
- p = eol;
- if (*p == '\n')
- p++;
- }
- die ("No author information found in %s",
- sha1_to_hex(commit->object.sha1));
-}
-
-static char *help_msg(const char *name)
-{
- struct strbuf helpbuf = STRBUF_INIT;
- char *msg = getenv("GIT_CHERRY_PICK_HELP");
-
- if (msg)
- return msg;
-
- strbuf_addstr(&helpbuf, " After resolving the conflicts,\n"
- "mark the corrected paths with 'git add <paths>' or 'git rm <paths>'\n"
- "and commit the result");
-
- if (action == CHERRY_PICK) {
- strbuf_addf(&helpbuf, " with: \n"
- "\n"
- " git commit -c %s\n",
- name);
- }
- else
- strbuf_addch(&helpbuf, '.');
- return strbuf_detach(&helpbuf, NULL);
-}
-
-static struct tree *empty_tree(void)
-{
- struct tree *tree = xcalloc(1, sizeof(struct tree));
-
- tree->object.parsed = 1;
- tree->object.type = OBJ_TREE;
- pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
- return tree;
-}
-
-static NORETURN void die_dirty_index(const char *me)
-{
- if (read_cache_unmerged()) {
- die_resolve_conflict(me);
- } else {
- if (advice_commit_before_merge)
- die("Your local changes would be overwritten by %s.\n"
- "Please, commit your changes or stash them to proceed.", me);
- else
- die("Your local changes would be overwritten by %s.\n", me);
- }
-}
-
-static int revert_or_cherry_pick(int argc, const char **argv)
-{
- unsigned char head[20];
- struct commit *base, *next, *parent;
- int i, index_fd, clean;
- char *oneline, *reencoded_message = NULL;
- const char *message, *encoding;
- char *defmsg = git_pathdup("MERGE_MSG");
- struct merge_options o;
- struct tree *result, *next_tree, *base_tree, *head_tree;
- static struct lock_file index_lock;
-
- git_config(git_default_config, NULL);
- me = action == REVERT ? "revert" : "cherry-pick";
- setenv(GIT_REFLOG_ACTION, me, 0);
- parse_args(argc, argv);
-
- /* this is copied from the shell script, but it's never triggered... */
- if (action == REVERT && !no_replay)
- die("revert is incompatible with replay");
-
- if (read_cache() < 0)
- die("git %s: failed to read the index", me);
- if (no_commit) {
- /*
- * We do not intend to commit immediately. We just want to
- * merge the differences in, so let's compute the tree
- * that represents the "current" state for merge-recursive
- * to work on.
- */
- if (write_cache_as_tree(head, 0, NULL))
- die ("Your index file is unmerged.");
- } else {
- if (get_sha1("HEAD", head))
- die ("You do not have a valid HEAD");
- if (index_differs_from("HEAD", 0))
- die_dirty_index(me);
- }
- discard_cache();
-
- index_fd = hold_locked_index(&index_lock, 1);
-
- if (!commit->parents) {
- if (action == REVERT)
- die ("Cannot revert a root commit");
- parent = NULL;
- }
- else if (commit->parents->next) {
- /* Reverting or cherry-picking a merge commit */
- int cnt;
- struct commit_list *p;
-
- if (!mainline)
- die("Commit %s is a merge but no -m option was given.",
- sha1_to_hex(commit->object.sha1));
-
- for (cnt = 1, p = commit->parents;
- cnt != mainline && p;
- cnt++)
- p = p->next;
- if (cnt != mainline || !p)
- die("Commit %s does not have parent %d",
- sha1_to_hex(commit->object.sha1), mainline);
- parent = p->item;
- } else if (0 < mainline)
- die("Mainline was specified but commit %s is not a merge.",
- sha1_to_hex(commit->object.sha1));
- else
- parent = commit->parents->item;
-
- if (!(message = commit->buffer))
- die ("Cannot get commit message for %s",
- sha1_to_hex(commit->object.sha1));
-
- if (parent && parse_commit(parent) < 0)
- die("%s: cannot parse parent commit %s",
- me, sha1_to_hex(parent->object.sha1));
-
- /*
- * "commit" is an existing commit. We would want to apply
- * the difference it introduces since its first parent "prev"
- * on top of the current HEAD if we are cherry-pick. Or the
- * reverse of it if we are revert.
- */
-
- msg_fd = hold_lock_file_for_update(&msg_file, defmsg,
- LOCK_DIE_ON_ERROR);
-
- encoding = get_encoding(message);
- if (!encoding)
- encoding = "UTF-8";
- if (!git_commit_encoding)
- git_commit_encoding = "UTF-8";
- if ((reencoded_message = reencode_string(message,
- git_commit_encoding, encoding)))
- message = reencoded_message;
-
- oneline = get_oneline(message);
-
- if (action == REVERT) {
- char *oneline_body = strchr(oneline, ' ');
-
- base = commit;
- next = parent;
- add_to_msg("Revert \"");
- add_to_msg(oneline_body + 1);
- add_to_msg("\"\n\nThis reverts commit ");
- add_to_msg(sha1_to_hex(commit->object.sha1));
-
- if (commit->parents->next) {
- add_to_msg(", reversing\nchanges made to ");
- add_to_msg(sha1_to_hex(parent->object.sha1));
- }
- add_to_msg(".\n");
- } else {
- base = parent;
- next = commit;
- set_author_ident_env(message);
- add_message_to_msg(message);
- if (no_replay) {
- add_to_msg("(cherry picked from commit ");
- add_to_msg(sha1_to_hex(commit->object.sha1));
- add_to_msg(")\n");
- }
- }
-
- read_cache();
- init_merge_options(&o);
- o.branch1 = "HEAD";
- o.branch2 = oneline;
-
- head_tree = parse_tree_indirect(head);
- next_tree = next ? next->tree : empty_tree();
- base_tree = base ? base->tree : empty_tree();
-
- clean = merge_trees(&o,
- head_tree,
- next_tree, base_tree, &result);
-
- if (active_cache_changed &&
- (write_cache(index_fd, active_cache, active_nr) ||
- commit_locked_index(&index_lock)))
- die("%s: Unable to write new index file", me);
- rollback_lock_file(&index_lock);
-
- if (!clean) {
- add_to_msg("\nConflicts:\n\n");
- for (i = 0; i < active_nr;) {
- struct cache_entry *ce = active_cache[i++];
- if (ce_stage(ce)) {
- add_to_msg("\t");
- add_to_msg(ce->name);
- add_to_msg("\n");
- while (i < active_nr && !strcmp(ce->name,
- active_cache[i]->name))
- i++;
- }
- }
- if (commit_lock_file(&msg_file) < 0)
- die ("Error wrapping up %s", defmsg);
- fprintf(stderr, "Automatic %s failed.%s\n",
- me, help_msg(commit_name));
- rerere(allow_rerere_auto);
- exit(1);
- }
- if (commit_lock_file(&msg_file) < 0)
- die ("Error wrapping up %s", defmsg);
- fprintf(stderr, "Finished one %s.\n", me);
-
- /*
- *
- * If we are cherry-pick, and if the merge did not result in
- * hand-editing, we will hit this commit and inherit the original
- * author date and name.
- * If we are revert, or if our cherry-pick results in a hand merge,
- * we had better say that the current user is responsible for that.
- */
-
- if (!no_commit) {
- /* 6 is max possible length of our args array including NULL */
- const char *args[6];
- int i = 0;
- args[i++] = "commit";
- args[i++] = "-n";
- if (signoff)
- args[i++] = "-s";
- if (!edit) {
- args[i++] = "-F";
- args[i++] = defmsg;
- }
- args[i] = NULL;
- return execv_git_cmd(args);
- }
- free(reencoded_message);
- free(defmsg);
-
- return 0;
-}
-
-int cmd_revert(int argc, const char **argv, const char *prefix)
-{
- if (isatty(0))
- edit = 1;
- no_replay = 1;
- action = REVERT;
- return revert_or_cherry_pick(argc, argv);
-}
-
-int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
-{
- no_replay = 0;
- action = CHERRY_PICK;
- return revert_or_cherry_pick(argc, argv);
-}
+++ /dev/null
-/*
- * "git rm" builtin command
- *
- * Copyright (C) Linus Torvalds 2006
- */
-#include "cache.h"
-#include "builtin.h"
-#include "dir.h"
-#include "cache-tree.h"
-#include "tree-walk.h"
-#include "parse-options.h"
-
-static const char * const builtin_rm_usage[] = {
- "git rm [options] [--] <file>...",
- NULL
-};
-
-static struct {
- int nr, alloc;
- const char **name;
-} list;
-
-static void add_list(const char *name)
-{
- if (list.nr >= list.alloc) {
- list.alloc = alloc_nr(list.alloc);
- list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
- }
- list.name[list.nr++] = name;
-}
-
-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
- * tree_desc based traversal if we wanted to, but I am
- * lazy, and who cares if removal of files is a tad
- * slower than the theoretical maximum speed?
- */
- int i, no_head;
- int errs = 0;
-
- no_head = is_null_sha1(head);
- for (i = 0; i < list.nr; i++) {
- struct stat st;
- int pos;
- struct cache_entry *ce;
- 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; /* removing unmerged entry */
- ce = active_cache[pos];
-
- if (lstat(ce->name, &st) < 0) {
- if (errno != ENOENT)
- warning("'%s': %s", ce->name, strerror(errno));
- /* It already vanished from the working tree */
- continue;
- }
- else if (S_ISDIR(st.st_mode)) {
- /* if a file was removed and it is now a
- * directory, that is the same as ENOENT as
- * far as git is concerned; we do not track
- * directories.
- */
- continue;
- }
-
- /*
- * "rm" of a path that has changes need to be treated
- * carefully not to allow losing local changes
- * accidentally. A local change could be (1) file in
- * work tree is different since the index; and/or (2)
- * the user staged a content that is different from
- * the current commit in the index.
- *
- * In such a case, you would need to --force the
- * removal. However, "rm --cached" (remove only from
- * the index) is safe if the index matches the file in
- * the work tree or the HEAD commit, as it means that
- * the content being removed is available elsewhere.
- */
-
- /*
- * Is the index different from the file in the work tree?
- */
- if (ce_match_stat(ce, &st, 0))
- local_changes = 1;
-
- /*
- * Is the index different from the HEAD commit? By
- * definition, before the very initial commit,
- * anything staged in the index is treated by the same
- * way as changed from the HEAD.
- */
- if (no_head
- || get_tree_entry(head, name, sha1, &mode)
- || ce->ce_mode != create_ce_mode(mode)
- || hashcmp(ce->sha1, sha1))
- staged_changes = 1;
-
- /*
- * If the index does not match the file in the work
- * tree and if it does not match the HEAD commit
- * either, (1) "git rm" without --cached definitely
- * will lose information; (2) "git rm --cached" will
- * lose information unless it is about removing an
- * "intent to add" entry.
- */
- if (local_changes && staged_changes) {
- if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
- errs = error("'%s' has staged content different "
- "from both the file and the HEAD\n"
- "(use -f to force removal)", name);
- }
- else if (!index_only) {
- if (staged_changes)
- errs = error("'%s' has changes staged in the index\n"
- "(use --cached to keep the file, "
- "or -f to force removal)", name);
- if (local_changes)
- errs = error("'%s' has local modifications\n"
- "(use --cached to keep the file, "
- "or -f to force removal)", name);
- }
- }
- return errs;
-}
-
-static struct lock_file lock_file;
-
-static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
-static int ignore_unmatch = 0;
-
-static struct option builtin_rm_options[] = {
- OPT__DRY_RUN(&show_only),
- OPT__QUIET(&quiet),
- OPT_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"),
- OPT_BOOLEAN('f', "force", &force, "override the up-to-date check"),
- OPT_BOOLEAN('r', NULL, &recursive, "allow recursive removal"),
- OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
- "exit with a zero status even if nothing matched"),
- OPT_END(),
-};
-
-int cmd_rm(int argc, const char **argv, const char *prefix)
-{
- int i, newfd;
- const char **pathspec;
- char *seen;
-
- git_config(git_default_config, NULL);
-
- argc = parse_options(argc, argv, prefix, builtin_rm_options,
- builtin_rm_usage, 0);
- if (!argc)
- usage_with_options(builtin_rm_usage, builtin_rm_options);
-
- if (!index_only)
- setup_work_tree();
-
- newfd = hold_locked_index(&lock_file, 1);
-
- if (read_cache() < 0)
- die("index file corrupt");
-
- pathspec = get_pathspec(prefix, argv);
- refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL);
-
- seen = NULL;
- for (i = 0; pathspec[i] ; i++)
- /* nothing */;
- seen = xcalloc(i, 1);
-
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
- continue;
- add_list(ce->name);
- }
-
- if (pathspec) {
- const char *match;
- int seen_any = 0;
- for (i = 0; (match = pathspec[i]) != NULL ; i++) {
- if (!seen[i]) {
- if (!ignore_unmatch) {
- die("pathspec '%s' did not match any files",
- match);
- }
- }
- else {
- seen_any = 1;
- }
- if (!recursive && seen[i] == MATCHED_RECURSIVELY)
- die("not removing '%s' recursively without -r",
- *match ? match : ".");
- }
-
- if (! seen_any)
- exit(0);
- }
-
- /*
- * If not forced, the file, the index and the HEAD (if exists)
- * must match; but the file can already been removed, since
- * this sequence is a natural "novice" way:
- *
- * rm F; git rm F
- *
- * Further, if HEAD commit exists, "diff-index --cached" must
- * report no changes unless forced.
- */
- if (!force) {
- unsigned char sha1[20];
- if (get_sha1("HEAD", sha1))
- hashclr(sha1);
- if (check_local_mod(sha1, index_only))
- exit(1);
- }
-
- /*
- * First remove the names from the index: we won't commit
- * the index unless all of them succeed.
- */
- for (i = 0; i < list.nr; i++) {
- const char *path = list.name[i];
- if (!quiet)
- printf("rm '%s'\n", path);
-
- if (remove_file_from_cache(path))
- die("git rm: unable to remove %s", path);
- }
-
- if (show_only)
- return 0;
-
- /*
- * Then, unless we used "--cached", remove the filenames from
- * the workspace. If we fail to remove the first one, we
- * abort the "git rm" (but once we've successfully removed
- * any file at all, we'll go ahead and commit to it all:
- * by then we've already committed ourselves and can't fail
- * in the middle)
- */
- if (!index_only) {
- int removed = 0;
- for (i = 0; i < list.nr; i++) {
- const char *path = list.name[i];
- if (!remove_path(path)) {
- removed = 1;
- continue;
- }
- if (!removed)
- die_errno("git rm: '%s'", path);
- }
- }
-
- if (active_cache_changed) {
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
- die("Unable to write new index file");
- }
-
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "commit.h"
-#include "refs.h"
-#include "pkt-line.h"
-#include "sideband.h"
-#include "run-command.h"
-#include "remote.h"
-#include "send-pack.h"
-#include "quote.h"
-
-static const char send_pack_usage[] =
-"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
-" --all and explicit <ref> specification are mutually exclusive.";
-
-static struct send_pack_args args;
-
-static int feed_object(const unsigned char *sha1, int fd, int negative)
-{
- char buf[42];
-
- if (negative && !has_sha1_file(sha1))
- return 1;
-
- memcpy(buf + negative, sha1_to_hex(sha1), 40);
- if (negative)
- buf[0] = '^';
- buf[40 + negative] = '\n';
- return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs");
-}
-
-/*
- * Make a pack stream and spit it out into file descriptor fd
- */
-static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args)
-{
- /*
- * The child becomes pack-objects --revs; we feed
- * the revision parameters to it via its stdin and
- * let its stdout go back to the other end.
- */
- const char *argv[] = {
- "pack-objects",
- "--all-progress-implied",
- "--revs",
- "--stdout",
- NULL,
- NULL,
- NULL,
- NULL,
- };
- struct child_process po;
- int i;
-
- i = 4;
- if (args->use_thin_pack)
- argv[i++] = "--thin";
- if (args->use_ofs_delta)
- argv[i++] = "--delta-base-offset";
- if (args->quiet)
- argv[i++] = "-q";
- memset(&po, 0, sizeof(po));
- po.argv = argv;
- po.in = -1;
- po.out = args->stateless_rpc ? -1 : fd;
- po.git_cmd = 1;
- if (start_command(&po))
- die_errno("git pack-objects failed");
-
- /*
- * We feed the pack-objects we just spawned with revision
- * parameters by writing to the pipe.
- */
- for (i = 0; i < extra->nr; i++)
- if (!feed_object(extra->array[i], po.in, 1))
- break;
-
- while (refs) {
- if (!is_null_sha1(refs->old_sha1) &&
- !feed_object(refs->old_sha1, po.in, 1))
- break;
- if (!is_null_sha1(refs->new_sha1) &&
- !feed_object(refs->new_sha1, po.in, 0))
- break;
- refs = refs->next;
- }
-
- close(po.in);
-
- if (args->stateless_rpc) {
- char *buf = xmalloc(LARGE_PACKET_MAX);
- while (1) {
- ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX);
- if (n <= 0)
- break;
- send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX);
- }
- free(buf);
- close(po.out);
- po.out = -1;
- }
-
- if (finish_command(&po))
- return error("pack-objects died with strange error");
- return 0;
-}
-
-static int receive_status(int in, struct ref *refs)
-{
- struct ref *hint;
- char line[1000];
- int ret = 0;
- int len = packet_read_line(in, line, sizeof(line));
- if (len < 10 || memcmp(line, "unpack ", 7))
- return error("did not receive remote status");
- if (memcmp(line, "unpack ok\n", 10)) {
- char *p = line + strlen(line) - 1;
- if (*p == '\n')
- *p = '\0';
- error("unpack failed: %s", line + 7);
- ret = -1;
- }
- hint = NULL;
- while (1) {
- char *refname;
- char *msg;
- len = packet_read_line(in, line, sizeof(line));
- if (!len)
- break;
- if (len < 3 ||
- (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
- fprintf(stderr, "protocol error: %s\n", line);
- ret = -1;
- break;
- }
-
- line[strlen(line)-1] = '\0';
- refname = line + 3;
- msg = strchr(refname, ' ');
- if (msg)
- *msg++ = '\0';
-
- /* first try searching at our hint, falling back to all refs */
- if (hint)
- hint = find_ref_by_name(hint, refname);
- if (!hint)
- hint = find_ref_by_name(refs, refname);
- if (!hint) {
- warning("remote reported status on unknown ref: %s",
- refname);
- continue;
- }
- if (hint->status != REF_STATUS_EXPECTING_REPORT) {
- warning("remote reported status on unexpected ref: %s",
- refname);
- continue;
- }
-
- if (line[0] == 'o' && line[1] == 'k')
- hint->status = REF_STATUS_OK;
- else {
- hint->status = REF_STATUS_REMOTE_REJECT;
- ret = -1;
- }
- if (msg)
- hint->remote_status = xstrdup(msg);
- /* start our next search from the next ref */
- hint = hint->next;
- }
- return ret;
-}
-
-static void update_tracking_ref(struct remote *remote, struct ref *ref)
-{
- struct refspec rs;
-
- if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)
- return;
-
- rs.src = ref->name;
- rs.dst = NULL;
-
- if (!remote_find_tracking(remote, &rs)) {
- if (args.verbose)
- fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
- if (ref->deletion) {
- delete_ref(rs.dst, NULL, 0);
- } else
- update_ref("update by push", rs.dst,
- ref->new_sha1, NULL, 0, 0);
- free(rs.dst);
- }
-}
-
-#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
-
-static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg)
-{
- fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
- if (from)
- fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
- else
- fputs(prettify_refname(to->name), stderr);
- if (msg) {
- fputs(" (", stderr);
- fputs(msg, stderr);
- fputc(')', stderr);
- }
- fputc('\n', stderr);
-}
-
-static const char *status_abbrev(unsigned char sha1[20])
-{
- return find_unique_abbrev(sha1, DEFAULT_ABBREV);
-}
-
-static void print_ok_ref_status(struct ref *ref)
-{
- if (ref->deletion)
- print_ref_status('-', "[deleted]", ref, NULL, NULL);
- else if (is_null_sha1(ref->old_sha1))
- print_ref_status('*',
- (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
- "[new branch]"),
- ref, ref->peer_ref, NULL);
- else {
- char quickref[84];
- char type;
- const char *msg;
-
- strcpy(quickref, status_abbrev(ref->old_sha1));
- if (ref->nonfastforward) {
- strcat(quickref, "...");
- type = '+';
- msg = "forced update";
- } else {
- strcat(quickref, "..");
- type = ' ';
- msg = NULL;
- }
- strcat(quickref, status_abbrev(ref->new_sha1));
-
- print_ref_status(type, quickref, ref, ref->peer_ref, msg);
- }
-}
-
-static int print_one_push_status(struct ref *ref, const char *dest, int count)
-{
- if (!count)
- fprintf(stderr, "To %s\n", dest);
-
- switch(ref->status) {
- case REF_STATUS_NONE:
- print_ref_status('X', "[no match]", ref, NULL, NULL);
- break;
- case REF_STATUS_REJECT_NODELETE:
- print_ref_status('!', "[rejected]", ref, NULL,
- "remote does not support deleting refs");
- break;
- case REF_STATUS_UPTODATE:
- print_ref_status('=', "[up to date]", ref,
- ref->peer_ref, NULL);
- break;
- case REF_STATUS_REJECT_NONFASTFORWARD:
- print_ref_status('!', "[rejected]", ref, ref->peer_ref,
- "non-fast-forward");
- break;
- case REF_STATUS_REMOTE_REJECT:
- print_ref_status('!', "[remote rejected]", ref,
- ref->deletion ? NULL : ref->peer_ref,
- ref->remote_status);
- break;
- case REF_STATUS_EXPECTING_REPORT:
- print_ref_status('!', "[remote failure]", ref,
- ref->deletion ? NULL : ref->peer_ref,
- "remote failed to report status");
- break;
- case REF_STATUS_OK:
- print_ok_ref_status(ref);
- break;
- }
-
- return 1;
-}
-
-static void print_push_status(const char *dest, struct ref *refs)
-{
- struct ref *ref;
- int n = 0;
-
- if (args.verbose) {
- for (ref = refs; ref; ref = ref->next)
- if (ref->status == REF_STATUS_UPTODATE)
- n += print_one_push_status(ref, dest, n);
- }
-
- for (ref = refs; ref; ref = ref->next)
- if (ref->status == REF_STATUS_OK)
- n += print_one_push_status(ref, dest, n);
-
- for (ref = refs; ref; ref = ref->next) {
- if (ref->status != REF_STATUS_NONE &&
- ref->status != REF_STATUS_UPTODATE &&
- ref->status != REF_STATUS_OK)
- n += print_one_push_status(ref, dest, n);
- }
-}
-
-static int refs_pushed(struct ref *ref)
-{
- for (; ref; ref = ref->next) {
- switch(ref->status) {
- case REF_STATUS_NONE:
- case REF_STATUS_UPTODATE:
- break;
- default:
- return 1;
- }
- }
- return 0;
-}
-
-static void print_helper_status(struct ref *ref)
-{
- struct strbuf buf = STRBUF_INIT;
-
- for (; ref; ref = ref->next) {
- const char *msg = NULL;
- const char *res;
-
- switch(ref->status) {
- case REF_STATUS_NONE:
- res = "error";
- msg = "no match";
- break;
-
- case REF_STATUS_OK:
- res = "ok";
- break;
-
- case REF_STATUS_UPTODATE:
- res = "ok";
- msg = "up to date";
- break;
-
- case REF_STATUS_REJECT_NONFASTFORWARD:
- res = "error";
- msg = "non-fast forward";
- break;
-
- case REF_STATUS_REJECT_NODELETE:
- case REF_STATUS_REMOTE_REJECT:
- res = "error";
- break;
-
- case REF_STATUS_EXPECTING_REPORT:
- default:
- continue;
- }
-
- strbuf_reset(&buf);
- strbuf_addf(&buf, "%s %s", res, ref->name);
- if (ref->remote_status)
- msg = ref->remote_status;
- if (msg) {
- strbuf_addch(&buf, ' ');
- quote_two_c_style(&buf, "", msg, 0);
- }
- strbuf_addch(&buf, '\n');
-
- safe_write(1, buf.buf, buf.len);
- }
- strbuf_release(&buf);
-}
-
-static int sideband_demux(int in, int out, void *data)
-{
- int *fd = data;
- int ret = recv_sideband("send-pack", fd[0], out);
- close(out);
- return ret;
-}
-
-int send_pack(struct send_pack_args *args,
- int fd[], struct child_process *conn,
- struct ref *remote_refs,
- struct extra_have_objects *extra_have)
-{
- int in = fd[0];
- int out = fd[1];
- struct strbuf req_buf = STRBUF_INIT;
- struct ref *ref;
- int new_refs;
- int allow_deleting_refs = 0;
- int status_report = 0;
- int use_sideband = 0;
- unsigned cmds_sent = 0;
- int ret;
- struct async demux;
-
- /* Does the other end support the reporting? */
- if (server_supports("report-status"))
- status_report = 1;
- if (server_supports("delete-refs"))
- allow_deleting_refs = 1;
- if (server_supports("ofs-delta"))
- args->use_ofs_delta = 1;
- if (server_supports("side-band-64k"))
- use_sideband = 1;
-
- if (!remote_refs) {
- fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
- "Perhaps you should specify a branch such as 'master'.\n");
- return 0;
- }
-
- /*
- * Finally, tell the other end!
- */
- new_refs = 0;
- for (ref = remote_refs; ref; ref = ref->next) {
- if (!ref->peer_ref && !args->send_mirror)
- continue;
-
- /* Check for statuses set by set_ref_status_for_push() */
- switch (ref->status) {
- case REF_STATUS_REJECT_NONFASTFORWARD:
- case REF_STATUS_UPTODATE:
- continue;
- default:
- ; /* do nothing */
- }
-
- if (ref->deletion && !allow_deleting_refs) {
- ref->status = REF_STATUS_REJECT_NODELETE;
- continue;
- }
-
- if (!ref->deletion)
- new_refs++;
-
- if (args->dry_run) {
- ref->status = REF_STATUS_OK;
- } else {
- char *old_hex = sha1_to_hex(ref->old_sha1);
- char *new_hex = sha1_to_hex(ref->new_sha1);
-
- if (!cmds_sent && (status_report || use_sideband)) {
- packet_buf_write(&req_buf, "%s %s %s%c%s%s",
- old_hex, new_hex, ref->name, 0,
- status_report ? " report-status" : "",
- use_sideband ? " side-band-64k" : "");
- }
- else
- packet_buf_write(&req_buf, "%s %s %s",
- old_hex, new_hex, ref->name);
- ref->status = status_report ?
- REF_STATUS_EXPECTING_REPORT :
- REF_STATUS_OK;
- cmds_sent++;
- }
- }
-
- if (args->stateless_rpc) {
- if (!args->dry_run && cmds_sent) {
- packet_buf_flush(&req_buf);
- send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
- }
- } else {
- safe_write(out, req_buf.buf, req_buf.len);
- packet_flush(out);
- }
- strbuf_release(&req_buf);
-
- if (use_sideband && cmds_sent) {
- memset(&demux, 0, sizeof(demux));
- demux.proc = sideband_demux;
- demux.data = fd;
- demux.out = -1;
- if (start_async(&demux))
- die("receive-pack: unable to fork off sideband demultiplexer");
- in = demux.out;
- }
-
- if (new_refs && cmds_sent) {
- if (pack_objects(out, remote_refs, extra_have, args) < 0) {
- for (ref = remote_refs; ref; ref = ref->next)
- ref->status = REF_STATUS_NONE;
- if (use_sideband)
- finish_async(&demux);
- return -1;
- }
- }
- if (args->stateless_rpc && cmds_sent)
- packet_flush(out);
-
- if (status_report && cmds_sent)
- ret = receive_status(in, remote_refs);
- else
- ret = 0;
- if (args->stateless_rpc)
- packet_flush(out);
-
- if (use_sideband && cmds_sent) {
- if (finish_async(&demux)) {
- error("error in sideband demultiplexer");
- ret = -1;
- }
- close(demux.out);
- }
-
- if (ret < 0)
- return ret;
- for (ref = remote_refs; ref; ref = ref->next) {
- switch (ref->status) {
- case REF_STATUS_NONE:
- case REF_STATUS_UPTODATE:
- case REF_STATUS_OK:
- break;
- default:
- return -1;
- }
- }
- return 0;
-}
-
-static void verify_remote_names(int nr_heads, const char **heads)
-{
- int i;
-
- for (i = 0; i < nr_heads; i++) {
- const char *local = heads[i];
- const char *remote = strrchr(heads[i], ':');
-
- if (*local == '+')
- local++;
-
- /* A matching refspec is okay. */
- if (remote == local && remote[1] == '\0')
- continue;
-
- remote = remote ? (remote + 1) : local;
- switch (check_ref_format(remote)) {
- case 0: /* ok */
- case CHECK_REF_FORMAT_ONELEVEL:
- /* ok but a single level -- that is fine for
- * a match pattern.
- */
- case CHECK_REF_FORMAT_WILDCARD:
- /* ok but ends with a pattern-match character */
- continue;
- }
- die("remote part of refspec is not a valid name in %s",
- heads[i]);
- }
-}
-
-int cmd_send_pack(int argc, const char **argv, const char *prefix)
-{
- int i, nr_refspecs = 0;
- const char **refspecs = NULL;
- const char *remote_name = NULL;
- struct remote *remote = NULL;
- const char *dest = NULL;
- int fd[2];
- struct child_process *conn;
- struct extra_have_objects extra_have;
- struct ref *remote_refs, *local_refs;
- int ret;
- int helper_status = 0;
- int send_all = 0;
- const char *receivepack = "git-receive-pack";
- int flags;
-
- argv++;
- for (i = 1; i < argc; i++, argv++) {
- const char *arg = *argv;
-
- if (*arg == '-') {
- if (!prefixcmp(arg, "--receive-pack=")) {
- receivepack = arg + 15;
- continue;
- }
- if (!prefixcmp(arg, "--exec=")) {
- receivepack = arg + 7;
- continue;
- }
- if (!prefixcmp(arg, "--remote=")) {
- remote_name = arg + 9;
- continue;
- }
- if (!strcmp(arg, "--all")) {
- send_all = 1;
- continue;
- }
- if (!strcmp(arg, "--dry-run")) {
- args.dry_run = 1;
- continue;
- }
- if (!strcmp(arg, "--mirror")) {
- args.send_mirror = 1;
- continue;
- }
- if (!strcmp(arg, "--force")) {
- args.force_update = 1;
- continue;
- }
- if (!strcmp(arg, "--verbose")) {
- args.verbose = 1;
- continue;
- }
- if (!strcmp(arg, "--thin")) {
- args.use_thin_pack = 1;
- continue;
- }
- if (!strcmp(arg, "--stateless-rpc")) {
- args.stateless_rpc = 1;
- continue;
- }
- if (!strcmp(arg, "--helper-status")) {
- helper_status = 1;
- continue;
- }
- usage(send_pack_usage);
- }
- if (!dest) {
- dest = arg;
- continue;
- }
- refspecs = (const char **) argv;
- nr_refspecs = argc - i;
- break;
- }
- if (!dest)
- usage(send_pack_usage);
- /*
- * --all and --mirror are incompatible; neither makes sense
- * with any refspecs.
- */
- if ((refspecs && (send_all || args.send_mirror)) ||
- (send_all && args.send_mirror))
- usage(send_pack_usage);
-
- if (remote_name) {
- remote = remote_get(remote_name);
- if (!remote_has_url(remote, dest)) {
- die("Destination %s is not a uri for %s",
- dest, remote_name);
- }
- }
-
- if (args.stateless_rpc) {
- conn = NULL;
- fd[0] = 0;
- fd[1] = 1;
- } else {
- conn = git_connect(fd, dest, receivepack,
- args.verbose ? CONNECT_VERBOSE : 0);
- }
-
- memset(&extra_have, 0, sizeof(extra_have));
-
- get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL,
- &extra_have);
-
- verify_remote_names(nr_refspecs, refspecs);
-
- local_refs = get_local_heads();
-
- flags = MATCH_REFS_NONE;
-
- if (send_all)
- flags |= MATCH_REFS_ALL;
- if (args.send_mirror)
- flags |= MATCH_REFS_MIRROR;
-
- /* match them up */
- if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
- return -1;
-
- set_ref_status_for_push(remote_refs, args.send_mirror,
- args.force_update);
-
- ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
-
- if (helper_status)
- print_helper_status(remote_refs);
-
- close(fd[1]);
- close(fd[0]);
-
- ret |= finish_connect(conn);
-
- if (!helper_status)
- print_push_status(dest, remote_refs);
-
- if (!args.dry_run && remote) {
- struct ref *ref;
- for (ref = remote_refs; ref; ref = ref->next)
- update_tracking_ref(remote, ref);
- }
-
- if (!ret && !refs_pushed(remote_refs))
- fprintf(stderr, "Everything up-to-date\n");
-
- return ret;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "commit.h"
-#include "diff.h"
-#include "string-list.h"
-#include "revision.h"
-#include "utf8.h"
-#include "mailmap.h"
-#include "shortlog.h"
-#include "parse-options.h"
-
-static char const * const shortlog_usage[] = {
- "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]",
- "",
- "[rev-opts] are documented in git-rev-list(1)",
- NULL
-};
-
-static int compare_by_number(const void *a1, const void *a2)
-{
- const struct string_list_item *i1 = a1, *i2 = a2;
- const struct string_list *l1 = i1->util, *l2 = i2->util;
-
- if (l1->nr < l2->nr)
- return 1;
- else if (l1->nr == l2->nr)
- return 0;
- else
- return -1;
-}
-
-const char *format_subject(struct strbuf *sb, const char *msg,
- const char *line_separator);
-
-static void insert_one_record(struct shortlog *log,
- const char *author,
- const char *oneline)
-{
- const char *dot3 = log->common_repo_prefix;
- char *buffer, *p;
- struct string_list_item *item;
- char namebuf[1024];
- char emailbuf[1024];
- size_t len;
- const char *eol;
- const char *boemail, *eoemail;
- struct strbuf subject = STRBUF_INIT;
-
- boemail = strchr(author, '<');
- if (!boemail)
- return;
- eoemail = strchr(boemail, '>');
- if (!eoemail)
- return;
-
- /* copy author name to namebuf, to support matching on both name and email */
- memcpy(namebuf, author, boemail - author);
- len = boemail - author;
- while (len > 0 && isspace(namebuf[len-1]))
- len--;
- namebuf[len] = 0;
-
- /* copy email name to emailbuf, to allow email replacement as well */
- memcpy(emailbuf, boemail+1, eoemail - boemail);
- emailbuf[eoemail - boemail - 1] = 0;
-
- if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
- while (author < boemail && isspace(*author))
- author++;
- for (len = 0;
- len < sizeof(namebuf) - 1 && author + len < boemail;
- len++)
- namebuf[len] = author[len];
- while (0 < len && isspace(namebuf[len-1]))
- len--;
- namebuf[len] = '\0';
- }
- else
- len = strlen(namebuf);
-
- if (log->email) {
- size_t room = sizeof(namebuf) - len - 1;
- int maillen = strlen(emailbuf);
- snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
- }
-
- item = string_list_insert(namebuf, &log->list);
- if (item->util == NULL)
- item->util = xcalloc(1, sizeof(struct string_list));
-
- /* Skip any leading whitespace, including any blank lines. */
- while (*oneline && isspace(*oneline))
- oneline++;
- eol = strchr(oneline, '\n');
- if (!eol)
- eol = oneline + strlen(oneline);
- if (!prefixcmp(oneline, "[PATCH")) {
- char *eob = strchr(oneline, ']');
- if (eob && (!eol || eob < eol))
- oneline = eob + 1;
- }
- while (*oneline && isspace(*oneline) && *oneline != '\n')
- oneline++;
- format_subject(&subject, oneline, " ");
- buffer = strbuf_detach(&subject, NULL);
-
- if (dot3) {
- int dot3len = strlen(dot3);
- if (dot3len > 5) {
- while ((p = strstr(buffer, dot3)) != NULL) {
- int taillen = strlen(p) - dot3len;
- memcpy(p, "/.../", 5);
- memmove(p + 5, p + dot3len, taillen + 1);
- }
- }
- }
-
- string_list_append(buffer, item->util);
-}
-
-static void read_from_stdin(struct shortlog *log)
-{
- char author[1024], oneline[1024];
-
- while (fgets(author, sizeof(author), stdin) != NULL) {
- if (!(author[0] == 'A' || author[0] == 'a') ||
- prefixcmp(author + 1, "uthor: "))
- continue;
- while (fgets(oneline, sizeof(oneline), stdin) &&
- oneline[0] != '\n')
- ; /* discard headers */
- while (fgets(oneline, sizeof(oneline), stdin) &&
- oneline[0] == '\n')
- ; /* discard blanks */
- insert_one_record(log, author + 8, oneline);
- }
-}
-
-void shortlog_add_commit(struct shortlog *log, struct commit *commit)
-{
- const char *author = NULL, *buffer;
- struct strbuf buf = STRBUF_INIT;
- struct strbuf ufbuf = STRBUF_INIT;
- struct pretty_print_context ctx = {0};
-
- pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx);
- buffer = buf.buf;
- while (*buffer && *buffer != '\n') {
- const char *eol = strchr(buffer, '\n');
-
- if (eol == NULL)
- eol = buffer + strlen(buffer);
- else
- eol++;
-
- if (!prefixcmp(buffer, "author "))
- author = buffer + 7;
- buffer = eol;
- }
- if (!author)
- die("Missing author: %s",
- sha1_to_hex(commit->object.sha1));
- if (log->user_format) {
- struct pretty_print_context ctx = {0};
- ctx.abbrev = DEFAULT_ABBREV;
- ctx.subject = "";
- ctx.after_subject = "";
- ctx.date_mode = DATE_NORMAL;
- pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx);
- buffer = ufbuf.buf;
- } else if (*buffer) {
- buffer++;
- }
- insert_one_record(log, author, !*buffer ? "<none>" : buffer);
- strbuf_release(&ufbuf);
- strbuf_release(&buf);
-}
-
-static void get_from_rev(struct rev_info *rev, struct shortlog *log)
-{
- struct commit *commit;
-
- if (prepare_revision_walk(rev))
- die("revision walk setup failed");
- while ((commit = get_revision(rev)) != NULL)
- shortlog_add_commit(log, commit);
-}
-
-static int parse_uint(char const **arg, int comma, int defval)
-{
- unsigned long ul;
- int ret;
- char *endp;
-
- ul = strtoul(*arg, &endp, 10);
- if (*endp && *endp != comma)
- return -1;
- if (ul > INT_MAX)
- return -1;
- ret = *arg == endp ? defval : (int)ul;
- *arg = *endp ? endp + 1 : endp;
- return ret;
-}
-
-static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]";
-#define DEFAULT_WRAPLEN 76
-#define DEFAULT_INDENT1 6
-#define DEFAULT_INDENT2 9
-
-static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
-{
- struct shortlog *log = opt->value;
-
- log->wrap_lines = !unset;
- if (unset)
- return 0;
- if (!arg) {
- log->wrap = DEFAULT_WRAPLEN;
- log->in1 = DEFAULT_INDENT1;
- log->in2 = DEFAULT_INDENT2;
- return 0;
- }
-
- log->wrap = parse_uint(&arg, ',', DEFAULT_WRAPLEN);
- log->in1 = parse_uint(&arg, ',', DEFAULT_INDENT1);
- log->in2 = parse_uint(&arg, '\0', DEFAULT_INDENT2);
- if (log->wrap < 0 || log->in1 < 0 || log->in2 < 0)
- return error(wrap_arg_usage);
- if (log->wrap &&
- ((log->in1 && log->wrap <= log->in1) ||
- (log->in2 && log->wrap <= log->in2)))
- return error(wrap_arg_usage);
- return 0;
-}
-
-void shortlog_init(struct shortlog *log)
-{
- memset(log, 0, sizeof(*log));
-
- read_mailmap(&log->mailmap, &log->common_repo_prefix);
-
- log->list.strdup_strings = 1;
- log->wrap = DEFAULT_WRAPLEN;
- log->in1 = DEFAULT_INDENT1;
- log->in2 = DEFAULT_INDENT2;
-}
-
-int cmd_shortlog(int argc, const char **argv, const char *prefix)
-{
- static struct shortlog log;
- static struct rev_info rev;
- int nongit;
-
- static const struct option options[] = {
- OPT_BOOLEAN('n', "numbered", &log.sort_by_number,
- "sort output according to the number of commits per author"),
- OPT_BOOLEAN('s', "summary", &log.summary,
- "Suppress commit descriptions, only provides commit count"),
- OPT_BOOLEAN('e', "email", &log.email,
- "Show the email address of each author"),
- { OPTION_CALLBACK, 'w', NULL, &log, "w[,i1[,i2]]",
- "Linewrap output", PARSE_OPT_OPTARG, &parse_wrap_args },
- OPT_END(),
- };
-
- struct parse_opt_ctx_t ctx;
-
- prefix = setup_git_directory_gently(&nongit);
- git_config(git_default_config, NULL);
- shortlog_init(&log);
- init_revisions(&rev, prefix);
- parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_KEEP_ARGV0);
-
- for (;;) {
- switch (parse_options_step(&ctx, options, shortlog_usage)) {
- case PARSE_OPT_HELP:
- exit(129);
- case PARSE_OPT_DONE:
- goto parse_done;
- }
- parse_revision_opt(&rev, &ctx, options, shortlog_usage);
- }
-parse_done:
- argc = parse_options_end(&ctx);
-
- if (setup_revisions(argc, argv, &rev, NULL) != 1) {
- error("unrecognized argument: %s", argv[1]);
- usage_with_options(shortlog_usage, options);
- }
-
- log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
-
- /* assume HEAD if from a tty */
- if (!nongit && !rev.pending.nr && isatty(0))
- add_head_to_pending(&rev);
- if (rev.pending.nr == 0) {
- read_from_stdin(&log);
- }
- else
- get_from_rev(&rev, &log);
-
- shortlog_output(&log);
- return 0;
-}
-
-static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s,
- const struct shortlog *log)
-{
- int col = strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap);
- if (col != log->wrap)
- strbuf_addch(sb, '\n');
-}
-
-void shortlog_output(struct shortlog *log)
-{
- int i, j;
- struct strbuf sb = STRBUF_INIT;
-
- if (log->sort_by_number)
- qsort(log->list.items, log->list.nr, sizeof(struct string_list_item),
- compare_by_number);
- for (i = 0; i < log->list.nr; i++) {
- struct string_list *onelines = log->list.items[i].util;
-
- if (log->summary) {
- printf("%6d\t%s\n", onelines->nr, log->list.items[i].string);
- } else {
- printf("%s (%d):\n", log->list.items[i].string, onelines->nr);
- for (j = onelines->nr - 1; j >= 0; j--) {
- const char *msg = onelines->items[j].string;
-
- if (log->wrap_lines) {
- strbuf_reset(&sb);
- add_wrapped_shortlog_msg(&sb, msg, log);
- fwrite(sb.buf, sb.len, 1, stdout);
- }
- else
- printf(" %s\n", msg);
- }
- putchar('\n');
- }
-
- onelines->strdup_strings = 1;
- string_list_clear(onelines, 0);
- free(onelines);
- log->list.items[i].util = NULL;
- }
-
- strbuf_release(&sb);
- log->list.strdup_strings = 1;
- string_list_clear(&log->list, 1);
- clear_mailmap(&log->mailmap);
-}
+++ /dev/null
-#include "cache.h"
-#include "commit.h"
-#include "refs.h"
-#include "builtin.h"
-#include "color.h"
-#include "parse-options.h"
-
-static const char* show_branch_usage[] = {
- "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...",
- "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]",
- NULL
-};
-
-static int showbranch_use_color = -1;
-static char column_colors[][COLOR_MAXLEN] = {
- GIT_COLOR_RED,
- GIT_COLOR_GREEN,
- GIT_COLOR_YELLOW,
- GIT_COLOR_BLUE,
- GIT_COLOR_MAGENTA,
- GIT_COLOR_CYAN,
-};
-
-#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))
-
-static int default_num;
-static int default_alloc;
-static const char **default_arg;
-
-#define UNINTERESTING 01
-
-#define REV_SHIFT 2
-#define MAX_REVS (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
-
-#define DEFAULT_REFLOG 4
-
-static const char *get_color_code(int idx)
-{
- if (showbranch_use_color)
- return column_colors[idx];
- return "";
-}
-
-static const char *get_color_reset_code(void)
-{
- if (showbranch_use_color)
- return GIT_COLOR_RESET;
- return "";
-}
-
-static struct commit *interesting(struct commit_list *list)
-{
- while (list) {
- struct commit *commit = list->item;
- list = list->next;
- if (commit->object.flags & UNINTERESTING)
- continue;
- return commit;
- }
- return NULL;
-}
-
-static struct commit *pop_one_commit(struct commit_list **list_p)
-{
- struct commit *commit;
- struct commit_list *list;
- list = *list_p;
- commit = list->item;
- *list_p = list->next;
- free(list);
- return commit;
-}
-
-struct commit_name {
- const char *head_name; /* which head's ancestor? */
- int generation; /* how many parents away from head_name */
-};
-
-/* Name the commit as nth generation ancestor of head_name;
- * we count only the first-parent relationship for naming purposes.
- */
-static void name_commit(struct commit *commit, const char *head_name, int nth)
-{
- struct commit_name *name;
- if (!commit->util)
- commit->util = xmalloc(sizeof(struct commit_name));
- name = commit->util;
- name->head_name = head_name;
- name->generation = nth;
-}
-
-/* Parent is the first parent of the commit. We may name it
- * as (n+1)th generation ancestor of the same head_name as
- * commit is nth generation ancestor of, if that generation
- * number is better than the name it already has.
- */
-static void name_parent(struct commit *commit, struct commit *parent)
-{
- struct commit_name *commit_name = commit->util;
- struct commit_name *parent_name = parent->util;
- if (!commit_name)
- return;
- if (!parent_name ||
- commit_name->generation + 1 < parent_name->generation)
- name_commit(parent, commit_name->head_name,
- commit_name->generation + 1);
-}
-
-static int name_first_parent_chain(struct commit *c)
-{
- int i = 0;
- while (c) {
- struct commit *p;
- if (!c->util)
- break;
- if (!c->parents)
- break;
- p = c->parents->item;
- if (!p->util) {
- name_parent(c, p);
- i++;
- }
- else
- break;
- c = p;
- }
- return i;
-}
-
-static void name_commits(struct commit_list *list,
- struct commit **rev,
- char **ref_name,
- int num_rev)
-{
- struct commit_list *cl;
- struct commit *c;
- int i;
-
- /* First give names to the given heads */
- for (cl = list; cl; cl = cl->next) {
- c = cl->item;
- if (c->util)
- continue;
- for (i = 0; i < num_rev; i++) {
- if (rev[i] == c) {
- name_commit(c, ref_name[i], 0);
- break;
- }
- }
- }
-
- /* Then commits on the first parent ancestry chain */
- do {
- i = 0;
- for (cl = list; cl; cl = cl->next) {
- i += name_first_parent_chain(cl->item);
- }
- } while (i);
-
- /* Finally, any unnamed commits */
- do {
- i = 0;
- for (cl = list; cl; cl = cl->next) {
- struct commit_list *parents;
- struct commit_name *n;
- int nth;
- c = cl->item;
- if (!c->util)
- continue;
- n = c->util;
- parents = c->parents;
- nth = 0;
- while (parents) {
- struct commit *p = parents->item;
- char newname[1000], *en;
- parents = parents->next;
- nth++;
- if (p->util)
- continue;
- en = newname;
- switch (n->generation) {
- case 0:
- en += sprintf(en, "%s", n->head_name);
- break;
- case 1:
- en += sprintf(en, "%s^", n->head_name);
- break;
- default:
- en += sprintf(en, "%s~%d",
- n->head_name, n->generation);
- break;
- }
- if (nth == 1)
- en += sprintf(en, "^");
- else
- en += sprintf(en, "^%d", nth);
- name_commit(p, xstrdup(newname), 0);
- i++;
- name_first_parent_chain(p);
- }
- }
- } while (i);
-}
-
-static int mark_seen(struct commit *commit, struct commit_list **seen_p)
-{
- if (!commit->object.flags) {
- commit_list_insert(commit, seen_p);
- return 1;
- }
- return 0;
-}
-
-static void join_revs(struct commit_list **list_p,
- struct commit_list **seen_p,
- int num_rev, int extra)
-{
- int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
- int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
-
- while (*list_p) {
- struct commit_list *parents;
- int still_interesting = !!interesting(*list_p);
- struct commit *commit = pop_one_commit(list_p);
- int flags = commit->object.flags & all_mask;
-
- if (!still_interesting && extra <= 0)
- break;
-
- mark_seen(commit, seen_p);
- if ((flags & all_revs) == all_revs)
- flags |= UNINTERESTING;
- parents = commit->parents;
-
- while (parents) {
- struct commit *p = parents->item;
- int this_flag = p->object.flags;
- parents = parents->next;
- if ((this_flag & flags) == flags)
- continue;
- if (!p->object.parsed)
- parse_commit(p);
- if (mark_seen(p, seen_p) && !still_interesting)
- extra--;
- p->object.flags |= flags;
- insert_by_date(p, list_p);
- }
- }
-
- /*
- * Postprocess to complete well-poisoning.
- *
- * At this point we have all the commits we have seen in
- * seen_p list. Mark anything that can be reached from
- * uninteresting commits not interesting.
- */
- for (;;) {
- int changed = 0;
- struct commit_list *s;
- for (s = *seen_p; s; s = s->next) {
- struct commit *c = s->item;
- struct commit_list *parents;
-
- if (((c->object.flags & all_revs) != all_revs) &&
- !(c->object.flags & UNINTERESTING))
- continue;
-
- /* The current commit is either a merge base or
- * already uninteresting one. Mark its parents
- * as uninteresting commits _only_ if they are
- * already parsed. No reason to find new ones
- * here.
- */
- parents = c->parents;
- while (parents) {
- struct commit *p = parents->item;
- parents = parents->next;
- if (!(p->object.flags & UNINTERESTING)) {
- p->object.flags |= UNINTERESTING;
- changed = 1;
- }
- }
- }
- if (!changed)
- break;
- }
-}
-
-static void show_one_commit(struct commit *commit, int no_name)
-{
- struct strbuf pretty = STRBUF_INIT;
- const char *pretty_str = "(unavailable)";
- struct commit_name *name = commit->util;
-
- if (commit->object.parsed) {
- struct pretty_print_context ctx = {0};
- pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx);
- pretty_str = pretty.buf;
- }
- if (!prefixcmp(pretty_str, "[PATCH] "))
- pretty_str += 8;
-
- if (!no_name) {
- if (name && name->head_name) {
- printf("[%s", name->head_name);
- if (name->generation) {
- if (name->generation == 1)
- printf("^");
- else
- printf("~%d", name->generation);
- }
- printf("] ");
- }
- else
- printf("[%s] ",
- find_unique_abbrev(commit->object.sha1, 7));
- }
- puts(pretty_str);
- strbuf_release(&pretty);
-}
-
-static char *ref_name[MAX_REVS + 1];
-static int ref_name_cnt;
-
-static const char *find_digit_prefix(const char *s, int *v)
-{
- const char *p;
- int ver;
- char ch;
-
- for (p = s, ver = 0;
- '0' <= (ch = *p) && ch <= '9';
- p++)
- ver = ver * 10 + ch - '0';
- *v = ver;
- return p;
-}
-
-
-static int version_cmp(const char *a, const char *b)
-{
- while (1) {
- int va, vb;
-
- a = find_digit_prefix(a, &va);
- b = find_digit_prefix(b, &vb);
- if (va != vb)
- return va - vb;
-
- while (1) {
- int ca = *a;
- int cb = *b;
- if ('0' <= ca && ca <= '9')
- ca = 0;
- if ('0' <= cb && cb <= '9')
- cb = 0;
- if (ca != cb)
- return ca - cb;
- if (!ca)
- break;
- a++;
- b++;
- }
- if (!*a && !*b)
- return 0;
- }
-}
-
-static int compare_ref_name(const void *a_, const void *b_)
-{
- const char * const*a = a_, * const*b = b_;
- return version_cmp(*a, *b);
-}
-
-static void sort_ref_range(int bottom, int top)
-{
- qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]),
- compare_ref_name);
-}
-
-static int append_ref(const char *refname, const unsigned char *sha1,
- int allow_dups)
-{
- struct commit *commit = lookup_commit_reference_gently(sha1, 1);
- int i;
-
- if (!commit)
- return 0;
-
- if (!allow_dups) {
- /* Avoid adding the same thing twice */
- for (i = 0; i < ref_name_cnt; i++)
- if (!strcmp(refname, ref_name[i]))
- return 0;
- }
- if (MAX_REVS <= ref_name_cnt) {
- warning("ignoring %s; cannot handle more than %d refs",
- refname, MAX_REVS);
- return 0;
- }
- ref_name[ref_name_cnt++] = xstrdup(refname);
- ref_name[ref_name_cnt] = NULL;
- return 0;
-}
-
-static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- unsigned char tmp[20];
- int ofs = 11;
- if (prefixcmp(refname, "refs/heads/"))
- return 0;
- /* If both heads/foo and tags/foo exists, get_sha1 would
- * get confused.
- */
- if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
- ofs = 5;
- return append_ref(refname + ofs, sha1, 0);
-}
-
-static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- unsigned char tmp[20];
- int ofs = 13;
- if (prefixcmp(refname, "refs/remotes/"))
- return 0;
- /* If both heads/foo and tags/foo exists, get_sha1 would
- * get confused.
- */
- if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
- ofs = 5;
- return append_ref(refname + ofs, sha1, 0);
-}
-
-static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- if (prefixcmp(refname, "refs/tags/"))
- return 0;
- return append_ref(refname + 5, sha1, 0);
-}
-
-static const char *match_ref_pattern = NULL;
-static int match_ref_slash = 0;
-static int count_slash(const char *s)
-{
- int cnt = 0;
- while (*s)
- if (*s++ == '/')
- cnt++;
- return cnt;
-}
-
-static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- /* we want to allow pattern hold/<asterisk> to show all
- * branches under refs/heads/hold/, and v0.99.9? to show
- * refs/tags/v0.99.9a and friends.
- */
- const char *tail;
- int slash = count_slash(refname);
- for (tail = refname; *tail && match_ref_slash < slash; )
- if (*tail++ == '/')
- slash--;
- if (!*tail)
- return 0;
- if (fnmatch(match_ref_pattern, tail, 0))
- return 0;
- if (!prefixcmp(refname, "refs/heads/"))
- return append_head_ref(refname, sha1, flag, cb_data);
- if (!prefixcmp(refname, "refs/tags/"))
- return append_tag_ref(refname, sha1, flag, cb_data);
- return append_ref(refname, sha1, 0);
-}
-
-static void snarf_refs(int head, int remotes)
-{
- if (head) {
- int orig_cnt = ref_name_cnt;
- for_each_ref(append_head_ref, NULL);
- sort_ref_range(orig_cnt, ref_name_cnt);
- }
- if (remotes) {
- int orig_cnt = ref_name_cnt;
- for_each_ref(append_remote_ref, NULL);
- sort_ref_range(orig_cnt, ref_name_cnt);
- }
-}
-
-static int rev_is_head(char *head, int headlen, char *name,
- unsigned char *head_sha1, unsigned char *sha1)
-{
- if ((!head[0]) ||
- (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
- return 0;
- if (!prefixcmp(head, "refs/heads/"))
- head += 11;
- if (!prefixcmp(name, "refs/heads/"))
- name += 11;
- else if (!prefixcmp(name, "heads/"))
- name += 6;
- return !strcmp(head, name);
-}
-
-static int show_merge_base(struct commit_list *seen, int num_rev)
-{
- int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
- int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
- int exit_status = 1;
-
- while (seen) {
- struct commit *commit = pop_one_commit(&seen);
- int flags = commit->object.flags & all_mask;
- if (!(flags & UNINTERESTING) &&
- ((flags & all_revs) == all_revs)) {
- puts(sha1_to_hex(commit->object.sha1));
- exit_status = 0;
- commit->object.flags |= UNINTERESTING;
- }
- }
- return exit_status;
-}
-
-static int show_independent(struct commit **rev,
- int num_rev,
- char **ref_name,
- unsigned int *rev_mask)
-{
- int i;
-
- for (i = 0; i < num_rev; i++) {
- struct commit *commit = rev[i];
- unsigned int flag = rev_mask[i];
-
- if (commit->object.flags == flag)
- puts(sha1_to_hex(commit->object.sha1));
- commit->object.flags |= UNINTERESTING;
- }
- return 0;
-}
-
-static void append_one_rev(const char *av)
-{
- unsigned char revkey[20];
- if (!get_sha1(av, revkey)) {
- append_ref(av, revkey, 0);
- return;
- }
- if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
- /* glob style match */
- int saved_matches = ref_name_cnt;
- match_ref_pattern = av;
- match_ref_slash = count_slash(av);
- for_each_ref(append_matching_ref, NULL);
- if (saved_matches == ref_name_cnt &&
- ref_name_cnt < MAX_REVS)
- error("no matching refs with %s", av);
- if (saved_matches + 1 < ref_name_cnt)
- sort_ref_range(saved_matches, ref_name_cnt);
- return;
- }
- die("bad sha1 reference %s", av);
-}
-
-static int git_show_branch_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "showbranch.default")) {
- if (!value)
- return config_error_nonbool(var);
- /*
- * default_arg is now passed to parse_options(), so we need to
- * mimic the real argv a bit better.
- */
- if (!default_num) {
- default_alloc = 20;
- default_arg = xcalloc(default_alloc, sizeof(*default_arg));
- default_arg[default_num++] = "show-branch";
- } else if (default_alloc <= default_num + 1) {
- default_alloc = default_alloc * 3 / 2 + 20;
- default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
- }
- default_arg[default_num++] = xstrdup(value);
- default_arg[default_num] = NULL;
- return 0;
- }
-
- if (!strcmp(var, "color.showbranch")) {
- showbranch_use_color = git_config_colorbool(var, value, -1);
- return 0;
- }
-
- return git_color_default_config(var, value, cb);
-}
-
-static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
-{
- /* If the commit is tip of the named branches, do not
- * omit it.
- * Otherwise, if it is a merge that is reachable from only one
- * tip, it is not that interesting.
- */
- int i, flag, count;
- for (i = 0; i < n; i++)
- if (rev[i] == commit)
- return 0;
- flag = commit->object.flags;
- for (i = count = 0; i < n; i++) {
- if (flag & (1u << (i + REV_SHIFT)))
- count++;
- }
- if (count == 1)
- return 1;
- return 0;
-}
-
-static int reflog = 0;
-
-static int parse_reflog_param(const struct option *opt, const char *arg,
- int unset)
-{
- char *ep;
- const char **base = (const char **)opt->value;
- if (!arg)
- arg = "";
- reflog = strtoul(arg, &ep, 10);
- if (*ep == ',')
- *base = ep + 1;
- else if (*ep)
- return error("unrecognized reflog param '%s'", arg);
- else
- *base = NULL;
- if (reflog <= 0)
- reflog = DEFAULT_REFLOG;
- return 0;
-}
-
-int cmd_show_branch(int ac, const char **av, const char *prefix)
-{
- struct commit *rev[MAX_REVS], *commit;
- char *reflog_msg[MAX_REVS];
- struct commit_list *list = NULL, *seen = NULL;
- unsigned int rev_mask[MAX_REVS];
- int num_rev, i, extra = 0;
- int all_heads = 0, all_remotes = 0;
- int all_mask, all_revs;
- int lifo = 1;
- char head[128];
- const char *head_p;
- int head_len;
- unsigned char head_sha1[20];
- int merge_base = 0;
- int independent = 0;
- int no_name = 0;
- int sha1_name = 0;
- int shown_merge_point = 0;
- int with_current_branch = 0;
- int head_at = -1;
- int topics = 0;
- int dense = 1;
- const char *reflog_base = NULL;
- struct option builtin_show_branch_options[] = {
- OPT_BOOLEAN('a', "all", &all_heads,
- "show remote-tracking and local branches"),
- OPT_BOOLEAN('r', "remotes", &all_remotes,
- "show remote-tracking branches"),
- OPT_BOOLEAN(0, "color", &showbranch_use_color,
- "color '*!+-' corresponding to the branch"),
- { OPTION_INTEGER, 0, "more", &extra, "n",
- "show <n> more commits after the common ancestor",
- PARSE_OPT_OPTARG, NULL, (intptr_t)1 },
- OPT_SET_INT(0, "list", &extra, "synonym to more=-1", -1),
- OPT_BOOLEAN(0, "no-name", &no_name, "suppress naming strings"),
- OPT_BOOLEAN(0, "current", &with_current_branch,
- "include the current branch"),
- OPT_BOOLEAN(0, "sha1-name", &sha1_name,
- "name commits with their object names"),
- OPT_BOOLEAN(0, "merge-base", &merge_base,
- "show possible merge bases"),
- OPT_BOOLEAN(0, "independent", &independent,
- "show refs unreachable from any other ref"),
- OPT_BOOLEAN(0, "topo-order", &lifo,
- "show commits in topological order"),
- OPT_BOOLEAN(0, "topics", &topics,
- "show only commits not on the first branch"),
- OPT_SET_INT(0, "sparse", &dense,
- "show merges reachable from only one tip", 0),
- OPT_SET_INT(0, "date-order", &lifo,
- "show commits where no parent comes before its "
- "children", 0),
- { OPTION_CALLBACK, 'g', "reflog", &reflog_base, "<n>[,<base>]",
- "show <n> most recent ref-log entries starting at "
- "base",
- PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
- parse_reflog_param },
- OPT_END()
- };
-
- git_config(git_show_branch_config, NULL);
-
- if (showbranch_use_color == -1)
- showbranch_use_color = git_use_color_default;
-
- /* If nothing is specified, try the default first */
- if (ac == 1 && default_num) {
- ac = default_num;
- av = default_arg;
- }
-
- ac = parse_options(ac, av, prefix, builtin_show_branch_options,
- show_branch_usage, PARSE_OPT_STOP_AT_NON_OPTION);
- if (all_heads)
- all_remotes = 1;
-
- if (extra || reflog) {
- /* "listing" mode is incompatible with
- * independent nor merge-base modes.
- */
- if (independent || merge_base)
- usage_with_options(show_branch_usage,
- builtin_show_branch_options);
- if (reflog && ((0 < extra) || all_heads || all_remotes))
- /*
- * Asking for --more in reflog mode does not
- * make sense. --list is Ok.
- *
- * Also --all and --remotes do not make sense either.
- */
- die("--reflog is incompatible with --all, --remotes, "
- "--independent or --merge-base");
- }
-
- /* If nothing is specified, show all branches by default */
- if (ac + all_heads + all_remotes == 0)
- all_heads = 1;
-
- if (reflog) {
- unsigned char sha1[20];
- char nth_desc[256];
- char *ref;
- int base = 0;
-
- if (ac == 0) {
- static const char *fake_av[2];
- const char *refname;
-
- refname = resolve_ref("HEAD", sha1, 1, NULL);
- fake_av[0] = xstrdup(refname);
- fake_av[1] = NULL;
- av = fake_av;
- ac = 1;
- }
- if (ac != 1)
- die("--reflog option needs one branch name");
-
- if (MAX_REVS < reflog)
- die("Only %d entries can be shown at one time.",
- MAX_REVS);
- if (!dwim_ref(*av, strlen(*av), sha1, &ref))
- die("No such ref %s", *av);
-
- /* Has the base been specified? */
- if (reflog_base) {
- char *ep;
- base = strtoul(reflog_base, &ep, 10);
- if (*ep) {
- /* Ah, that is a date spec... */
- unsigned long at;
- at = approxidate(reflog_base);
- read_ref_at(ref, at, -1, sha1, NULL,
- NULL, NULL, &base);
- }
- }
-
- for (i = 0; i < reflog; i++) {
- char *logmsg, *m;
- const char *msg;
- unsigned long timestamp;
- int tz;
-
- if (read_ref_at(ref, 0, base+i, sha1, &logmsg,
- ×tamp, &tz, NULL)) {
- reflog = i;
- break;
- }
- msg = strchr(logmsg, '\t');
- if (!msg)
- msg = "(none)";
- else
- msg++;
- m = xmalloc(strlen(msg) + 200);
- sprintf(m, "(%s) %s",
- show_date(timestamp, tz, 1),
- msg);
- reflog_msg[i] = m;
- free(logmsg);
- sprintf(nth_desc, "%s@{%d}", *av, base+i);
- append_ref(nth_desc, sha1, 1);
- }
- }
- else if (all_heads + all_remotes)
- snarf_refs(all_heads, all_remotes);
- else {
- while (0 < ac) {
- append_one_rev(*av);
- ac--; av++;
- }
- }
-
- head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
- if (head_p) {
- head_len = strlen(head_p);
- memcpy(head, head_p, head_len + 1);
- }
- else {
- head_len = 0;
- head[0] = 0;
- }
-
- if (with_current_branch && head_p) {
- int has_head = 0;
- for (i = 0; !has_head && i < ref_name_cnt; i++) {
- /* We are only interested in adding the branch
- * HEAD points at.
- */
- if (rev_is_head(head,
- head_len,
- ref_name[i],
- head_sha1, NULL))
- has_head++;
- }
- if (!has_head) {
- int offset = !prefixcmp(head, "refs/heads/") ? 11 : 0;
- append_one_rev(head + offset);
- }
- }
-
- if (!ref_name_cnt) {
- fprintf(stderr, "No revs to be shown.\n");
- exit(0);
- }
-
- for (num_rev = 0; ref_name[num_rev]; num_rev++) {
- unsigned char revkey[20];
- unsigned int flag = 1u << (num_rev + REV_SHIFT);
-
- if (MAX_REVS <= num_rev)
- die("cannot handle more than %d revs.", MAX_REVS);
- if (get_sha1(ref_name[num_rev], revkey))
- die("'%s' is not a valid ref.", ref_name[num_rev]);
- commit = lookup_commit_reference(revkey);
- if (!commit)
- die("cannot find commit %s (%s)",
- ref_name[num_rev], revkey);
- parse_commit(commit);
- mark_seen(commit, &seen);
-
- /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
- * and so on. REV_SHIFT bits from bit 0 are used for
- * internal bookkeeping.
- */
- commit->object.flags |= flag;
- if (commit->object.flags == flag)
- insert_by_date(commit, &list);
- rev[num_rev] = commit;
- }
- for (i = 0; i < num_rev; i++)
- rev_mask[i] = rev[i]->object.flags;
-
- if (0 <= extra)
- join_revs(&list, &seen, num_rev, extra);
-
- sort_by_date(&seen);
-
- if (merge_base)
- return show_merge_base(seen, num_rev);
-
- if (independent)
- return show_independent(rev, num_rev, ref_name, rev_mask);
-
- /* Show list; --more=-1 means list-only */
- if (1 < num_rev || extra < 0) {
- for (i = 0; i < num_rev; i++) {
- int j;
- int is_head = rev_is_head(head,
- head_len,
- ref_name[i],
- head_sha1,
- rev[i]->object.sha1);
- if (extra < 0)
- printf("%c [%s] ",
- is_head ? '*' : ' ', ref_name[i]);
- else {
- for (j = 0; j < i; j++)
- putchar(' ');
- printf("%s%c%s [%s] ",
- get_color_code(i % COLUMN_COLORS_MAX),
- is_head ? '*' : '!',
- get_color_reset_code(), ref_name[i]);
- }
-
- if (!reflog) {
- /* header lines never need name */
- show_one_commit(rev[i], 1);
- }
- else
- puts(reflog_msg[i]);
-
- if (is_head)
- head_at = i;
- }
- if (0 <= extra) {
- for (i = 0; i < num_rev; i++)
- putchar('-');
- putchar('\n');
- }
- }
- if (extra < 0)
- exit(0);
-
- /* Sort topologically */
- sort_in_topological_order(&seen, lifo);
-
- /* Give names to commits */
- if (!sha1_name && !no_name)
- name_commits(seen, rev, ref_name, num_rev);
-
- all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
- all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
-
- while (seen) {
- struct commit *commit = pop_one_commit(&seen);
- int this_flag = commit->object.flags;
- int is_merge_point = ((this_flag & all_revs) == all_revs);
-
- shown_merge_point |= is_merge_point;
-
- if (1 < num_rev) {
- int is_merge = !!(commit->parents &&
- commit->parents->next);
- if (topics &&
- !is_merge_point &&
- (this_flag & (1u << REV_SHIFT)))
- continue;
- if (dense && is_merge &&
- omit_in_dense(commit, rev, num_rev))
- continue;
- for (i = 0; i < num_rev; i++) {
- int mark;
- if (!(this_flag & (1u << (i + REV_SHIFT))))
- mark = ' ';
- else if (is_merge)
- mark = '-';
- else if (i == head_at)
- mark = '*';
- else
- mark = '+';
- printf("%s%c%s",
- get_color_code(i % COLUMN_COLORS_MAX),
- mark, get_color_reset_code());
- }
- putchar(' ');
- }
- show_one_commit(commit, no_name);
-
- if (shown_merge_point && --extra < 0)
- break;
- }
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "refs.h"
-#include "object.h"
-#include "tag.h"
-#include "string-list.h"
-#include "parse-options.h"
-
-static const char * const show_ref_usage[] = {
- "git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] ",
- "git show-ref --exclude-existing[=pattern] < ref-list",
- NULL
-};
-
-static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
- quiet, hash_only, abbrev, exclude_arg;
-static const char **pattern;
-static const char *exclude_existing_arg;
-
-static void show_one(const char *refname, const unsigned char *sha1)
-{
- const char *hex = find_unique_abbrev(sha1, abbrev);
- if (hash_only)
- printf("%s\n", hex);
- else
- printf("%s %s\n", hex, refname);
-}
-
-static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
-{
- struct object *obj;
- const char *hex;
- unsigned char peeled[20];
-
- if (tags_only || heads_only) {
- int match;
-
- match = heads_only && !prefixcmp(refname, "refs/heads/");
- match |= tags_only && !prefixcmp(refname, "refs/tags/");
- if (!match)
- return 0;
- }
- if (pattern) {
- int reflen = strlen(refname);
- const char **p = pattern, *m;
- while ((m = *p++) != NULL) {
- int len = strlen(m);
- if (len > reflen)
- continue;
- if (memcmp(m, refname + reflen - len, len))
- continue;
- if (len == reflen)
- goto match;
- /* "--verify" requires an exact match */
- if (verify)
- continue;
- if (refname[reflen - len - 1] == '/')
- goto match;
- }
- return 0;
- }
-
-match:
- found_match++;
-
- /* This changes the semantics slightly that even under quiet we
- * detect and return error if the repository is corrupt and
- * ref points at a nonexistent object.
- */
- if (!has_sha1_file(sha1))
- die("git show-ref: bad ref %s (%s)", refname,
- sha1_to_hex(sha1));
-
- if (quiet)
- return 0;
-
- show_one(refname, sha1);
-
- if (!deref_tags)
- return 0;
-
- if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) {
- if (!is_null_sha1(peeled)) {
- hex = find_unique_abbrev(peeled, abbrev);
- printf("%s %s^{}\n", hex, refname);
- }
- }
- else {
- obj = parse_object(sha1);
- if (!obj)
- die("git show-ref: bad ref %s (%s)", refname,
- sha1_to_hex(sha1));
- if (obj->type == OBJ_TAG) {
- obj = deref_tag(obj, refname, 0);
- if (!obj)
- die("git show-ref: bad tag at ref %s (%s)", refname,
- sha1_to_hex(sha1));
- hex = find_unique_abbrev(obj->sha1, abbrev);
- printf("%s %s^{}\n", hex, refname);
- }
- }
- return 0;
-}
-
-static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
-{
- struct string_list *list = (struct string_list *)cbdata;
- string_list_insert(refname, list);
- return 0;
-}
-
-/*
- * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
- * and
- * (1) strip "^{}" at the end of line if any;
- * (2) ignore if match is provided and does not head-match refname;
- * (3) warn if refname is not a well-formed refname and skip;
- * (4) ignore if refname is a ref that exists in the local repository;
- * (5) otherwise output the line.
- */
-static int exclude_existing(const char *match)
-{
- static struct string_list existing_refs = { NULL, 0, 0, 0 };
- char buf[1024];
- int matchlen = match ? strlen(match) : 0;
-
- for_each_ref(add_existing, &existing_refs);
- while (fgets(buf, sizeof(buf), stdin)) {
- char *ref;
- int len = strlen(buf);
-
- if (len > 0 && buf[len - 1] == '\n')
- buf[--len] = '\0';
- if (3 <= len && !strcmp(buf + len - 3, "^{}")) {
- len -= 3;
- buf[len] = '\0';
- }
- for (ref = buf + len; buf < ref; ref--)
- if (isspace(ref[-1]))
- break;
- if (match) {
- int reflen = buf + len - ref;
- if (reflen < matchlen)
- continue;
- if (strncmp(ref, match, matchlen))
- continue;
- }
- if (check_ref_format(ref)) {
- warning("ref '%s' ignored", ref);
- continue;
- }
- if (!string_list_has_string(&existing_refs, ref)) {
- printf("%s\n", buf);
- }
- }
- return 0;
-}
-
-static int hash_callback(const struct option *opt, const char *arg, int unset)
-{
- hash_only = 1;
- /* Use full length SHA1 if no argument */
- if (!arg)
- return 0;
- return parse_opt_abbrev_cb(opt, arg, unset);
-}
-
-static int exclude_existing_callback(const struct option *opt, const char *arg,
- int unset)
-{
- exclude_arg = 1;
- *(const char **)opt->value = arg;
- return 0;
-}
-
-static int help_callback(const struct option *opt, const char *arg, int unset)
-{
- return -1;
-}
-
-static const struct option show_ref_options[] = {
- OPT_BOOLEAN(0, "tags", &tags_only, "only show tags (can be combined with heads)"),
- OPT_BOOLEAN(0, "heads", &heads_only, "only show heads (can be combined with tags)"),
- OPT_BOOLEAN(0, "verify", &verify, "stricter reference checking, "
- "requires exact ref path"),
- { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL,
- "show the HEAD reference",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- OPT_BOOLEAN(0, "head", &show_head, "show the HEAD reference"),
- OPT_BOOLEAN('d', "dereference", &deref_tags,
- "dereference tags into object IDs"),
- { OPTION_CALLBACK, 's', "hash", &abbrev, "n",
- "only show SHA1 hash using <n> digits",
- PARSE_OPT_OPTARG, &hash_callback },
- OPT__ABBREV(&abbrev),
- OPT__QUIET(&quiet),
- { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg,
- "pattern", "show refs from stdin that aren't in local repository",
- PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback },
- { OPTION_CALLBACK, 0, "help-all", NULL, NULL, "show usage",
- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
- OPT_END()
-};
-
-int cmd_show_ref(int argc, const char **argv, const char *prefix)
-{
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage_with_options(show_ref_usage, show_ref_options);
-
- argc = parse_options(argc, argv, prefix, show_ref_options,
- show_ref_usage, PARSE_OPT_NO_INTERNAL_HELP);
-
- if (exclude_arg)
- return exclude_existing(exclude_existing_arg);
-
- pattern = argv;
- if (!*pattern)
- pattern = NULL;
-
- if (verify) {
- if (!pattern)
- die("--verify requires a reference");
- while (*pattern) {
- unsigned char sha1[20];
-
- if (!prefixcmp(*pattern, "refs/") &&
- resolve_ref(*pattern, sha1, 1, NULL)) {
- if (!quiet)
- show_one(*pattern, sha1);
- }
- else if (!quiet)
- die("'%s' - not a valid ref", *pattern);
- else
- return 1;
- pattern++;
- }
- return 0;
- }
-
- if (show_head)
- head_ref(show_ref, NULL);
- for_each_ref(show_ref, NULL);
- if (!found_match) {
- if (verify && !quiet)
- die("No match");
- return 1;
- }
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-
-/*
- * Returns the length of a line, without trailing spaces.
- *
- * If the line ends with newline, it will be removed too.
- */
-static size_t cleanup(char *line, size_t len)
-{
- while (len) {
- unsigned char c = line[len - 1];
- if (!isspace(c))
- break;
- len--;
- }
-
- return len;
-}
-
-/*
- * 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 does not have a newline at the end, one is added.
- *
- * Enable skip_comments to skip every line starting with "#".
- */
-void stripspace(struct strbuf *sb, int skip_comments)
-{
- int empties = 0;
- size_t i, j, len, newlen;
- char *eol;
-
- /* We may have to add a newline. */
- strbuf_grow(sb, 1);
-
- for (i = j = 0; i < sb->len; i += len, j += newlen) {
- eol = memchr(sb->buf + i, '\n', sb->len - i);
- len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
-
- if (skip_comments && len && sb->buf[i] == '#') {
- newlen = 0;
- continue;
- }
- newlen = cleanup(sb->buf + i, len);
-
- /* Not just an empty line? */
- if (newlen) {
- if (empties > 0 && j > 0)
- sb->buf[j++] = '\n';
- empties = 0;
- memmove(sb->buf + j, sb->buf + i, newlen);
- sb->buf[newlen + j++] = '\n';
- } else {
- empties++;
- }
- }
-
- strbuf_setlen(sb, j);
-}
-
-int cmd_stripspace(int argc, const char **argv, const char *prefix)
-{
- struct strbuf buf = STRBUF_INIT;
- int strip_comments = 0;
-
- if (argc == 2 && (!strcmp(argv[1], "-s") ||
- !strcmp(argv[1], "--strip-comments")))
- strip_comments = 1;
- else if (argc > 1)
- usage("git stripspace [-s | --strip-comments] < <stream>");
-
- if (strbuf_read(&buf, 0, 1024) < 0)
- die_errno("could not read the input");
-
- stripspace(&buf, strip_comments);
-
- write_or_die(1, buf.buf, buf.len);
- strbuf_release(&buf);
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "refs.h"
-#include "parse-options.h"
-
-static const char * const git_symbolic_ref_usage[] = {
- "git symbolic-ref [options] name [ref]",
- NULL
-};
-
-static void check_symref(const char *HEAD, int quiet)
-{
- unsigned char sha1[20];
- int flag;
- const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
-
- if (!refs_heads_master)
- die("No such ref: %s", HEAD);
- else if (!(flag & REF_ISSYMREF)) {
- if (!quiet)
- die("ref %s is not a symbolic ref", HEAD);
- else
- exit(1);
- }
- puts(refs_heads_master);
-}
-
-int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
-{
- int quiet = 0;
- const char *msg = NULL;
- struct option options[] = {
- OPT__QUIET(&quiet),
- OPT_STRING('m', NULL, &msg, "reason", "reason of the update"),
- OPT_END(),
- };
-
- git_config(git_default_config, NULL);
- argc = parse_options(argc, argv, prefix, options,
- git_symbolic_ref_usage, 0);
- if (msg &&!*msg)
- die("Refusing to perform update with empty message");
- switch (argc) {
- case 1:
- check_symref(argv[0], quiet);
- break;
- case 2:
- if (!strcmp(argv[0], "HEAD") &&
- prefixcmp(argv[1], "refs/"))
- die("Refusing to point HEAD outside of refs/");
- create_symref(argv[0], argv[1], msg);
- break;
- default:
- usage_with_options(git_symbolic_ref_usage, options);
- }
- return 0;
-}
+++ /dev/null
-/*
- * Builtin "git tag"
- *
- * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
- * Carlos Rica <jasampler@gmail.com>
- * Based on git-tag.sh and mktag.c by Linus Torvalds.
- */
-
-#include "cache.h"
-#include "builtin.h"
-#include "refs.h"
-#include "tag.h"
-#include "run-command.h"
-#include "parse-options.h"
-
-static const char * const git_tag_usage[] = {
- "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
- "git tag -d <tagname>...",
- "git tag -l [-n[<num>]] [<pattern>]",
- "git tag -v <tagname>...",
- NULL
-};
-
-static char signingkey[1000];
-
-struct tag_filter {
- const char *pattern;
- int lines;
- struct commit_list *with_commit;
-};
-
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-
-static int show_reference(const char *refname, const unsigned char *sha1,
- int flag, void *cb_data)
-{
- struct tag_filter *filter = cb_data;
-
- if (!fnmatch(filter->pattern, refname, 0)) {
- int i;
- unsigned long size;
- enum object_type type;
- char *buf, *sp, *eol;
- size_t len;
-
- if (filter->with_commit) {
- struct commit *commit;
-
- commit = lookup_commit_reference_gently(sha1, 1);
- if (!commit)
- return 0;
- if (!is_descendant_of(commit, filter->with_commit))
- return 0;
- }
-
- if (!filter->lines) {
- printf("%s\n", refname);
- return 0;
- }
- printf("%-15s ", refname);
-
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf || !size)
- return 0;
-
- /* skip header */
- sp = strstr(buf, "\n\n");
- if (!sp) {
- free(buf);
- return 0;
- }
- /* only take up to "lines" lines, and strip the signature */
- for (i = 0, sp += 2;
- i < filter->lines && sp < buf + size &&
- prefixcmp(sp, PGP_SIGNATURE "\n");
- i++) {
- if (i)
- printf("\n ");
- eol = memchr(sp, '\n', size - (sp - buf));
- len = eol ? eol - sp : size - (sp - buf);
- fwrite(sp, len, 1, stdout);
- if (!eol)
- break;
- sp = eol + 1;
- }
- putchar('\n');
- free(buf);
- }
-
- return 0;
-}
-
-static int list_tags(const char *pattern, int lines,
- struct commit_list *with_commit)
-{
- struct tag_filter filter;
-
- if (pattern == NULL)
- pattern = "*";
-
- filter.pattern = pattern;
- filter.lines = lines;
- filter.with_commit = with_commit;
-
- for_each_tag_ref(show_reference, (void *) &filter);
-
- return 0;
-}
-
-typedef int (*each_tag_name_fn)(const char *name, const char *ref,
- const unsigned char *sha1);
-
-static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
-{
- const char **p;
- char ref[PATH_MAX];
- int had_error = 0;
- unsigned char sha1[20];
-
- for (p = argv; *p; p++) {
- if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p)
- >= sizeof(ref)) {
- error("tag name too long: %.*s...", 50, *p);
- had_error = 1;
- continue;
- }
- if (!resolve_ref(ref, sha1, 1, NULL)) {
- error("tag '%s' not found.", *p);
- had_error = 1;
- continue;
- }
- if (fn(*p, ref, sha1))
- had_error = 1;
- }
- return had_error;
-}
-
-static int delete_tag(const char *name, const char *ref,
- const unsigned char *sha1)
-{
- if (delete_ref(ref, sha1, 0))
- return 1;
- printf("Deleted tag '%s' (was %s)\n", name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
- return 0;
-}
-
-static int verify_tag(const char *name, const char *ref,
- const unsigned char *sha1)
-{
- const char *argv_verify_tag[] = {"git-verify-tag",
- "-v", "SHA1_HEX", NULL};
- argv_verify_tag[2] = sha1_to_hex(sha1);
-
- if (run_command_v_opt(argv_verify_tag, 0))
- return error("could not verify the tag '%s'", name);
- return 0;
-}
-
-static int do_sign(struct strbuf *buffer)
-{
- struct child_process gpg;
- const char *args[4];
- char *bracket;
- int len;
- int i, j;
-
- if (!*signingkey) {
- if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
- sizeof(signingkey)) > sizeof(signingkey) - 1)
- return error("committer info too long.");
- bracket = strchr(signingkey, '>');
- if (bracket)
- bracket[1] = '\0';
- }
-
- /* When the username signingkey is bad, program could be terminated
- * because gpg exits without reading and then write gets SIGPIPE. */
- signal(SIGPIPE, SIG_IGN);
-
- memset(&gpg, 0, sizeof(gpg));
- gpg.argv = args;
- gpg.in = -1;
- gpg.out = -1;
- args[0] = "gpg";
- args[1] = "-bsau";
- args[2] = signingkey;
- args[3] = NULL;
-
- if (start_command(&gpg))
- return error("could not run gpg.");
-
- if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
- close(gpg.in);
- close(gpg.out);
- finish_command(&gpg);
- return error("gpg did not accept the tag data");
- }
- close(gpg.in);
- len = strbuf_read(buffer, gpg.out, 1024);
- close(gpg.out);
-
- if (finish_command(&gpg) || !len || len < 0)
- return error("gpg failed to sign the tag");
-
- /* Strip CR from the line endings, in case we are on Windows. */
- for (i = j = 0; i < buffer->len; i++)
- if (buffer->buf[i] != '\r') {
- if (i != j)
- buffer->buf[j] = buffer->buf[i];
- j++;
- }
- strbuf_setlen(buffer, j);
-
- return 0;
-}
-
-static const char tag_template[] =
- "\n"
- "#\n"
- "# Write a tag message\n"
- "#\n";
-
-static void set_signingkey(const char *value)
-{
- if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))
- die("signing key value too long (%.10s...)", value);
-}
-
-static int git_tag_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "user.signingkey")) {
- if (!value)
- return config_error_nonbool(var);
- set_signingkey(value);
- return 0;
- }
-
- return git_default_config(var, value, cb);
-}
-
-static void write_tag_body(int fd, const unsigned char *sha1)
-{
- unsigned long size;
- enum object_type type;
- char *buf, *sp, *eob;
- size_t len;
-
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf)
- return;
- /* skip header */
- sp = strstr(buf, "\n\n");
-
- if (!sp || !size || type != OBJ_TAG) {
- free(buf);
- return;
- }
- sp += 2; /* skip the 2 LFs */
- eob = strstr(sp, "\n" PGP_SIGNATURE "\n");
- if (eob)
- len = eob - sp;
- else
- len = buf + size - sp;
- write_or_die(fd, sp, len);
-
- free(buf);
-}
-
-static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
-{
- if (sign && do_sign(buf) < 0)
- return error("unable to sign the tag");
- if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0)
- return error("unable to write tag file");
- return 0;
-}
-
-static void create_tag(const unsigned char *object, const char *tag,
- struct strbuf *buf, int message, int sign,
- unsigned char *prev, unsigned char *result)
-{
- enum object_type type;
- char header_buf[1024];
- int header_len;
- char *path = NULL;
-
- type = sha1_object_info(object, NULL);
- if (type <= OBJ_NONE)
- die("bad object type.");
-
- header_len = snprintf(header_buf, sizeof(header_buf),
- "object %s\n"
- "type %s\n"
- "tag %s\n"
- "tagger %s\n\n",
- sha1_to_hex(object),
- typename(type),
- tag,
- git_committer_info(IDENT_ERROR_ON_NO_NAME));
-
- if (header_len > sizeof(header_buf) - 1)
- die("tag header too big.");
-
- if (!message) {
- int fd;
-
- /* write the template message before editing: */
- path = git_pathdup("TAG_EDITMSG");
- fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
- if (fd < 0)
- die_errno("could not create file '%s'", path);
-
- if (!is_null_sha1(prev))
- write_tag_body(fd, prev);
- else
- write_or_die(fd, tag_template, strlen(tag_template));
- close(fd);
-
- if (launch_editor(path, buf, NULL)) {
- fprintf(stderr,
- "Please supply the message using either -m or -F option.\n");
- exit(1);
- }
- }
-
- stripspace(buf, 1);
-
- if (!message && !buf->len)
- die("no tag message?");
-
- strbuf_insert(buf, 0, header_buf, header_len);
-
- if (build_tag_object(buf, sign, result) < 0) {
- if (path)
- fprintf(stderr, "The tag message has been left in %s\n",
- path);
- exit(128);
- }
- if (path) {
- unlink_or_warn(path);
- free(path);
- }
-}
-
-struct msg_arg {
- int given;
- struct strbuf buf;
-};
-
-static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
-{
- struct msg_arg *msg = opt->value;
-
- if (!arg)
- return -1;
- if (msg->buf.len)
- strbuf_addstr(&(msg->buf), "\n\n");
- strbuf_addstr(&(msg->buf), arg);
- msg->given = 1;
- return 0;
-}
-
-int cmd_tag(int argc, const char **argv, const char *prefix)
-{
- struct strbuf buf = STRBUF_INIT;
- unsigned char object[20], prev[20];
- char ref[PATH_MAX];
- const char *object_ref, *tag;
- struct ref_lock *lock;
-
- int annotate = 0, sign = 0, force = 0, lines = -1,
- list = 0, delete = 0, verify = 0;
- const char *msgfile = NULL, *keyid = NULL;
- struct msg_arg msg = { 0, STRBUF_INIT };
- struct commit_list *with_commit = NULL;
- struct option options[] = {
- OPT_BOOLEAN('l', NULL, &list, "list tag names"),
- { OPTION_INTEGER, 'n', NULL, &lines, "n",
- "print <n> lines of each tag message",
- PARSE_OPT_OPTARG, NULL, 1 },
- OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
- OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
-
- OPT_GROUP("Tag creation options"),
- OPT_BOOLEAN('a', NULL, &annotate,
- "annotated tag, needs a message"),
- OPT_CALLBACK('m', NULL, &msg, "msg",
- "message for the tag", parse_msg_arg),
- OPT_FILENAME('F', NULL, &msgfile, "message in a file"),
- OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
- OPT_STRING('u', NULL, &keyid, "key-id",
- "use another key to sign the tag"),
- OPT_BOOLEAN('f', "force", &force, "replace the tag if exists"),
-
- OPT_GROUP("Tag listing options"),
- {
- OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
- "print only tags that contain the commit",
- PARSE_OPT_LASTARG_DEFAULT,
- parse_opt_with_commit, (intptr_t)"HEAD",
- },
- OPT_END()
- };
-
- git_config(git_tag_config, NULL);
-
- argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
-
- if (keyid) {
- sign = 1;
- set_signingkey(keyid);
- }
- if (sign)
- annotate = 1;
- if (argc == 0 && !(delete || verify))
- list = 1;
-
- if ((annotate || msg.given || msgfile || force) &&
- (list || delete || verify))
- usage_with_options(git_tag_usage, options);
-
- if (list + delete + verify > 1)
- usage_with_options(git_tag_usage, options);
- if (list)
- return list_tags(argv[0], lines == -1 ? 0 : lines,
- with_commit);
- if (lines != -1)
- die("-n option is only allowed with -l.");
- if (with_commit)
- die("--contains option is only allowed with -l.");
- if (delete)
- return for_each_tag_name(argv, delete_tag);
- if (verify)
- return for_each_tag_name(argv, verify_tag);
-
- if (msg.given || msgfile) {
- if (msg.given && msgfile)
- die("only one -F or -m option is allowed.");
- annotate = 1;
- if (msg.given)
- strbuf_addbuf(&buf, &(msg.buf));
- else {
- if (!strcmp(msgfile, "-")) {
- if (strbuf_read(&buf, 0, 1024) < 0)
- die_errno("cannot read '%s'", msgfile);
- } else {
- if (strbuf_read_file(&buf, msgfile, 1024) < 0)
- die_errno("could not open or read '%s'",
- msgfile);
- }
- }
- }
-
- tag = argv[0];
-
- object_ref = argc == 2 ? argv[1] : "HEAD";
- if (argc > 2)
- die("too many params");
-
- if (get_sha1(object_ref, object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
-
- if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1)
- die("tag name too long: %.*s...", 50, tag);
- if (check_ref_format(ref))
- die("'%s' is not a valid tag name.", tag);
-
- if (!resolve_ref(ref, prev, 1, NULL))
- hashclr(prev);
- else if (!force)
- die("tag '%s' already exists", tag);
-
- if (annotate)
- create_tag(object, tag, &buf, msg.given || msgfile,
- sign, prev, object);
-
- lock = lock_any_ref_for_update(ref, prev, 0);
- if (!lock)
- die("%s: cannot lock the ref", ref);
- if (write_ref_sha1(lock, object, NULL) < 0)
- die("%s: cannot update the ref", ref);
- if (force && hashcmp(prev, object))
- printf("Updated tag '%s' (was %s)\n", tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
-
- strbuf_release(&buf);
- return 0;
-}
+++ /dev/null
-/*
- * Copyright (c) 2005, 2006 Rene Scharfe
- */
-#include "cache.h"
-#include "commit.h"
-#include "tar.h"
-#include "builtin.h"
-#include "quote.h"
-
-static const char tar_tree_usage[] =
-"git tar-tree [--remote=<repo>] <tree-ish> [basedir]\n"
-"*** Note that this command is now deprecated; use \"git archive\" instead.";
-
-static const char builtin_get_tar_commit_id_usage[] =
-"git get-tar-commit-id < <tarfile>";
-
-int cmd_tar_tree(int argc, const char **argv, const char *prefix)
-{
- /*
- * "git tar-tree" is now a wrapper around "git archive --format=tar"
- *
- * $0 --remote=<repo> arg... ==>
- * git archive --format=tar --remote=<repo> arg...
- * $0 tree-ish ==>
- * git archive --format=tar tree-ish
- * $0 tree-ish basedir ==>
- * git archive --format-tar --prefix=basedir tree-ish
- */
- int i;
- const char **nargv = xcalloc(sizeof(*nargv), argc + 3);
- char *basedir_arg;
- int nargc = 0;
-
- nargv[nargc++] = "archive";
- nargv[nargc++] = "--format=tar";
-
- if (2 <= argc && !prefixcmp(argv[1], "--remote=")) {
- nargv[nargc++] = argv[1];
- argv++;
- argc--;
- }
-
- /*
- * Because it's just a compatibility wrapper, tar-tree supports only
- * the old behaviour of reading attributes from the work tree.
- */
- nargv[nargc++] = "--worktree-attributes";
-
- switch (argc) {
- default:
- usage(tar_tree_usage);
- break;
- case 3:
- /* base-path */
- basedir_arg = xmalloc(strlen(argv[2]) + 11);
- sprintf(basedir_arg, "--prefix=%s/", argv[2]);
- nargv[nargc++] = basedir_arg;
- /* fallthru */
- case 2:
- /* tree-ish */
- nargv[nargc++] = argv[1];
- }
- nargv[nargc] = NULL;
-
- fprintf(stderr,
- "*** \"git tar-tree\" is now deprecated.\n"
- "*** Running \"git archive\" instead.\n***");
- for (i = 0; i < nargc; i++) {
- fputc(' ', stderr);
- sq_quote_print(stderr, nargv[i]);
- }
- fputc('\n', stderr);
- return cmd_archive(nargc, nargv, prefix);
-}
-
-/* ustar header + extended global header content */
-#define RECORDSIZE (512)
-#define HEADERSIZE (2 * RECORDSIZE)
-
-int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
-{
- char buffer[HEADERSIZE];
- struct ustar_header *header = (struct ustar_header *)buffer;
- char *content = buffer + RECORDSIZE;
- ssize_t n;
-
- if (argc != 1)
- usage(builtin_get_tar_commit_id_usage);
-
- n = read_in_full(0, buffer, HEADERSIZE);
- if (n < HEADERSIZE)
- die("git get-tar-commit-id: read error");
- if (header->typeflag[0] != 'g')
- return 1;
- if (memcmp(content, "52 comment=", 11))
- return 1;
-
- n = write_in_full(1, content + 11, 41);
- if (n < 41)
- die_errno("git get-tar-commit-id: write error");
-
- return 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "blob.h"
-#include "exec_cmd.h"
-
-static char *create_temp_file(unsigned char *sha1)
-{
- static char path[50];
- void *buf;
- enum object_type type;
- unsigned long size;
- int fd;
-
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf || type != OBJ_BLOB)
- die("unable to read blob object %s", sha1_to_hex(sha1));
-
- strcpy(path, ".merge_file_XXXXXX");
- fd = xmkstemp(path);
- if (write_in_full(fd, buf, size) != size)
- die_errno("unable to write temp-file");
- close(fd);
- return path;
-}
-
-int cmd_unpack_file(int argc, const char **argv, const char *prefix)
-{
- unsigned char sha1[20];
-
- if (argc != 2 || !strcmp(argv[1], "-h"))
- usage("git unpack-file <sha1>");
- if (get_sha1(argv[1], sha1))
- die("Not a valid object name %s", argv[1]);
-
- git_config(git_default_config, NULL);
-
- puts(create_temp_file(sha1));
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "object.h"
-#include "delta.h"
-#include "pack.h"
-#include "blob.h"
-#include "commit.h"
-#include "tag.h"
-#include "tree.h"
-#include "tree-walk.h"
-#include "progress.h"
-#include "decorate.h"
-#include "fsck.h"
-
-static int dry_run, quiet, recover, has_errors, strict;
-static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict] < pack-file";
-
-/* We always read in 4kB chunks. */
-static unsigned char buffer[4096];
-static unsigned int offset, len;
-static off_t consumed_bytes;
-static git_SHA_CTX ctx;
-
-/*
- * When running under --strict mode, objects whose reachability are
- * suspect are kept in core without getting written in the object
- * store.
- */
-struct obj_buffer {
- char *buffer;
- unsigned long size;
-};
-
-static struct decoration obj_decorate;
-
-static struct obj_buffer *lookup_object_buffer(struct object *base)
-{
- return lookup_decoration(&obj_decorate, base);
-}
-
-static void add_object_buffer(struct object *object, char *buffer, unsigned long size)
-{
- struct obj_buffer *obj;
- obj = xcalloc(1, sizeof(struct obj_buffer));
- obj->buffer = buffer;
- obj->size = size;
- if (add_decoration(&obj_decorate, object, obj))
- die("object %s tried to add buffer twice!", sha1_to_hex(object->sha1));
-}
-
-/*
- * Make sure at least "min" bytes are available in the buffer, and
- * return the pointer to the buffer.
- */
-static void *fill(int min)
-{
- if (min <= len)
- return buffer + offset;
- if (min > sizeof(buffer))
- die("cannot fill %d bytes", min);
- if (offset) {
- git_SHA1_Update(&ctx, buffer, offset);
- memmove(buffer, buffer + offset, len);
- offset = 0;
- }
- do {
- ssize_t ret = xread(0, buffer + len, sizeof(buffer) - len);
- if (ret <= 0) {
- if (!ret)
- die("early EOF");
- die_errno("read error on input");
- }
- len += ret;
- } while (len < min);
- return buffer;
-}
-
-static void use(int bytes)
-{
- if (bytes > len)
- die("used more bytes than were available");
- len -= bytes;
- offset += bytes;
-
- /* make sure off_t is sufficiently large not to wrap */
- if (consumed_bytes > consumed_bytes + bytes)
- die("pack too large for current definition of off_t");
- consumed_bytes += bytes;
-}
-
-static void *get_data(unsigned long size)
-{
- z_stream stream;
- void *buf = xmalloc(size);
-
- memset(&stream, 0, sizeof(stream));
-
- stream.next_out = buf;
- stream.avail_out = size;
- stream.next_in = fill(1);
- stream.avail_in = len;
- git_inflate_init(&stream);
-
- for (;;) {
- int ret = git_inflate(&stream, 0);
- use(len - stream.avail_in);
- if (stream.total_out == size && ret == Z_STREAM_END)
- break;
- if (ret != Z_OK) {
- error("inflate returned %d\n", ret);
- free(buf);
- buf = NULL;
- if (!recover)
- exit(1);
- has_errors = 1;
- break;
- }
- stream.next_in = fill(1);
- stream.avail_in = len;
- }
- git_inflate_end(&stream);
- return buf;
-}
-
-struct delta_info {
- unsigned char base_sha1[20];
- unsigned nr;
- off_t base_offset;
- unsigned long size;
- void *delta;
- struct delta_info *next;
-};
-
-static struct delta_info *delta_list;
-
-static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
- off_t base_offset,
- void *delta, unsigned long size)
-{
- struct delta_info *info = xmalloc(sizeof(*info));
-
- hashcpy(info->base_sha1, base_sha1);
- info->base_offset = base_offset;
- info->size = size;
- info->delta = delta;
- info->nr = nr;
- info->next = delta_list;
- delta_list = info;
-}
-
-struct obj_info {
- off_t offset;
- unsigned char sha1[20];
- struct object *obj;
-};
-
-#define FLAG_OPEN (1u<<20)
-#define FLAG_WRITTEN (1u<<21)
-
-static struct obj_info *obj_list;
-static unsigned nr_objects;
-
-/*
- * Called only from check_object() after it verified this object
- * is Ok.
- */
-static void write_cached_object(struct object *obj)
-{
- unsigned char sha1[20];
- struct obj_buffer *obj_buf = lookup_object_buffer(obj);
- if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0)
- die("failed to write object %s", sha1_to_hex(obj->sha1));
- obj->flags |= FLAG_WRITTEN;
-}
-
-/*
- * At the very end of the processing, write_rest() scans the objects
- * that have reachability requirements and calls this function.
- * Verify its reachability and validity recursively and write it out.
- */
-static int check_object(struct object *obj, int type, void *data)
-{
- if (!obj)
- return 1;
-
- if (obj->flags & FLAG_WRITTEN)
- return 0;
-
- if (type != OBJ_ANY && obj->type != type)
- die("object type mismatch");
-
- if (!(obj->flags & FLAG_OPEN)) {
- unsigned long size;
- int type = sha1_object_info(obj->sha1, &size);
- if (type != obj->type || type <= 0)
- die("object of unexpected type");
- obj->flags |= FLAG_WRITTEN;
- return 0;
- }
-
- if (fsck_object(obj, 1, fsck_error_function))
- die("Error in object");
- if (fsck_walk(obj, check_object, NULL))
- die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
- write_cached_object(obj);
- return 0;
-}
-
-static void write_rest(void)
-{
- unsigned i;
- for (i = 0; i < nr_objects; i++) {
- if (obj_list[i].obj)
- check_object(obj_list[i].obj, OBJ_ANY, NULL);
- }
-}
-
-static void added_object(unsigned nr, enum object_type type,
- void *data, unsigned long size);
-
-/*
- * Write out nr-th object from the list, now we know the contents
- * of it. Under --strict, this buffers structured objects in-core,
- * to be checked at the end.
- */
-static void write_object(unsigned nr, enum object_type type,
- void *buf, unsigned long size)
-{
- if (!strict) {
- if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
- die("failed to write object");
- added_object(nr, type, buf, size);
- free(buf);
- obj_list[nr].obj = NULL;
- } else if (type == OBJ_BLOB) {
- struct blob *blob;
- if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
- die("failed to write object");
- added_object(nr, type, buf, size);
- free(buf);
-
- blob = lookup_blob(obj_list[nr].sha1);
- if (blob)
- blob->object.flags |= FLAG_WRITTEN;
- else
- die("invalid blob object");
- obj_list[nr].obj = NULL;
- } else {
- struct object *obj;
- int eaten;
- hash_sha1_file(buf, size, typename(type), obj_list[nr].sha1);
- added_object(nr, type, buf, size);
- obj = parse_object_buffer(obj_list[nr].sha1, type, size, buf, &eaten);
- if (!obj)
- die("invalid %s", typename(type));
- add_object_buffer(obj, buf, size);
- obj->flags |= FLAG_OPEN;
- obj_list[nr].obj = obj;
- }
-}
-
-static void resolve_delta(unsigned nr, enum object_type type,
- void *base, unsigned long base_size,
- void *delta, unsigned long delta_size)
-{
- void *result;
- unsigned long result_size;
-
- result = patch_delta(base, base_size,
- delta, delta_size,
- &result_size);
- if (!result)
- die("failed to apply delta");
- free(delta);
- write_object(nr, type, result, result_size);
-}
-
-/*
- * We now know the contents of an object (which is nr-th in the pack);
- * resolve all the deltified objects that are based on it.
- */
-static void added_object(unsigned nr, enum object_type type,
- void *data, unsigned long size)
-{
- struct delta_info **p = &delta_list;
- struct delta_info *info;
-
- while ((info = *p) != NULL) {
- if (!hashcmp(info->base_sha1, obj_list[nr].sha1) ||
- info->base_offset == obj_list[nr].offset) {
- *p = info->next;
- p = &delta_list;
- resolve_delta(info->nr, type, data, size,
- info->delta, info->size);
- free(info);
- continue;
- }
- p = &info->next;
- }
-}
-
-static void unpack_non_delta_entry(enum object_type type, unsigned long size,
- unsigned nr)
-{
- void *buf = get_data(size);
-
- if (!dry_run && buf)
- write_object(nr, type, buf, size);
- else
- free(buf);
-}
-
-static int resolve_against_held(unsigned nr, const unsigned char *base,
- void *delta_data, unsigned long delta_size)
-{
- struct object *obj;
- struct obj_buffer *obj_buffer;
- obj = lookup_object(base);
- if (!obj)
- return 0;
- obj_buffer = lookup_object_buffer(obj);
- if (!obj_buffer)
- return 0;
- resolve_delta(nr, obj->type, obj_buffer->buffer,
- obj_buffer->size, delta_data, delta_size);
- return 1;
-}
-
-static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
- unsigned nr)
-{
- void *delta_data, *base;
- unsigned long base_size;
- unsigned char base_sha1[20];
-
- if (type == OBJ_REF_DELTA) {
- hashcpy(base_sha1, fill(20));
- use(20);
- delta_data = get_data(delta_size);
- if (dry_run || !delta_data) {
- free(delta_data);
- return;
- }
- if (has_sha1_file(base_sha1))
- ; /* Ok we have this one */
- else if (resolve_against_held(nr, base_sha1,
- delta_data, delta_size))
- return; /* we are done */
- else {
- /* cannot resolve yet --- queue it */
- hashcpy(obj_list[nr].sha1, null_sha1);
- add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size);
- return;
- }
- } else {
- unsigned base_found = 0;
- unsigned char *pack, c;
- off_t base_offset;
- unsigned lo, mid, hi;
-
- pack = fill(1);
- c = *pack;
- use(1);
- base_offset = c & 127;
- while (c & 128) {
- base_offset += 1;
- if (!base_offset || MSB(base_offset, 7))
- die("offset value overflow for delta base object");
- pack = fill(1);
- c = *pack;
- use(1);
- base_offset = (base_offset << 7) + (c & 127);
- }
- base_offset = obj_list[nr].offset - base_offset;
- if (base_offset <= 0 || base_offset >= obj_list[nr].offset)
- die("offset value out of bound for delta base object");
-
- delta_data = get_data(delta_size);
- if (dry_run || !delta_data) {
- free(delta_data);
- return;
- }
- lo = 0;
- hi = nr;
- while (lo < hi) {
- mid = (lo + hi)/2;
- if (base_offset < obj_list[mid].offset) {
- hi = mid;
- } else if (base_offset > obj_list[mid].offset) {
- lo = mid + 1;
- } else {
- hashcpy(base_sha1, obj_list[mid].sha1);
- base_found = !is_null_sha1(base_sha1);
- break;
- }
- }
- if (!base_found) {
- /*
- * The delta base object is itself a delta that
- * has not been resolved yet.
- */
- hashcpy(obj_list[nr].sha1, null_sha1);
- add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size);
- return;
- }
- }
-
- if (resolve_against_held(nr, base_sha1, delta_data, delta_size))
- return;
-
- base = read_sha1_file(base_sha1, &type, &base_size);
- if (!base) {
- error("failed to read delta-pack base object %s",
- sha1_to_hex(base_sha1));
- if (!recover)
- exit(1);
- has_errors = 1;
- return;
- }
- resolve_delta(nr, type, base, base_size, delta_data, delta_size);
- free(base);
-}
-
-static void unpack_one(unsigned nr)
-{
- unsigned shift;
- unsigned char *pack;
- unsigned long size, c;
- enum object_type type;
-
- obj_list[nr].offset = consumed_bytes;
-
- pack = fill(1);
- c = *pack;
- use(1);
- type = (c >> 4) & 7;
- size = (c & 15);
- shift = 4;
- while (c & 0x80) {
- pack = fill(1);
- c = *pack;
- use(1);
- size += (c & 0x7f) << shift;
- shift += 7;
- }
-
- switch (type) {
- case OBJ_COMMIT:
- case OBJ_TREE:
- case OBJ_BLOB:
- case OBJ_TAG:
- unpack_non_delta_entry(type, size, nr);
- return;
- case OBJ_REF_DELTA:
- case OBJ_OFS_DELTA:
- unpack_delta_entry(type, size, nr);
- return;
- default:
- error("bad object type %d", type);
- has_errors = 1;
- if (recover)
- return;
- exit(1);
- }
-}
-
-static void unpack_all(void)
-{
- int i;
- struct progress *progress = NULL;
- struct pack_header *hdr = fill(sizeof(struct pack_header));
-
- nr_objects = ntohl(hdr->hdr_entries);
-
- if (ntohl(hdr->hdr_signature) != PACK_SIGNATURE)
- die("bad pack file");
- if (!pack_version_ok(hdr->hdr_version))
- die("unknown pack file version %"PRIu32,
- ntohl(hdr->hdr_version));
- use(sizeof(struct pack_header));
-
- if (!quiet)
- progress = start_progress("Unpacking objects", nr_objects);
- obj_list = xcalloc(nr_objects, sizeof(*obj_list));
- for (i = 0; i < nr_objects; i++) {
- unpack_one(i);
- display_progress(progress, i + 1);
- }
- stop_progress(&progress);
-
- if (delta_list)
- die("unresolved deltas left after unpacking");
-}
-
-int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
-{
- int i;
- unsigned char sha1[20];
-
- read_replace_refs = 0;
-
- git_config(git_default_config, NULL);
-
- quiet = !isatty(2);
-
- for (i = 1 ; i < argc; i++) {
- const char *arg = argv[i];
-
- if (*arg == '-') {
- if (!strcmp(arg, "-n")) {
- dry_run = 1;
- continue;
- }
- if (!strcmp(arg, "-q")) {
- quiet = 1;
- continue;
- }
- if (!strcmp(arg, "-r")) {
- recover = 1;
- continue;
- }
- if (!strcmp(arg, "--strict")) {
- strict = 1;
- continue;
- }
- if (!prefixcmp(arg, "--pack_header=")) {
- struct pack_header *hdr;
- char *c;
-
- hdr = (struct pack_header *)buffer;
- hdr->hdr_signature = htonl(PACK_SIGNATURE);
- hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
- if (*c != ',')
- die("bad %s", arg);
- hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
- if (*c)
- die("bad %s", arg);
- len = sizeof(*hdr);
- continue;
- }
- usage(unpack_usage);
- }
-
- /* We don't take any non-flag arguments now.. Maybe some day */
- usage(unpack_usage);
- }
- git_SHA1_Init(&ctx);
- unpack_all();
- git_SHA1_Update(&ctx, buffer, offset);
- git_SHA1_Final(sha1, &ctx);
- if (strict)
- write_rest();
- if (hashcmp(fill(20), sha1))
- die("final sha1 did not match");
- use(20);
-
- /* Write the last part of the buffer to stdout */
- while (len) {
- int ret = xwrite(1, buffer + offset, len);
- if (ret <= 0)
- break;
- len -= ret;
- offset += ret;
- }
-
- /* All done */
- return has_errors;
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "quote.h"
-#include "cache-tree.h"
-#include "tree-walk.h"
-#include "builtin.h"
-#include "refs.h"
-#include "resolve-undo.h"
-
-/*
- * Default to not allowing changes to the list of files. The
- * tool doesn't actually care, but this makes it harder to add
- * files to the revision control by mistake by doing something
- * like "git update-index *" and suddenly having all the object
- * files be revision controlled.
- */
-static int allow_add;
-static int allow_remove;
-static int allow_replace;
-static int info_only;
-static int force_remove;
-static int verbose;
-static int mark_valid_only;
-static int mark_skip_worktree_only;
-#define MARK_FLAG 1
-#define UNMARK_FLAG 2
-
-__attribute__((format (printf, 1, 2)))
-static void report(const char *fmt, ...)
-{
- va_list vp;
-
- if (!verbose)
- return;
-
- va_start(vp, fmt);
- vprintf(fmt, vp);
- putchar('\n');
- va_end(vp);
-}
-
-static int mark_ce_flags(const char *path, int flag, int mark)
-{
- int namelen = strlen(path);
- int pos = cache_name_pos(path, namelen);
- if (0 <= pos) {
- if (mark)
- active_cache[pos]->ce_flags |= flag;
- else
- active_cache[pos]->ce_flags &= ~flag;
- cache_tree_invalidate_path(active_cache_tree, path);
- active_cache_changed = 1;
- return 0;
- }
- return -1;
-}
-
-static int remove_one_path(const char *path)
-{
- if (!allow_remove)
- return error("%s: does not exist and --remove not passed", path);
- if (remove_file_from_cache(path))
- return error("%s: cannot remove from the index", path);
- return 0;
-}
-
-/*
- * Handle a path that couldn't be lstat'ed. It's either:
- * - missing file (ENOENT or ENOTDIR). That's ok if we're
- * supposed to be removing it and the removal actually
- * succeeds.
- * - permission error. That's never ok.
- */
-static int process_lstat_error(const char *path, int err)
-{
- if (err == ENOENT || err == ENOTDIR)
- return remove_one_path(path);
- return error("lstat(\"%s\"): %s", path, strerror(errno));
-}
-
-static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
-{
- int option, size;
- struct cache_entry *ce;
-
- /* Was the old index entry already up-to-date? */
- if (old && !ce_stage(old) && !ce_match_stat(old, st, 0))
- return 0;
-
- size = cache_entry_size(len);
- ce = xcalloc(1, size);
- memcpy(ce->name, path, len);
- ce->ce_flags = len;
- fill_stat_cache_info(ce, st);
- ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
-
- if (index_path(ce->sha1, path, st, !info_only))
- return -1;
- option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
- option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
- if (add_cache_entry(ce, option))
- return error("%s: cannot add to the index - missing --add option?", path);
- return 0;
-}
-
-/*
- * Handle a path that was a directory. Four cases:
- *
- * - it's already a gitlink in the index, and we keep it that
- * way, and update it if we can (if we cannot find the HEAD,
- * we're going to keep it unchanged in the index!)
- *
- * - it's a *file* in the index, in which case it should be
- * removed as a file if removal is allowed, since it doesn't
- * exist as such any more. If removal isn't allowed, it's
- * an error.
- *
- * (NOTE! This is old and arguably fairly strange behaviour.
- * We might want to make this an error unconditionally, and
- * use "--force-remove" if you actually want to force removal).
- *
- * - it used to exist as a subdirectory (ie multiple files with
- * this particular prefix) in the index, in which case it's wrong
- * to try to update it as a directory.
- *
- * - it doesn't exist at all in the index, but it is a valid
- * git directory, and it should be *added* as a gitlink.
- */
-static int process_directory(const char *path, int len, struct stat *st)
-{
- unsigned char sha1[20];
- int pos = cache_name_pos(path, len);
-
- /* Exact match: file or existing gitlink */
- if (pos >= 0) {
- struct cache_entry *ce = active_cache[pos];
- if (S_ISGITLINK(ce->ce_mode)) {
-
- /* Do nothing to the index if there is no HEAD! */
- if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
- return 0;
-
- return add_one_path(ce, path, len, st);
- }
- /* Should this be an unconditional error? */
- return remove_one_path(path);
- }
-
- /* Inexact match: is there perhaps a subdirectory match? */
- pos = -pos-1;
- while (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos++];
-
- if (strncmp(ce->name, path, len))
- break;
- if (ce->name[len] > '/')
- break;
- if (ce->name[len] < '/')
- continue;
-
- /* Subdirectory match - error out */
- return error("%s: is a directory - add individual files instead", path);
- }
-
- /* No match - should we add it as a gitlink? */
- if (!resolve_gitlink_ref(path, "HEAD", sha1))
- return add_one_path(NULL, path, len, st);
-
- /* Error out. */
- return error("%s: is a directory - add files inside instead", path);
-}
-
-static int process_path(const char *path)
-{
- int pos, len;
- struct stat st;
- struct cache_entry *ce;
-
- len = strlen(path);
- if (has_symlink_leading_path(path, len))
- return error("'%s' is beyond a symbolic link", path);
-
- pos = cache_name_pos(path, len);
- ce = pos < 0 ? NULL : active_cache[pos];
- if (ce && ce_skip_worktree(ce)) {
- /*
- * working directory version is assumed "good"
- * so updating it does not make sense.
- * On the other hand, removing it from index should work
- */
- if (allow_remove && remove_file_from_cache(path))
- return error("%s: cannot remove from the index", path);
- return 0;
- }
-
- /*
- * First things first: get the stat information, to decide
- * what to do about the pathname!
- */
- if (lstat(path, &st) < 0)
- return process_lstat_error(path, errno);
-
- if (S_ISDIR(st.st_mode))
- return process_directory(path, len, &st);
-
- /*
- * Process a regular file
- */
- if (ce && S_ISGITLINK(ce->ce_mode))
- return error("%s is already a gitlink, not replacing", path);
-
- return add_one_path(ce, path, len, &st);
-}
-
-static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
- const char *path, int stage)
-{
- int size, len, option;
- struct cache_entry *ce;
-
- if (!verify_path(path))
- return error("Invalid path '%s'", path);
-
- len = strlen(path);
- size = cache_entry_size(len);
- ce = xcalloc(1, size);
-
- hashcpy(ce->sha1, sha1);
- memcpy(ce->name, path, len);
- ce->ce_flags = create_ce_flags(len, stage);
- ce->ce_mode = create_ce_mode(mode);
- if (assume_unchanged)
- ce->ce_flags |= CE_VALID;
- option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
- option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
- if (add_cache_entry(ce, option))
- return error("%s: cannot add to the index - missing --add option?",
- path);
- report("add '%s'", path);
- return 0;
-}
-
-static void chmod_path(int flip, const char *path)
-{
- int pos;
- struct cache_entry *ce;
- unsigned int mode;
-
- pos = cache_name_pos(path, strlen(path));
- if (pos < 0)
- goto fail;
- ce = active_cache[pos];
- mode = ce->ce_mode;
- if (!S_ISREG(mode))
- goto fail;
- switch (flip) {
- case '+':
- ce->ce_mode |= 0111; break;
- case '-':
- ce->ce_mode &= ~0111; break;
- default:
- goto fail;
- }
- cache_tree_invalidate_path(active_cache_tree, path);
- active_cache_changed = 1;
- report("chmod %cx '%s'", flip, path);
- return;
- fail:
- die("git update-index: cannot chmod %cx '%s'", flip, path);
-}
-
-static void update_one(const char *path, const char *prefix, int prefix_length)
-{
- const char *p = prefix_path(prefix, prefix_length, path);
- if (!verify_path(p)) {
- fprintf(stderr, "Ignoring path %s\n", path);
- goto free_return;
- }
- if (mark_valid_only) {
- if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG))
- die("Unable to mark file %s", path);
- goto free_return;
- }
- if (mark_skip_worktree_only) {
- if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG))
- die("Unable to mark file %s", path);
- goto free_return;
- }
-
- if (force_remove) {
- if (remove_file_from_cache(p))
- die("git update-index: unable to remove %s", path);
- report("remove '%s'", path);
- goto free_return;
- }
- if (process_path(p))
- die("Unable to process path %s", path);
- report("add '%s'", path);
- free_return:
- if (p < path || p > path + strlen(path))
- free((char *)p);
-}
-
-static void read_index_info(int line_termination)
-{
- struct strbuf buf = STRBUF_INIT;
- struct strbuf uq = STRBUF_INIT;
-
- while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
- char *ptr, *tab;
- char *path_name;
- unsigned char sha1[20];
- unsigned int mode;
- unsigned long ul;
- int stage;
-
- /* This reads lines formatted in one of three formats:
- *
- * (1) mode SP sha1 TAB path
- * The first format is what "git apply --index-info"
- * reports, and used to reconstruct a partial tree
- * that is used for phony merge base tree when falling
- * back on 3-way merge.
- *
- * (2) mode SP type SP sha1 TAB path
- * The second format is to stuff "git ls-tree" output
- * into the index file.
- *
- * (3) mode SP sha1 SP stage TAB path
- * This format is to put higher order stages into the
- * index file and matches "git ls-files --stage" output.
- */
- errno = 0;
- ul = strtoul(buf.buf, &ptr, 8);
- if (ptr == buf.buf || *ptr != ' '
- || errno || (unsigned int) ul != ul)
- goto bad_line;
- mode = ul;
-
- tab = strchr(ptr, '\t');
- if (!tab || tab - ptr < 41)
- goto bad_line;
-
- if (tab[-2] == ' ' && '0' <= tab[-1] && tab[-1] <= '3') {
- stage = tab[-1] - '0';
- ptr = tab + 1; /* point at the head of path */
- tab = tab - 2; /* point at tail of sha1 */
- }
- else {
- stage = 0;
- ptr = tab + 1; /* point at the head of path */
- }
-
- if (get_sha1_hex(tab - 40, sha1) || tab[-41] != ' ')
- goto bad_line;
-
- path_name = ptr;
- if (line_termination && path_name[0] == '"') {
- strbuf_reset(&uq);
- if (unquote_c_style(&uq, path_name, NULL)) {
- die("git update-index: bad quoting of path name");
- }
- path_name = uq.buf;
- }
-
- if (!verify_path(path_name)) {
- fprintf(stderr, "Ignoring path %s\n", path_name);
- continue;
- }
-
- if (!mode) {
- /* mode == 0 means there is no such path -- remove */
- if (remove_file_from_cache(path_name))
- die("git update-index: unable to remove %s",
- ptr);
- }
- else {
- /* mode ' ' sha1 '\t' name
- * ptr[-1] points at tab,
- * ptr[-41] is at the beginning of sha1
- */
- ptr[-42] = ptr[-1] = 0;
- if (add_cacheinfo(mode, sha1, path_name, stage))
- die("git update-index: unable to update %s",
- path_name);
- }
- continue;
-
- bad_line:
- die("malformed index info %s", buf.buf);
- }
- strbuf_release(&buf);
- strbuf_release(&uq);
-}
-
-static const char update_index_usage[] =
-"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
-
-static unsigned char head_sha1[20];
-static unsigned char merge_head_sha1[20];
-
-static struct cache_entry *read_one_ent(const char *which,
- unsigned char *ent, const char *path,
- int namelen, int stage)
-{
- unsigned mode;
- unsigned char sha1[20];
- int size;
- struct cache_entry *ce;
-
- if (get_tree_entry(ent, path, sha1, &mode)) {
- if (which)
- error("%s: not in %s branch.", path, which);
- return NULL;
- }
- if (mode == S_IFDIR) {
- if (which)
- error("%s: not a blob in %s branch.", path, which);
- return NULL;
- }
- size = cache_entry_size(namelen);
- ce = xcalloc(1, size);
-
- hashcpy(ce->sha1, sha1);
- memcpy(ce->name, path, namelen);
- ce->ce_flags = create_ce_flags(namelen, stage);
- ce->ce_mode = create_ce_mode(mode);
- return ce;
-}
-
-static int unresolve_one(const char *path)
-{
- int namelen = strlen(path);
- int pos;
- int ret = 0;
- struct cache_entry *ce_2 = NULL, *ce_3 = NULL;
-
- /* See if there is such entry in the index. */
- pos = cache_name_pos(path, namelen);
- if (0 <= pos) {
- /* already merged */
- pos = unmerge_cache_entry_at(pos);
- if (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos];
- if (ce_stage(ce) &&
- ce_namelen(ce) == namelen &&
- !memcmp(ce->name, path, namelen))
- return 0;
- }
- /* no resolve-undo information; fall back */
- } else {
- /* If there isn't, either it is unmerged, or
- * resolved as "removed" by mistake. We do not
- * want to do anything in the former case.
- */
- pos = -pos-1;
- if (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos];
- if (ce_namelen(ce) == namelen &&
- !memcmp(ce->name, path, namelen)) {
- fprintf(stderr,
- "%s: skipping still unmerged path.\n",
- path);
- goto free_return;
- }
- }
- }
-
- /* Grab blobs from given path from HEAD and MERGE_HEAD,
- * stuff HEAD version in stage #2,
- * stuff MERGE_HEAD version in stage #3.
- */
- ce_2 = read_one_ent("our", head_sha1, path, namelen, 2);
- ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3);
-
- if (!ce_2 || !ce_3) {
- ret = -1;
- goto free_return;
- }
- if (!hashcmp(ce_2->sha1, ce_3->sha1) &&
- ce_2->ce_mode == ce_3->ce_mode) {
- fprintf(stderr, "%s: identical in both, skipping.\n",
- path);
- goto free_return;
- }
-
- remove_file_from_cache(path);
- if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
- error("%s: cannot add our version to the index.", path);
- ret = -1;
- goto free_return;
- }
- if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD))
- return 0;
- error("%s: cannot add their version to the index.", path);
- ret = -1;
- free_return:
- free(ce_2);
- free(ce_3);
- return ret;
-}
-
-static void read_head_pointers(void)
-{
- if (read_ref("HEAD", head_sha1))
- die("No HEAD -- no initial commit yet?");
- if (read_ref("MERGE_HEAD", merge_head_sha1)) {
- fprintf(stderr, "Not in the middle of a merge.\n");
- exit(0);
- }
-}
-
-static int do_unresolve(int ac, const char **av,
- const char *prefix, int prefix_length)
-{
- int i;
- int err = 0;
-
- /* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we
- * are not doing a merge, so exit with success status.
- */
- read_head_pointers();
-
- for (i = 1; i < ac; i++) {
- const char *arg = av[i];
- const char *p = prefix_path(prefix, prefix_length, arg);
- err |= unresolve_one(p);
- if (p < arg || p > arg + strlen(arg))
- free((char *)p);
- }
- return err;
-}
-
-static int do_reupdate(int ac, const char **av,
- const char *prefix, int prefix_length)
-{
- /* Read HEAD and run update-index on paths that are
- * merged and already different between index and HEAD.
- */
- int pos;
- int has_head = 1;
- const char **pathspec = get_pathspec(prefix, av + 1);
-
- if (read_ref("HEAD", head_sha1))
- /* If there is no HEAD, that means it is an initial
- * commit. Update everything in the index.
- */
- has_head = 0;
- redo:
- for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
- struct cache_entry *old = NULL;
- int save_nr;
-
- if (ce_stage(ce) || !ce_path_match(ce, pathspec))
- continue;
- if (has_head)
- old = read_one_ent(NULL, head_sha1,
- ce->name, ce_namelen(ce), 0);
- if (old && ce->ce_mode == old->ce_mode &&
- !hashcmp(ce->sha1, old->sha1)) {
- free(old);
- continue; /* unchanged */
- }
- /* Be careful. The working tree may not have the
- * path anymore, in which case, under 'allow_remove',
- * or worse yet 'allow_replace', active_nr may decrease.
- */
- save_nr = active_nr;
- update_one(ce->name + prefix_length, prefix, prefix_length);
- if (save_nr != active_nr)
- goto redo;
- }
- return 0;
-}
-
-int cmd_update_index(int argc, const char **argv, const char *prefix)
-{
- int i, newfd, entries, has_errors = 0, line_termination = '\n';
- int allow_options = 1;
- int read_from_stdin = 0;
- int prefix_length = prefix ? strlen(prefix) : 0;
- char set_executable_bit = 0;
- unsigned int refresh_flags = 0;
- int lock_error = 0;
- struct lock_file *lock_file;
-
- git_config(git_default_config, NULL);
-
- /* We can't free this memory, it becomes part of a linked list parsed atexit() */
- lock_file = xcalloc(1, sizeof(struct lock_file));
-
- newfd = hold_locked_index(lock_file, 0);
- if (newfd < 0)
- lock_error = errno;
-
- entries = read_cache();
- if (entries < 0)
- die("cache corrupted");
-
- for (i = 1 ; i < argc; i++) {
- const char *path = argv[i];
- const char *p;
-
- if (allow_options && *path == '-') {
- if (!strcmp(path, "--")) {
- allow_options = 0;
- continue;
- }
- if (!strcmp(path, "-q")) {
- refresh_flags |= REFRESH_QUIET;
- continue;
- }
- if (!strcmp(path, "--ignore-submodules")) {
- refresh_flags |= REFRESH_IGNORE_SUBMODULES;
- continue;
- }
- if (!strcmp(path, "--add")) {
- allow_add = 1;
- continue;
- }
- if (!strcmp(path, "--replace")) {
- allow_replace = 1;
- continue;
- }
- if (!strcmp(path, "--remove")) {
- allow_remove = 1;
- continue;
- }
- if (!strcmp(path, "--unmerged")) {
- refresh_flags |= REFRESH_UNMERGED;
- continue;
- }
- if (!strcmp(path, "--refresh")) {
- setup_work_tree();
- has_errors |= refresh_cache(refresh_flags);
- continue;
- }
- if (!strcmp(path, "--really-refresh")) {
- setup_work_tree();
- has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags);
- continue;
- }
- if (!strcmp(path, "--cacheinfo")) {
- unsigned char sha1[20];
- unsigned int mode;
-
- if (i+3 >= argc)
- die("git update-index: --cacheinfo <mode> <sha1> <path>");
-
- if (strtoul_ui(argv[i+1], 8, &mode) ||
- get_sha1_hex(argv[i+2], sha1) ||
- add_cacheinfo(mode, sha1, argv[i+3], 0))
- die("git update-index: --cacheinfo"
- " cannot add %s", argv[i+3]);
- i += 3;
- continue;
- }
- if (!strcmp(path, "--chmod=-x") ||
- !strcmp(path, "--chmod=+x")) {
- if (argc <= i+1)
- die("git update-index: %s <path>", path);
- set_executable_bit = path[8];
- continue;
- }
- if (!strcmp(path, "--assume-unchanged")) {
- mark_valid_only = MARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--no-assume-unchanged")) {
- mark_valid_only = UNMARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--no-skip-worktree")) {
- mark_skip_worktree_only = UNMARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--skip-worktree")) {
- mark_skip_worktree_only = MARK_FLAG;
- continue;
- }
- if (!strcmp(path, "--info-only")) {
- info_only = 1;
- continue;
- }
- if (!strcmp(path, "--force-remove")) {
- force_remove = 1;
- continue;
- }
- if (!strcmp(path, "-z")) {
- line_termination = 0;
- continue;
- }
- if (!strcmp(path, "--stdin")) {
- if (i != argc - 1)
- die("--stdin must be at the end");
- read_from_stdin = 1;
- break;
- }
- if (!strcmp(path, "--index-info")) {
- if (i != argc - 1)
- die("--index-info must be at the end");
- allow_add = allow_replace = allow_remove = 1;
- read_index_info(line_termination);
- break;
- }
- if (!strcmp(path, "--unresolve")) {
- has_errors = do_unresolve(argc - i, argv + i,
- prefix, prefix_length);
- if (has_errors)
- active_cache_changed = 0;
- goto finish;
- }
- if (!strcmp(path, "--again") || !strcmp(path, "-g")) {
- setup_work_tree();
- has_errors = do_reupdate(argc - i, argv + i,
- prefix, prefix_length);
- if (has_errors)
- active_cache_changed = 0;
- goto finish;
- }
- if (!strcmp(path, "--ignore-missing")) {
- refresh_flags |= REFRESH_IGNORE_MISSING;
- continue;
- }
- if (!strcmp(path, "--verbose")) {
- verbose = 1;
- continue;
- }
- if (!strcmp(path, "--clear-resolve-undo")) {
- resolve_undo_clear();
- continue;
- }
- if (!strcmp(path, "-h") || !strcmp(path, "--help"))
- usage(update_index_usage);
- die("unknown option %s", path);
- }
- setup_work_tree();
- p = prefix_path(prefix, prefix_length, path);
- update_one(p, NULL, 0);
- if (set_executable_bit)
- chmod_path(set_executable_bit, p);
- if (p < path || p > path + strlen(path))
- free((char *)p);
- }
- if (read_from_stdin) {
- struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
-
- setup_work_tree();
- while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
- const char *p;
- if (line_termination && buf.buf[0] == '"') {
- strbuf_reset(&nbuf);
- if (unquote_c_style(&nbuf, buf.buf, NULL))
- die("line is badly quoted");
- strbuf_swap(&buf, &nbuf);
- }
- p = prefix_path(prefix, prefix_length, buf.buf);
- update_one(p, NULL, 0);
- if (set_executable_bit)
- chmod_path(set_executable_bit, p);
- if (p < buf.buf || p > buf.buf + buf.len)
- free((char *)p);
- }
- strbuf_release(&nbuf);
- strbuf_release(&buf);
- }
-
- finish:
- if (active_cache_changed) {
- if (newfd < 0) {
- if (refresh_flags & REFRESH_QUIET)
- exit(128);
- unable_to_lock_index_die(get_index_file(), lock_error);
- }
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
- die("Unable to write new index file");
- }
-
- rollback_lock_file(lock_file);
-
- return has_errors ? 1 : 0;
-}
+++ /dev/null
-#include "cache.h"
-#include "refs.h"
-#include "builtin.h"
-#include "parse-options.h"
-
-static const char * const git_update_ref_usage[] = {
- "git update-ref [options] -d <refname> [<oldval>]",
- "git update-ref [options] <refname> <newval> [<oldval>]",
- NULL
-};
-
-int cmd_update_ref(int argc, const char **argv, const char *prefix)
-{
- const char *refname, *oldval, *msg=NULL;
- unsigned char sha1[20], oldsha1[20];
- int delete = 0, no_deref = 0, flags = 0;
- struct option options[] = {
- OPT_STRING( 'm', NULL, &msg, "reason", "reason of the update"),
- OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"),
- OPT_BOOLEAN( 0 , "no-deref", &no_deref,
- "update <refname> not the one it points to"),
- OPT_END(),
- };
-
- git_config(git_default_config, NULL);
- argc = parse_options(argc, argv, prefix, options, git_update_ref_usage,
- 0);
- if (msg && !*msg)
- die("Refusing to perform update with empty message.");
-
- if (delete) {
- if (argc < 1 || argc > 2)
- usage_with_options(git_update_ref_usage, options);
- refname = argv[0];
- oldval = argv[1];
- } else {
- const char *value;
- if (argc < 2 || argc > 3)
- usage_with_options(git_update_ref_usage, options);
- refname = argv[0];
- value = argv[1];
- oldval = argv[2];
- if (get_sha1(value, sha1))
- die("%s: not a valid SHA1", value);
- }
-
- hashclr(oldsha1); /* all-zero hash in case oldval is the empty string */
- if (oldval && *oldval && get_sha1(oldval, oldsha1))
- die("%s: not a valid old SHA1", oldval);
-
- if (no_deref)
- flags = REF_NODEREF;
- if (delete)
- return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
- else
- return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
- flags, DIE_ON_ERR);
-}
+++ /dev/null
-#include "cache.h"
-#include "builtin.h"
-#include "parse-options.h"
-
-static const char * const update_server_info_usage[] = {
- "git update-server-info [--force]",
- NULL
-};
-
-int cmd_update_server_info(int argc, const char **argv, const char *prefix)
-{
- int force = 0;
- struct option options[] = {
- OPT_BOOLEAN('f', "force", &force,
- "update the info files from scratch"),
- OPT_END()
- };
-
- argc = parse_options(argc, argv, prefix, options,
- update_server_info_usage, 0);
- if (argc > 0)
- usage_with_options(update_server_info_usage, options);
-
- return !!update_server_info(force);
-}
+++ /dev/null
-/*
- * Copyright (c) 2006 Franck Bui-Huu
- */
-#include "cache.h"
-#include "builtin.h"
-#include "archive.h"
-#include "pkt-line.h"
-#include "sideband.h"
-
-static const char upload_archive_usage[] =
- "git upload-archive <repo>";
-
-static const char deadchild[] =
-"git upload-archive: archiver died with error";
-
-static const char lostchild[] =
-"git upload-archive: archiver process was lost";
-
-#define MAX_ARGS (64)
-
-static int run_upload_archive(int argc, const char **argv, const char *prefix)
-{
- const char *sent_argv[MAX_ARGS];
- const char *arg_cmd = "argument ";
- char *p, buf[4096];
- int sent_argc;
- int len;
-
- if (argc != 2)
- usage(upload_archive_usage);
-
- if (strlen(argv[1]) + 1 > sizeof(buf))
- die("insanely long repository name");
-
- strcpy(buf, argv[1]); /* enter-repo smudges its argument */
-
- if (!enter_repo(buf, 0))
- die("'%s' does not appear to be a git repository", buf);
-
- /* put received options in sent_argv[] */
- sent_argc = 1;
- sent_argv[0] = "git-upload-archive";
- for (p = buf;;) {
- /* This will die if not enough free space in buf */
- len = packet_read_line(0, p, (buf + sizeof buf) - p);
- if (len == 0)
- break; /* got a flush */
- if (sent_argc > MAX_ARGS - 2)
- die("Too many options (>%d)", MAX_ARGS - 2);
-
- if (p[len-1] == '\n') {
- p[--len] = 0;
- }
- if (len < strlen(arg_cmd) ||
- strncmp(arg_cmd, p, strlen(arg_cmd)))
- die("'argument' token or flush expected");
-
- len -= strlen(arg_cmd);
- memmove(p, p + strlen(arg_cmd), len);
- sent_argv[sent_argc++] = p;
- p += len;
- *p++ = 0;
- }
- sent_argv[sent_argc] = NULL;
-
- /* parse all options sent by the client */
- return write_archive(sent_argc, sent_argv, prefix, 0);
-}
-
-__attribute__((format (printf, 1, 2)))
-static void error_clnt(const char *fmt, ...)
-{
- char buf[1024];
- va_list params;
- int len;
-
- va_start(params, fmt);
- len = vsprintf(buf, fmt, params);
- va_end(params);
- send_sideband(1, 3, buf, len, LARGE_PACKET_MAX);
- die("sent error to the client: %s", buf);
-}
-
-static ssize_t process_input(int child_fd, int band)
-{
- char buf[16384];
- ssize_t sz = read(child_fd, buf, sizeof(buf));
- if (sz < 0) {
- if (errno != EAGAIN && errno != EINTR)
- error_clnt("read error: %s\n", strerror(errno));
- return sz;
- }
- send_sideband(1, band, buf, sz, LARGE_PACKET_MAX);
- return sz;
-}
-
-int cmd_upload_archive(int argc, const char **argv, const char *prefix)
-{
- pid_t writer;
- int fd1[2], fd2[2];
- /*
- * Set up sideband subprocess.
- *
- * We (parent) monitor and read from child, sending its fd#1 and fd#2
- * multiplexed out to our fd#1. If the child dies, we tell the other
- * end over channel #3.
- */
- if (pipe(fd1) < 0 || pipe(fd2) < 0) {
- int err = errno;
- packet_write(1, "NACK pipe failed on the remote side\n");
- die("upload-archive: %s", strerror(err));
- }
- writer = fork();
- if (writer < 0) {
- int err = errno;
- packet_write(1, "NACK fork failed on the remote side\n");
- die("upload-archive: %s", strerror(err));
- }
- if (!writer) {
- /* child - connect fd#1 and fd#2 to the pipe */
- dup2(fd1[1], 1);
- dup2(fd2[1], 2);
- close(fd1[1]); close(fd2[1]);
- close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
-
- exit(run_upload_archive(argc, argv, prefix));
- }
-
- /* parent - read from child, multiplex and send out to fd#1 */
- close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
- packet_write(1, "ACK\n");
- packet_flush(1);
-
- while (1) {
- struct pollfd pfd[2];
- int status;
-
- pfd[0].fd = fd1[0];
- pfd[0].events = POLLIN;
- pfd[1].fd = fd2[0];
- pfd[1].events = POLLIN;
- if (poll(pfd, 2, -1) < 0) {
- if (errno != EINTR) {
- error("poll failed resuming: %s",
- strerror(errno));
- sleep(1);
- }
- continue;
- }
- if (pfd[1].revents & POLLIN)
- /* Status stream ready */
- if (process_input(pfd[1].fd, 2))
- continue;
- if (pfd[0].revents & POLLIN)
- /* Data stream ready */
- if (process_input(pfd[0].fd, 1))
- continue;
-
- if (waitpid(writer, &status, 0) < 0)
- error_clnt("%s", lostchild);
- else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
- error_clnt("%s", deadchild);
- packet_flush(1);
- break;
- }
- return 0;
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Eric Biederman, 2005
- */
-#include "cache.h"
-#include "exec_cmd.h"
-
-static const char var_usage[] = "git var (-l | <variable>)";
-
-static const char *editor(int flag)
-{
- const char *pgm = git_editor();
-
- if (!pgm && flag & IDENT_ERROR_ON_NO_NAME)
- die("Terminal is dumb, but EDITOR unset");
-
- return pgm;
-}
-
-static const char *pager(int flag)
-{
- const char *pgm = git_pager(1);
-
- if (!pgm)
- pgm = "cat";
- return pgm;
-}
-
-struct git_var {
- const char *name;
- const char *(*read)(int);
-};
-static struct git_var git_vars[] = {
- { "GIT_COMMITTER_IDENT", git_committer_info },
- { "GIT_AUTHOR_IDENT", git_author_info },
- { "GIT_EDITOR", editor },
- { "GIT_PAGER", pager },
- { "", NULL },
-};
-
-static void list_vars(void)
-{
- struct git_var *ptr;
- const char *val;
-
- for (ptr = git_vars; ptr->read; ptr++)
- if ((val = ptr->read(0)))
- printf("%s=%s\n", ptr->name, val);
-}
-
-static const char *read_var(const char *var)
-{
- struct git_var *ptr;
- const char *val;
- val = NULL;
- for (ptr = git_vars; ptr->read; ptr++) {
- if (strcmp(var, ptr->name) == 0) {
- val = ptr->read(IDENT_ERROR_ON_NO_NAME);
- break;
- }
- }
- return val;
-}
-
-static int show_config(const char *var, const char *value, void *cb)
-{
- if (value)
- printf("%s=%s\n", var, value);
- else
- printf("%s\n", var);
- return git_default_config(var, value, cb);
-}
-
-int cmd_var(int argc, const char **argv, const char *prefix)
-{
- const char *val;
- int nongit;
- if (argc != 2) {
- usage(var_usage);
- }
-
- setup_git_directory_gently(&nongit);
- val = NULL;
-
- if (strcmp(argv[1], "-l") == 0) {
- git_config(show_config, NULL);
- list_vars();
- return 0;
- }
- git_config(git_default_config, NULL);
- val = read_var(argv[1]);
- if (!val)
- usage(var_usage);
-
- printf("%s\n", val);
-
- return 0;
-}
+++ /dev/null
-#include "builtin.h"
-#include "cache.h"
-#include "pack.h"
-#include "pack-revindex.h"
-#include "parse-options.h"
-
-#define MAX_CHAIN 50
-
-#define VERIFY_PACK_VERBOSE 01
-#define VERIFY_PACK_STAT_ONLY 02
-
-static void show_pack_info(struct packed_git *p, unsigned int flags)
-{
- uint32_t nr_objects, i;
- int cnt;
- int stat_only = flags & VERIFY_PACK_STAT_ONLY;
- unsigned long chain_histogram[MAX_CHAIN+1], baseobjects;
-
- nr_objects = p->num_objects;
- memset(chain_histogram, 0, sizeof(chain_histogram));
- baseobjects = 0;
-
- for (i = 0; i < nr_objects; i++) {
- const unsigned char *sha1;
- unsigned char base_sha1[20];
- const char *type;
- unsigned long size;
- unsigned long store_size;
- off_t offset;
- unsigned int delta_chain_length;
-
- sha1 = nth_packed_object_sha1(p, i);
- if (!sha1)
- die("internal error pack-check nth-packed-object");
- offset = nth_packed_object_offset(p, i);
- type = packed_object_info_detail(p, offset, &size, &store_size,
- &delta_chain_length,
- base_sha1);
- if (!stat_only)
- printf("%s ", sha1_to_hex(sha1));
- if (!delta_chain_length) {
- if (!stat_only)
- printf("%-6s %lu %lu %"PRIuMAX"\n",
- type, size, store_size, (uintmax_t)offset);
- baseobjects++;
- }
- else {
- if (!stat_only)
- printf("%-6s %lu %lu %"PRIuMAX" %u %s\n",
- type, size, store_size, (uintmax_t)offset,
- delta_chain_length, sha1_to_hex(base_sha1));
- if (delta_chain_length <= MAX_CHAIN)
- chain_histogram[delta_chain_length]++;
- else
- chain_histogram[0]++;
- }
- }
-
- if (baseobjects)
- printf("non delta: %lu object%s\n",
- baseobjects, baseobjects > 1 ? "s" : "");
-
- for (cnt = 1; cnt <= MAX_CHAIN; cnt++) {
- if (!chain_histogram[cnt])
- continue;
- printf("chain length = %d: %lu object%s\n", cnt,
- chain_histogram[cnt],
- chain_histogram[cnt] > 1 ? "s" : "");
- }
- if (chain_histogram[0])
- printf("chain length > %d: %lu object%s\n", MAX_CHAIN,
- chain_histogram[0],
- chain_histogram[0] > 1 ? "s" : "");
-}
-
-static int verify_one_pack(const char *path, unsigned int flags)
-{
- char arg[PATH_MAX];
- int len;
- int verbose = flags & VERIFY_PACK_VERBOSE;
- int stat_only = flags & VERIFY_PACK_STAT_ONLY;
- struct packed_git *pack;
- int err;
-
- len = strlcpy(arg, path, PATH_MAX);
- if (len >= PATH_MAX)
- return error("name too long: %s", path);
-
- /*
- * In addition to "foo.idx" we accept "foo.pack" and "foo";
- * normalize these forms to "foo.idx" for add_packed_git().
- */
- if (has_extension(arg, ".pack")) {
- strcpy(arg + len - 5, ".idx");
- len--;
- } else if (!has_extension(arg, ".idx")) {
- if (len + 4 >= PATH_MAX)
- return error("name too long: %s.idx", arg);
- strcpy(arg + len, ".idx");
- len += 4;
- }
-
- /*
- * add_packed_git() uses our buffer (containing "foo.idx") to
- * build the pack filename ("foo.pack"). Make sure it fits.
- */
- if (len + 1 >= PATH_MAX) {
- arg[len - 4] = '\0';
- return error("name too long: %s.pack", arg);
- }
-
- pack = add_packed_git(arg, len, 1);
- if (!pack)
- return error("packfile %s not found.", arg);
-
- install_packed_git(pack);
-
- if (!stat_only)
- err = verify_pack(pack);
- else
- err = open_pack_index(pack);
-
- if (verbose || stat_only) {
- if (err)
- printf("%s: bad\n", pack->pack_name);
- else {
- show_pack_info(pack, flags);
- if (!stat_only)
- printf("%s: ok\n", pack->pack_name);
- }
- }
-
- return err;
-}
-
-static const char * const verify_pack_usage[] = {
- "git verify-pack [-v|--verbose] [-s|--stat-only] <pack>...",
- NULL
-};
-
-int cmd_verify_pack(int argc, const char **argv, const char *prefix)
-{
- int err = 0;
- unsigned int flags = 0;
- int i;
- const struct option verify_pack_options[] = {
- OPT_BIT('v', "verbose", &flags, "verbose",
- VERIFY_PACK_VERBOSE),
- OPT_BIT('s', "stat-only", &flags, "show statistics only",
- VERIFY_PACK_STAT_ONLY),
- OPT_END()
- };
-
- git_config(git_default_config, NULL);
- argc = parse_options(argc, argv, prefix, verify_pack_options,
- verify_pack_usage, 0);
- if (argc < 1)
- usage_with_options(verify_pack_usage, verify_pack_options);
- for (i = 0; i < argc; i++) {
- if (verify_one_pack(argv[i], flags))
- err = 1;
- discard_revindex();
- }
-
- return err;
-}
+++ /dev/null
-/*
- * Builtin "git verify-tag"
- *
- * Copyright (c) 2007 Carlos Rica <jasampler@gmail.com>
- *
- * Based on git-verify-tag.sh
- */
-#include "cache.h"
-#include "builtin.h"
-#include "tag.h"
-#include "run-command.h"
-#include <signal.h>
-#include "parse-options.h"
-
-static const char * const verify_tag_usage[] = {
- "git verify-tag [-v|--verbose] <tag>...",
- NULL
-};
-
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-
-static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
-{
- struct child_process gpg;
- const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
- char path[PATH_MAX], *eol;
- size_t len;
- int fd, ret;
-
- fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
- if (fd < 0)
- return error("could not create temporary file '%s': %s",
- path, strerror(errno));
- if (write_in_full(fd, buf, size) < 0)
- return error("failed writing temporary file '%s': %s",
- path, strerror(errno));
- close(fd);
-
- /* find the length without signature */
- len = 0;
- while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) {
- eol = memchr(buf + len, '\n', size - len);
- len += eol ? eol - (buf + len) + 1 : size - len;
- }
- if (verbose)
- write_in_full(1, buf, len);
-
- memset(&gpg, 0, sizeof(gpg));
- gpg.argv = args_gpg;
- gpg.in = -1;
- args_gpg[2] = path;
- if (start_command(&gpg)) {
- unlink(path);
- return error("could not run gpg.");
- }
-
- write_in_full(gpg.in, buf, len);
- close(gpg.in);
- ret = finish_command(&gpg);
-
- unlink_or_warn(path);
-
- return ret;
-}
-
-static int verify_tag(const char *name, int verbose)
-{
- enum object_type type;
- unsigned char sha1[20];
- char *buf;
- unsigned long size;
- int ret;
-
- if (get_sha1(name, sha1))
- return error("tag '%s' not found.", name);
-
- type = sha1_object_info(sha1, NULL);
- if (type != OBJ_TAG)
- return error("%s: cannot verify a non-tag object of type %s.",
- name, typename(type));
-
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf)
- return error("%s: unable to read file.", name);
-
- ret = run_gpg_verify(buf, size, verbose);
-
- free(buf);
- return ret;
-}
-
-int cmd_verify_tag(int argc, const char **argv, const char *prefix)
-{
- int i = 1, verbose = 0, had_error = 0;
- const struct option verify_tag_options[] = {
- OPT__VERBOSE(&verbose),
- OPT_END()
- };
-
- git_config(git_default_config, NULL);
-
- argc = parse_options(argc, argv, prefix, verify_tag_options,
- verify_tag_usage, PARSE_OPT_KEEP_ARGV0);
- if (argc <= i)
- usage_with_options(verify_tag_usage, verify_tag_options);
-
- /* sometimes the program was terminated because this signal
- * was received in the process of writing the gpg input: */
- signal(SIGPIPE, SIG_IGN);
- while (i < argc)
- if (verify_tag(argv[i++], verbose))
- had_error = 1;
- return had_error;
-}
+++ /dev/null
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "builtin.h"
-#include "cache.h"
-#include "tree.h"
-#include "cache-tree.h"
-#include "parse-options.h"
-
-static const char * const write_tree_usage[] = {
- "git write-tree [--missing-ok] [--prefix=<prefix>/]",
- NULL
-};
-
-int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
-{
- int flags = 0, ret;
- const char *prefix = NULL;
- unsigned char sha1[20];
- const char *me = "git-write-tree";
- struct option write_tree_options[] = {
- OPT_BIT(0, "missing-ok", &flags, "allow missing objects",
- WRITE_TREE_MISSING_OK),
- { OPTION_STRING, 0, "prefix", &prefix, "<prefix>/",
- "write tree object for a subdirectory <prefix>" ,
- PARSE_OPT_LITERAL_ARGHELP },
- { OPTION_BIT, 0, "ignore-cache-tree", &flags, NULL,
- "only useful for debugging",
- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, NULL,
- WRITE_TREE_IGNORE_CACHE_TREE },
- OPT_END()
- };
-
- git_config(git_default_config, NULL);
- argc = parse_options(argc, argv, unused_prefix, write_tree_options,
- write_tree_usage, 0);
-
- ret = write_cache_as_tree(sha1, flags, prefix);
- switch (ret) {
- case 0:
- printf("%s\n", sha1_to_hex(sha1));
- break;
- case WRITE_TREE_UNREADABLE_INDEX:
- die("%s: error reading the index", me);
- break;
- case WRITE_TREE_UNMERGED_INDEX:
- die("%s: error building trees", me);
- break;
- case WRITE_TREE_PREFIX_ERROR:
- die("%s: prefix %s not found", me, prefix);
- break;
- }
- return ret;
-}
--- /dev/null
+/*
+ * "git add" builtin command
+ *
+ * Copyright (C) 2006 Linus Torvalds
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "exec_cmd.h"
+#include "cache-tree.h"
+#include "run-command.h"
+#include "parse-options.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+
+static const char * const builtin_add_usage[] = {
+ "git add [options] [--] <filepattern>...",
+ NULL
+};
+static int patch_interactive, add_interactive, edit_interactive;
+static int take_worktree_changes;
+
+struct update_callback_data
+{
+ int flags;
+ int add_errors;
+};
+
+static void update_callback(struct diff_queue_struct *q,
+ struct diff_options *opt, void *cbdata)
+{
+ int i;
+ struct update_callback_data *data = cbdata;
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ const char *path = p->one->path;
+ switch (p->status) {
+ default:
+ die("unexpected diff status %c", p->status);
+ case DIFF_STATUS_UNMERGED:
+ /*
+ * ADD_CACHE_IGNORE_REMOVAL is unset if "git
+ * add -u" is calling us, In such a case, a
+ * missing work tree file needs to be removed
+ * if there is an unmerged entry at stage #2,
+ * but such a diff record is followed by
+ * another with DIFF_STATUS_DELETED (and if
+ * there is no stage #2, we won't see DELETED
+ * nor MODIFIED). We can simply continue
+ * either way.
+ */
+ if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
+ continue;
+ /*
+ * Otherwise, it is "git add path" is asking
+ * to explicitly add it; we fall through. A
+ * missing work tree file is an error and is
+ * caught by add_file_to_index() in such a
+ * case.
+ */
+ case DIFF_STATUS_MODIFIED:
+ case DIFF_STATUS_TYPE_CHANGED:
+ if (add_file_to_index(&the_index, path, data->flags)) {
+ if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
+ die("updating files failed");
+ data->add_errors++;
+ }
+ break;
+ case DIFF_STATUS_DELETED:
+ if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
+ break;
+ if (!(data->flags & ADD_CACHE_PRETEND))
+ remove_file_from_index(&the_index, path);
+ if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
+ printf("remove '%s'\n", path);
+ break;
+ }
+ }
+}
+
+int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
+{
+ struct update_callback_data data;
+ struct rev_info rev;
+ init_revisions(&rev, prefix);
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.prune_data = pathspec;
+ rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = update_callback;
+ data.flags = flags;
+ data.add_errors = 0;
+ rev.diffopt.format_callback_data = &data;
+ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+ return !!data.add_errors;
+}
+
+static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
+{
+ int num_unmatched = 0, i;
+
+ /*
+ * Since we are walking the index as if we were walking the directory,
+ * we have to mark the matched pathspec as seen; otherwise we will
+ * mistakenly think that the user gave a pathspec that did not match
+ * anything.
+ */
+ for (i = 0; i < specs; i++)
+ if (!seen[i])
+ num_unmatched++;
+ if (!num_unmatched)
+ return;
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
+ }
+}
+
+static char *find_used_pathspec(const char **pathspec)
+{
+ char *seen;
+ int i;
+
+ for (i = 0; pathspec[i]; i++)
+ ; /* just counting */
+ seen = xcalloc(i, 1);
+ fill_pathspec_matches(pathspec, seen, i);
+ return seen;
+}
+
+static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
+{
+ char *seen;
+ int i, specs;
+ struct dir_entry **src, **dst;
+
+ for (specs = 0; pathspec[specs]; specs++)
+ /* nothing */;
+ seen = xcalloc(specs, 1);
+
+ src = dst = dir->entries;
+ i = dir->nr;
+ while (--i >= 0) {
+ struct dir_entry *entry = *src++;
+ if (match_pathspec(pathspec, entry->name, entry->len,
+ prefix, seen))
+ *dst++ = entry;
+ }
+ dir->nr = dst - dir->entries;
+ fill_pathspec_matches(pathspec, seen, specs);
+ return seen;
+}
+
+static void treat_gitlinks(const char **pathspec)
+{
+ int i;
+
+ if (!pathspec || !*pathspec)
+ return;
+
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (S_ISGITLINK(ce->ce_mode)) {
+ int len = ce_namelen(ce), j;
+ for (j = 0; pathspec[j]; j++) {
+ int len2 = strlen(pathspec[j]);
+ if (len2 <= len || pathspec[j][len] != '/' ||
+ memcmp(ce->name, pathspec[j], len))
+ continue;
+ if (len2 == len + 1)
+ /* strip trailing slash */
+ pathspec[j] = xstrndup(ce->name, len);
+ else
+ die ("Path '%s' is in submodule '%.*s'",
+ pathspec[j], len, ce->name);
+ }
+ }
+ }
+}
+
+static void refresh(int verbose, const char **pathspec)
+{
+ char *seen;
+ int i, specs;
+
+ for (specs = 0; pathspec[specs]; specs++)
+ /* nothing */;
+ seen = xcalloc(specs, 1);
+ refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
+ pathspec, seen, "Unstaged changes after refreshing the index:");
+ for (i = 0; i < specs; i++) {
+ if (!seen[i])
+ die("pathspec '%s' did not match any files", pathspec[i]);
+ }
+ free(seen);
+}
+
+static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
+{
+ const char **pathspec = get_pathspec(prefix, argv);
+
+ if (pathspec) {
+ const char **p;
+ for (p = pathspec; *p; p++) {
+ if (has_symlink_leading_path(*p, strlen(*p))) {
+ int len = prefix ? strlen(prefix) : 0;
+ die("'%s' is beyond a symbolic link", *p + len);
+ }
+ }
+ }
+
+ return pathspec;
+}
+
+int run_add_interactive(const char *revision, const char *patch_mode,
+ const char **pathspec)
+{
+ int status, ac, pc = 0;
+ const char **args;
+
+ if (pathspec)
+ while (pathspec[pc])
+ pc++;
+
+ args = xcalloc(sizeof(const char *), (pc + 5));
+ ac = 0;
+ args[ac++] = "add--interactive";
+ if (patch_mode)
+ args[ac++] = patch_mode;
+ if (revision)
+ args[ac++] = revision;
+ args[ac++] = "--";
+ if (pc) {
+ memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
+ ac += pc;
+ }
+ args[ac] = NULL;
+
+ status = run_command_v_opt(args, RUN_GIT_CMD);
+ free(args);
+ return status;
+}
+
+int interactive_add(int argc, const char **argv, const char *prefix)
+{
+ const char **pathspec = NULL;
+
+ if (argc) {
+ pathspec = validate_pathspec(argc, argv, prefix);
+ if (!pathspec)
+ return -1;
+ }
+
+ return run_add_interactive(NULL,
+ patch_interactive ? "--patch" : NULL,
+ pathspec);
+}
+
+static int edit_patch(int argc, const char **argv, const char *prefix)
+{
+ char *file = xstrdup(git_path("ADD_EDIT.patch"));
+ const char *apply_argv[] = { "apply", "--recount", "--cached",
+ file, NULL };
+ struct child_process child;
+ struct rev_info rev;
+ int out;
+ struct stat st;
+
+ git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
+
+ if (read_cache() < 0)
+ die ("Could not read the index");
+
+ init_revisions(&rev, prefix);
+ rev.diffopt.context = 7;
+
+ argc = setup_revisions(argc, argv, &rev, NULL);
+ rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+ out = open(file, O_CREAT | O_WRONLY, 0644);
+ if (out < 0)
+ die ("Could not open '%s' for writing.", file);
+ rev.diffopt.file = xfdopen(out, "w");
+ rev.diffopt.close_file = 1;
+ if (run_diff_files(&rev, 0))
+ die ("Could not write patch");
+
+ launch_editor(file, NULL, NULL);
+
+ if (stat(file, &st))
+ die_errno("Could not stat '%s'", file);
+ if (!st.st_size)
+ die("Empty patch. Aborted.");
+
+ memset(&child, 0, sizeof(child));
+ child.git_cmd = 1;
+ child.argv = apply_argv;
+ if (run_command(&child))
+ die ("Could not apply '%s'", file);
+
+ unlink(file);
+ return 0;
+}
+
+static struct lock_file lock_file;
+
+static const char ignore_error[] =
+"The following paths are ignored by one of your .gitignore files:\n";
+
+static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
+static int ignore_add_errors, addremove, intent_to_add;
+
+static struct option builtin_add_options[] = {
+ OPT__DRY_RUN(&show_only),
+ OPT__VERBOSE(&verbose),
+ OPT_GROUP(""),
+ OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
+ OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
+ OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"),
+ OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"),
+ OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
+ OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
+ OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"),
+ OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
+ OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
+ OPT_END(),
+};
+
+static int add_config(const char *var, const char *value, void *cb)
+{
+ if (!strcasecmp(var, "add.ignore-errors")) {
+ ignore_add_errors = git_config_bool(var, value);
+ return 0;
+ }
+ return git_default_config(var, value, cb);
+}
+
+static int add_files(struct dir_struct *dir, int flags)
+{
+ int i, exit_status = 0;
+
+ if (dir->ignored_nr) {
+ fprintf(stderr, ignore_error);
+ 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");
+ die("no files added");
+ }
+
+ for (i = 0; i < dir->nr; i++)
+ if (add_file_to_cache(dir->entries[i]->name, flags)) {
+ if (!ignore_add_errors)
+ die("adding files failed");
+ exit_status = 1;
+ }
+ return exit_status;
+}
+
+int cmd_add(int argc, const char **argv, const char *prefix)
+{
+ int exit_status = 0;
+ int newfd;
+ const char **pathspec;
+ struct dir_struct dir;
+ int flags;
+ int add_new_files;
+ int require_pathspec;
+ char *seen = NULL;
+
+ git_config(add_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, builtin_add_options,
+ builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
+ if (patch_interactive)
+ add_interactive = 1;
+ if (add_interactive)
+ exit(interactive_add(argc - 1, argv + 1, prefix));
+
+ if (edit_interactive)
+ return(edit_patch(argc, argv, prefix));
+ argc--;
+ argv++;
+
+ if (addremove && take_worktree_changes)
+ die("-A and -u are mutually incompatible");
+ if ((addremove || take_worktree_changes) && !argc) {
+ static const char *here[2] = { ".", NULL };
+ argc = 1;
+ argv = here;
+ }
+
+ add_new_files = !take_worktree_changes && !refresh_only;
+ require_pathspec = !take_worktree_changes;
+
+ newfd = hold_locked_index(&lock_file, 1);
+
+ flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
+ (show_only ? ADD_CACHE_PRETEND : 0) |
+ (intent_to_add ? ADD_CACHE_INTENT : 0) |
+ (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
+ (!(addremove || take_worktree_changes)
+ ? ADD_CACHE_IGNORE_REMOVAL : 0));
+
+ if (require_pathspec && argc == 0) {
+ fprintf(stderr, "Nothing specified, nothing added.\n");
+ fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
+ return 0;
+ }
+ pathspec = validate_pathspec(argc, argv, prefix);
+
+ if (read_cache() < 0)
+ die("index file corrupt");
+ treat_gitlinks(pathspec);
+
+ if (add_new_files) {
+ int baselen;
+
+ /* Set up the default git porcelain excludes */
+ memset(&dir, 0, sizeof(dir));
+ if (!ignored_too) {
+ dir.flags |= DIR_COLLECT_IGNORED;
+ setup_standard_excludes(&dir);
+ }
+
+ /* This picks up the paths that are not tracked */
+ baselen = fill_directory(&dir, pathspec);
+ if (pathspec)
+ seen = prune_directory(&dir, pathspec, baselen);
+ }
+
+ if (refresh_only) {
+ refresh(verbose, pathspec);
+ goto finish;
+ }
+
+ if (pathspec) {
+ int i;
+ if (!seen)
+ seen = find_used_pathspec(pathspec);
+ for (i = 0; pathspec[i]; i++) {
+ if (!seen[i] && pathspec[i][0]
+ && !file_exists(pathspec[i]))
+ die("pathspec '%s' did not match any files",
+ pathspec[i]);
+ }
+ free(seen);
+ }
+
+ exit_status |= add_files_to_cache(prefix, pathspec, flags);
+
+ if (add_new_files)
+ exit_status |= add_files(&dir, flags);
+
+ finish:
+ if (active_cache_changed) {
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(&lock_file))
+ die("Unable to write new index file");
+ }
+
+ return exit_status;
+}
--- /dev/null
+/*
+ * "git annotate" builtin alias
+ *
+ * Copyright (C) 2006 Ryan Anderson
+ */
+#include "git-compat-util.h"
+#include "builtin.h"
+
+int cmd_annotate(int argc, const char **argv, const char *prefix)
+{
+ const char **nargv;
+ int i;
+ nargv = xmalloc(sizeof(char *) * (argc + 2));
+
+ nargv[0] = "annotate";
+ nargv[1] = "-c";
+
+ for (i = 1; i < argc; i++) {
+ nargv[i+1] = argv[i];
+ }
+ nargv[argc + 1] = NULL;
+
+ return cmd_blame(argc + 1, nargv, prefix);
+}
--- /dev/null
+/*
+ * apply.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ *
+ * This applies patches on top of some (arbitrary) version of the SCM.
+ *
+ */
+#include "cache.h"
+#include "cache-tree.h"
+#include "quote.h"
+#include "blob.h"
+#include "delta.h"
+#include "builtin.h"
+#include "string-list.h"
+#include "dir.h"
+#include "parse-options.h"
+
+/*
+ * --check turns on checking that the working tree matches the
+ * files that are being modified, but doesn't apply the patch
+ * --stat does just a diffstat, and doesn't actually apply
+ * --numstat does numeric diffstat, and doesn't actually apply
+ * --index-info shows the old and new index info for paths if available.
+ * --index updates the cache as well.
+ * --cached updates only the cache without ever touching the working tree.
+ */
+static const char *prefix;
+static int prefix_length = -1;
+static int newfd = -1;
+
+static int unidiff_zero;
+static int p_value = 1;
+static int p_value_known;
+static int check_index;
+static int update_index;
+static int cached;
+static int diffstat;
+static int numstat;
+static int summary;
+static int check;
+static int apply = 1;
+static int apply_in_reverse;
+static int apply_with_reject;
+static int apply_verbosely;
+static int no_add;
+static const char *fake_ancestor;
+static int line_termination = '\n';
+static unsigned int p_context = UINT_MAX;
+static const char * const apply_usage[] = {
+ "git apply [options] [<patch>...]",
+ NULL
+};
+
+static enum ws_error_action {
+ nowarn_ws_error,
+ warn_on_ws_error,
+ die_on_ws_error,
+ correct_ws_error,
+} ws_error_action = warn_on_ws_error;
+static int whitespace_error;
+static int squelch_whitespace_errors = 5;
+static int applied_after_fixing_ws;
+
+static enum ws_ignore {
+ ignore_ws_none,
+ ignore_ws_change,
+} ws_ignore_action = ignore_ws_none;
+
+
+static const char *patch_input_file;
+static const char *root;
+static int root_len;
+static int read_stdin = 1;
+static int options;
+
+static void parse_whitespace_option(const char *option)
+{
+ if (!option) {
+ ws_error_action = warn_on_ws_error;
+ return;
+ }
+ if (!strcmp(option, "warn")) {
+ ws_error_action = warn_on_ws_error;
+ return;
+ }
+ if (!strcmp(option, "nowarn")) {
+ ws_error_action = nowarn_ws_error;
+ return;
+ }
+ if (!strcmp(option, "error")) {
+ ws_error_action = die_on_ws_error;
+ return;
+ }
+ if (!strcmp(option, "error-all")) {
+ ws_error_action = die_on_ws_error;
+ squelch_whitespace_errors = 0;
+ return;
+ }
+ if (!strcmp(option, "strip") || !strcmp(option, "fix")) {
+ ws_error_action = correct_ws_error;
+ return;
+ }
+ die("unrecognized whitespace option '%s'", option);
+}
+
+static void parse_ignorewhitespace_option(const char *option)
+{
+ if (!option || !strcmp(option, "no") ||
+ !strcmp(option, "false") || !strcmp(option, "never") ||
+ !strcmp(option, "none")) {
+ ws_ignore_action = ignore_ws_none;
+ return;
+ }
+ if (!strcmp(option, "change")) {
+ ws_ignore_action = ignore_ws_change;
+ return;
+ }
+ die("unrecognized whitespace ignore option '%s'", option);
+}
+
+static void set_default_whitespace_mode(const char *whitespace_option)
+{
+ if (!whitespace_option && !apply_default_whitespace)
+ ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error);
+}
+
+/*
+ * For "diff-stat" like behaviour, we keep track of the biggest change
+ * we've seen, and the longest filename. That allows us to do simple
+ * scaling.
+ */
+static int max_change, max_len;
+
+/*
+ * Various "current state", notably line numbers and what
+ * file (and how) we're patching right now.. The "is_xxxx"
+ * things are flags, where -1 means "don't know yet".
+ */
+static int linenr = 1;
+
+/*
+ * This represents one "hunk" from a patch, starting with
+ * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The
+ * patch text is pointed at by patch, and its byte length
+ * is stored in size. leading and trailing are the number
+ * of context lines.
+ */
+struct fragment {
+ unsigned long leading, trailing;
+ unsigned long oldpos, oldlines;
+ unsigned long newpos, newlines;
+ const char *patch;
+ int size;
+ int rejected;
+ int linenr;
+ struct fragment *next;
+};
+
+/*
+ * When dealing with a binary patch, we reuse "leading" field
+ * to store the type of the binary hunk, either deflated "delta"
+ * or deflated "literal".
+ */
+#define binary_patch_method leading
+#define BINARY_DELTA_DEFLATED 1
+#define BINARY_LITERAL_DEFLATED 2
+
+/*
+ * This represents a "patch" to a file, both metainfo changes
+ * such as creation/deletion, filemode and content changes represented
+ * as a series of fragments.
+ */
+struct patch {
+ char *new_name, *old_name, *def_name;
+ unsigned int old_mode, new_mode;
+ int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
+ int rejected;
+ unsigned ws_rule;
+ unsigned long deflate_origlen;
+ int lines_added, lines_deleted;
+ int score;
+ unsigned int is_toplevel_relative:1;
+ unsigned int inaccurate_eof:1;
+ unsigned int is_binary:1;
+ unsigned int is_copy:1;
+ unsigned int is_rename:1;
+ unsigned int recount:1;
+ struct fragment *fragments;
+ char *result;
+ size_t resultsize;
+ char old_sha1_prefix[41];
+ char new_sha1_prefix[41];
+ struct patch *next;
+};
+
+/*
+ * A line in a file, len-bytes long (includes the terminating LF,
+ * except for an incomplete line at the end if the file ends with
+ * one), and its contents hashes to 'hash'.
+ */
+struct line {
+ size_t len;
+ unsigned hash : 24;
+ unsigned flag : 8;
+#define LINE_COMMON 1
+};
+
+/*
+ * This represents a "file", which is an array of "lines".
+ */
+struct image {
+ char *buf;
+ size_t len;
+ size_t nr;
+ size_t alloc;
+ struct line *line_allocated;
+ struct line *line;
+};
+
+/*
+ * Records filenames that have been touched, in order to handle
+ * the case where more than one patches touch the same file.
+ */
+
+static struct string_list fn_table;
+
+static uint32_t hash_line(const char *cp, size_t len)
+{
+ size_t i;
+ uint32_t h;
+ for (i = 0, h = 0; i < len; i++) {
+ if (!isspace(cp[i])) {
+ h = h * 3 + (cp[i] & 0xff);
+ }
+ }
+ return h;
+}
+
+/*
+ * Compare lines s1 of length n1 and s2 of length n2, ignoring
+ * whitespace difference. Returns 1 if they match, 0 otherwise
+ */
+static int fuzzy_matchlines(const char *s1, size_t n1,
+ const char *s2, size_t n2)
+{
+ const char *last1 = s1 + n1 - 1;
+ const char *last2 = s2 + n2 - 1;
+ int result = 0;
+
+ if (n1 < 0 || n2 < 0)
+ return 0;
+
+ /* ignore line endings */
+ while ((*last1 == '\r') || (*last1 == '\n'))
+ last1--;
+ while ((*last2 == '\r') || (*last2 == '\n'))
+ last2--;
+
+ /* skip leading whitespace */
+ while (isspace(*s1) && (s1 <= last1))
+ s1++;
+ while (isspace(*s2) && (s2 <= last2))
+ s2++;
+ /* early return if both lines are empty */
+ if ((s1 > last1) && (s2 > last2))
+ return 1;
+ while (!result) {
+ result = *s1++ - *s2++;
+ /*
+ * Skip whitespace inside. We check for whitespace on
+ * both buffers because we don't want "a b" to match
+ * "ab"
+ */
+ if (isspace(*s1) && isspace(*s2)) {
+ while (isspace(*s1) && s1 <= last1)
+ s1++;
+ while (isspace(*s2) && s2 <= last2)
+ s2++;
+ }
+ /*
+ * If we reached the end on one side only,
+ * lines don't match
+ */
+ if (
+ ((s2 > last2) && (s1 <= last1)) ||
+ ((s1 > last1) && (s2 <= last2)))
+ return 0;
+ if ((s1 > last1) && (s2 > last2))
+ break;
+ }
+
+ return !result;
+}
+
+static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
+{
+ ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
+ img->line_allocated[img->nr].len = len;
+ img->line_allocated[img->nr].hash = hash_line(bol, len);
+ img->line_allocated[img->nr].flag = flag;
+ img->nr++;
+}
+
+static void prepare_image(struct image *image, char *buf, size_t len,
+ int prepare_linetable)
+{
+ const char *cp, *ep;
+
+ memset(image, 0, sizeof(*image));
+ image->buf = buf;
+ image->len = len;
+
+ if (!prepare_linetable)
+ return;
+
+ ep = image->buf + image->len;
+ cp = image->buf;
+ while (cp < ep) {
+ const char *next;
+ for (next = cp; next < ep && *next != '\n'; next++)
+ ;
+ if (next < ep)
+ next++;
+ add_line_info(image, cp, next - cp, 0);
+ cp = next;
+ }
+ image->line = image->line_allocated;
+}
+
+static void clear_image(struct image *image)
+{
+ free(image->buf);
+ image->buf = NULL;
+ image->len = 0;
+}
+
+static void say_patch_name(FILE *output, const char *pre,
+ struct patch *patch, const char *post)
+{
+ fputs(pre, output);
+ if (patch->old_name && patch->new_name &&
+ strcmp(patch->old_name, patch->new_name)) {
+ quote_c_style(patch->old_name, NULL, output, 0);
+ fputs(" => ", output);
+ quote_c_style(patch->new_name, NULL, output, 0);
+ } else {
+ const char *n = patch->new_name;
+ if (!n)
+ n = patch->old_name;
+ quote_c_style(n, NULL, output, 0);
+ }
+ fputs(post, output);
+}
+
+#define CHUNKSIZE (8192)
+#define SLOP (16)
+
+static void read_patch_file(struct strbuf *sb, int fd)
+{
+ if (strbuf_read(sb, fd, 0) < 0)
+ die_errno("git apply: failed to read");
+
+ /*
+ * Make sure that we have some slop in the buffer
+ * so that we can do speculative "memcmp" etc, and
+ * see to it that it is NUL-filled.
+ */
+ strbuf_grow(sb, SLOP);
+ memset(sb->buf + sb->len, 0, SLOP);
+}
+
+static unsigned long linelen(const char *buffer, unsigned long size)
+{
+ unsigned long len = 0;
+ while (size--) {
+ len++;
+ if (*buffer++ == '\n')
+ break;
+ }
+ return len;
+}
+
+static int is_dev_null(const char *str)
+{
+ return !memcmp("/dev/null", str, 9) && isspace(str[9]);
+}
+
+#define TERM_SPACE 1
+#define TERM_TAB 2
+
+static int name_terminate(const char *name, int namelen, int c, int terminate)
+{
+ if (c == ' ' && !(terminate & TERM_SPACE))
+ return 0;
+ if (c == '\t' && !(terminate & TERM_TAB))
+ return 0;
+
+ return 1;
+}
+
+/* remove double slashes to make --index work with such filenames */
+static char *squash_slash(char *name)
+{
+ int i = 0, j = 0;
+
+ if (!name)
+ return NULL;
+
+ while (name[i]) {
+ if ((name[j++] = name[i++]) == '/')
+ while (name[i] == '/')
+ i++;
+ }
+ name[j] = '\0';
+ return name;
+}
+
+static char *find_name(const char *line, char *def, int p_value, int terminate)
+{
+ int len;
+ const char *start = NULL;
+
+ if (p_value == 0)
+ start = line;
+
+ if (*line == '"') {
+ struct strbuf name = STRBUF_INIT;
+
+ /*
+ * Proposed "new-style" GNU patch/diff format; see
+ * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
+ */
+ if (!unquote_c_style(&name, line, NULL)) {
+ char *cp;
+
+ for (cp = name.buf; p_value; p_value--) {
+ cp = strchr(cp, '/');
+ if (!cp)
+ break;
+ cp++;
+ }
+ if (cp) {
+ /* name can later be freed, so we need
+ * to memmove, not just return cp
+ */
+ strbuf_remove(&name, 0, cp - name.buf);
+ free(def);
+ if (root)
+ strbuf_insert(&name, 0, root, root_len);
+ return squash_slash(strbuf_detach(&name, NULL));
+ }
+ }
+ strbuf_release(&name);
+ }
+
+ for (;;) {
+ char c = *line;
+
+ if (isspace(c)) {
+ if (c == '\n')
+ break;
+ if (name_terminate(start, line-start, c, terminate))
+ break;
+ }
+ line++;
+ if (c == '/' && !--p_value)
+ start = line;
+ }
+ if (!start)
+ return squash_slash(def);
+ len = line - start;
+ if (!len)
+ return squash_slash(def);
+
+ /*
+ * Generally we prefer the shorter name, especially
+ * if the other one is just a variation of that with
+ * something else tacked on to the end (ie "file.orig"
+ * or "file~").
+ */
+ if (def) {
+ int deflen = strlen(def);
+ if (deflen < len && !strncmp(start, def, deflen))
+ return squash_slash(def);
+ free(def);
+ }
+
+ if (root) {
+ char *ret = xmalloc(root_len + len + 1);
+ strcpy(ret, root);
+ memcpy(ret + root_len, start, len);
+ ret[root_len + len] = '\0';
+ return squash_slash(ret);
+ }
+
+ return squash_slash(xmemdupz(start, len));
+}
+
+static int count_slashes(const char *cp)
+{
+ int cnt = 0;
+ char ch;
+
+ while ((ch = *cp++))
+ if (ch == '/')
+ cnt++;
+ return cnt;
+}
+
+/*
+ * Given the string after "--- " or "+++ ", guess the appropriate
+ * p_value for the given patch.
+ */
+static int guess_p_value(const char *nameline)
+{
+ char *name, *cp;
+ int val = -1;
+
+ if (is_dev_null(nameline))
+ return -1;
+ name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB);
+ if (!name)
+ return -1;
+ cp = strchr(name, '/');
+ if (!cp)
+ val = 0;
+ else if (prefix) {
+ /*
+ * Does it begin with "a/$our-prefix" and such? Then this is
+ * very likely to apply to our directory.
+ */
+ if (!strncmp(name, prefix, prefix_length))
+ val = count_slashes(prefix);
+ else {
+ cp++;
+ if (!strncmp(cp, prefix, prefix_length))
+ val = count_slashes(prefix) + 1;
+ }
+ }
+ free(name);
+ return val;
+}
+
+/*
+ * Does the ---/+++ line has the POSIX timestamp after the last HT?
+ * GNU diff puts epoch there to signal a creation/deletion event. Is
+ * this such a timestamp?
+ */
+static int has_epoch_timestamp(const char *nameline)
+{
+ /*
+ * We are only interested in epoch timestamp; any non-zero
+ * fraction cannot be one, hence "(\.0+)?" in the regexp below.
+ * For the same reason, the date must be either 1969-12-31 or
+ * 1970-01-01, and the seconds part must be "00".
+ */
+ const char stamp_regexp[] =
+ "^(1969-12-31|1970-01-01)"
+ " "
+ "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
+ " "
+ "([-+][0-2][0-9][0-5][0-9])\n";
+ const char *timestamp = NULL, *cp;
+ static regex_t *stamp;
+ regmatch_t m[10];
+ int zoneoffset;
+ int hourminute;
+ int status;
+
+ for (cp = nameline; *cp != '\n'; cp++) {
+ if (*cp == '\t')
+ timestamp = cp + 1;
+ }
+ if (!timestamp)
+ return 0;
+ if (!stamp) {
+ stamp = xmalloc(sizeof(*stamp));
+ if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
+ warning("Cannot prepare timestamp regexp %s",
+ stamp_regexp);
+ return 0;
+ }
+ }
+
+ status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
+ if (status) {
+ if (status != REG_NOMATCH)
+ warning("regexec returned %d for input: %s",
+ status, timestamp);
+ return 0;
+ }
+
+ zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10);
+ zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
+ if (timestamp[m[3].rm_so] == '-')
+ zoneoffset = -zoneoffset;
+
+ /*
+ * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31
+ * (west of GMT) or 1970-01-01 (east of GMT)
+ */
+ if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) ||
+ (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10)))
+ return 0;
+
+ hourminute = (strtol(timestamp + 11, NULL, 10) * 60 +
+ strtol(timestamp + 14, NULL, 10) -
+ zoneoffset);
+
+ return ((zoneoffset < 0 && hourminute == 1440) ||
+ (0 <= zoneoffset && !hourminute));
+}
+
+/*
+ * Get the name etc info from the ---/+++ lines of a traditional patch header
+ *
+ * FIXME! The end-of-filename heuristics are kind of screwy. For existing
+ * files, we can happily check the index for a match, but for creating a
+ * new file we should try to match whatever "patch" does. I have no idea.
+ */
+static void parse_traditional_patch(const char *first, const char *second, struct patch *patch)
+{
+ char *name;
+
+ first += 4; /* skip "--- " */
+ second += 4; /* skip "+++ " */
+ if (!p_value_known) {
+ int p, q;
+ p = guess_p_value(first);
+ q = guess_p_value(second);
+ if (p < 0) p = q;
+ if (0 <= p && p == q) {
+ p_value = p;
+ p_value_known = 1;
+ }
+ }
+ if (is_dev_null(first)) {
+ patch->is_new = 1;
+ patch->is_delete = 0;
+ name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB);
+ patch->new_name = name;
+ } else if (is_dev_null(second)) {
+ patch->is_new = 0;
+ patch->is_delete = 1;
+ name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
+ patch->old_name = name;
+ } else {
+ name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
+ name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
+ if (has_epoch_timestamp(first)) {
+ patch->is_new = 1;
+ patch->is_delete = 0;
+ patch->new_name = name;
+ } else if (has_epoch_timestamp(second)) {
+ patch->is_new = 0;
+ patch->is_delete = 1;
+ patch->old_name = name;
+ } else {
+ patch->old_name = patch->new_name = name;
+ }
+ }
+ if (!name)
+ die("unable to find filename in patch at line %d", linenr);
+}
+
+static int gitdiff_hdrend(const char *line, struct patch *patch)
+{
+ return -1;
+}
+
+/*
+ * We're anal about diff header consistency, to make
+ * sure that we don't end up having strange ambiguous
+ * patches floating around.
+ *
+ * As a result, gitdiff_{old|new}name() will check
+ * their names against any previous information, just
+ * to make sure..
+ */
+static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
+{
+ if (!orig_name && !isnull)
+ return find_name(line, NULL, p_value, TERM_TAB);
+
+ if (orig_name) {
+ int len;
+ const char *name;
+ char *another;
+ name = orig_name;
+ len = strlen(name);
+ if (isnull)
+ die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
+ another = find_name(line, NULL, p_value, TERM_TAB);
+ if (!another || memcmp(another, name, len + 1))
+ die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+ free(another);
+ return orig_name;
+ }
+ else {
+ /* expect "/dev/null" */
+ if (memcmp("/dev/null", line, 9) || line[9] != '\n')
+ die("git apply: bad git-diff - expected /dev/null on line %d", linenr);
+ return NULL;
+ }
+}
+
+static int gitdiff_oldname(const char *line, struct patch *patch)
+{
+ patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
+ return 0;
+}
+
+static int gitdiff_newname(const char *line, struct patch *patch)
+{
+ patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
+ return 0;
+}
+
+static int gitdiff_oldmode(const char *line, struct patch *patch)
+{
+ patch->old_mode = strtoul(line, NULL, 8);
+ return 0;
+}
+
+static int gitdiff_newmode(const char *line, struct patch *patch)
+{
+ patch->new_mode = strtoul(line, NULL, 8);
+ return 0;
+}
+
+static int gitdiff_delete(const char *line, struct patch *patch)
+{
+ patch->is_delete = 1;
+ patch->old_name = patch->def_name;
+ return gitdiff_oldmode(line, patch);
+}
+
+static int gitdiff_newfile(const char *line, struct patch *patch)
+{
+ patch->is_new = 1;
+ patch->new_name = patch->def_name;
+ return gitdiff_newmode(line, patch);
+}
+
+static int gitdiff_copysrc(const char *line, struct patch *patch)
+{
+ patch->is_copy = 1;
+ patch->old_name = find_name(line, NULL, 0, 0);
+ return 0;
+}
+
+static int gitdiff_copydst(const char *line, struct patch *patch)
+{
+ patch->is_copy = 1;
+ patch->new_name = find_name(line, NULL, 0, 0);
+ return 0;
+}
+
+static int gitdiff_renamesrc(const char *line, struct patch *patch)
+{
+ patch->is_rename = 1;
+ patch->old_name = find_name(line, NULL, 0, 0);
+ return 0;
+}
+
+static int gitdiff_renamedst(const char *line, struct patch *patch)
+{
+ patch->is_rename = 1;
+ patch->new_name = find_name(line, NULL, 0, 0);
+ return 0;
+}
+
+static int gitdiff_similarity(const char *line, struct patch *patch)
+{
+ if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+ patch->score = 0;
+ return 0;
+}
+
+static int gitdiff_dissimilarity(const char *line, struct patch *patch)
+{
+ if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+ patch->score = 0;
+ return 0;
+}
+
+static int gitdiff_index(const char *line, struct patch *patch)
+{
+ /*
+ * index line is N hexadecimal, "..", N hexadecimal,
+ * and optional space with octal mode.
+ */
+ const char *ptr, *eol;
+ int len;
+
+ ptr = strchr(line, '.');
+ if (!ptr || ptr[1] != '.' || 40 < ptr - line)
+ return 0;
+ len = ptr - line;
+ memcpy(patch->old_sha1_prefix, line, len);
+ patch->old_sha1_prefix[len] = 0;
+
+ line = ptr + 2;
+ ptr = strchr(line, ' ');
+ eol = strchr(line, '\n');
+
+ if (!ptr || eol < ptr)
+ ptr = eol;
+ len = ptr - line;
+
+ if (40 < len)
+ return 0;
+ memcpy(patch->new_sha1_prefix, line, len);
+ patch->new_sha1_prefix[len] = 0;
+ if (*ptr == ' ')
+ patch->old_mode = strtoul(ptr+1, NULL, 8);
+ return 0;
+}
+
+/*
+ * This is normal for a diff that doesn't change anything: we'll fall through
+ * into the next diff. Tell the parser to break out.
+ */
+static int gitdiff_unrecognized(const char *line, struct patch *patch)
+{
+ return -1;
+}
+
+static const char *stop_at_slash(const char *line, int llen)
+{
+ int nslash = p_value;
+ int i;
+
+ for (i = 0; i < llen; i++) {
+ int ch = line[i];
+ if (ch == '/' && --nslash <= 0)
+ return &line[i];
+ }
+ return NULL;
+}
+
+/*
+ * This is to extract the same name that appears on "diff --git"
+ * line. We do not find and return anything if it is a rename
+ * patch, and it is OK because we will find the name elsewhere.
+ * We need to reliably find name only when it is mode-change only,
+ * creation or deletion of an empty file. In any of these cases,
+ * both sides are the same name under a/ and b/ respectively.
+ */
+static char *git_header_name(char *line, int llen)
+{
+ const char *name;
+ const char *second = NULL;
+ size_t len;
+
+ line += strlen("diff --git ");
+ llen -= strlen("diff --git ");
+
+ if (*line == '"') {
+ const char *cp;
+ struct strbuf first = STRBUF_INIT;
+ struct strbuf sp = STRBUF_INIT;
+
+ if (unquote_c_style(&first, line, &second))
+ goto free_and_fail1;
+
+ /* advance to the first slash */
+ cp = stop_at_slash(first.buf, first.len);
+ /* we do not accept absolute paths */
+ if (!cp || cp == first.buf)
+ goto free_and_fail1;
+ strbuf_remove(&first, 0, cp + 1 - first.buf);
+
+ /*
+ * second points at one past closing dq of name.
+ * find the second name.
+ */
+ while ((second < line + llen) && isspace(*second))
+ second++;
+
+ if (line + llen <= second)
+ goto free_and_fail1;
+ if (*second == '"') {
+ if (unquote_c_style(&sp, second, NULL))
+ goto free_and_fail1;
+ cp = stop_at_slash(sp.buf, sp.len);
+ if (!cp || cp == sp.buf)
+ goto free_and_fail1;
+ /* They must match, otherwise ignore */
+ if (strcmp(cp + 1, first.buf))
+ goto free_and_fail1;
+ strbuf_release(&sp);
+ return strbuf_detach(&first, NULL);
+ }
+
+ /* unquoted second */
+ cp = stop_at_slash(second, line + llen - second);
+ if (!cp || cp == second)
+ goto free_and_fail1;
+ cp++;
+ if (line + llen - cp != first.len + 1 ||
+ memcmp(first.buf, cp, first.len))
+ goto free_and_fail1;
+ return strbuf_detach(&first, NULL);
+
+ free_and_fail1:
+ strbuf_release(&first);
+ strbuf_release(&sp);
+ return NULL;
+ }
+
+ /* unquoted first name */
+ name = stop_at_slash(line, llen);
+ if (!name || name == line)
+ return NULL;
+ name++;
+
+ /*
+ * since the first name is unquoted, a dq if exists must be
+ * the beginning of the second name.
+ */
+ for (second = name; second < line + llen; second++) {
+ if (*second == '"') {
+ struct strbuf sp = STRBUF_INIT;
+ const char *np;
+
+ if (unquote_c_style(&sp, second, NULL))
+ goto free_and_fail2;
+
+ np = stop_at_slash(sp.buf, sp.len);
+ if (!np || np == sp.buf)
+ goto free_and_fail2;
+ np++;
+
+ len = sp.buf + sp.len - np;
+ if (len < second - name &&
+ !strncmp(np, name, len) &&
+ isspace(name[len])) {
+ /* Good */
+ strbuf_remove(&sp, 0, np - sp.buf);
+ return strbuf_detach(&sp, NULL);
+ }
+
+ free_and_fail2:
+ strbuf_release(&sp);
+ return NULL;
+ }
+ }
+
+ /*
+ * Accept a name only if it shows up twice, exactly the same
+ * form.
+ */
+ for (len = 0 ; ; len++) {
+ switch (name[len]) {
+ default:
+ continue;
+ case '\n':
+ return NULL;
+ case '\t': case ' ':
+ second = name+len;
+ for (;;) {
+ char c = *second++;
+ if (c == '\n')
+ return NULL;
+ if (c == '/')
+ break;
+ }
+ if (second[len] == '\n' && !memcmp(name, second, len)) {
+ return xmemdupz(name, len);
+ }
+ }
+ }
+}
+
+/* Verify that we recognize the lines following a git header */
+static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch)
+{
+ unsigned long offset;
+
+ /* A git diff has explicit new/delete information, so we don't guess */
+ patch->is_new = 0;
+ patch->is_delete = 0;
+
+ /*
+ * Some things may not have the old name in the
+ * rest of the headers anywhere (pure mode changes,
+ * or removing or adding empty files), so we get
+ * the default name from the header.
+ */
+ patch->def_name = git_header_name(line, len);
+ if (patch->def_name && root) {
+ char *s = xmalloc(root_len + strlen(patch->def_name) + 1);
+ strcpy(s, root);
+ strcpy(s + root_len, patch->def_name);
+ free(patch->def_name);
+ patch->def_name = s;
+ }
+
+ line += len;
+ size -= len;
+ linenr++;
+ for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) {
+ static const struct opentry {
+ const char *str;
+ int (*fn)(const char *, struct patch *);
+ } optable[] = {
+ { "@@ -", gitdiff_hdrend },
+ { "--- ", gitdiff_oldname },
+ { "+++ ", gitdiff_newname },
+ { "old mode ", gitdiff_oldmode },
+ { "new mode ", gitdiff_newmode },
+ { "deleted file mode ", gitdiff_delete },
+ { "new file mode ", gitdiff_newfile },
+ { "copy from ", gitdiff_copysrc },
+ { "copy to ", gitdiff_copydst },
+ { "rename old ", gitdiff_renamesrc },
+ { "rename new ", gitdiff_renamedst },
+ { "rename from ", gitdiff_renamesrc },
+ { "rename to ", gitdiff_renamedst },
+ { "similarity index ", gitdiff_similarity },
+ { "dissimilarity index ", gitdiff_dissimilarity },
+ { "index ", gitdiff_index },
+ { "", gitdiff_unrecognized },
+ };
+ int i;
+
+ len = linelen(line, size);
+ if (!len || line[len-1] != '\n')
+ break;
+ for (i = 0; i < ARRAY_SIZE(optable); i++) {
+ const struct opentry *p = optable + i;
+ int oplen = strlen(p->str);
+ if (len < oplen || memcmp(p->str, line, oplen))
+ continue;
+ if (p->fn(line + oplen, patch) < 0)
+ return offset;
+ break;
+ }
+ }
+
+ return offset;
+}
+
+static int parse_num(const char *line, unsigned long *p)
+{
+ char *ptr;
+
+ if (!isdigit(*line))
+ return 0;
+ *p = strtoul(line, &ptr, 10);
+ return ptr - line;
+}
+
+static int parse_range(const char *line, int len, int offset, const char *expect,
+ unsigned long *p1, unsigned long *p2)
+{
+ int digits, ex;
+
+ if (offset < 0 || offset >= len)
+ return -1;
+ line += offset;
+ len -= offset;
+
+ digits = parse_num(line, p1);
+ if (!digits)
+ return -1;
+
+ offset += digits;
+ line += digits;
+ len -= digits;
+
+ *p2 = 1;
+ if (*line == ',') {
+ digits = parse_num(line+1, p2);
+ if (!digits)
+ return -1;
+
+ offset += digits+1;
+ line += digits+1;
+ len -= digits+1;
+ }
+
+ ex = strlen(expect);
+ if (ex > len)
+ return -1;
+ if (memcmp(line, expect, ex))
+ return -1;
+
+ return offset + ex;
+}
+
+static void recount_diff(char *line, int size, struct fragment *fragment)
+{
+ int oldlines = 0, newlines = 0, ret = 0;
+
+ if (size < 1) {
+ warning("recount: ignore empty hunk");
+ return;
+ }
+
+ for (;;) {
+ int len = linelen(line, size);
+ size -= len;
+ line += len;
+
+ if (size < 1)
+ break;
+
+ switch (*line) {
+ case ' ': case '\n':
+ newlines++;
+ /* fall through */
+ case '-':
+ oldlines++;
+ continue;
+ case '+':
+ newlines++;
+ continue;
+ case '\\':
+ continue;
+ case '@':
+ ret = size < 3 || prefixcmp(line, "@@ ");
+ break;
+ case 'd':
+ ret = size < 5 || prefixcmp(line, "diff ");
+ break;
+ default:
+ ret = -1;
+ break;
+ }
+ if (ret) {
+ warning("recount: unexpected line: %.*s",
+ (int)linelen(line, size), line);
+ return;
+ }
+ break;
+ }
+ fragment->oldlines = oldlines;
+ fragment->newlines = newlines;
+}
+
+/*
+ * Parse a unified diff fragment header of the
+ * form "@@ -a,b +c,d @@"
+ */
+static int parse_fragment_header(char *line, int len, struct fragment *fragment)
+{
+ int offset;
+
+ if (!len || line[len-1] != '\n')
+ return -1;
+
+ /* Figure out the number of lines in a fragment */
+ offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines);
+ offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines);
+
+ return offset;
+}
+
+static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch)
+{
+ unsigned long offset, len;
+
+ patch->is_toplevel_relative = 0;
+ patch->is_rename = patch->is_copy = 0;
+ patch->is_new = patch->is_delete = -1;
+ patch->old_mode = patch->new_mode = 0;
+ patch->old_name = patch->new_name = NULL;
+ for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) {
+ unsigned long nextlen;
+
+ len = linelen(line, size);
+ if (!len)
+ break;
+
+ /* Testing this early allows us to take a few shortcuts.. */
+ if (len < 6)
+ continue;
+
+ /*
+ * Make sure we don't find any unconnected patch fragments.
+ * That's a sign that we didn't find a header, and that a
+ * patch has become corrupted/broken up.
+ */
+ if (!memcmp("@@ -", line, 4)) {
+ struct fragment dummy;
+ if (parse_fragment_header(line, len, &dummy) < 0)
+ continue;
+ die("patch fragment without header at line %d: %.*s",
+ linenr, (int)len-1, line);
+ }
+
+ if (size < len + 6)
+ break;
+
+ /*
+ * Git patch? It might not have a real patch, just a rename
+ * or mode change, so we handle that specially
+ */
+ if (!memcmp("diff --git ", line, 11)) {
+ int git_hdr_len = parse_git_header(line, len, size, patch);
+ if (git_hdr_len <= len)
+ continue;
+ if (!patch->old_name && !patch->new_name) {
+ if (!patch->def_name)
+ die("git diff header lacks filename information when removing "
+ "%d leading pathname components (line %d)" , p_value, linenr);
+ patch->old_name = patch->new_name = patch->def_name;
+ }
+ patch->is_toplevel_relative = 1;
+ *hdrsize = git_hdr_len;
+ return offset;
+ }
+
+ /* --- followed by +++ ? */
+ if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4))
+ continue;
+
+ /*
+ * We only accept unified patches, so we want it to
+ * at least have "@@ -a,b +c,d @@\n", which is 14 chars
+ * minimum ("@@ -0,0 +1 @@\n" is the shortest).
+ */
+ nextlen = linelen(line + len, size - len);
+ if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
+ continue;
+
+ /* Ok, we'll consider it a patch */
+ parse_traditional_patch(line, line+len, patch);
+ *hdrsize = len + nextlen;
+ linenr += 2;
+ return offset;
+ }
+ return -1;
+}
+
+static void record_ws_error(unsigned result, const char *line, int len, int linenr)
+{
+ char *err;
+
+ if (!result)
+ return;
+
+ whitespace_error++;
+ if (squelch_whitespace_errors &&
+ squelch_whitespace_errors < whitespace_error)
+ return;
+
+ err = whitespace_error_string(result);
+ fprintf(stderr, "%s:%d: %s.\n%.*s\n",
+ patch_input_file, linenr, err, len, line);
+ free(err);
+}
+
+static void check_whitespace(const char *line, int len, unsigned ws_rule)
+{
+ unsigned result = ws_check(line + 1, len - 1, ws_rule);
+
+ record_ws_error(result, line + 1, len - 2, linenr);
+}
+
+/*
+ * Parse a unified diff. Note that this really needs to parse each
+ * fragment separately, since the only way to know the difference
+ * between a "---" that is part of a patch, and a "---" that starts
+ * the next patch is to look at the line counts..
+ */
+static int parse_fragment(char *line, unsigned long size,
+ struct patch *patch, struct fragment *fragment)
+{
+ int added, deleted;
+ int len = linelen(line, size), offset;
+ unsigned long oldlines, newlines;
+ unsigned long leading, trailing;
+
+ offset = parse_fragment_header(line, len, fragment);
+ if (offset < 0)
+ return -1;
+ if (offset > 0 && patch->recount)
+ recount_diff(line + offset, size - offset, fragment);
+ oldlines = fragment->oldlines;
+ newlines = fragment->newlines;
+ leading = 0;
+ trailing = 0;
+
+ /* Parse the thing.. */
+ line += len;
+ size -= len;
+ linenr++;
+ added = deleted = 0;
+ for (offset = len;
+ 0 < size;
+ offset += len, size -= len, line += len, linenr++) {
+ if (!oldlines && !newlines)
+ break;
+ len = linelen(line, size);
+ if (!len || line[len-1] != '\n')
+ return -1;
+ switch (*line) {
+ default:
+ return -1;
+ case '\n': /* newer GNU diff, an empty context line */
+ case ' ':
+ oldlines--;
+ newlines--;
+ if (!deleted && !added)
+ leading++;
+ trailing++;
+ break;
+ case '-':
+ if (apply_in_reverse &&
+ ws_error_action != nowarn_ws_error)
+ check_whitespace(line, len, patch->ws_rule);
+ deleted++;
+ oldlines--;
+ trailing = 0;
+ break;
+ case '+':
+ if (!apply_in_reverse &&
+ ws_error_action != nowarn_ws_error)
+ check_whitespace(line, len, patch->ws_rule);
+ added++;
+ newlines--;
+ trailing = 0;
+ break;
+
+ /*
+ * We allow "\ No newline at end of file". Depending
+ * on locale settings when the patch was produced we
+ * don't know what this line looks like. The only
+ * thing we do know is that it begins with "\ ".
+ * Checking for 12 is just for sanity check -- any
+ * l10n of "\ No newline..." is at least that long.
+ */
+ case '\\':
+ if (len < 12 || memcmp(line, "\\ ", 2))
+ return -1;
+ break;
+ }
+ }
+ if (oldlines || newlines)
+ return -1;
+ fragment->leading = leading;
+ fragment->trailing = trailing;
+
+ /*
+ * If a fragment ends with an incomplete line, we failed to include
+ * it in the above loop because we hit oldlines == newlines == 0
+ * before seeing it.
+ */
+ if (12 < size && !memcmp(line, "\\ ", 2))
+ offset += linelen(line, size);
+
+ patch->lines_added += added;
+ patch->lines_deleted += deleted;
+
+ if (0 < patch->is_new && oldlines)
+ return error("new file depends on old contents");
+ if (0 < patch->is_delete && newlines)
+ return error("deleted file still has contents");
+ return offset;
+}
+
+static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
+{
+ unsigned long offset = 0;
+ unsigned long oldlines = 0, newlines = 0, context = 0;
+ struct fragment **fragp = &patch->fragments;
+
+ while (size > 4 && !memcmp(line, "@@ -", 4)) {
+ struct fragment *fragment;
+ int len;
+
+ fragment = xcalloc(1, sizeof(*fragment));
+ fragment->linenr = linenr;
+ len = parse_fragment(line, size, patch, fragment);
+ if (len <= 0)
+ die("corrupt patch at line %d", linenr);
+ fragment->patch = line;
+ fragment->size = len;
+ oldlines += fragment->oldlines;
+ newlines += fragment->newlines;
+ context += fragment->leading + fragment->trailing;
+
+ *fragp = fragment;
+ fragp = &fragment->next;
+
+ offset += len;
+ line += len;
+ size -= len;
+ }
+
+ /*
+ * If something was removed (i.e. we have old-lines) it cannot
+ * be creation, and if something was added it cannot be
+ * deletion. However, the reverse is not true; --unified=0
+ * patches that only add are not necessarily creation even
+ * though they do not have any old lines, and ones that only
+ * delete are not necessarily deletion.
+ *
+ * Unfortunately, a real creation/deletion patch do _not_ have
+ * any context line by definition, so we cannot safely tell it
+ * apart with --unified=0 insanity. At least if the patch has
+ * more than one hunk it is not creation or deletion.
+ */
+ if (patch->is_new < 0 &&
+ (oldlines || (patch->fragments && patch->fragments->next)))
+ patch->is_new = 0;
+ if (patch->is_delete < 0 &&
+ (newlines || (patch->fragments && patch->fragments->next)))
+ patch->is_delete = 0;
+
+ if (0 < patch->is_new && oldlines)
+ die("new file %s depends on old contents", patch->new_name);
+ if (0 < patch->is_delete && newlines)
+ die("deleted file %s still has contents", patch->old_name);
+ if (!patch->is_delete && !newlines && context)
+ fprintf(stderr, "** warning: file %s becomes empty but "
+ "is not deleted\n", patch->new_name);
+
+ return offset;
+}
+
+static inline int metadata_changes(struct patch *patch)
+{
+ return patch->is_rename > 0 ||
+ patch->is_copy > 0 ||
+ patch->is_new > 0 ||
+ patch->is_delete ||
+ (patch->old_mode && patch->new_mode &&
+ patch->old_mode != patch->new_mode);
+}
+
+static char *inflate_it(const void *data, unsigned long size,
+ unsigned long inflated_size)
+{
+ z_stream stream;
+ void *out;
+ int st;
+
+ memset(&stream, 0, sizeof(stream));
+
+ stream.next_in = (unsigned char *)data;
+ stream.avail_in = size;
+ stream.next_out = out = xmalloc(inflated_size);
+ stream.avail_out = inflated_size;
+ git_inflate_init(&stream);
+ st = git_inflate(&stream, Z_FINISH);
+ git_inflate_end(&stream);
+ if ((st != Z_STREAM_END) || stream.total_out != inflated_size) {
+ free(out);
+ return NULL;
+ }
+ return out;
+}
+
+static struct fragment *parse_binary_hunk(char **buf_p,
+ unsigned long *sz_p,
+ int *status_p,
+ int *used_p)
+{
+ /*
+ * Expect a line that begins with binary patch method ("literal"
+ * or "delta"), followed by the length of data before deflating.
+ * a sequence of 'length-byte' followed by base-85 encoded data
+ * should follow, terminated by a newline.
+ *
+ * Each 5-byte sequence of base-85 encodes up to 4 bytes,
+ * and we would limit the patch line to 66 characters,
+ * so one line can fit up to 13 groups that would decode
+ * to 52 bytes max. The length byte 'A'-'Z' corresponds
+ * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes.
+ */
+ int llen, used;
+ unsigned long size = *sz_p;
+ char *buffer = *buf_p;
+ int patch_method;
+ unsigned long origlen;
+ char *data = NULL;
+ int hunk_size = 0;
+ struct fragment *frag;
+
+ llen = linelen(buffer, size);
+ used = llen;
+
+ *status_p = 0;
+
+ if (!prefixcmp(buffer, "delta ")) {
+ patch_method = BINARY_DELTA_DEFLATED;
+ origlen = strtoul(buffer + 6, NULL, 10);
+ }
+ else if (!prefixcmp(buffer, "literal ")) {
+ patch_method = BINARY_LITERAL_DEFLATED;
+ origlen = strtoul(buffer + 8, NULL, 10);
+ }
+ else
+ return NULL;
+
+ linenr++;
+ buffer += llen;
+ while (1) {
+ int byte_length, max_byte_length, newsize;
+ llen = linelen(buffer, size);
+ used += llen;
+ linenr++;
+ if (llen == 1) {
+ /* consume the blank line */
+ buffer++;
+ size--;
+ break;
+ }
+ /*
+ * Minimum line is "A00000\n" which is 7-byte long,
+ * and the line length must be multiple of 5 plus 2.
+ */
+ if ((llen < 7) || (llen-2) % 5)
+ goto corrupt;
+ max_byte_length = (llen - 2) / 5 * 4;
+ byte_length = *buffer;
+ if ('A' <= byte_length && byte_length <= 'Z')
+ byte_length = byte_length - 'A' + 1;
+ else if ('a' <= byte_length && byte_length <= 'z')
+ byte_length = byte_length - 'a' + 27;
+ else
+ goto corrupt;
+ /* if the input length was not multiple of 4, we would
+ * have filler at the end but the filler should never
+ * exceed 3 bytes
+ */
+ if (max_byte_length < byte_length ||
+ byte_length <= max_byte_length - 4)
+ goto corrupt;
+ newsize = hunk_size + byte_length;
+ data = xrealloc(data, newsize);
+ if (decode_85(data + hunk_size, buffer + 1, byte_length))
+ goto corrupt;
+ hunk_size = newsize;
+ buffer += llen;
+ size -= llen;
+ }
+
+ frag = xcalloc(1, sizeof(*frag));
+ frag->patch = inflate_it(data, hunk_size, origlen);
+ if (!frag->patch)
+ goto corrupt;
+ free(data);
+ frag->size = origlen;
+ *buf_p = buffer;
+ *sz_p = size;
+ *used_p = used;
+ frag->binary_patch_method = patch_method;
+ return frag;
+
+ corrupt:
+ free(data);
+ *status_p = -1;
+ error("corrupt binary patch at line %d: %.*s",
+ linenr-1, llen-1, buffer);
+ return NULL;
+}
+
+static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
+{
+ /*
+ * We have read "GIT binary patch\n"; what follows is a line
+ * that says the patch method (currently, either "literal" or
+ * "delta") and the length of data before deflating; a
+ * sequence of 'length-byte' followed by base-85 encoded data
+ * follows.
+ *
+ * When a binary patch is reversible, there is another binary
+ * hunk in the same format, starting with patch method (either
+ * "literal" or "delta") with the length of data, and a sequence
+ * of length-byte + base-85 encoded data, terminated with another
+ * empty line. This data, when applied to the postimage, produces
+ * the preimage.
+ */
+ struct fragment *forward;
+ struct fragment *reverse;
+ int status;
+ int used, used_1;
+
+ forward = parse_binary_hunk(&buffer, &size, &status, &used);
+ if (!forward && !status)
+ /* there has to be one hunk (forward hunk) */
+ return error("unrecognized binary patch at line %d", linenr-1);
+ if (status)
+ /* otherwise we already gave an error message */
+ return status;
+
+ reverse = parse_binary_hunk(&buffer, &size, &status, &used_1);
+ if (reverse)
+ used += used_1;
+ else if (status) {
+ /*
+ * Not having reverse hunk is not an error, but having
+ * a corrupt reverse hunk is.
+ */
+ free((void*) forward->patch);
+ free(forward);
+ return status;
+ }
+ forward->next = reverse;
+ patch->fragments = forward;
+ patch->is_binary = 1;
+ return used;
+}
+
+static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
+{
+ int hdrsize, patchsize;
+ int offset = find_header(buffer, size, &hdrsize, patch);
+
+ if (offset < 0)
+ return offset;
+
+ patch->ws_rule = whitespace_rule(patch->new_name
+ ? patch->new_name
+ : patch->old_name);
+
+ patchsize = parse_single_patch(buffer + offset + hdrsize,
+ size - offset - hdrsize, patch);
+
+ if (!patchsize) {
+ static const char *binhdr[] = {
+ "Binary files ",
+ "Files ",
+ NULL,
+ };
+ static const char git_binary[] = "GIT binary patch\n";
+ int i;
+ int hd = hdrsize + offset;
+ unsigned long llen = linelen(buffer + hd, size - hd);
+
+ if (llen == sizeof(git_binary) - 1 &&
+ !memcmp(git_binary, buffer + hd, llen)) {
+ int used;
+ linenr++;
+ used = parse_binary(buffer + hd + llen,
+ size - hd - llen, patch);
+ if (used)
+ patchsize = used + llen;
+ else
+ patchsize = 0;
+ }
+ else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) {
+ for (i = 0; binhdr[i]; i++) {
+ int len = strlen(binhdr[i]);
+ if (len < size - hd &&
+ !memcmp(binhdr[i], buffer + hd, len)) {
+ linenr++;
+ patch->is_binary = 1;
+ patchsize = llen;
+ break;
+ }
+ }
+ }
+
+ /* Empty patch cannot be applied if it is a text patch
+ * without metadata change. A binary patch appears
+ * empty to us here.
+ */
+ if ((apply || check) &&
+ (!patch->is_binary && !metadata_changes(patch)))
+ die("patch with only garbage at line %d", linenr);
+ }
+
+ return offset + hdrsize + patchsize;
+}
+
+#define swap(a,b) myswap((a),(b),sizeof(a))
+
+#define myswap(a, b, size) do { \
+ unsigned char mytmp[size]; \
+ memcpy(mytmp, &a, size); \
+ memcpy(&a, &b, size); \
+ memcpy(&b, mytmp, size); \
+} while (0)
+
+static void reverse_patches(struct patch *p)
+{
+ for (; p; p = p->next) {
+ struct fragment *frag = p->fragments;
+
+ swap(p->new_name, p->old_name);
+ swap(p->new_mode, p->old_mode);
+ swap(p->is_new, p->is_delete);
+ swap(p->lines_added, p->lines_deleted);
+ swap(p->old_sha1_prefix, p->new_sha1_prefix);
+
+ for (; frag; frag = frag->next) {
+ swap(frag->newpos, frag->oldpos);
+ swap(frag->newlines, frag->oldlines);
+ }
+ }
+}
+
+static const char pluses[] =
+"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]=
+"----------------------------------------------------------------------";
+
+static void show_stats(struct patch *patch)
+{
+ struct strbuf qname = STRBUF_INIT;
+ char *cp = patch->new_name ? patch->new_name : patch->old_name;
+ int max, add, del;
+
+ quote_c_style(cp, &qname, NULL, 0);
+
+ /*
+ * "scale" the filename
+ */
+ max = max_len;
+ if (max > 50)
+ max = 50;
+
+ if (qname.len > max) {
+ cp = strchr(qname.buf + qname.len + 3 - max, '/');
+ if (!cp)
+ cp = qname.buf + qname.len + 3 - max;
+ strbuf_splice(&qname, 0, cp - qname.buf, "...", 3);
+ }
+
+ if (patch->is_binary) {
+ printf(" %-*s | Bin\n", max, qname.buf);
+ strbuf_release(&qname);
+ return;
+ }
+
+ printf(" %-*s |", max, qname.buf);
+ strbuf_release(&qname);
+
+ /*
+ * scale the add/delete
+ */
+ max = max + max_change > 70 ? 70 - max : max_change;
+ add = patch->lines_added;
+ del = patch->lines_deleted;
+
+ if (max_change > 0) {
+ int total = ((add + del) * max + max_change / 2) / max_change;
+ add = (add * max + max_change / 2) / max_change;
+ del = total - add;
+ }
+ printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted,
+ add, pluses, del, minuses);
+}
+
+static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
+{
+ switch (st->st_mode & S_IFMT) {
+ case S_IFLNK:
+ if (strbuf_readlink(buf, path, st->st_size) < 0)
+ return error("unable to read symlink %s", path);
+ return 0;
+ case S_IFREG:
+ if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
+ return error("unable to open or read %s", path);
+ convert_to_git(path, buf->buf, buf->len, buf, 0);
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+/*
+ * Update the preimage, and the common lines in postimage,
+ * from buffer buf of length len. If postlen is 0 the postimage
+ * is updated in place, otherwise it's updated on a new buffer
+ * of length postlen
+ */
+
+static void update_pre_post_images(struct image *preimage,
+ struct image *postimage,
+ char *buf,
+ size_t len, size_t postlen)
+{
+ int i, ctx;
+ char *new, *old, *fixed;
+ struct image fixed_preimage;
+
+ /*
+ * Update the preimage with whitespace fixes. Note that we
+ * are not losing preimage->buf -- apply_one_fragment() will
+ * free "oldlines".
+ */
+ prepare_image(&fixed_preimage, buf, len, 1);
+ assert(fixed_preimage.nr == preimage->nr);
+ for (i = 0; i < preimage->nr; i++)
+ fixed_preimage.line[i].flag = preimage->line[i].flag;
+ free(preimage->line_allocated);
+ *preimage = fixed_preimage;
+
+ /*
+ * Adjust the common context lines in postimage. This can be
+ * done in-place when we are just doing whitespace fixing,
+ * which does not make the string grow, but needs a new buffer
+ * when ignoring whitespace causes the update, since in this case
+ * we could have e.g. tabs converted to multiple spaces.
+ * We trust the caller to tell us if the update can be done
+ * in place (postlen==0) or not.
+ */
+ old = postimage->buf;
+ if (postlen)
+ new = postimage->buf = xmalloc(postlen);
+ else
+ new = old;
+ fixed = preimage->buf;
+ for (i = ctx = 0; i < postimage->nr; i++) {
+ size_t len = postimage->line[i].len;
+ if (!(postimage->line[i].flag & LINE_COMMON)) {
+ /* an added line -- no counterparts in preimage */
+ memmove(new, old, len);
+ old += len;
+ new += len;
+ continue;
+ }
+
+ /* a common context -- skip it in the original postimage */
+ old += len;
+
+ /* and find the corresponding one in the fixed preimage */
+ while (ctx < preimage->nr &&
+ !(preimage->line[ctx].flag & LINE_COMMON)) {
+ fixed += preimage->line[ctx].len;
+ ctx++;
+ }
+ if (preimage->nr <= ctx)
+ die("oops");
+
+ /* and copy it in, while fixing the line length */
+ len = preimage->line[ctx].len;
+ memcpy(new, fixed, len);
+ new += len;
+ fixed += len;
+ postimage->line[i].len = len;
+ ctx++;
+ }
+
+ /* Fix the length of the whole thing */
+ postimage->len = new - postimage->buf;
+}
+
+static int match_fragment(struct image *img,
+ struct image *preimage,
+ struct image *postimage,
+ unsigned long try,
+ int try_lno,
+ unsigned ws_rule,
+ int match_beginning, int match_end)
+{
+ int i;
+ char *fixed_buf, *buf, *orig, *target;
+
+ if (preimage->nr + try_lno > img->nr)
+ return 0;
+
+ if (match_beginning && try_lno)
+ return 0;
+
+ if (match_end && preimage->nr + try_lno != img->nr)
+ return 0;
+
+ /* Quick hash check */
+ for (i = 0; i < preimage->nr; i++)
+ if (preimage->line[i].hash != img->line[try_lno + i].hash)
+ return 0;
+
+ /*
+ * Do we have an exact match? If we were told to match
+ * at the end, size must be exactly at try+fragsize,
+ * otherwise try+fragsize must be still within the preimage,
+ * and either case, the old piece should match the preimage
+ * exactly.
+ */
+ if ((match_end
+ ? (try + preimage->len == img->len)
+ : (try + preimage->len <= img->len)) &&
+ !memcmp(img->buf + try, preimage->buf, preimage->len))
+ return 1;
+
+ /*
+ * No exact match. If we are ignoring whitespace, run a line-by-line
+ * fuzzy matching. We collect all the line length information because
+ * we need it to adjust whitespace if we match.
+ */
+ if (ws_ignore_action == ignore_ws_change) {
+ size_t imgoff = 0;
+ size_t preoff = 0;
+ size_t postlen = postimage->len;
+ for (i = 0; i < preimage->nr; i++) {
+ size_t prelen = preimage->line[i].len;
+ size_t imglen = img->line[try_lno+i].len;
+
+ if (!fuzzy_matchlines(img->buf + try + imgoff, imglen,
+ preimage->buf + preoff, prelen))
+ return 0;
+ if (preimage->line[i].flag & LINE_COMMON)
+ postlen += imglen - prelen;
+ imgoff += imglen;
+ preoff += prelen;
+ }
+
+ /*
+ * Ok, the preimage matches with whitespace fuzz. Update it and
+ * the common postimage lines to use the same whitespace as the
+ * target. imgoff now holds the true length of the target that
+ * matches the preimage, and we need to update the line lengths
+ * of the preimage to match the target ones.
+ */
+ fixed_buf = xmalloc(imgoff);
+ memcpy(fixed_buf, img->buf + try, imgoff);
+ for (i = 0; i < preimage->nr; i++)
+ preimage->line[i].len = img->line[try_lno+i].len;
+
+ /*
+ * Update the preimage buffer and the postimage context lines.
+ */
+ update_pre_post_images(preimage, postimage,
+ fixed_buf, imgoff, postlen);
+ return 1;
+ }
+
+ if (ws_error_action != correct_ws_error)
+ return 0;
+
+ /*
+ * The hunk does not apply byte-by-byte, but the hash says
+ * it might with whitespace fuzz. We haven't been asked to
+ * ignore whitespace, we were asked to correct whitespace
+ * errors, so let's try matching after whitespace correction.
+ */
+ fixed_buf = xmalloc(preimage->len + 1);
+ buf = fixed_buf;
+ orig = preimage->buf;
+ target = img->buf + try;
+ for (i = 0; i < preimage->nr; i++) {
+ size_t fixlen; /* length after fixing the preimage */
+ size_t oldlen = preimage->line[i].len;
+ size_t tgtlen = img->line[try_lno + i].len;
+ size_t tgtfixlen; /* length after fixing the target line */
+ char tgtfixbuf[1024], *tgtfix;
+ int match;
+
+ /* Try fixing the line in the preimage */
+ fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL);
+
+ /* Try fixing the line in the target */
+ if (sizeof(tgtfixbuf) > tgtlen)
+ tgtfix = tgtfixbuf;
+ else
+ tgtfix = xmalloc(tgtlen);
+ tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL);
+
+ /*
+ * If they match, either the preimage was based on
+ * a version before our tree fixed whitespace breakage,
+ * or we are lacking a whitespace-fix patch the tree
+ * the preimage was based on already had (i.e. target
+ * has whitespace breakage, the preimage doesn't).
+ * In either case, we are fixing the whitespace breakages
+ * so we might as well take the fix together with their
+ * real change.
+ */
+ match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen));
+
+ if (tgtfix != tgtfixbuf)
+ free(tgtfix);
+ if (!match)
+ goto unmatch_exit;
+
+ orig += oldlen;
+ buf += fixlen;
+ target += tgtlen;
+ }
+
+ /*
+ * Yes, the preimage is based on an older version that still
+ * has whitespace breakages unfixed, and fixing them makes the
+ * hunk match. Update the context lines in the postimage.
+ */
+ update_pre_post_images(preimage, postimage,
+ fixed_buf, buf - fixed_buf, 0);
+ return 1;
+
+ unmatch_exit:
+ free(fixed_buf);
+ return 0;
+}
+
+static int find_pos(struct image *img,
+ struct image *preimage,
+ struct image *postimage,
+ int line,
+ unsigned ws_rule,
+ int match_beginning, int match_end)
+{
+ int i;
+ unsigned long backwards, forwards, try;
+ int backwards_lno, forwards_lno, try_lno;
+
+ if (preimage->nr > img->nr)
+ return -1;
+
+ /*
+ * If match_beginning or match_end is specified, there is no
+ * point starting from a wrong line that will never match and
+ * wander around and wait for a match at the specified end.
+ */
+ if (match_beginning)
+ line = 0;
+ else if (match_end)
+ line = img->nr - preimage->nr;
+
+ if (line > img->nr)
+ line = img->nr;
+
+ try = 0;
+ for (i = 0; i < line; i++)
+ try += img->line[i].len;
+
+ /*
+ * There's probably some smart way to do this, but I'll leave
+ * that to the smart and beautiful people. I'm simple and stupid.
+ */
+ backwards = try;
+ backwards_lno = line;
+ forwards = try;
+ forwards_lno = line;
+ try_lno = line;
+
+ for (i = 0; ; i++) {
+ if (match_fragment(img, preimage, postimage,
+ try, try_lno, ws_rule,
+ match_beginning, match_end))
+ return try_lno;
+
+ again:
+ if (backwards_lno == 0 && forwards_lno == img->nr)
+ break;
+
+ if (i & 1) {
+ if (backwards_lno == 0) {
+ i++;
+ goto again;
+ }
+ backwards_lno--;
+ backwards -= img->line[backwards_lno].len;
+ try = backwards;
+ try_lno = backwards_lno;
+ } else {
+ if (forwards_lno == img->nr) {
+ i++;
+ goto again;
+ }
+ forwards += img->line[forwards_lno].len;
+ forwards_lno++;
+ try = forwards;
+ try_lno = forwards_lno;
+ }
+
+ }
+ return -1;
+}
+
+static void remove_first_line(struct image *img)
+{
+ img->buf += img->line[0].len;
+ img->len -= img->line[0].len;
+ img->line++;
+ img->nr--;
+}
+
+static void remove_last_line(struct image *img)
+{
+ img->len -= img->line[--img->nr].len;
+}
+
+static void update_image(struct image *img,
+ int applied_pos,
+ struct image *preimage,
+ struct image *postimage)
+{
+ /*
+ * remove the copy of preimage at offset in img
+ * and replace it with postimage
+ */
+ int i, nr;
+ size_t remove_count, insert_count, applied_at = 0;
+ char *result;
+
+ for (i = 0; i < applied_pos; i++)
+ applied_at += img->line[i].len;
+
+ remove_count = 0;
+ for (i = 0; i < preimage->nr; i++)
+ remove_count += img->line[applied_pos + i].len;
+ insert_count = postimage->len;
+
+ /* Adjust the contents */
+ result = xmalloc(img->len + insert_count - remove_count + 1);
+ memcpy(result, img->buf, applied_at);
+ memcpy(result + applied_at, postimage->buf, postimage->len);
+ memcpy(result + applied_at + postimage->len,
+ img->buf + (applied_at + remove_count),
+ img->len - (applied_at + remove_count));
+ free(img->buf);
+ img->buf = result;
+ img->len += insert_count - remove_count;
+ result[img->len] = '\0';
+
+ /* Adjust the line table */
+ nr = img->nr + postimage->nr - preimage->nr;
+ if (preimage->nr < postimage->nr) {
+ /*
+ * NOTE: this knows that we never call remove_first_line()
+ * on anything other than pre/post image.
+ */
+ img->line = xrealloc(img->line, nr * sizeof(*img->line));
+ img->line_allocated = img->line;
+ }
+ if (preimage->nr != postimage->nr)
+ memmove(img->line + applied_pos + postimage->nr,
+ img->line + applied_pos + preimage->nr,
+ (img->nr - (applied_pos + preimage->nr)) *
+ sizeof(*img->line));
+ memcpy(img->line + applied_pos,
+ postimage->line,
+ postimage->nr * sizeof(*img->line));
+ img->nr = nr;
+}
+
+static int apply_one_fragment(struct image *img, struct fragment *frag,
+ int inaccurate_eof, unsigned ws_rule)
+{
+ int match_beginning, match_end;
+ const char *patch = frag->patch;
+ int size = frag->size;
+ char *old, *new, *oldlines, *newlines;
+ int new_blank_lines_at_end = 0;
+ unsigned long leading, trailing;
+ int pos, applied_pos;
+ struct image preimage;
+ struct image postimage;
+
+ memset(&preimage, 0, sizeof(preimage));
+ memset(&postimage, 0, sizeof(postimage));
+ oldlines = xmalloc(size);
+ newlines = xmalloc(size);
+
+ old = oldlines;
+ new = newlines;
+ while (size > 0) {
+ char first;
+ int len = linelen(patch, size);
+ int plen, added;
+ int added_blank_line = 0;
+ int is_blank_context = 0;
+
+ if (!len)
+ break;
+
+ /*
+ * "plen" is how much of the line we should use for
+ * the actual patch data. Normally we just remove the
+ * first character on the line, but if the line is
+ * followed by "\ No newline", then we also remove the
+ * last one (which is the newline, of course).
+ */
+ plen = len - 1;
+ if (len < size && patch[len] == '\\')
+ plen--;
+ first = *patch;
+ if (apply_in_reverse) {
+ if (first == '-')
+ first = '+';
+ else if (first == '+')
+ first = '-';
+ }
+
+ switch (first) {
+ case '\n':
+ /* Newer GNU diff, empty context line */
+ if (plen < 0)
+ /* ... followed by '\No newline'; nothing */
+ break;
+ *old++ = '\n';
+ *new++ = '\n';
+ add_line_info(&preimage, "\n", 1, LINE_COMMON);
+ add_line_info(&postimage, "\n", 1, LINE_COMMON);
+ is_blank_context = 1;
+ break;
+ case ' ':
+ if (plen && (ws_rule & WS_BLANK_AT_EOF) &&
+ ws_blank_line(patch + 1, plen, ws_rule))
+ is_blank_context = 1;
+ case '-':
+ memcpy(old, patch + 1, plen);
+ add_line_info(&preimage, old, plen,
+ (first == ' ' ? LINE_COMMON : 0));
+ old += plen;
+ if (first == '-')
+ break;
+ /* Fall-through for ' ' */
+ case '+':
+ /* --no-add does not add new lines */
+ if (first == '+' && no_add)
+ break;
+
+ if (first != '+' ||
+ !whitespace_error ||
+ ws_error_action != correct_ws_error) {
+ memcpy(new, patch + 1, plen);
+ added = plen;
+ }
+ else {
+ added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws);
+ }
+ add_line_info(&postimage, new, added,
+ (first == '+' ? 0 : LINE_COMMON));
+ new += added;
+ if (first == '+' &&
+ (ws_rule & WS_BLANK_AT_EOF) &&
+ ws_blank_line(patch + 1, plen, ws_rule))
+ added_blank_line = 1;
+ break;
+ case '@': case '\\':
+ /* Ignore it, we already handled it */
+ break;
+ default:
+ if (apply_verbosely)
+ error("invalid start of line: '%c'", first);
+ return -1;
+ }
+ if (added_blank_line)
+ new_blank_lines_at_end++;
+ else if (is_blank_context)
+ ;
+ else
+ new_blank_lines_at_end = 0;
+ patch += len;
+ size -= len;
+ }
+ if (inaccurate_eof &&
+ old > oldlines && old[-1] == '\n' &&
+ new > newlines && new[-1] == '\n') {
+ old--;
+ new--;
+ }
+
+ leading = frag->leading;
+ trailing = frag->trailing;
+
+ /*
+ * A hunk to change lines at the beginning would begin with
+ * @@ -1,L +N,M @@
+ * but we need to be careful. -U0 that inserts before the second
+ * line also has this pattern.
+ *
+ * And a hunk to add to an empty file would begin with
+ * @@ -0,0 +N,M @@
+ *
+ * In other words, a hunk that is (frag->oldpos <= 1) with or
+ * without leading context must match at the beginning.
+ */
+ match_beginning = (!frag->oldpos ||
+ (frag->oldpos == 1 && !unidiff_zero));
+
+ /*
+ * A hunk without trailing lines must match at the end.
+ * However, we simply cannot tell if a hunk must match end
+ * from the lack of trailing lines if the patch was generated
+ * with unidiff without any context.
+ */
+ match_end = !unidiff_zero && !trailing;
+
+ pos = frag->newpos ? (frag->newpos - 1) : 0;
+ preimage.buf = oldlines;
+ preimage.len = old - oldlines;
+ postimage.buf = newlines;
+ postimage.len = new - newlines;
+ preimage.line = preimage.line_allocated;
+ postimage.line = postimage.line_allocated;
+
+ for (;;) {
+
+ applied_pos = find_pos(img, &preimage, &postimage, pos,
+ ws_rule, match_beginning, match_end);
+
+ if (applied_pos >= 0)
+ break;
+
+ /* Am I at my context limits? */
+ if ((leading <= p_context) && (trailing <= p_context))
+ break;
+ if (match_beginning || match_end) {
+ match_beginning = match_end = 0;
+ continue;
+ }
+
+ /*
+ * Reduce the number of context lines; reduce both
+ * leading and trailing if they are equal otherwise
+ * just reduce the larger context.
+ */
+ if (leading >= trailing) {
+ remove_first_line(&preimage);
+ remove_first_line(&postimage);
+ pos--;
+ leading--;
+ }
+ if (trailing > leading) {
+ remove_last_line(&preimage);
+ remove_last_line(&postimage);
+ trailing--;
+ }
+ }
+
+ if (applied_pos >= 0) {
+ if (new_blank_lines_at_end &&
+ preimage.nr + applied_pos == img->nr &&
+ (ws_rule & WS_BLANK_AT_EOF) &&
+ ws_error_action != nowarn_ws_error) {
+ record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr);
+ if (ws_error_action == correct_ws_error) {
+ while (new_blank_lines_at_end--)
+ remove_last_line(&postimage);
+ }
+ /*
+ * We would want to prevent write_out_results()
+ * from taking place in apply_patch() that follows
+ * the callchain led us here, which is:
+ * apply_patch->check_patch_list->check_patch->
+ * apply_data->apply_fragments->apply_one_fragment
+ */
+ if (ws_error_action == die_on_ws_error)
+ apply = 0;
+ }
+
+ /*
+ * Warn if it was necessary to reduce the number
+ * of context lines.
+ */
+ if ((leading != frag->leading) ||
+ (trailing != frag->trailing))
+ fprintf(stderr, "Context reduced to (%ld/%ld)"
+ " to apply fragment at %d\n",
+ leading, trailing, applied_pos+1);
+ update_image(img, applied_pos, &preimage, &postimage);
+ } else {
+ if (apply_verbosely)
+ error("while searching for:\n%.*s",
+ (int)(old - oldlines), oldlines);
+ }
+
+ free(oldlines);
+ free(newlines);
+ free(preimage.line_allocated);
+ free(postimage.line_allocated);
+
+ return (applied_pos < 0);
+}
+
+static int apply_binary_fragment(struct image *img, struct patch *patch)
+{
+ struct fragment *fragment = patch->fragments;
+ unsigned long len;
+ void *dst;
+
+ /* Binary patch is irreversible without the optional second hunk */
+ if (apply_in_reverse) {
+ if (!fragment->next)
+ return error("cannot reverse-apply a binary patch "
+ "without the reverse hunk to '%s'",
+ patch->new_name
+ ? patch->new_name : patch->old_name);
+ fragment = fragment->next;
+ }
+ switch (fragment->binary_patch_method) {
+ case BINARY_DELTA_DEFLATED:
+ dst = patch_delta(img->buf, img->len, fragment->patch,
+ fragment->size, &len);
+ if (!dst)
+ return -1;
+ clear_image(img);
+ img->buf = dst;
+ img->len = len;
+ return 0;
+ case BINARY_LITERAL_DEFLATED:
+ clear_image(img);
+ img->len = fragment->size;
+ img->buf = xmalloc(img->len+1);
+ memcpy(img->buf, fragment->patch, img->len);
+ img->buf[img->len] = '\0';
+ return 0;
+ }
+ return -1;
+}
+
+static int apply_binary(struct image *img, struct patch *patch)
+{
+ const char *name = patch->old_name ? patch->old_name : patch->new_name;
+ unsigned char sha1[20];
+
+ /*
+ * For safety, we require patch index line to contain
+ * full 40-byte textual SHA1 for old and new, at least for now.
+ */
+ if (strlen(patch->old_sha1_prefix) != 40 ||
+ strlen(patch->new_sha1_prefix) != 40 ||
+ get_sha1_hex(patch->old_sha1_prefix, sha1) ||
+ get_sha1_hex(patch->new_sha1_prefix, sha1))
+ return error("cannot apply binary patch to '%s' "
+ "without full index line", name);
+
+ if (patch->old_name) {
+ /*
+ * See if the old one matches what the patch
+ * applies to.
+ */
+ hash_sha1_file(img->buf, img->len, blob_type, sha1);
+ if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
+ return error("the patch applies to '%s' (%s), "
+ "which does not match the "
+ "current contents.",
+ name, sha1_to_hex(sha1));
+ }
+ else {
+ /* Otherwise, the old one must be empty. */
+ if (img->len)
+ return error("the patch applies to an empty "
+ "'%s' but it is not empty", name);
+ }
+
+ get_sha1_hex(patch->new_sha1_prefix, sha1);
+ if (is_null_sha1(sha1)) {
+ clear_image(img);
+ return 0; /* deletion patch */
+ }
+
+ if (has_sha1_file(sha1)) {
+ /* We already have the postimage */
+ enum object_type type;
+ unsigned long size;
+ char *result;
+
+ result = read_sha1_file(sha1, &type, &size);
+ if (!result)
+ return error("the necessary postimage %s for "
+ "'%s' cannot be read",
+ patch->new_sha1_prefix, name);
+ clear_image(img);
+ img->buf = result;
+ img->len = size;
+ } else {
+ /*
+ * We have verified buf matches the preimage;
+ * apply the patch data to it, which is stored
+ * in the patch->fragments->{patch,size}.
+ */
+ if (apply_binary_fragment(img, patch))
+ return error("binary patch does not apply to '%s'",
+ name);
+
+ /* verify that the result matches */
+ hash_sha1_file(img->buf, img->len, blob_type, sha1);
+ if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
+ return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
+ name, patch->new_sha1_prefix, sha1_to_hex(sha1));
+ }
+
+ return 0;
+}
+
+static int apply_fragments(struct image *img, struct patch *patch)
+{
+ struct fragment *frag = patch->fragments;
+ const char *name = patch->old_name ? patch->old_name : patch->new_name;
+ unsigned ws_rule = patch->ws_rule;
+ unsigned inaccurate_eof = patch->inaccurate_eof;
+
+ if (patch->is_binary)
+ return apply_binary(img, patch);
+
+ while (frag) {
+ if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) {
+ error("patch failed: %s:%ld", name, frag->oldpos);
+ if (!apply_with_reject)
+ return -1;
+ frag->rejected = 1;
+ }
+ frag = frag->next;
+ }
+ return 0;
+}
+
+static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
+{
+ if (!ce)
+ return 0;
+
+ if (S_ISGITLINK(ce->ce_mode)) {
+ strbuf_grow(buf, 100);
+ strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
+ } else {
+ enum object_type type;
+ unsigned long sz;
+ char *result;
+
+ result = read_sha1_file(ce->sha1, &type, &sz);
+ if (!result)
+ return -1;
+ /* XXX read_sha1_file NUL-terminates */
+ strbuf_attach(buf, result, sz, sz + 1);
+ }
+ return 0;
+}
+
+static struct patch *in_fn_table(const char *name)
+{
+ struct string_list_item *item;
+
+ if (name == NULL)
+ return NULL;
+
+ item = string_list_lookup(name, &fn_table);
+ if (item != NULL)
+ return (struct patch *)item->util;
+
+ return NULL;
+}
+
+/*
+ * item->util in the filename table records the status of the path.
+ * Usually it points at a patch (whose result records the contents
+ * of it after applying it), but it could be PATH_WAS_DELETED for a
+ * path that a previously applied patch has already removed.
+ */
+ #define PATH_TO_BE_DELETED ((struct patch *) -2)
+#define PATH_WAS_DELETED ((struct patch *) -1)
+
+static int to_be_deleted(struct patch *patch)
+{
+ return patch == PATH_TO_BE_DELETED;
+}
+
+static int was_deleted(struct patch *patch)
+{
+ return patch == PATH_WAS_DELETED;
+}
+
+static void add_to_fn_table(struct patch *patch)
+{
+ struct string_list_item *item;
+
+ /*
+ * Always add new_name unless patch is a deletion
+ * This should cover the cases for normal diffs,
+ * file creations and copies
+ */
+ if (patch->new_name != NULL) {
+ item = string_list_insert(patch->new_name, &fn_table);
+ item->util = patch;
+ }
+
+ /*
+ * store a failure on rename/deletion cases because
+ * later chunks shouldn't patch old names
+ */
+ if ((patch->new_name == NULL) || (patch->is_rename)) {
+ item = string_list_insert(patch->old_name, &fn_table);
+ item->util = PATH_WAS_DELETED;
+ }
+}
+
+static void prepare_fn_table(struct patch *patch)
+{
+ /*
+ * store information about incoming file deletion
+ */
+ while (patch) {
+ if ((patch->new_name == NULL) || (patch->is_rename)) {
+ struct string_list_item *item;
+ item = string_list_insert(patch->old_name, &fn_table);
+ item->util = PATH_TO_BE_DELETED;
+ }
+ patch = patch->next;
+ }
+}
+
+static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct image image;
+ size_t len;
+ char *img;
+ struct patch *tpatch;
+
+ if (!(patch->is_copy || patch->is_rename) &&
+ (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
+ if (was_deleted(tpatch)) {
+ return error("patch %s has been renamed/deleted",
+ patch->old_name);
+ }
+ /* We have a patched copy in memory use that */
+ strbuf_add(&buf, tpatch->result, tpatch->resultsize);
+ } else if (cached) {
+ if (read_file_or_gitlink(ce, &buf))
+ return error("read of %s failed", patch->old_name);
+ } else if (patch->old_name) {
+ if (S_ISGITLINK(patch->old_mode)) {
+ if (ce) {
+ read_file_or_gitlink(ce, &buf);
+ } else {
+ /*
+ * There is no way to apply subproject
+ * patch without looking at the index.
+ */
+ patch->fragments = NULL;
+ }
+ } else {
+ if (read_old_data(st, patch->old_name, &buf))
+ return error("read of %s failed", patch->old_name);
+ }
+ }
+
+ img = strbuf_detach(&buf, &len);
+ prepare_image(&image, img, len, !patch->is_binary);
+
+ if (apply_fragments(&image, patch) < 0)
+ return -1; /* note with --reject this succeeds. */
+ patch->result = image.buf;
+ patch->resultsize = image.len;
+ add_to_fn_table(patch);
+ free(image.line_allocated);
+
+ if (0 < patch->is_delete && patch->resultsize)
+ return error("removal patch leaves file contents");
+
+ return 0;
+}
+
+static int check_to_create_blob(const char *new_name, int ok_if_exists)
+{
+ struct stat nst;
+ if (!lstat(new_name, &nst)) {
+ if (S_ISDIR(nst.st_mode) || ok_if_exists)
+ return 0;
+ /*
+ * A leading component of new_name might be a symlink
+ * that is going to be removed with this patch, but
+ * still pointing at somewhere that has the path.
+ * In such a case, path "new_name" does not exist as
+ * far as git is concerned.
+ */
+ if (has_symlink_leading_path(new_name, strlen(new_name)))
+ return 0;
+
+ return error("%s: already exists in working directory", new_name);
+ }
+ else if ((errno != ENOENT) && (errno != ENOTDIR))
+ return error("%s: %s", new_name, strerror(errno));
+ return 0;
+}
+
+static int verify_index_match(struct cache_entry *ce, struct stat *st)
+{
+ if (S_ISGITLINK(ce->ce_mode)) {
+ if (!S_ISDIR(st->st_mode))
+ return -1;
+ return 0;
+ }
+ return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
+}
+
+static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
+{
+ const char *old_name = patch->old_name;
+ struct patch *tpatch = NULL;
+ int stat_ret = 0;
+ unsigned st_mode = 0;
+
+ /*
+ * Make sure that we do not have local modifications from the
+ * index when we are looking at the index. Also make sure
+ * we have the preimage file to be patched in the work tree,
+ * unless --cached, which tells git to apply only in the index.
+ */
+ if (!old_name)
+ return 0;
+
+ assert(patch->is_new <= 0);
+
+ if (!(patch->is_copy || patch->is_rename) &&
+ (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
+ if (was_deleted(tpatch))
+ return error("%s: has been deleted/renamed", old_name);
+ st_mode = tpatch->new_mode;
+ } else if (!cached) {
+ stat_ret = lstat(old_name, st);
+ if (stat_ret && errno != ENOENT)
+ return error("%s: %s", old_name, strerror(errno));
+ }
+
+ if (to_be_deleted(tpatch))
+ tpatch = NULL;
+
+ if (check_index && !tpatch) {
+ int pos = cache_name_pos(old_name, strlen(old_name));
+ if (pos < 0) {
+ if (patch->is_new < 0)
+ goto is_new;
+ return error("%s: does not exist in index", old_name);
+ }
+ *ce = active_cache[pos];
+ if (stat_ret < 0) {
+ struct checkout costate;
+ /* checkout */
+ costate.base_dir = "";
+ costate.base_dir_len = 0;
+ costate.force = 0;
+ costate.quiet = 0;
+ costate.not_new = 0;
+ costate.refresh_cache = 1;
+ if (checkout_entry(*ce, &costate, NULL) ||
+ lstat(old_name, st))
+ return -1;
+ }
+ if (!cached && verify_index_match(*ce, st))
+ return error("%s: does not match index", old_name);
+ if (cached)
+ st_mode = (*ce)->ce_mode;
+ } else if (stat_ret < 0) {
+ if (patch->is_new < 0)
+ goto is_new;
+ return error("%s: %s", old_name, strerror(errno));
+ }
+
+ if (!cached && !tpatch)
+ st_mode = ce_mode_from_stat(*ce, st->st_mode);
+
+ if (patch->is_new < 0)
+ patch->is_new = 0;
+ if (!patch->old_mode)
+ patch->old_mode = st_mode;
+ if ((st_mode ^ patch->old_mode) & S_IFMT)
+ return error("%s: wrong type", old_name);
+ if (st_mode != patch->old_mode)
+ warning("%s has type %o, expected %o",
+ old_name, st_mode, patch->old_mode);
+ if (!patch->new_mode && !patch->is_delete)
+ patch->new_mode = st_mode;
+ return 0;
+
+ is_new:
+ patch->is_new = 1;
+ patch->is_delete = 0;
+ patch->old_name = NULL;
+ return 0;
+}
+
+static int check_patch(struct patch *patch)
+{
+ struct stat st;
+ const char *old_name = patch->old_name;
+ const char *new_name = patch->new_name;
+ const char *name = old_name ? old_name : new_name;
+ struct cache_entry *ce = NULL;
+ struct patch *tpatch;
+ int ok_if_exists;
+ int status;
+
+ patch->rejected = 1; /* we will drop this after we succeed */
+
+ status = check_preimage(patch, &ce, &st);
+ if (status)
+ return status;
+ old_name = patch->old_name;
+
+ if ((tpatch = in_fn_table(new_name)) &&
+ (was_deleted(tpatch) || to_be_deleted(tpatch)))
+ /*
+ * A type-change diff is always split into a patch to
+ * delete old, immediately followed by a patch to
+ * create new (see diff.c::run_diff()); in such a case
+ * it is Ok that the entry to be deleted by the
+ * previous patch is still in the working tree and in
+ * the index.
+ */
+ ok_if_exists = 1;
+ else
+ ok_if_exists = 0;
+
+ if (new_name &&
+ ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
+ if (check_index &&
+ cache_name_pos(new_name, strlen(new_name)) >= 0 &&
+ !ok_if_exists)
+ return error("%s: already exists in index", new_name);
+ if (!cached) {
+ int err = check_to_create_blob(new_name, ok_if_exists);
+ if (err)
+ return err;
+ }
+ if (!patch->new_mode) {
+ if (0 < patch->is_new)
+ patch->new_mode = S_IFREG | 0644;
+ else
+ patch->new_mode = patch->old_mode;
+ }
+ }
+
+ if (new_name && old_name) {
+ int same = !strcmp(old_name, new_name);
+ if (!patch->new_mode)
+ patch->new_mode = patch->old_mode;
+ if ((patch->old_mode ^ patch->new_mode) & S_IFMT)
+ return error("new mode (%o) of %s does not match old mode (%o)%s%s",
+ patch->new_mode, new_name, patch->old_mode,
+ same ? "" : " of ", same ? "" : old_name);
+ }
+
+ if (apply_data(patch, &st, ce) < 0)
+ return error("%s: patch does not apply", name);
+ patch->rejected = 0;
+ return 0;
+}
+
+static int check_patch_list(struct patch *patch)
+{
+ int err = 0;
+
+ prepare_fn_table(patch);
+ while (patch) {
+ if (apply_verbosely)
+ say_patch_name(stderr,
+ "Checking patch ", patch, "...\n");
+ err |= check_patch(patch);
+ patch = patch->next;
+ }
+ return err;
+}
+
+/* This function tries to read the sha1 from the current index */
+static int get_current_sha1(const char *path, unsigned char *sha1)
+{
+ int pos;
+
+ if (read_cache() < 0)
+ return -1;
+ pos = cache_name_pos(path, strlen(path));
+ if (pos < 0)
+ return -1;
+ hashcpy(sha1, active_cache[pos]->sha1);
+ return 0;
+}
+
+/* Build an index that contains the just the files needed for a 3way merge */
+static void build_fake_ancestor(struct patch *list, const char *filename)
+{
+ struct patch *patch;
+ struct index_state result = { NULL };
+ int fd;
+
+ /* Once we start supporting the reverse patch, it may be
+ * worth showing the new sha1 prefix, but until then...
+ */
+ for (patch = list; patch; patch = patch->next) {
+ const unsigned char *sha1_ptr;
+ unsigned char sha1[20];
+ struct cache_entry *ce;
+ const char *name;
+
+ name = patch->old_name ? patch->old_name : patch->new_name;
+ if (0 < patch->is_new)
+ continue;
+ else if (get_sha1(patch->old_sha1_prefix, sha1))
+ /* git diff has no index line for mode/type changes */
+ if (!patch->lines_added && !patch->lines_deleted) {
+ if (get_current_sha1(patch->new_name, sha1) ||
+ get_current_sha1(patch->old_name, sha1))
+ die("mode change for %s, which is not "
+ "in current HEAD", name);
+ sha1_ptr = sha1;
+ } else
+ die("sha1 information is lacking or useless "
+ "(%s).", name);
+ else
+ sha1_ptr = sha1;
+
+ ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
+ if (!ce)
+ die("make_cache_entry failed for path '%s'", name);
+ if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
+ die ("Could not add %s to temporary index", name);
+ }
+
+ fd = open(filename, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0 || write_index(&result, fd) || close(fd))
+ die ("Could not write temporary index to %s", filename);
+
+ discard_index(&result);
+}
+
+static void stat_patch_list(struct patch *patch)
+{
+ int files, adds, dels;
+
+ for (files = adds = dels = 0 ; patch ; patch = patch->next) {
+ files++;
+ adds += patch->lines_added;
+ dels += patch->lines_deleted;
+ show_stats(patch);
+ }
+
+ printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
+}
+
+static void numstat_patch_list(struct patch *patch)
+{
+ for ( ; patch; patch = patch->next) {
+ const char *name;
+ name = patch->new_name ? patch->new_name : patch->old_name;
+ if (patch->is_binary)
+ printf("-\t-\t");
+ else
+ printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
+ write_name_quoted(name, stdout, line_termination);
+ }
+}
+
+static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
+{
+ if (mode)
+ printf(" %s mode %06o %s\n", newdelete, mode, name);
+ else
+ printf(" %s %s\n", newdelete, name);
+}
+
+static void show_mode_change(struct patch *p, int show_name)
+{
+ if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
+ if (show_name)
+ printf(" mode change %06o => %06o %s\n",
+ p->old_mode, p->new_mode, p->new_name);
+ else
+ printf(" mode change %06o => %06o\n",
+ p->old_mode, p->new_mode);
+ }
+}
+
+static void show_rename_copy(struct patch *p)
+{
+ const char *renamecopy = p->is_rename ? "rename" : "copy";
+ const char *old, *new;
+
+ /* Find common prefix */
+ old = p->old_name;
+ new = p->new_name;
+ while (1) {
+ const char *slash_old, *slash_new;
+ slash_old = strchr(old, '/');
+ slash_new = strchr(new, '/');
+ if (!slash_old ||
+ !slash_new ||
+ slash_old - old != slash_new - new ||
+ memcmp(old, new, slash_new - new))
+ break;
+ old = slash_old + 1;
+ new = slash_new + 1;
+ }
+ /* p->old_name thru old is the common prefix, and old and new
+ * through the end of names are renames
+ */
+ if (old != p->old_name)
+ printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
+ (int)(old - p->old_name), p->old_name,
+ old, new, p->score);
+ else
+ printf(" %s %s => %s (%d%%)\n", renamecopy,
+ p->old_name, p->new_name, p->score);
+ show_mode_change(p, 0);
+}
+
+static void summary_patch_list(struct patch *patch)
+{
+ struct patch *p;
+
+ for (p = patch; p; p = p->next) {
+ if (p->is_new)
+ show_file_mode_name("create", p->new_mode, p->new_name);
+ else if (p->is_delete)
+ show_file_mode_name("delete", p->old_mode, p->old_name);
+ else {
+ if (p->is_rename || p->is_copy)
+ show_rename_copy(p);
+ else {
+ if (p->score) {
+ printf(" rewrite %s (%d%%)\n",
+ p->new_name, p->score);
+ show_mode_change(p, 0);
+ }
+ else
+ show_mode_change(p, 1);
+ }
+ }
+ }
+}
+
+static void patch_stats(struct patch *patch)
+{
+ int lines = patch->lines_added + patch->lines_deleted;
+
+ if (lines > max_change)
+ max_change = lines;
+ if (patch->old_name) {
+ int len = quote_c_style(patch->old_name, NULL, NULL, 0);
+ if (!len)
+ len = strlen(patch->old_name);
+ if (len > max_len)
+ max_len = len;
+ }
+ if (patch->new_name) {
+ int len = quote_c_style(patch->new_name, NULL, NULL, 0);
+ if (!len)
+ len = strlen(patch->new_name);
+ if (len > max_len)
+ max_len = len;
+ }
+}
+
+static void remove_file(struct patch *patch, int rmdir_empty)
+{
+ if (update_index) {
+ if (remove_file_from_cache(patch->old_name) < 0)
+ die("unable to remove %s from index", patch->old_name);
+ }
+ if (!cached) {
+ if (S_ISGITLINK(patch->old_mode)) {
+ if (rmdir(patch->old_name))
+ warning("unable to remove submodule %s",
+ patch->old_name);
+ } else if (!unlink_or_warn(patch->old_name) && rmdir_empty) {
+ remove_path(patch->old_name);
+ }
+ }
+}
+
+static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size)
+{
+ struct stat st;
+ struct cache_entry *ce;
+ int namelen = strlen(path);
+ unsigned ce_size = cache_entry_size(namelen);
+
+ if (!update_index)
+ return;
+
+ ce = xcalloc(1, ce_size);
+ memcpy(ce->name, path, namelen);
+ ce->ce_mode = create_ce_mode(mode);
+ ce->ce_flags = namelen;
+ if (S_ISGITLINK(mode)) {
+ const char *s = buf;
+
+ if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
+ die("corrupt patch for subproject %s", path);
+ } else {
+ if (!cached) {
+ if (lstat(path, &st) < 0)
+ die_errno("unable to stat newly created file '%s'",
+ path);
+ fill_stat_cache_info(ce, &st);
+ }
+ if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
+ die("unable to create backing store for newly created file %s", path);
+ }
+ if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
+ die("unable to add cache entry for %s", path);
+}
+
+static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
+{
+ int fd;
+ struct strbuf nbuf = STRBUF_INIT;
+
+ if (S_ISGITLINK(mode)) {
+ struct stat st;
+ if (!lstat(path, &st) && S_ISDIR(st.st_mode))
+ return 0;
+ return mkdir(path, 0777);
+ }
+
+ if (has_symlinks && S_ISLNK(mode))
+ /* Although buf:size is counted string, it also is NUL
+ * terminated.
+ */
+ return symlink(buf, path);
+
+ fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
+ if (fd < 0)
+ return -1;
+
+ if (convert_to_working_tree(path, buf, size, &nbuf)) {
+ size = nbuf.len;
+ buf = nbuf.buf;
+ }
+ write_or_die(fd, buf, size);
+ strbuf_release(&nbuf);
+
+ if (close(fd) < 0)
+ die_errno("closing file '%s'", path);
+ return 0;
+}
+
+/*
+ * We optimistically assume that the directories exist,
+ * which is true 99% of the time anyway. If they don't,
+ * we create them and try again.
+ */
+static void create_one_file(char *path, unsigned mode, const char *buf, unsigned long size)
+{
+ if (cached)
+ return;
+ if (!try_create_file(path, mode, buf, size))
+ return;
+
+ if (errno == ENOENT) {
+ if (safe_create_leading_directories(path))
+ return;
+ if (!try_create_file(path, mode, buf, size))
+ return;
+ }
+
+ if (errno == EEXIST || errno == EACCES) {
+ /* We may be trying to create a file where a directory
+ * used to be.
+ */
+ struct stat st;
+ if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path)))
+ errno = EEXIST;
+ }
+
+ if (errno == EEXIST) {
+ unsigned int nr = getpid();
+
+ for (;;) {
+ char newpath[PATH_MAX];
+ mksnpath(newpath, sizeof(newpath), "%s~%u", path, nr);
+ if (!try_create_file(newpath, mode, buf, size)) {
+ if (!rename(newpath, path))
+ return;
+ unlink_or_warn(newpath);
+ break;
+ }
+ if (errno != EEXIST)
+ break;
+ ++nr;
+ }
+ }
+ die_errno("unable to write file '%s' mode %o", path, mode);
+}
+
+static void create_file(struct patch *patch)
+{
+ char *path = patch->new_name;
+ unsigned mode = patch->new_mode;
+ unsigned long size = patch->resultsize;
+ char *buf = patch->result;
+
+ if (!mode)
+ mode = S_IFREG | 0644;
+ create_one_file(path, mode, buf, size);
+ add_index_file(path, mode, buf, size);
+}
+
+/* phase zero is to remove, phase one is to create */
+static void write_out_one_result(struct patch *patch, int phase)
+{
+ if (patch->is_delete > 0) {
+ if (phase == 0)
+ remove_file(patch, 1);
+ return;
+ }
+ if (patch->is_new > 0 || patch->is_copy) {
+ if (phase == 1)
+ create_file(patch);
+ return;
+ }
+ /*
+ * Rename or modification boils down to the same
+ * thing: remove the old, write the new
+ */
+ if (phase == 0)
+ remove_file(patch, patch->is_rename);
+ if (phase == 1)
+ create_file(patch);
+}
+
+static int write_out_one_reject(struct patch *patch)
+{
+ FILE *rej;
+ char namebuf[PATH_MAX];
+ struct fragment *frag;
+ int cnt = 0;
+
+ for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
+ if (!frag->rejected)
+ continue;
+ cnt++;
+ }
+
+ if (!cnt) {
+ if (apply_verbosely)
+ say_patch_name(stderr,
+ "Applied patch ", patch, " cleanly.\n");
+ return 0;
+ }
+
+ /* This should not happen, because a removal patch that leaves
+ * contents are marked "rejected" at the patch level.
+ */
+ if (!patch->new_name)
+ die("internal error");
+
+ /* Say this even without --verbose */
+ say_patch_name(stderr, "Applying patch ", patch, " with");
+ fprintf(stderr, " %d rejects...\n", cnt);
+
+ cnt = strlen(patch->new_name);
+ if (ARRAY_SIZE(namebuf) <= cnt + 5) {
+ cnt = ARRAY_SIZE(namebuf) - 5;
+ warning("truncating .rej filename to %.*s.rej",
+ cnt - 1, patch->new_name);
+ }
+ memcpy(namebuf, patch->new_name, cnt);
+ memcpy(namebuf + cnt, ".rej", 5);
+
+ rej = fopen(namebuf, "w");
+ if (!rej)
+ return error("cannot open %s: %s", namebuf, strerror(errno));
+
+ /* Normal git tools never deal with .rej, so do not pretend
+ * this is a git patch by saying --git nor give extended
+ * headers. While at it, maybe please "kompare" that wants
+ * the trailing TAB and some garbage at the end of line ;-).
+ */
+ fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n",
+ patch->new_name, patch->new_name);
+ for (cnt = 1, frag = patch->fragments;
+ frag;
+ cnt++, frag = frag->next) {
+ if (!frag->rejected) {
+ fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
+ continue;
+ }
+ fprintf(stderr, "Rejected hunk #%d.\n", cnt);
+ fprintf(rej, "%.*s", frag->size, frag->patch);
+ if (frag->patch[frag->size-1] != '\n')
+ fputc('\n', rej);
+ }
+ fclose(rej);
+ return -1;
+}
+
+static int write_out_results(struct patch *list, int skipped_patch)
+{
+ int phase;
+ int errs = 0;
+ struct patch *l;
+
+ if (!list && !skipped_patch)
+ return error("No changes");
+
+ for (phase = 0; phase < 2; phase++) {
+ l = list;
+ while (l) {
+ if (l->rejected)
+ errs = 1;
+ else {
+ write_out_one_result(l, phase);
+ if (phase == 1 && write_out_one_reject(l))
+ errs = 1;
+ }
+ l = l->next;
+ }
+ }
+ return errs;
+}
+
+static struct lock_file lock_file;
+
+static struct string_list limit_by_name;
+static int has_include;
+static void add_name_limit(const char *name, int exclude)
+{
+ struct string_list_item *it;
+
+ it = string_list_append(name, &limit_by_name);
+ it->util = exclude ? NULL : (void *) 1;
+}
+
+static int use_patch(struct patch *p)
+{
+ const char *pathname = p->new_name ? p->new_name : p->old_name;
+ int i;
+
+ /* Paths outside are not touched regardless of "--include" */
+ if (0 < prefix_length) {
+ int pathlen = strlen(pathname);
+ if (pathlen <= prefix_length ||
+ memcmp(prefix, pathname, prefix_length))
+ return 0;
+ }
+
+ /* See if it matches any of exclude/include rule */
+ for (i = 0; i < limit_by_name.nr; i++) {
+ struct string_list_item *it = &limit_by_name.items[i];
+ if (!fnmatch(it->string, pathname, 0))
+ return (it->util != NULL);
+ }
+
+ /*
+ * If we had any include, a path that does not match any rule is
+ * not used. Otherwise, we saw bunch of exclude rules (or none)
+ * and such a path is used.
+ */
+ return !has_include;
+}
+
+
+static void prefix_one(char **name)
+{
+ char *old_name = *name;
+ if (!old_name)
+ return;
+ *name = xstrdup(prefix_filename(prefix, prefix_length, *name));
+ free(old_name);
+}
+
+static void prefix_patches(struct patch *p)
+{
+ if (!prefix || p->is_toplevel_relative)
+ return;
+ for ( ; p; p = p->next) {
+ if (p->new_name == p->old_name) {
+ char *prefixed = p->new_name;
+ prefix_one(&prefixed);
+ p->new_name = p->old_name = prefixed;
+ }
+ else {
+ prefix_one(&p->new_name);
+ prefix_one(&p->old_name);
+ }
+ }
+}
+
+#define INACCURATE_EOF (1<<0)
+#define RECOUNT (1<<1)
+
+static int apply_patch(int fd, const char *filename, int options)
+{
+ size_t offset;
+ struct strbuf buf = STRBUF_INIT;
+ struct patch *list = NULL, **listp = &list;
+ int skipped_patch = 0;
+
+ /* FIXME - memory leak when using multiple patch files as inputs */
+ memset(&fn_table, 0, sizeof(struct string_list));
+ patch_input_file = filename;
+ read_patch_file(&buf, fd);
+ offset = 0;
+ while (offset < buf.len) {
+ struct patch *patch;
+ int nr;
+
+ patch = xcalloc(1, sizeof(*patch));
+ patch->inaccurate_eof = !!(options & INACCURATE_EOF);
+ patch->recount = !!(options & RECOUNT);
+ nr = parse_chunk(buf.buf + offset, buf.len - offset, patch);
+ if (nr < 0)
+ break;
+ if (apply_in_reverse)
+ reverse_patches(patch);
+ if (prefix)
+ prefix_patches(patch);
+ if (use_patch(patch)) {
+ patch_stats(patch);
+ *listp = patch;
+ listp = &patch->next;
+ }
+ else {
+ /* perhaps free it a bit better? */
+ free(patch);
+ skipped_patch++;
+ }
+ offset += nr;
+ }
+
+ if (whitespace_error && (ws_error_action == die_on_ws_error))
+ apply = 0;
+
+ update_index = check_index && apply;
+ if (update_index && newfd < 0)
+ newfd = hold_locked_index(&lock_file, 1);
+
+ if (check_index) {
+ if (read_cache() < 0)
+ die("unable to read index file");
+ }
+
+ if ((check || apply) &&
+ check_patch_list(list) < 0 &&
+ !apply_with_reject)
+ exit(1);
+
+ if (apply && write_out_results(list, skipped_patch))
+ exit(1);
+
+ if (fake_ancestor)
+ build_fake_ancestor(list, fake_ancestor);
+
+ if (diffstat)
+ stat_patch_list(list);
+
+ if (numstat)
+ numstat_patch_list(list);
+
+ if (summary)
+ summary_patch_list(list);
+
+ strbuf_release(&buf);
+ return 0;
+}
+
+static int git_apply_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "apply.whitespace"))
+ return git_config_string(&apply_default_whitespace, var, value);
+ else if (!strcmp(var, "apply.ignorewhitespace"))
+ return git_config_string(&apply_default_ignorewhitespace, var, value);
+ return git_default_config(var, value, cb);
+}
+
+static int option_parse_exclude(const struct option *opt,
+ const char *arg, int unset)
+{
+ add_name_limit(arg, 1);
+ return 0;
+}
+
+static int option_parse_include(const struct option *opt,
+ const char *arg, int unset)
+{
+ add_name_limit(arg, 0);
+ has_include = 1;
+ return 0;
+}
+
+static int option_parse_p(const struct option *opt,
+ const char *arg, int unset)
+{
+ p_value = atoi(arg);
+ p_value_known = 1;
+ return 0;
+}
+
+static int option_parse_z(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ line_termination = '\n';
+ else
+ line_termination = 0;
+ return 0;
+}
+
+static int option_parse_space_change(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ ws_ignore_action = ignore_ws_none;
+ else
+ ws_ignore_action = ignore_ws_change;
+ return 0;
+}
+
+static int option_parse_whitespace(const struct option *opt,
+ const char *arg, int unset)
+{
+ const char **whitespace_option = opt->value;
+
+ *whitespace_option = arg;
+ parse_whitespace_option(arg);
+ return 0;
+}
+
+static int option_parse_directory(const struct option *opt,
+ const char *arg, int unset)
+{
+ root_len = strlen(arg);
+ if (root_len && arg[root_len - 1] != '/') {
+ char *new_root;
+ root = new_root = xmalloc(root_len + 2);
+ strcpy(new_root, arg);
+ strcpy(new_root + root_len++, "/");
+ } else
+ root = arg;
+ return 0;
+}
+
+int cmd_apply(int argc, const char **argv, const char *unused_prefix)
+{
+ int i;
+ int errs = 0;
+ int is_not_gitdir;
+ int binary;
+ int force_apply = 0;
+
+ const char *whitespace_option = NULL;
+
+ struct option builtin_apply_options[] = {
+ { OPTION_CALLBACK, 0, "exclude", NULL, "path",
+ "don't apply changes matching the given path",
+ 0, option_parse_exclude },
+ { OPTION_CALLBACK, 0, "include", NULL, "path",
+ "apply changes matching the given path",
+ 0, option_parse_include },
+ { OPTION_CALLBACK, 'p', NULL, NULL, "num",
+ "remove <num> leading slashes from traditional diff paths",
+ 0, option_parse_p },
+ OPT_BOOLEAN(0, "no-add", &no_add,
+ "ignore additions made by the patch"),
+ OPT_BOOLEAN(0, "stat", &diffstat,
+ "instead of applying the patch, output diffstat for the input"),
+ { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary,
+ NULL, "old option, now no-op",
+ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
+ { OPTION_BOOLEAN, 0, "binary", &binary,
+ NULL, "old option, now no-op",
+ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
+ OPT_BOOLEAN(0, "numstat", &numstat,
+ "shows number of added and deleted lines in decimal notation"),
+ OPT_BOOLEAN(0, "summary", &summary,
+ "instead of applying the patch, output a summary for the input"),
+ OPT_BOOLEAN(0, "check", &check,
+ "instead of applying the patch, see if the patch is applicable"),
+ OPT_BOOLEAN(0, "index", &check_index,
+ "make sure the patch is applicable to the current index"),
+ OPT_BOOLEAN(0, "cached", &cached,
+ "apply a patch without touching the working tree"),
+ OPT_BOOLEAN(0, "apply", &force_apply,
+ "also apply the patch (use with --stat/--summary/--check)"),
+ OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
+ "build a temporary index based on embedded index information"),
+ { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+ "paths are separated with NUL character",
+ PARSE_OPT_NOARG, option_parse_z },
+ OPT_INTEGER('C', NULL, &p_context,
+ "ensure at least <n> lines of context match"),
+ { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
+ "detect new or modified lines that have whitespace errors",
+ 0, option_parse_whitespace },
+ { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
+ "ignore changes in whitespace when finding context",
+ PARSE_OPT_NOARG, option_parse_space_change },
+ { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
+ "ignore changes in whitespace when finding context",
+ PARSE_OPT_NOARG, option_parse_space_change },
+ OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
+ "apply the patch in reverse"),
+ OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
+ "don't expect at least one line of context"),
+ OPT_BOOLEAN(0, "reject", &apply_with_reject,
+ "leave the rejected hunks in corresponding *.rej files"),
+ OPT__VERBOSE(&apply_verbosely),
+ OPT_BIT(0, "inaccurate-eof", &options,
+ "tolerate incorrectly detected missing new-line at the end of file",
+ INACCURATE_EOF),
+ OPT_BIT(0, "recount", &options,
+ "do not trust the line counts in the hunk headers",
+ RECOUNT),
+ { OPTION_CALLBACK, 0, "directory", NULL, "root",
+ "prepend <root> to all filenames",
+ 0, option_parse_directory },
+ OPT_END()
+ };
+
+ prefix = setup_git_directory_gently(&is_not_gitdir);
+ prefix_length = prefix ? strlen(prefix) : 0;
+ git_config(git_apply_config, NULL);
+ if (apply_default_whitespace)
+ parse_whitespace_option(apply_default_whitespace);
+ if (apply_default_ignorewhitespace)
+ parse_ignorewhitespace_option(apply_default_ignorewhitespace);
+
+ argc = parse_options(argc, argv, prefix, builtin_apply_options,
+ apply_usage, 0);
+
+ if (apply_with_reject)
+ apply = apply_verbosely = 1;
+ if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
+ apply = 0;
+ if (check_index && is_not_gitdir)
+ die("--index outside a repository");
+ if (cached) {
+ if (is_not_gitdir)
+ die("--cached outside a repository");
+ check_index = 1;
+ }
+ for (i = 0; i < argc; i++) {
+ const char *arg = argv[i];
+ int fd;
+
+ if (!strcmp(arg, "-")) {
+ errs |= apply_patch(0, "<stdin>", options);
+ read_stdin = 0;
+ continue;
+ } else if (0 < prefix_length)
+ arg = prefix_filename(prefix, prefix_length, arg);
+
+ fd = open(arg, O_RDONLY);
+ if (fd < 0)
+ die_errno("can't open patch '%s'", arg);
+ read_stdin = 0;
+ set_default_whitespace_mode(whitespace_option);
+ errs |= apply_patch(fd, arg, options);
+ close(fd);
+ }
+ set_default_whitespace_mode(whitespace_option);
+ if (read_stdin)
+ errs |= apply_patch(0, "<stdin>", options);
+ if (whitespace_error) {
+ if (squelch_whitespace_errors &&
+ squelch_whitespace_errors < whitespace_error) {
+ int squelched =
+ whitespace_error - squelch_whitespace_errors;
+ warning("squelched %d "
+ "whitespace error%s",
+ squelched,
+ squelched == 1 ? "" : "s");
+ }
+ if (ws_error_action == die_on_ws_error)
+ die("%d line%s add%s whitespace errors.",
+ whitespace_error,
+ whitespace_error == 1 ? "" : "s",
+ whitespace_error == 1 ? "s" : "");
+ if (applied_after_fixing_ws && apply)
+ warning("%d line%s applied after"
+ " fixing whitespace errors.",
+ applied_after_fixing_ws,
+ applied_after_fixing_ws == 1 ? "" : "s");
+ else if (whitespace_error)
+ warning("%d line%s add%s whitespace errors.",
+ whitespace_error,
+ whitespace_error == 1 ? "" : "s",
+ whitespace_error == 1 ? "s" : "");
+ }
+
+ if (update_index) {
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(&lock_file))
+ die("Unable to write new index file");
+ }
+
+ return !!errs;
+}
--- /dev/null
+/*
+ * Copyright (c) 2006 Franck Bui-Huu
+ * Copyright (c) 2006 Rene Scharfe
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "archive.h"
+#include "transport.h"
+#include "parse-options.h"
+#include "pkt-line.h"
+#include "sideband.h"
+
+static void create_output_file(const char *output_file)
+{
+ int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ if (output_fd < 0)
+ die_errno("could not create archive file '%s'", output_file);
+ if (output_fd != 1) {
+ if (dup2(output_fd, 1) < 0)
+ die_errno("could not redirect output");
+ else
+ close(output_fd);
+ }
+}
+
+static int run_remote_archiver(int argc, const char **argv,
+ const char *remote, const char *exec)
+{
+ char buf[LARGE_PACKET_MAX];
+ int fd[2], i, len, rv;
+ struct transport *transport;
+ struct remote *_remote;
+
+ _remote = remote_get(remote);
+ if (!_remote->url[0])
+ die("git archive: Remote with no URL");
+ transport = transport_get(_remote, _remote->url[0]);
+ transport_connect(transport, "git-upload-archive", exec, fd);
+
+ for (i = 1; i < argc; i++)
+ packet_write(fd[1], "argument %s\n", argv[i]);
+ packet_flush(fd[1]);
+
+ len = packet_read_line(fd[0], buf, sizeof(buf));
+ if (!len)
+ die("git archive: expected ACK/NAK, got EOF");
+ if (buf[len-1] == '\n')
+ buf[--len] = 0;
+ if (strcmp(buf, "ACK")) {
+ if (len > 5 && !prefixcmp(buf, "NACK "))
+ die("git archive: NACK %s", buf + 5);
+ die("git archive: protocol error");
+ }
+
+ len = packet_read_line(fd[0], buf, sizeof(buf));
+ if (len)
+ die("git archive: expected a flush");
+
+ /* Now, start reading from fd[0] and spit it out to stdout */
+ rv = recv_sideband("archive", fd[0], 1);
+ rv |= transport_disconnect(transport);
+
+ return !!rv;
+}
+
+static const char *format_from_name(const char *filename)
+{
+ const char *ext = strrchr(filename, '.');
+ if (!ext)
+ return NULL;
+ ext++;
+ if (!strcasecmp(ext, "zip"))
+ return "--format=zip";
+ return NULL;
+}
+
+#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \
+ PARSE_OPT_KEEP_ARGV0 | \
+ PARSE_OPT_KEEP_UNKNOWN | \
+ PARSE_OPT_NO_INTERNAL_HELP )
+
+int cmd_archive(int argc, const char **argv, const char *prefix)
+{
+ const char *exec = "git-upload-archive";
+ const char *output = NULL;
+ const char *remote = NULL;
+ const char *format_option = NULL;
+ struct option local_opts[] = {
+ OPT_STRING('o', "output", &output, "file",
+ "write the archive to this file"),
+ OPT_STRING(0, "remote", &remote, "repo",
+ "retrieve the archive from remote repository <repo>"),
+ OPT_STRING(0, "exec", &exec, "cmd",
+ "path to the remote git-upload-archive command"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, local_opts, NULL,
+ PARSE_OPT_KEEP_ALL);
+
+ if (output) {
+ create_output_file(output);
+ format_option = format_from_name(output);
+ }
+
+ /*
+ * We have enough room in argv[] to muck it in place, because
+ * --output must have been given on the original command line
+ * if we get to this point, and parse_options() must have eaten
+ * it, i.e. we can add back one element to the array.
+ *
+ * We add a fake --format option at the beginning, with the
+ * format inferred from our output filename. This way explicit
+ * --format options can override it, and the fake option is
+ * inserted before any "--" that might have been given.
+ */
+ if (format_option) {
+ memmove(argv + 2, argv + 1, sizeof(*argv) * argc);
+ argv[1] = format_option;
+ argv[++argc] = NULL;
+ }
+
+ if (remote)
+ return run_remote_archiver(argc, argv, remote, exec);
+
+ setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
+
+ return write_archive(argc, argv, prefix, 1);
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "bisect.h"
+
+static const char * const git_bisect_helper_usage[] = {
+ "git bisect--helper --next-all",
+ NULL
+};
+
+int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
+{
+ int next_all = 0;
+ struct option options[] = {
+ OPT_BOOLEAN(0, "next-all", &next_all,
+ "perform 'git bisect next'"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_bisect_helper_usage, 0);
+
+ if (!next_all)
+ usage_with_options(git_bisect_helper_usage, options);
+
+ /* next-all */
+ return bisect_next_all(prefix);
+}
--- /dev/null
+/*
+ * Blame
+ *
+ * Copyright (c) 2006, Junio C Hamano
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "quote.h"
+#include "xdiff-interface.h"
+#include "cache-tree.h"
+#include "string-list.h"
+#include "mailmap.h"
+#include "parse-options.h"
+#include "utf8.h"
+
+static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
+
+static const char *blame_opt_usage[] = {
+ blame_usage,
+ "",
+ "[rev-opts] are documented in git-rev-list(1)",
+ NULL
+};
+
+static int longest_file;
+static int longest_author;
+static int max_orig_digits;
+static int max_digits;
+static int max_score_digits;
+static int show_root;
+static int reverse;
+static int blank_boundary;
+static int incremental;
+static int xdl_opts = XDF_NEED_MINIMAL;
+
+static enum date_mode blame_date_mode = DATE_ISO8601;
+static size_t blame_date_width;
+
+static struct string_list mailmap;
+
+#ifndef DEBUG
+#define DEBUG 0
+#endif
+
+/* stats */
+static int num_read_blob;
+static int num_get_patch;
+static int num_commits;
+
+#define PICKAXE_BLAME_MOVE 01
+#define PICKAXE_BLAME_COPY 02
+#define PICKAXE_BLAME_COPY_HARDER 04
+#define PICKAXE_BLAME_COPY_HARDEST 010
+
+/*
+ * blame for a blame_entry with score lower than these thresholds
+ * is not passed to the parent using move/copy logic.
+ */
+static unsigned blame_move_score;
+static unsigned blame_copy_score;
+#define BLAME_DEFAULT_MOVE_SCORE 20
+#define BLAME_DEFAULT_COPY_SCORE 40
+
+/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */
+#define METAINFO_SHOWN (1u<<12)
+#define MORE_THAN_ONE_PATH (1u<<13)
+
+/*
+ * One blob in a commit that is being suspected
+ */
+struct origin {
+ int refcnt;
+ struct origin *previous;
+ struct commit *commit;
+ mmfile_t file;
+ unsigned char blob_sha1[20];
+ char path[FLEX_ARRAY];
+};
+
+/*
+ * Given an origin, prepare mmfile_t structure to be used by the
+ * diff machinery
+ */
+static void fill_origin_blob(struct origin *o, mmfile_t *file)
+{
+ if (!o->file.ptr) {
+ enum object_type type;
+ num_read_blob++;
+ file->ptr = read_sha1_file(o->blob_sha1, &type,
+ (unsigned long *)(&(file->size)));
+ if (!file->ptr)
+ die("Cannot read blob %s for path %s",
+ sha1_to_hex(o->blob_sha1),
+ o->path);
+ o->file = *file;
+ }
+ else
+ *file = o->file;
+}
+
+/*
+ * Origin is refcounted and usually we keep the blob contents to be
+ * reused.
+ */
+static inline struct origin *origin_incref(struct origin *o)
+{
+ if (o)
+ o->refcnt++;
+ return o;
+}
+
+static void origin_decref(struct origin *o)
+{
+ if (o && --o->refcnt <= 0) {
+ if (o->previous)
+ origin_decref(o->previous);
+ free(o->file.ptr);
+ free(o);
+ }
+}
+
+static void drop_origin_blob(struct origin *o)
+{
+ if (o->file.ptr) {
+ free(o->file.ptr);
+ o->file.ptr = NULL;
+ }
+}
+
+/*
+ * Each group of lines is described by a blame_entry; it can be split
+ * as we pass blame to the parents. They form a linked list in the
+ * scoreboard structure, sorted by the target line number.
+ */
+struct blame_entry {
+ struct blame_entry *prev;
+ struct blame_entry *next;
+
+ /* the first line of this group in the final image;
+ * internally all line numbers are 0 based.
+ */
+ int lno;
+
+ /* how many lines this group has */
+ int num_lines;
+
+ /* the commit that introduced this group into the final image */
+ struct origin *suspect;
+
+ /* true if the suspect is truly guilty; false while we have not
+ * checked if the group came from one of its parents.
+ */
+ char guilty;
+
+ /* true if the entry has been scanned for copies in the current parent
+ */
+ char scanned;
+
+ /* the line number of the first line of this group in the
+ * suspect's file; internally all line numbers are 0 based.
+ */
+ int s_lno;
+
+ /* how significant this entry is -- cached to avoid
+ * scanning the lines over and over.
+ */
+ unsigned score;
+};
+
+/*
+ * The current state of the blame assignment.
+ */
+struct scoreboard {
+ /* the final commit (i.e. where we started digging from) */
+ struct commit *final;
+ struct rev_info *revs;
+ const char *path;
+
+ /*
+ * The contents in the final image.
+ * Used by many functions to obtain contents of the nth line,
+ * indexed with scoreboard.lineno[blame_entry.lno].
+ */
+ const char *final_buf;
+ unsigned long final_buf_size;
+
+ /* linked list of blames */
+ struct blame_entry *ent;
+
+ /* look-up a line in the final buffer */
+ int num_lines;
+ int *lineno;
+};
+
+static inline int same_suspect(struct origin *a, struct origin *b)
+{
+ if (a == b)
+ return 1;
+ if (a->commit != b->commit)
+ return 0;
+ return !strcmp(a->path, b->path);
+}
+
+static void sanity_check_refcnt(struct scoreboard *);
+
+/*
+ * If two blame entries that are next to each other came from
+ * contiguous lines in the same origin (i.e. <commit, path> pair),
+ * merge them together.
+ */
+static void coalesce(struct scoreboard *sb)
+{
+ struct blame_entry *ent, *next;
+
+ for (ent = sb->ent; ent && (next = ent->next); ent = next) {
+ if (same_suspect(ent->suspect, next->suspect) &&
+ ent->guilty == next->guilty &&
+ ent->s_lno + ent->num_lines == next->s_lno) {
+ ent->num_lines += next->num_lines;
+ ent->next = next->next;
+ if (ent->next)
+ ent->next->prev = ent;
+ origin_decref(next->suspect);
+ free(next);
+ ent->score = 0;
+ next = ent; /* again */
+ }
+ }
+
+ if (DEBUG) /* sanity */
+ sanity_check_refcnt(sb);
+}
+
+/*
+ * Given a commit and a path in it, create a new origin structure.
+ * The callers that add blame to the scoreboard should use
+ * get_origin() to obtain shared, refcounted copy instead of calling
+ * this function directly.
+ */
+static struct origin *make_origin(struct commit *commit, const char *path)
+{
+ struct origin *o;
+ o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
+ o->commit = commit;
+ o->refcnt = 1;
+ strcpy(o->path, path);
+ return o;
+}
+
+/*
+ * Locate an existing origin or create a new one.
+ */
+static struct origin *get_origin(struct scoreboard *sb,
+ struct commit *commit,
+ const char *path)
+{
+ struct blame_entry *e;
+
+ for (e = sb->ent; e; e = e->next) {
+ if (e->suspect->commit == commit &&
+ !strcmp(e->suspect->path, path))
+ return origin_incref(e->suspect);
+ }
+ return make_origin(commit, path);
+}
+
+/*
+ * Fill the blob_sha1 field of an origin if it hasn't, so that later
+ * call to fill_origin_blob() can use it to locate the data. blob_sha1
+ * for an origin is also used to pass the blame for the entire file to
+ * the parent to detect the case where a child's blob is identical to
+ * that of its parent's.
+ */
+static int fill_blob_sha1(struct origin *origin)
+{
+ unsigned mode;
+
+ if (!is_null_sha1(origin->blob_sha1))
+ return 0;
+ if (get_tree_entry(origin->commit->object.sha1,
+ origin->path,
+ origin->blob_sha1, &mode))
+ goto error_out;
+ if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
+ goto error_out;
+ return 0;
+ error_out:
+ hashclr(origin->blob_sha1);
+ return -1;
+}
+
+/*
+ * We have an origin -- check if the same path exists in the
+ * parent and return an origin structure to represent it.
+ */
+static struct origin *find_origin(struct scoreboard *sb,
+ struct commit *parent,
+ struct origin *origin)
+{
+ struct origin *porigin = NULL;
+ struct diff_options diff_opts;
+ const char *paths[2];
+
+ if (parent->util) {
+ /*
+ * Each commit object can cache one origin in that
+ * commit. This is a freestanding copy of origin and
+ * not refcounted.
+ */
+ struct origin *cached = parent->util;
+ if (!strcmp(cached->path, origin->path)) {
+ /*
+ * The same path between origin and its parent
+ * without renaming -- the most common case.
+ */
+ porigin = get_origin(sb, parent, cached->path);
+
+ /*
+ * If the origin was newly created (i.e. get_origin
+ * would call make_origin if none is found in the
+ * scoreboard), it does not know the blob_sha1,
+ * so copy it. Otherwise porigin was in the
+ * scoreboard and already knows blob_sha1.
+ */
+ if (porigin->refcnt == 1)
+ hashcpy(porigin->blob_sha1, cached->blob_sha1);
+ return porigin;
+ }
+ /* otherwise it was not very useful; free it */
+ free(parent->util);
+ parent->util = NULL;
+ }
+
+ /* See if the origin->path is different between parent
+ * and origin first. Most of the time they are the
+ * same and diff-tree is fairly efficient about this.
+ */
+ diff_setup(&diff_opts);
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
+ diff_opts.detect_rename = 0;
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ paths[0] = origin->path;
+ paths[1] = NULL;
+
+ diff_tree_setup_paths(paths, &diff_opts);
+ if (diff_setup_done(&diff_opts) < 0)
+ die("diff-setup");
+
+ if (is_null_sha1(origin->commit->object.sha1))
+ do_diff_cache(parent->tree->object.sha1, &diff_opts);
+ else
+ diff_tree_sha1(parent->tree->object.sha1,
+ origin->commit->tree->object.sha1,
+ "", &diff_opts);
+ diffcore_std(&diff_opts);
+
+ if (!diff_queued_diff.nr) {
+ /* The path is the same as parent */
+ porigin = get_origin(sb, parent, origin->path);
+ hashcpy(porigin->blob_sha1, origin->blob_sha1);
+ } else {
+ /*
+ * Since origin->path is a pathspec, if the parent
+ * commit had it as a directory, we will see a whole
+ * bunch of deletion of files in the directory that we
+ * do not care about.
+ */
+ int i;
+ struct diff_filepair *p = NULL;
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ const char *name;
+ p = diff_queued_diff.queue[i];
+ name = p->one->path ? p->one->path : p->two->path;
+ if (!strcmp(name, origin->path))
+ break;
+ }
+ if (!p)
+ die("internal error in blame::find_origin");
+ switch (p->status) {
+ default:
+ die("internal error in blame::find_origin (%c)",
+ p->status);
+ case 'M':
+ porigin = get_origin(sb, parent, origin->path);
+ hashcpy(porigin->blob_sha1, p->one->sha1);
+ break;
+ case 'A':
+ case 'T':
+ /* Did not exist in parent, or type changed */
+ break;
+ }
+ }
+ diff_flush(&diff_opts);
+ diff_tree_release_paths(&diff_opts);
+ if (porigin) {
+ /*
+ * Create a freestanding copy that is not part of
+ * the refcounted origin found in the scoreboard, and
+ * cache it in the commit.
+ */
+ struct origin *cached;
+
+ cached = make_origin(porigin->commit, porigin->path);
+ hashcpy(cached->blob_sha1, porigin->blob_sha1);
+ parent->util = cached;
+ }
+ return porigin;
+}
+
+/*
+ * We have an origin -- find the path that corresponds to it in its
+ * parent and return an origin structure to represent it.
+ */
+static struct origin *find_rename(struct scoreboard *sb,
+ struct commit *parent,
+ struct origin *origin)
+{
+ struct origin *porigin = NULL;
+ struct diff_options diff_opts;
+ int i;
+ const char *paths[2];
+
+ diff_setup(&diff_opts);
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
+ diff_opts.detect_rename = DIFF_DETECT_RENAME;
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ diff_opts.single_follow = origin->path;
+ paths[0] = NULL;
+ diff_tree_setup_paths(paths, &diff_opts);
+ if (diff_setup_done(&diff_opts) < 0)
+ die("diff-setup");
+
+ if (is_null_sha1(origin->commit->object.sha1))
+ do_diff_cache(parent->tree->object.sha1, &diff_opts);
+ else
+ diff_tree_sha1(parent->tree->object.sha1,
+ origin->commit->tree->object.sha1,
+ "", &diff_opts);
+ diffcore_std(&diff_opts);
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p = diff_queued_diff.queue[i];
+ if ((p->status == 'R' || p->status == 'C') &&
+ !strcmp(p->two->path, origin->path)) {
+ porigin = get_origin(sb, parent, p->one->path);
+ hashcpy(porigin->blob_sha1, p->one->sha1);
+ break;
+ }
+ }
+ diff_flush(&diff_opts);
+ diff_tree_release_paths(&diff_opts);
+ return porigin;
+}
+
+/*
+ * Link in a new blame entry to the scoreboard. Entries that cover the
+ * same line range have been removed from the scoreboard previously.
+ */
+static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
+{
+ struct blame_entry *ent, *prev = NULL;
+
+ origin_incref(e->suspect);
+
+ for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next)
+ prev = ent;
+
+ /* prev, if not NULL, is the last one that is below e */
+ e->prev = prev;
+ if (prev) {
+ e->next = prev->next;
+ prev->next = e;
+ }
+ else {
+ e->next = sb->ent;
+ sb->ent = e;
+ }
+ if (e->next)
+ e->next->prev = e;
+}
+
+/*
+ * src typically is on-stack; we want to copy the information in it to
+ * a malloced blame_entry that is already on the linked list of the
+ * scoreboard. The origin of dst loses a refcnt while the origin of src
+ * gains one.
+ */
+static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
+{
+ struct blame_entry *p, *n;
+
+ p = dst->prev;
+ n = dst->next;
+ origin_incref(src->suspect);
+ origin_decref(dst->suspect);
+ memcpy(dst, src, sizeof(*src));
+ dst->prev = p;
+ dst->next = n;
+ dst->score = 0;
+}
+
+static const char *nth_line(struct scoreboard *sb, int lno)
+{
+ return sb->final_buf + sb->lineno[lno];
+}
+
+/*
+ * It is known that lines between tlno to same came from parent, and e
+ * has an overlap with that range. it also is known that parent's
+ * line plno corresponds to e's line tlno.
+ *
+ * <---- e ----->
+ * <------>
+ * <------------>
+ * <------------>
+ * <------------------>
+ *
+ * Split e into potentially three parts; before this chunk, the chunk
+ * to be blamed for the parent, and after that portion.
+ */
+static void split_overlap(struct blame_entry *split,
+ struct blame_entry *e,
+ int tlno, int plno, int same,
+ struct origin *parent)
+{
+ int chunk_end_lno;
+ memset(split, 0, sizeof(struct blame_entry [3]));
+
+ if (e->s_lno < tlno) {
+ /* there is a pre-chunk part not blamed on parent */
+ split[0].suspect = origin_incref(e->suspect);
+ split[0].lno = e->lno;
+ split[0].s_lno = e->s_lno;
+ split[0].num_lines = tlno - e->s_lno;
+ split[1].lno = e->lno + tlno - e->s_lno;
+ split[1].s_lno = plno;
+ }
+ else {
+ split[1].lno = e->lno;
+ split[1].s_lno = plno + (e->s_lno - tlno);
+ }
+
+ if (same < e->s_lno + e->num_lines) {
+ /* there is a post-chunk part not blamed on parent */
+ split[2].suspect = origin_incref(e->suspect);
+ split[2].lno = e->lno + (same - e->s_lno);
+ split[2].s_lno = e->s_lno + (same - e->s_lno);
+ split[2].num_lines = e->s_lno + e->num_lines - same;
+ chunk_end_lno = split[2].lno;
+ }
+ else
+ chunk_end_lno = e->lno + e->num_lines;
+ split[1].num_lines = chunk_end_lno - split[1].lno;
+
+ /*
+ * if it turns out there is nothing to blame the parent for,
+ * forget about the splitting. !split[1].suspect signals this.
+ */
+ if (split[1].num_lines < 1)
+ return;
+ split[1].suspect = origin_incref(parent);
+}
+
+/*
+ * split_overlap() divided an existing blame e into up to three parts
+ * in split. Adjust the linked list of blames in the scoreboard to
+ * reflect the split.
+ */
+static void split_blame(struct scoreboard *sb,
+ struct blame_entry *split,
+ struct blame_entry *e)
+{
+ struct blame_entry *new_entry;
+
+ if (split[0].suspect && split[2].suspect) {
+ /* The first part (reuse storage for the existing entry e) */
+ dup_entry(e, &split[0]);
+
+ /* The last part -- me */
+ new_entry = xmalloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
+ add_blame_entry(sb, new_entry);
+
+ /* ... and the middle part -- parent */
+ new_entry = xmalloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
+ add_blame_entry(sb, new_entry);
+ }
+ else if (!split[0].suspect && !split[2].suspect)
+ /*
+ * The parent covers the entire area; reuse storage for
+ * e and replace it with the parent.
+ */
+ dup_entry(e, &split[1]);
+ else if (split[0].suspect) {
+ /* me and then parent */
+ dup_entry(e, &split[0]);
+
+ new_entry = xmalloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
+ add_blame_entry(sb, new_entry);
+ }
+ else {
+ /* parent and then me */
+ dup_entry(e, &split[1]);
+
+ new_entry = xmalloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
+ add_blame_entry(sb, new_entry);
+ }
+
+ if (DEBUG) { /* sanity */
+ struct blame_entry *ent;
+ int lno = sb->ent->lno, corrupt = 0;
+
+ for (ent = sb->ent; ent; ent = ent->next) {
+ if (lno != ent->lno)
+ corrupt = 1;
+ if (ent->s_lno < 0)
+ corrupt = 1;
+ lno += ent->num_lines;
+ }
+ if (corrupt) {
+ lno = sb->ent->lno;
+ for (ent = sb->ent; ent; ent = ent->next) {
+ printf("L %8d l %8d n %8d\n",
+ lno, ent->lno, ent->num_lines);
+ lno = ent->lno + ent->num_lines;
+ }
+ die("oops");
+ }
+ }
+}
+
+/*
+ * After splitting the blame, the origins used by the
+ * on-stack blame_entry should lose one refcnt each.
+ */
+static void decref_split(struct blame_entry *split)
+{
+ int i;
+
+ for (i = 0; i < 3; i++)
+ origin_decref(split[i].suspect);
+}
+
+/*
+ * Helper for blame_chunk(). blame_entry e is known to overlap with
+ * the patch hunk; split it and pass blame to the parent.
+ */
+static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
+ int tlno, int plno, int same,
+ struct origin *parent)
+{
+ struct blame_entry split[3];
+
+ split_overlap(split, e, tlno, plno, same, parent);
+ if (split[1].suspect)
+ split_blame(sb, split, e);
+ decref_split(split);
+}
+
+/*
+ * Find the line number of the last line the target is suspected for.
+ */
+static int find_last_in_target(struct scoreboard *sb, struct origin *target)
+{
+ struct blame_entry *e;
+ int last_in_target = -1;
+
+ for (e = sb->ent; e; e = e->next) {
+ if (e->guilty || !same_suspect(e->suspect, target))
+ continue;
+ if (last_in_target < e->s_lno + e->num_lines)
+ last_in_target = e->s_lno + e->num_lines;
+ }
+ return last_in_target;
+}
+
+/*
+ * Process one hunk from the patch between the current suspect for
+ * blame_entry e and its parent. Find and split the overlap, and
+ * pass blame to the overlapping part to the parent.
+ */
+static void blame_chunk(struct scoreboard *sb,
+ int tlno, int plno, int same,
+ struct origin *target, struct origin *parent)
+{
+ struct blame_entry *e;
+
+ for (e = sb->ent; e; e = e->next) {
+ if (e->guilty || !same_suspect(e->suspect, target))
+ continue;
+ if (same <= e->s_lno)
+ continue;
+ if (tlno < e->s_lno + e->num_lines)
+ blame_overlap(sb, e, tlno, plno, same, parent);
+ }
+}
+
+struct blame_chunk_cb_data {
+ struct scoreboard *sb;
+ struct origin *target;
+ struct origin *parent;
+ long plno;
+ long tlno;
+};
+
+static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
+{
+ struct blame_chunk_cb_data *d = data;
+ blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent);
+ d->plno = p_next;
+ d->tlno = t_next;
+}
+
+/*
+ * We are looking at the origin 'target' and aiming to pass blame
+ * for the lines it is suspected to its parent. Run diff to find
+ * which lines came from parent and pass blame for them.
+ */
+static int pass_blame_to_parent(struct scoreboard *sb,
+ struct origin *target,
+ struct origin *parent)
+{
+ int last_in_target;
+ mmfile_t file_p, file_o;
+ struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 };
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+
+ last_in_target = find_last_in_target(sb, target);
+ if (last_in_target < 0)
+ return 1; /* nothing remains for this target */
+
+ fill_origin_blob(parent, &file_p);
+ fill_origin_blob(target, &file_o);
+ num_get_patch++;
+
+ memset(&xpp, 0, sizeof(xpp));
+ xpp.flags = xdl_opts;
+ memset(&xecfg, 0, sizeof(xecfg));
+ xecfg.ctxlen = 0;
+ xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg);
+ /* The rest (i.e. anything after tlno) are the same as the parent */
+ blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
+
+ return 0;
+}
+
+/*
+ * The lines in blame_entry after splitting blames many times can become
+ * very small and trivial, and at some point it becomes pointless to
+ * blame the parents. E.g. "\t\t}\n\t}\n\n" appears everywhere in any
+ * ordinary C program, and it is not worth to say it was copied from
+ * totally unrelated file in the parent.
+ *
+ * Compute how trivial the lines in the blame_entry are.
+ */
+static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
+{
+ unsigned score;
+ const char *cp, *ep;
+
+ if (e->score)
+ return e->score;
+
+ score = 1;
+ cp = nth_line(sb, e->lno);
+ ep = nth_line(sb, e->lno + e->num_lines);
+ while (cp < ep) {
+ unsigned ch = *((unsigned char *)cp);
+ if (isalnum(ch))
+ score++;
+ cp++;
+ }
+ e->score = score;
+ return score;
+}
+
+/*
+ * best_so_far[] and this[] are both a split of an existing blame_entry
+ * that passes blame to the parent. Maintain best_so_far the best split
+ * so far, by comparing this and best_so_far and copying this into
+ * bst_so_far as needed.
+ */
+static void copy_split_if_better(struct scoreboard *sb,
+ struct blame_entry *best_so_far,
+ struct blame_entry *this)
+{
+ int i;
+
+ if (!this[1].suspect)
+ return;
+ if (best_so_far[1].suspect) {
+ if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1]))
+ return;
+ }
+
+ for (i = 0; i < 3; i++)
+ origin_incref(this[i].suspect);
+ decref_split(best_so_far);
+ memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
+}
+
+/*
+ * We are looking at a part of the final image represented by
+ * ent (tlno and same are offset by ent->s_lno).
+ * tlno is where we are looking at in the final image.
+ * up to (but not including) same match preimage.
+ * plno is where we are looking at in the preimage.
+ *
+ * <-------------- final image ---------------------->
+ * <------ent------>
+ * ^tlno ^same
+ * <---------preimage----->
+ * ^plno
+ *
+ * All line numbers are 0-based.
+ */
+static void handle_split(struct scoreboard *sb,
+ struct blame_entry *ent,
+ int tlno, int plno, int same,
+ struct origin *parent,
+ struct blame_entry *split)
+{
+ if (ent->num_lines <= tlno)
+ return;
+ if (tlno < same) {
+ struct blame_entry this[3];
+ tlno += ent->s_lno;
+ same += ent->s_lno;
+ split_overlap(this, ent, tlno, plno, same, parent);
+ copy_split_if_better(sb, split, this);
+ decref_split(this);
+ }
+}
+
+struct handle_split_cb_data {
+ struct scoreboard *sb;
+ struct blame_entry *ent;
+ struct origin *parent;
+ struct blame_entry *split;
+ long plno;
+ long tlno;
+};
+
+static void handle_split_cb(void *data, long same, long p_next, long t_next)
+{
+ struct handle_split_cb_data *d = data;
+ handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split);
+ d->plno = p_next;
+ d->tlno = t_next;
+}
+
+/*
+ * Find the lines from parent that are the same as ent so that
+ * we can pass blames to it. file_p has the blob contents for
+ * the parent.
+ */
+static void find_copy_in_blob(struct scoreboard *sb,
+ struct blame_entry *ent,
+ struct origin *parent,
+ struct blame_entry *split,
+ mmfile_t *file_p)
+{
+ const char *cp;
+ int cnt;
+ mmfile_t file_o;
+ struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 };
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+
+ /*
+ * Prepare mmfile that contains only the lines in ent.
+ */
+ cp = nth_line(sb, ent->lno);
+ file_o.ptr = (char *) cp;
+ cnt = ent->num_lines;
+
+ while (cnt && cp < sb->final_buf + sb->final_buf_size) {
+ if (*cp++ == '\n')
+ cnt--;
+ }
+ file_o.size = cp - file_o.ptr;
+
+ /*
+ * file_o is a part of final image we are annotating.
+ * file_p partially may match that image.
+ */
+ memset(&xpp, 0, sizeof(xpp));
+ xpp.flags = xdl_opts;
+ memset(&xecfg, 0, sizeof(xecfg));
+ xecfg.ctxlen = 1;
+ memset(split, 0, sizeof(struct blame_entry [3]));
+ xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
+ /* remainder, if any, all match the preimage */
+ handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
+}
+
+/*
+ * See if lines currently target is suspected for can be attributed to
+ * parent.
+ */
+static int find_move_in_parent(struct scoreboard *sb,
+ struct origin *target,
+ struct origin *parent)
+{
+ int last_in_target, made_progress;
+ struct blame_entry *e, split[3];
+ mmfile_t file_p;
+
+ last_in_target = find_last_in_target(sb, target);
+ if (last_in_target < 0)
+ return 1; /* nothing remains for this target */
+
+ fill_origin_blob(parent, &file_p);
+ if (!file_p.ptr)
+ return 0;
+
+ made_progress = 1;
+ while (made_progress) {
+ made_progress = 0;
+ for (e = sb->ent; e; e = e->next) {
+ if (e->guilty || !same_suspect(e->suspect, target) ||
+ ent_score(sb, e) < blame_move_score)
+ continue;
+ find_copy_in_blob(sb, e, parent, split, &file_p);
+ if (split[1].suspect &&
+ blame_move_score < ent_score(sb, &split[1])) {
+ split_blame(sb, split, e);
+ made_progress = 1;
+ }
+ decref_split(split);
+ }
+ }
+ return 0;
+}
+
+struct blame_list {
+ struct blame_entry *ent;
+ struct blame_entry split[3];
+};
+
+/*
+ * Count the number of entries the target is suspected for,
+ * and prepare a list of entry and the best split.
+ */
+static struct blame_list *setup_blame_list(struct scoreboard *sb,
+ struct origin *target,
+ int min_score,
+ int *num_ents_p)
+{
+ struct blame_entry *e;
+ int num_ents, i;
+ struct blame_list *blame_list = NULL;
+
+ for (e = sb->ent, num_ents = 0; e; e = e->next)
+ if (!e->scanned && !e->guilty &&
+ same_suspect(e->suspect, target) &&
+ min_score < ent_score(sb, e))
+ num_ents++;
+ if (num_ents) {
+ blame_list = xcalloc(num_ents, sizeof(struct blame_list));
+ for (e = sb->ent, i = 0; e; e = e->next)
+ if (!e->scanned && !e->guilty &&
+ same_suspect(e->suspect, target) &&
+ min_score < ent_score(sb, e))
+ blame_list[i++].ent = e;
+ }
+ *num_ents_p = num_ents;
+ return blame_list;
+}
+
+/*
+ * Reset the scanned status on all entries.
+ */
+static void reset_scanned_flag(struct scoreboard *sb)
+{
+ struct blame_entry *e;
+ for (e = sb->ent; e; e = e->next)
+ e->scanned = 0;
+}
+
+/*
+ * For lines target is suspected for, see if we can find code movement
+ * across file boundary from the parent commit. porigin is the path
+ * in the parent we already tried.
+ */
+static int find_copy_in_parent(struct scoreboard *sb,
+ struct origin *target,
+ struct commit *parent,
+ struct origin *porigin,
+ int opt)
+{
+ struct diff_options diff_opts;
+ const char *paths[1];
+ int i, j;
+ int retval;
+ struct blame_list *blame_list;
+ int num_ents;
+
+ blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
+ if (!blame_list)
+ return 1; /* nothing remains for this target */
+
+ diff_setup(&diff_opts);
+ DIFF_OPT_SET(&diff_opts, RECURSIVE);
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+
+ paths[0] = NULL;
+ diff_tree_setup_paths(paths, &diff_opts);
+ if (diff_setup_done(&diff_opts) < 0)
+ die("diff-setup");
+
+ /* Try "find copies harder" on new path if requested;
+ * we do not want to use diffcore_rename() actually to
+ * match things up; find_copies_harder is set only to
+ * force diff_tree_sha1() to feed all filepairs to diff_queue,
+ * and this code needs to be after diff_setup_done(), which
+ * usually makes find-copies-harder imply copy detection.
+ */
+ if ((opt & PICKAXE_BLAME_COPY_HARDEST)
+ || ((opt & PICKAXE_BLAME_COPY_HARDER)
+ && (!porigin || strcmp(target->path, porigin->path))))
+ DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
+
+ if (is_null_sha1(target->commit->object.sha1))
+ do_diff_cache(parent->tree->object.sha1, &diff_opts);
+ else
+ diff_tree_sha1(parent->tree->object.sha1,
+ target->commit->tree->object.sha1,
+ "", &diff_opts);
+
+ if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
+ diffcore_std(&diff_opts);
+
+ retval = 0;
+ while (1) {
+ int made_progress = 0;
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p = diff_queued_diff.queue[i];
+ struct origin *norigin;
+ mmfile_t file_p;
+ struct blame_entry this[3];
+
+ if (!DIFF_FILE_VALID(p->one))
+ continue; /* does not exist in parent */
+ if (S_ISGITLINK(p->one->mode))
+ continue; /* ignore git links */
+ if (porigin && !strcmp(p->one->path, porigin->path))
+ /* find_move already dealt with this path */
+ continue;
+
+ norigin = get_origin(sb, parent, p->one->path);
+ hashcpy(norigin->blob_sha1, p->one->sha1);
+ fill_origin_blob(norigin, &file_p);
+ if (!file_p.ptr)
+ continue;
+
+ for (j = 0; j < num_ents; j++) {
+ find_copy_in_blob(sb, blame_list[j].ent,
+ norigin, this, &file_p);
+ copy_split_if_better(sb, blame_list[j].split,
+ this);
+ decref_split(this);
+ }
+ origin_decref(norigin);
+ }
+
+ for (j = 0; j < num_ents; j++) {
+ struct blame_entry *split = blame_list[j].split;
+ if (split[1].suspect &&
+ blame_copy_score < ent_score(sb, &split[1])) {
+ split_blame(sb, split, blame_list[j].ent);
+ made_progress = 1;
+ }
+ else
+ blame_list[j].ent->scanned = 1;
+ decref_split(split);
+ }
+ free(blame_list);
+
+ if (!made_progress)
+ break;
+ blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
+ if (!blame_list) {
+ retval = 1;
+ break;
+ }
+ }
+ reset_scanned_flag(sb);
+ diff_flush(&diff_opts);
+ diff_tree_release_paths(&diff_opts);
+ return retval;
+}
+
+/*
+ * The blobs of origin and porigin exactly match, so everything
+ * origin is suspected for can be blamed on the parent.
+ */
+static void pass_whole_blame(struct scoreboard *sb,
+ struct origin *origin, struct origin *porigin)
+{
+ struct blame_entry *e;
+
+ if (!porigin->file.ptr && origin->file.ptr) {
+ /* Steal its file */
+ porigin->file = origin->file;
+ origin->file.ptr = NULL;
+ }
+ for (e = sb->ent; e; e = e->next) {
+ if (!same_suspect(e->suspect, origin))
+ continue;
+ origin_incref(porigin);
+ origin_decref(e->suspect);
+ e->suspect = porigin;
+ }
+}
+
+/*
+ * We pass blame from the current commit to its parents. We keep saying
+ * "parent" (and "porigin"), but what we mean is to find scapegoat to
+ * exonerate ourselves.
+ */
+static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
+{
+ if (!reverse)
+ return commit->parents;
+ return lookup_decoration(&revs->children, &commit->object);
+}
+
+static int num_scapegoats(struct rev_info *revs, struct commit *commit)
+{
+ int cnt;
+ struct commit_list *l = first_scapegoat(revs, commit);
+ for (cnt = 0; l; l = l->next)
+ cnt++;
+ return cnt;
+}
+
+#define MAXSG 16
+
+static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
+{
+ struct rev_info *revs = sb->revs;
+ int i, pass, num_sg;
+ struct commit *commit = origin->commit;
+ struct commit_list *sg;
+ struct origin *sg_buf[MAXSG];
+ struct origin *porigin, **sg_origin = sg_buf;
+
+ num_sg = num_scapegoats(revs, commit);
+ if (!num_sg)
+ goto finish;
+ else if (num_sg < ARRAY_SIZE(sg_buf))
+ memset(sg_buf, 0, sizeof(sg_buf));
+ else
+ sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
+
+ /*
+ * The first pass looks for unrenamed path to optimize for
+ * common cases, then we look for renames in the second pass.
+ */
+ for (pass = 0; pass < 2; pass++) {
+ struct origin *(*find)(struct scoreboard *,
+ struct commit *, struct origin *);
+ find = pass ? find_rename : find_origin;
+
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct commit *p = sg->item;
+ int j, same;
+
+ if (sg_origin[i])
+ continue;
+ if (parse_commit(p))
+ continue;
+ porigin = find(sb, p, origin);
+ if (!porigin)
+ continue;
+ if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) {
+ pass_whole_blame(sb, origin, porigin);
+ origin_decref(porigin);
+ goto finish;
+ }
+ for (j = same = 0; j < i; j++)
+ if (sg_origin[j] &&
+ !hashcmp(sg_origin[j]->blob_sha1,
+ porigin->blob_sha1)) {
+ same = 1;
+ break;
+ }
+ if (!same)
+ sg_origin[i] = porigin;
+ else
+ origin_decref(porigin);
+ }
+ }
+
+ num_commits++;
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
+ if (!porigin)
+ continue;
+ if (!origin->previous) {
+ origin_incref(porigin);
+ origin->previous = porigin;
+ }
+ if (pass_blame_to_parent(sb, origin, porigin))
+ goto finish;
+ }
+
+ /*
+ * Optionally find moves in parents' files.
+ */
+ if (opt & PICKAXE_BLAME_MOVE)
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
+ if (!porigin)
+ continue;
+ if (find_move_in_parent(sb, origin, porigin))
+ goto finish;
+ }
+
+ /*
+ * Optionally find copies from parents' files.
+ */
+ if (opt & PICKAXE_BLAME_COPY)
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
+ if (find_copy_in_parent(sb, origin, sg->item,
+ porigin, opt))
+ goto finish;
+ }
+
+ finish:
+ for (i = 0; i < num_sg; i++) {
+ if (sg_origin[i]) {
+ drop_origin_blob(sg_origin[i]);
+ origin_decref(sg_origin[i]);
+ }
+ }
+ drop_origin_blob(origin);
+ if (sg_buf != sg_origin)
+ free(sg_origin);
+}
+
+/*
+ * Information on commits, used for output.
+ */
+struct commit_info
+{
+ const char *author;
+ const char *author_mail;
+ unsigned long author_time;
+ const char *author_tz;
+
+ /* filled only when asked for details */
+ const char *committer;
+ const char *committer_mail;
+ unsigned long committer_time;
+ const char *committer_tz;
+
+ const char *summary;
+};
+
+/*
+ * Parse author/committer line in the commit object buffer
+ */
+static void get_ac_line(const char *inbuf, const char *what,
+ int person_len, char *person,
+ int mail_len, char *mail,
+ unsigned long *time, const char **tz)
+{
+ int len, tzlen, maillen;
+ char *tmp, *endp, *timepos, *mailpos;
+
+ tmp = strstr(inbuf, what);
+ if (!tmp)
+ goto error_out;
+ tmp += strlen(what);
+ endp = strchr(tmp, '\n');
+ if (!endp)
+ len = strlen(tmp);
+ else
+ len = endp - tmp;
+ if (person_len <= len) {
+ error_out:
+ /* Ugh */
+ *tz = "(unknown)";
+ strcpy(person, *tz);
+ strcpy(mail, *tz);
+ *time = 0;
+ return;
+ }
+ memcpy(person, tmp, len);
+
+ tmp = person;
+ tmp += len;
+ *tmp = 0;
+ while (person < tmp && *tmp != ' ')
+ tmp--;
+ if (tmp <= person)
+ goto error_out;
+ *tz = tmp+1;
+ tzlen = (person+len)-(tmp+1);
+
+ *tmp = 0;
+ while (person < tmp && *tmp != ' ')
+ tmp--;
+ if (tmp <= person)
+ goto error_out;
+ *time = strtoul(tmp, NULL, 10);
+ timepos = tmp;
+
+ *tmp = 0;
+ while (person < tmp && *tmp != ' ')
+ tmp--;
+ if (tmp <= person)
+ return;
+ mailpos = tmp + 1;
+ *tmp = 0;
+ maillen = timepos - tmp;
+ memcpy(mail, mailpos, maillen);
+
+ if (!mailmap.nr)
+ return;
+
+ /*
+ * mailmap expansion may make the name longer.
+ * make room by pushing stuff down.
+ */
+ tmp = person + person_len - (tzlen + 1);
+ memmove(tmp, *tz, tzlen);
+ tmp[tzlen] = 0;
+ *tz = tmp;
+
+ /*
+ * Now, convert both name and e-mail using mailmap
+ */
+ if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
+ /* Add a trailing '>' to email, since map_user returns plain emails
+ Note: It already has '<', since we replace from mail+1 */
+ mailpos = memchr(mail, '\0', mail_len);
+ if (mailpos && mailpos-mail < mail_len - 1) {
+ *mailpos = '>';
+ *(mailpos+1) = '\0';
+ }
+ }
+}
+
+static void get_commit_info(struct commit *commit,
+ struct commit_info *ret,
+ int detailed)
+{
+ int len;
+ char *tmp, *endp, *reencoded, *message;
+ static char author_name[1024];
+ static char author_mail[1024];
+ static char committer_name[1024];
+ static char committer_mail[1024];
+ static char summary_buf[1024];
+
+ /*
+ * We've operated without save_commit_buffer, so
+ * we now need to populate them for output.
+ */
+ if (!commit->buffer) {
+ enum object_type type;
+ unsigned long size;
+ commit->buffer =
+ read_sha1_file(commit->object.sha1, &type, &size);
+ if (!commit->buffer)
+ die("Cannot read commit %s",
+ sha1_to_hex(commit->object.sha1));
+ }
+ reencoded = reencode_commit_message(commit, NULL);
+ message = reencoded ? reencoded : commit->buffer;
+ ret->author = author_name;
+ ret->author_mail = author_mail;
+ get_ac_line(message, "\nauthor ",
+ sizeof(author_name), author_name,
+ sizeof(author_mail), author_mail,
+ &ret->author_time, &ret->author_tz);
+
+ if (!detailed) {
+ free(reencoded);
+ return;
+ }
+
+ ret->committer = committer_name;
+ ret->committer_mail = committer_mail;
+ get_ac_line(message, "\ncommitter ",
+ sizeof(committer_name), committer_name,
+ sizeof(committer_mail), committer_mail,
+ &ret->committer_time, &ret->committer_tz);
+
+ ret->summary = summary_buf;
+ tmp = strstr(message, "\n\n");
+ if (!tmp) {
+ error_out:
+ sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+ free(reencoded);
+ return;
+ }
+ tmp += 2;
+ endp = strchr(tmp, '\n');
+ if (!endp)
+ endp = tmp + strlen(tmp);
+ len = endp - tmp;
+ if (len >= sizeof(summary_buf) || len == 0)
+ goto error_out;
+ memcpy(summary_buf, tmp, len);
+ summary_buf[len] = 0;
+ free(reencoded);
+}
+
+/*
+ * To allow LF and other nonportable characters in pathnames,
+ * they are c-style quoted as needed.
+ */
+static void write_filename_info(const char *path)
+{
+ printf("filename ");
+ write_name_quoted(path, stdout, '\n');
+}
+
+/*
+ * Porcelain/Incremental format wants to show a lot of details per
+ * commit. Instead of repeating this every line, emit it only once,
+ * the first time each commit appears in the output.
+ */
+static int emit_one_suspect_detail(struct origin *suspect)
+{
+ struct commit_info ci;
+
+ if (suspect->commit->object.flags & METAINFO_SHOWN)
+ return 0;
+
+ suspect->commit->object.flags |= METAINFO_SHOWN;
+ get_commit_info(suspect->commit, &ci, 1);
+ printf("author %s\n", ci.author);
+ printf("author-mail %s\n", ci.author_mail);
+ printf("author-time %lu\n", ci.author_time);
+ printf("author-tz %s\n", ci.author_tz);
+ printf("committer %s\n", ci.committer);
+ printf("committer-mail %s\n", ci.committer_mail);
+ printf("committer-time %lu\n", ci.committer_time);
+ printf("committer-tz %s\n", ci.committer_tz);
+ printf("summary %s\n", ci.summary);
+ if (suspect->commit->object.flags & UNINTERESTING)
+ printf("boundary\n");
+ if (suspect->previous) {
+ struct origin *prev = suspect->previous;
+ printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
+ write_name_quoted(prev->path, stdout, '\n');
+ }
+ return 1;
+}
+
+/*
+ * The blame_entry is found to be guilty for the range. Mark it
+ * as such, and show it in incremental output.
+ */
+static void found_guilty_entry(struct blame_entry *ent)
+{
+ if (ent->guilty)
+ return;
+ ent->guilty = 1;
+ if (incremental) {
+ struct origin *suspect = ent->suspect;
+
+ printf("%s %d %d %d\n",
+ sha1_to_hex(suspect->commit->object.sha1),
+ ent->s_lno + 1, ent->lno + 1, ent->num_lines);
+ emit_one_suspect_detail(suspect);
+ write_filename_info(suspect->path);
+ maybe_flush_or_die(stdout, "stdout");
+ }
+}
+
+/*
+ * The main loop -- while the scoreboard has lines whose true origin
+ * is still unknown, pick one blame_entry, and allow its current
+ * suspect to pass blames to its parents.
+ */
+static void assign_blame(struct scoreboard *sb, int opt)
+{
+ struct rev_info *revs = sb->revs;
+
+ while (1) {
+ struct blame_entry *ent;
+ struct commit *commit;
+ struct origin *suspect = NULL;
+
+ /* find one suspect to break down */
+ for (ent = sb->ent; !suspect && ent; ent = ent->next)
+ if (!ent->guilty)
+ suspect = ent->suspect;
+ if (!suspect)
+ return; /* all done */
+
+ /*
+ * We will use this suspect later in the loop,
+ * so hold onto it in the meantime.
+ */
+ origin_incref(suspect);
+ commit = suspect->commit;
+ if (!commit->object.parsed)
+ parse_commit(commit);
+ if (reverse ||
+ (!(commit->object.flags & UNINTERESTING) &&
+ !(revs->max_age != -1 && commit->date < revs->max_age)))
+ pass_blame(sb, suspect, opt);
+ else {
+ commit->object.flags |= UNINTERESTING;
+ if (commit->object.parsed)
+ mark_parents_uninteresting(commit);
+ }
+ /* treat root commit as boundary */
+ if (!commit->parents && !show_root)
+ commit->object.flags |= UNINTERESTING;
+
+ /* Take responsibility for the remaining entries */
+ for (ent = sb->ent; ent; ent = ent->next)
+ if (same_suspect(ent->suspect, suspect))
+ found_guilty_entry(ent);
+ origin_decref(suspect);
+
+ if (DEBUG) /* sanity */
+ sanity_check_refcnt(sb);
+ }
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
+ int show_raw_time)
+{
+ static char time_buf[128];
+ const char *time_str;
+ int time_len;
+ int tz;
+
+ if (show_raw_time) {
+ sprintf(time_buf, "%lu %s", time, tz_str);
+ }
+ else {
+ tz = atoi(tz_str);
+ time_str = show_date(time, tz, blame_date_mode);
+ time_len = strlen(time_str);
+ memcpy(time_buf, time_str, time_len);
+ memset(time_buf + time_len, ' ', blame_date_width - time_len);
+ }
+ return time_buf;
+}
+
+#define OUTPUT_ANNOTATE_COMPAT 001
+#define OUTPUT_LONG_OBJECT_NAME 002
+#define OUTPUT_RAW_TIMESTAMP 004
+#define OUTPUT_PORCELAIN 010
+#define OUTPUT_SHOW_NAME 020
+#define OUTPUT_SHOW_NUMBER 040
+#define OUTPUT_SHOW_SCORE 0100
+#define OUTPUT_NO_AUTHOR 0200
+
+static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
+{
+ int cnt;
+ const char *cp;
+ struct origin *suspect = ent->suspect;
+ char hex[41];
+
+ strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+ printf("%s%c%d %d %d\n",
+ hex,
+ ent->guilty ? ' ' : '*', // purely for debugging
+ ent->s_lno + 1,
+ ent->lno + 1,
+ ent->num_lines);
+ if (emit_one_suspect_detail(suspect) ||
+ (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
+ write_filename_info(suspect->path);
+
+ cp = nth_line(sb, ent->lno);
+ for (cnt = 0; cnt < ent->num_lines; cnt++) {
+ char ch;
+ if (cnt)
+ printf("%s %d %d\n", hex,
+ ent->s_lno + 1 + cnt,
+ ent->lno + 1 + cnt);
+ putchar('\t');
+ do {
+ ch = *cp++;
+ putchar(ch);
+ } while (ch != '\n' &&
+ cp < sb->final_buf + sb->final_buf_size);
+ }
+
+ if (sb->final_buf_size && cp[-1] != '\n')
+ putchar('\n');
+}
+
+static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
+{
+ int cnt;
+ const char *cp;
+ struct origin *suspect = ent->suspect;
+ struct commit_info ci;
+ char hex[41];
+ int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
+
+ get_commit_info(suspect->commit, &ci, 1);
+ strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+
+ cp = nth_line(sb, ent->lno);
+ for (cnt = 0; cnt < ent->num_lines; cnt++) {
+ char ch;
+ int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8;
+
+ if (suspect->commit->object.flags & UNINTERESTING) {
+ if (blank_boundary)
+ memset(hex, ' ', length);
+ else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
+ length--;
+ putchar('^');
+ }
+ }
+
+ printf("%.*s", length, hex);
+ if (opt & OUTPUT_ANNOTATE_COMPAT)
+ printf("\t(%10s\t%10s\t%d)", ci.author,
+ format_time(ci.author_time, ci.author_tz,
+ show_raw_time),
+ ent->lno + 1 + cnt);
+ else {
+ if (opt & OUTPUT_SHOW_SCORE)
+ printf(" %*d %02d",
+ max_score_digits, ent->score,
+ ent->suspect->refcnt);
+ if (opt & OUTPUT_SHOW_NAME)
+ printf(" %-*.*s", longest_file, longest_file,
+ suspect->path);
+ if (opt & OUTPUT_SHOW_NUMBER)
+ printf(" %*d", max_orig_digits,
+ ent->s_lno + 1 + cnt);
+
+ if (!(opt & OUTPUT_NO_AUTHOR)) {
+ int pad = longest_author - utf8_strwidth(ci.author);
+ printf(" (%s%*s %10s",
+ ci.author, pad, "",
+ format_time(ci.author_time,
+ ci.author_tz,
+ show_raw_time));
+ }
+ printf(" %*d) ",
+ max_digits, ent->lno + 1 + cnt);
+ }
+ do {
+ ch = *cp++;
+ putchar(ch);
+ } while (ch != '\n' &&
+ cp < sb->final_buf + sb->final_buf_size);
+ }
+
+ if (sb->final_buf_size && cp[-1] != '\n')
+ putchar('\n');
+}
+
+static void output(struct scoreboard *sb, int option)
+{
+ struct blame_entry *ent;
+
+ if (option & OUTPUT_PORCELAIN) {
+ for (ent = sb->ent; ent; ent = ent->next) {
+ struct blame_entry *oth;
+ struct origin *suspect = ent->suspect;
+ struct commit *commit = suspect->commit;
+ if (commit->object.flags & MORE_THAN_ONE_PATH)
+ continue;
+ for (oth = ent->next; oth; oth = oth->next) {
+ if ((oth->suspect->commit != commit) ||
+ !strcmp(oth->suspect->path, suspect->path))
+ continue;
+ commit->object.flags |= MORE_THAN_ONE_PATH;
+ break;
+ }
+ }
+ }
+
+ for (ent = sb->ent; ent; ent = ent->next) {
+ if (option & OUTPUT_PORCELAIN)
+ emit_porcelain(sb, ent);
+ else {
+ emit_other(sb, ent, option);
+ }
+ }
+}
+
+/*
+ * To allow quick access to the contents of nth line in the
+ * final image, prepare an index in the scoreboard.
+ */
+static int prepare_lines(struct scoreboard *sb)
+{
+ const char *buf = sb->final_buf;
+ unsigned long len = sb->final_buf_size;
+ int num = 0, incomplete = 0, bol = 1;
+
+ if (len && buf[len-1] != '\n')
+ incomplete++; /* incomplete line at the end */
+ while (len--) {
+ if (bol) {
+ sb->lineno = xrealloc(sb->lineno,
+ sizeof(int *) * (num + 1));
+ sb->lineno[num] = buf - sb->final_buf;
+ bol = 0;
+ }
+ if (*buf++ == '\n') {
+ num++;
+ bol = 1;
+ }
+ }
+ sb->lineno = xrealloc(sb->lineno,
+ sizeof(int *) * (num + incomplete + 1));
+ sb->lineno[num + incomplete] = buf - sb->final_buf;
+ sb->num_lines = num + incomplete;
+ return sb->num_lines;
+}
+
+/*
+ * Add phony grafts for use with -S; this is primarily to
+ * support git's cvsserver that wants to give a linear history
+ * to its clients.
+ */
+static int read_ancestry(const char *graft_file)
+{
+ FILE *fp = fopen(graft_file, "r");
+ char buf[1024];
+ if (!fp)
+ return -1;
+ while (fgets(buf, sizeof(buf), fp)) {
+ /* The format is just "Commit Parent1 Parent2 ...\n" */
+ int len = strlen(buf);
+ struct commit_graft *graft = read_graft_line(buf, len);
+ if (graft)
+ register_commit_graft(graft, 0);
+ }
+ fclose(fp);
+ return 0;
+}
+
+/*
+ * How many columns do we need to show line numbers in decimal?
+ */
+static int lineno_width(int lines)
+{
+ int i, width;
+
+ for (width = 1, i = 10; i <= lines; width++)
+ i *= 10;
+ return width;
+}
+
+/*
+ * How many columns do we need to show line numbers, authors,
+ * and filenames?
+ */
+static void find_alignment(struct scoreboard *sb, int *option)
+{
+ int longest_src_lines = 0;
+ int longest_dst_lines = 0;
+ unsigned largest_score = 0;
+ struct blame_entry *e;
+
+ for (e = sb->ent; e; e = e->next) {
+ struct origin *suspect = e->suspect;
+ struct commit_info ci;
+ int num;
+
+ if (strcmp(suspect->path, sb->path))
+ *option |= OUTPUT_SHOW_NAME;
+ num = strlen(suspect->path);
+ if (longest_file < num)
+ longest_file = num;
+ if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+ suspect->commit->object.flags |= METAINFO_SHOWN;
+ get_commit_info(suspect->commit, &ci, 1);
+ num = utf8_strwidth(ci.author);
+ if (longest_author < num)
+ longest_author = num;
+ }
+ num = e->s_lno + e->num_lines;
+ if (longest_src_lines < num)
+ longest_src_lines = num;
+ num = e->lno + e->num_lines;
+ if (longest_dst_lines < num)
+ longest_dst_lines = num;
+ if (largest_score < ent_score(sb, e))
+ largest_score = ent_score(sb, e);
+ }
+ max_orig_digits = lineno_width(longest_src_lines);
+ max_digits = lineno_width(longest_dst_lines);
+ max_score_digits = lineno_width(largest_score);
+}
+
+/*
+ * For debugging -- origin is refcounted, and this asserts that
+ * we do not underflow.
+ */
+static void sanity_check_refcnt(struct scoreboard *sb)
+{
+ int baa = 0;
+ struct blame_entry *ent;
+
+ for (ent = sb->ent; ent; ent = ent->next) {
+ /* Nobody should have zero or negative refcnt */
+ if (ent->suspect->refcnt <= 0) {
+ fprintf(stderr, "%s in %s has negative refcnt %d\n",
+ ent->suspect->path,
+ sha1_to_hex(ent->suspect->commit->object.sha1),
+ ent->suspect->refcnt);
+ baa = 1;
+ }
+ }
+ if (baa) {
+ int opt = 0160;
+ find_alignment(sb, &opt);
+ output(sb, opt);
+ die("Baa %d!", baa);
+ }
+}
+
+/*
+ * Used for the command line parsing; check if the path exists
+ * in the working tree.
+ */
+static int has_string_in_work_tree(const char *path)
+{
+ struct stat st;
+ return !lstat(path, &st);
+}
+
+static unsigned parse_score(const char *arg)
+{
+ char *end;
+ unsigned long score = strtoul(arg, &end, 10);
+ if (*end)
+ return 0;
+ return score;
+}
+
+static const char *add_prefix(const char *prefix, const char *path)
+{
+ return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
+}
+
+/*
+ * Parsing of (comma separated) one item in the -L option
+ */
+static const char *parse_loc(const char *spec,
+ struct scoreboard *sb, long lno,
+ long begin, long *ret)
+{
+ char *term;
+ const char *line;
+ long num;
+ int reg_error;
+ regex_t regexp;
+ regmatch_t match[1];
+
+ /* Allow "-L <something>,+20" to mean starting at <something>
+ * for 20 lines, or "-L <something>,-5" for 5 lines ending at
+ * <something>.
+ */
+ if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
+ num = strtol(spec + 1, &term, 10);
+ if (term != spec + 1) {
+ if (spec[0] == '-')
+ num = 0 - num;
+ if (0 < num)
+ *ret = begin + num - 2;
+ else if (!num)
+ *ret = begin;
+ else
+ *ret = begin + num;
+ return term;
+ }
+ return spec;
+ }
+ num = strtol(spec, &term, 10);
+ if (term != spec) {
+ *ret = num;
+ return term;
+ }
+ if (spec[0] != '/')
+ return spec;
+
+ /* it could be a regexp of form /.../ */
+ for (term = (char *) spec + 1; *term && *term != '/'; term++) {
+ if (*term == '\\')
+ term++;
+ }
+ if (*term != '/')
+ return spec;
+
+ /* try [spec+1 .. term-1] as regexp */
+ *term = 0;
+ begin--; /* input is in human terms */
+ line = nth_line(sb, begin);
+
+ if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) &&
+ !(reg_error = regexec(®exp, line, 1, match, 0))) {
+ const char *cp = line + match[0].rm_so;
+ const char *nline;
+
+ while (begin++ < lno) {
+ nline = nth_line(sb, begin);
+ if (line <= cp && cp < nline)
+ break;
+ line = nline;
+ }
+ *ret = begin;
+ regfree(®exp);
+ *term++ = '/';
+ return term;
+ }
+ else {
+ char errbuf[1024];
+ regerror(reg_error, ®exp, errbuf, 1024);
+ die("-L parameter '%s': %s", spec + 1, errbuf);
+ }
+}
+
+/*
+ * Parsing of -L option
+ */
+static void prepare_blame_range(struct scoreboard *sb,
+ const char *bottomtop,
+ long lno,
+ long *bottom, long *top)
+{
+ const char *term;
+
+ term = parse_loc(bottomtop, sb, lno, 1, bottom);
+ if (*term == ',') {
+ term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
+ if (*term)
+ usage(blame_usage);
+ }
+ if (*term)
+ usage(blame_usage);
+}
+
+static int git_blame_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "blame.showroot")) {
+ show_root = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "blame.blankboundary")) {
+ blank_boundary = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "blame.date")) {
+ if (!value)
+ return config_error_nonbool(var);
+ blame_date_mode = parse_date_format(value);
+ return 0;
+ }
+ return git_default_config(var, value, cb);
+}
+
+/*
+ * Prepare a dummy commit that represents the work tree (or staged) item.
+ * Note that annotating work tree item never works in the reverse.
+ */
+static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
+{
+ struct commit *commit;
+ struct origin *origin;
+ unsigned char head_sha1[20];
+ struct strbuf buf = STRBUF_INIT;
+ const char *ident;
+ time_t now;
+ int size, len;
+ struct cache_entry *ce;
+ unsigned mode;
+
+ if (get_sha1("HEAD", head_sha1))
+ die("No such ref: HEAD");
+
+ time(&now);
+ commit = xcalloc(1, sizeof(*commit));
+ commit->parents = xcalloc(1, sizeof(*commit->parents));
+ commit->parents->item = lookup_commit_reference(head_sha1);
+ commit->object.parsed = 1;
+ commit->date = now;
+ commit->object.type = OBJ_COMMIT;
+
+ origin = make_origin(commit, path);
+
+ if (!contents_from || strcmp("-", contents_from)) {
+ struct stat st;
+ const char *read_from;
+
+ if (contents_from) {
+ if (stat(contents_from, &st) < 0)
+ die_errno("Cannot stat '%s'", contents_from);
+ read_from = contents_from;
+ }
+ else {
+ if (lstat(path, &st) < 0)
+ die_errno("Cannot lstat '%s'", path);
+ read_from = path;
+ }
+ mode = canon_mode(st.st_mode);
+ switch (st.st_mode & S_IFMT) {
+ case S_IFREG:
+ if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
+ die_errno("cannot open or read '%s'", read_from);
+ break;
+ case S_IFLNK:
+ if (strbuf_readlink(&buf, read_from, st.st_size) < 0)
+ die_errno("cannot readlink '%s'", read_from);
+ break;
+ default:
+ die("unsupported file type %s", read_from);
+ }
+ }
+ else {
+ /* Reading from stdin */
+ contents_from = "standard input";
+ mode = 0;
+ if (strbuf_read(&buf, 0, 0) < 0)
+ die_errno("failed to read from stdin");
+ }
+ convert_to_git(path, buf.buf, buf.len, &buf, 0);
+ origin->file.ptr = buf.buf;
+ origin->file.size = buf.len;
+ pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
+ commit->util = origin;
+
+ /*
+ * Read the current index, replace the path entry with
+ * origin->blob_sha1 without mucking with its mode or type
+ * bits; we are not going to write this index out -- we just
+ * want to run "diff-index --cached".
+ */
+ discard_cache();
+ read_cache();
+
+ len = strlen(path);
+ if (!mode) {
+ int pos = cache_name_pos(path, len);
+ if (0 <= pos)
+ mode = active_cache[pos]->ce_mode;
+ else
+ /* Let's not bother reading from HEAD tree */
+ mode = S_IFREG | 0644;
+ }
+ size = cache_entry_size(len);
+ ce = xcalloc(1, size);
+ hashcpy(ce->sha1, origin->blob_sha1);
+ memcpy(ce->name, path, len);
+ ce->ce_flags = create_ce_flags(len, 0);
+ ce->ce_mode = create_ce_mode(mode);
+ add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
+
+ /*
+ * We are not going to write this out, so this does not matter
+ * right now, but someday we might optimize diff-index --cached
+ * with cache-tree information.
+ */
+ cache_tree_invalidate_path(active_cache_tree, path);
+
+ commit->buffer = xmalloc(400);
+ ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
+ snprintf(commit->buffer, 400,
+ "tree 0000000000000000000000000000000000000000\n"
+ "parent %s\n"
+ "author %s\n"
+ "committer %s\n\n"
+ "Version of %s from %s\n",
+ sha1_to_hex(head_sha1),
+ ident, ident, path, contents_from ? contents_from : path);
+ return commit;
+}
+
+static const char *prepare_final(struct scoreboard *sb)
+{
+ int i;
+ const char *final_commit_name = NULL;
+ struct rev_info *revs = sb->revs;
+
+ /*
+ * There must be one and only one positive commit in the
+ * revs->pending array.
+ */
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (obj->flags & UNINTERESTING)
+ continue;
+ while (obj->type == OBJ_TAG)
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ die("Non commit %s?", revs->pending.objects[i].name);
+ if (sb->final)
+ die("More than one commit to dig from %s and %s?",
+ revs->pending.objects[i].name,
+ final_commit_name);
+ sb->final = (struct commit *) obj;
+ final_commit_name = revs->pending.objects[i].name;
+ }
+ return final_commit_name;
+}
+
+static const char *prepare_initial(struct scoreboard *sb)
+{
+ int i;
+ const char *final_commit_name = NULL;
+ struct rev_info *revs = sb->revs;
+
+ /*
+ * There must be one and only one negative commit, and it must be
+ * the boundary.
+ */
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (!(obj->flags & UNINTERESTING))
+ continue;
+ while (obj->type == OBJ_TAG)
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ die("Non commit %s?", revs->pending.objects[i].name);
+ if (sb->final)
+ die("More than one commit to dig down to %s and %s?",
+ revs->pending.objects[i].name,
+ final_commit_name);
+ sb->final = (struct commit *) obj;
+ final_commit_name = revs->pending.objects[i].name;
+ }
+ if (!final_commit_name)
+ die("No commit to dig down to?");
+ return final_commit_name;
+}
+
+static int blame_copy_callback(const struct option *option, const char *arg, int unset)
+{
+ int *opt = option->value;
+
+ /*
+ * -C enables copy from removed files;
+ * -C -C enables copy from existing files, but only
+ * when blaming a new file;
+ * -C -C -C enables copy from existing files for
+ * everybody
+ */
+ if (*opt & PICKAXE_BLAME_COPY_HARDER)
+ *opt |= PICKAXE_BLAME_COPY_HARDEST;
+ if (*opt & PICKAXE_BLAME_COPY)
+ *opt |= PICKAXE_BLAME_COPY_HARDER;
+ *opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
+
+ if (arg)
+ blame_copy_score = parse_score(arg);
+ return 0;
+}
+
+static int blame_move_callback(const struct option *option, const char *arg, int unset)
+{
+ int *opt = option->value;
+
+ *opt |= PICKAXE_BLAME_MOVE;
+
+ if (arg)
+ blame_move_score = parse_score(arg);
+ return 0;
+}
+
+static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
+{
+ const char **bottomtop = option->value;
+ if (!arg)
+ return -1;
+ if (*bottomtop)
+ die("More than one '-L n,m' option given");
+ *bottomtop = arg;
+ return 0;
+}
+
+int cmd_blame(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info revs;
+ const char *path;
+ struct scoreboard sb;
+ struct origin *o;
+ struct blame_entry *ent;
+ long dashdash_pos, bottom, top, lno;
+ const char *final_commit_name = NULL;
+ enum object_type type;
+
+ static const char *bottomtop = NULL;
+ static int output_option = 0, opt = 0;
+ static int show_stats = 0;
+ static const char *revs_file = NULL;
+ static const char *contents_from = NULL;
+ static const struct option options[] = {
+ OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"),
+ OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
+ OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
+ OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
+ OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
+ OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
+ OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
+ OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
+ OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
+ OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
+ OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
+ OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
+ OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
+ OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
+ OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
+ { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
+ { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
+ OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
+ OPT_END()
+ };
+
+ struct parse_opt_ctx_t ctx;
+ int cmd_is_annotate = !strcmp(argv[0], "annotate");
+
+ git_config(git_blame_config, NULL);
+ init_revisions(&revs, NULL);
+ revs.date_mode = blame_date_mode;
+
+ save_commit_buffer = 0;
+ dashdash_pos = 0;
+
+ parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
+ PARSE_OPT_KEEP_ARGV0);
+ for (;;) {
+ switch (parse_options_step(&ctx, options, blame_opt_usage)) {
+ case PARSE_OPT_HELP:
+ exit(129);
+ case PARSE_OPT_DONE:
+ if (ctx.argv[0])
+ dashdash_pos = ctx.cpidx;
+ goto parse_done;
+ }
+
+ if (!strcmp(ctx.argv[0], "--reverse")) {
+ ctx.argv[0] = "--children";
+ reverse = 1;
+ }
+ parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
+ }
+parse_done:
+ argc = parse_options_end(&ctx);
+
+ if (revs_file && read_ancestry(revs_file))
+ die_errno("reading graft file '%s' failed", revs_file);
+
+ if (cmd_is_annotate) {
+ output_option |= OUTPUT_ANNOTATE_COMPAT;
+ blame_date_mode = DATE_ISO8601;
+ } else {
+ blame_date_mode = revs.date_mode;
+ }
+
+ /* The maximum width used to show the dates */
+ switch (blame_date_mode) {
+ case DATE_RFC2822:
+ blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
+ break;
+ case DATE_ISO8601:
+ blame_date_width = sizeof("2006-10-19 16:00:04 -0700");
+ break;
+ case DATE_RAW:
+ blame_date_width = sizeof("1161298804 -0700");
+ break;
+ case DATE_SHORT:
+ blame_date_width = sizeof("2006-10-19");
+ break;
+ case DATE_RELATIVE:
+ /* "normal" is used as the fallback for "relative" */
+ case DATE_LOCAL:
+ case DATE_NORMAL:
+ blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
+ break;
+ }
+ blame_date_width -= 1; /* strip the null */
+
+ if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
+ opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
+ PICKAXE_BLAME_COPY_HARDER);
+
+ if (!blame_move_score)
+ blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
+ if (!blame_copy_score)
+ blame_copy_score = BLAME_DEFAULT_COPY_SCORE;
+
+ /*
+ * We have collected options unknown to us in argv[1..unk]
+ * which are to be passed to revision machinery if we are
+ * going to do the "bottom" processing.
+ *
+ * The remaining are:
+ *
+ * (1) if dashdash_pos != 0, its either
+ * "blame [revisions] -- <path>" or
+ * "blame -- <path> <rev>"
+ *
+ * (2) otherwise, its one of the two:
+ * "blame [revisions] <path>"
+ * "blame <path> <rev>"
+ *
+ * Note that we must strip out <path> from the arguments: we do not
+ * want the path pruning but we may want "bottom" processing.
+ */
+ if (dashdash_pos) {
+ switch (argc - dashdash_pos - 1) {
+ case 2: /* (1b) */
+ if (argc != 4)
+ usage_with_options(blame_opt_usage, options);
+ /* reorder for the new way: <rev> -- <path> */
+ argv[1] = argv[3];
+ argv[3] = argv[2];
+ argv[2] = "--";
+ /* FALLTHROUGH */
+ case 1: /* (1a) */
+ path = add_prefix(prefix, argv[--argc]);
+ argv[argc] = NULL;
+ break;
+ default:
+ usage_with_options(blame_opt_usage, options);
+ }
+ } else {
+ if (argc < 2)
+ usage_with_options(blame_opt_usage, options);
+ path = add_prefix(prefix, argv[argc - 1]);
+ if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */
+ path = add_prefix(prefix, argv[1]);
+ argv[1] = argv[2];
+ }
+ argv[argc - 1] = "--";
+
+ setup_work_tree();
+ if (!has_string_in_work_tree(path))
+ die_errno("cannot stat path '%s'", path);
+ }
+
+ revs.disable_stdin = 1;
+ setup_revisions(argc, argv, &revs, NULL);
+ memset(&sb, 0, sizeof(sb));
+
+ sb.revs = &revs;
+ if (!reverse)
+ final_commit_name = prepare_final(&sb);
+ else if (contents_from)
+ die("--contents and --children do not blend well.");
+ else
+ final_commit_name = prepare_initial(&sb);
+
+ if (!sb.final) {
+ /*
+ * "--not A B -- path" without anything positive;
+ * do not default to HEAD, but use the working tree
+ * or "--contents".
+ */
+ setup_work_tree();
+ sb.final = fake_working_tree_commit(path, contents_from);
+ add_pending_object(&revs, &(sb.final->object), ":");
+ }
+ else if (contents_from)
+ die("Cannot use --contents with final commit object name");
+
+ /*
+ * If we have bottom, this will mark the ancestors of the
+ * bottom commits we would reach while traversing as
+ * uninteresting.
+ */
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+
+ if (is_null_sha1(sb.final->object.sha1)) {
+ char *buf;
+ o = sb.final->util;
+ buf = xmalloc(o->file.size + 1);
+ memcpy(buf, o->file.ptr, o->file.size + 1);
+ sb.final_buf = buf;
+ sb.final_buf_size = o->file.size;
+ }
+ else {
+ o = get_origin(&sb, sb.final, path);
+ if (fill_blob_sha1(o))
+ die("no such path %s in %s", path, final_commit_name);
+
+ sb.final_buf = read_sha1_file(o->blob_sha1, &type,
+ &sb.final_buf_size);
+ if (!sb.final_buf)
+ die("Cannot read blob %s for path %s",
+ sha1_to_hex(o->blob_sha1),
+ path);
+ }
+ num_read_blob++;
+ lno = prepare_lines(&sb);
+
+ bottom = top = 0;
+ if (bottomtop)
+ prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
+ if (bottom && top && top < bottom) {
+ long tmp;
+ tmp = top; top = bottom; bottom = tmp;
+ }
+ if (bottom < 1)
+ bottom = 1;
+ if (top < 1)
+ top = lno;
+ bottom--;
+ if (lno < top || lno < bottom)
+ die("file %s has only %lu lines", path, lno);
+
+ ent = xcalloc(1, sizeof(*ent));
+ ent->lno = bottom;
+ ent->num_lines = top - bottom;
+ ent->suspect = o;
+ ent->s_lno = bottom;
+
+ sb.ent = ent;
+ sb.path = path;
+
+ read_mailmap(&mailmap, NULL);
+
+ if (!incremental)
+ setup_pager();
+
+ assign_blame(&sb, opt);
+
+ if (incremental)
+ return 0;
+
+ coalesce(&sb);
+
+ if (!(output_option & OUTPUT_PORCELAIN))
+ find_alignment(&sb, &output_option);
+
+ output(&sb, output_option);
+ free((void *)sb.final_buf);
+ for (ent = sb.ent; ent; ) {
+ struct blame_entry *e = ent->next;
+ free(ent);
+ ent = e;
+ }
+
+ if (show_stats) {
+ printf("num read blob: %d\n", num_read_blob);
+ printf("num get patch: %d\n", num_get_patch);
+ printf("num commits: %d\n", num_commits);
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * Builtin "git branch"
+ *
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-branch.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "color.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+#include "remote.h"
+#include "parse-options.h"
+#include "branch.h"
+#include "diff.h"
+#include "revision.h"
+
+static const char * const builtin_branch_usage[] = {
+ "git branch [options] [-r | -a] [--merged | --no-merged]",
+ "git branch [options] [-l] [-f] <branchname> [<start-point>]",
+ "git branch [options] [-r] (-d | -D) <branchname>",
+ "git branch [options] (-m | -M) [<oldbranch>] <newbranch>",
+ NULL
+};
+
+#define REF_LOCAL_BRANCH 0x01
+#define REF_REMOTE_BRANCH 0x02
+
+static const char *head;
+static unsigned char head_sha1[20];
+
+static int branch_use_color = -1;
+static char branch_colors[][COLOR_MAXLEN] = {
+ GIT_COLOR_RESET,
+ GIT_COLOR_NORMAL, /* PLAIN */
+ GIT_COLOR_RED, /* REMOTE */
+ GIT_COLOR_NORMAL, /* LOCAL */
+ GIT_COLOR_GREEN, /* CURRENT */
+};
+enum color_branch {
+ BRANCH_COLOR_RESET = 0,
+ BRANCH_COLOR_PLAIN = 1,
+ BRANCH_COLOR_REMOTE = 2,
+ BRANCH_COLOR_LOCAL = 3,
+ BRANCH_COLOR_CURRENT = 4,
+};
+
+static enum merge_filter {
+ NO_FILTER = 0,
+ SHOW_NOT_MERGED,
+ SHOW_MERGED,
+} merge_filter;
+static unsigned char merge_filter_ref[20];
+
+static int parse_branch_color_slot(const char *var, int ofs)
+{
+ if (!strcasecmp(var+ofs, "plain"))
+ return BRANCH_COLOR_PLAIN;
+ if (!strcasecmp(var+ofs, "reset"))
+ return BRANCH_COLOR_RESET;
+ if (!strcasecmp(var+ofs, "remote"))
+ return BRANCH_COLOR_REMOTE;
+ if (!strcasecmp(var+ofs, "local"))
+ return BRANCH_COLOR_LOCAL;
+ if (!strcasecmp(var+ofs, "current"))
+ return BRANCH_COLOR_CURRENT;
+ return -1;
+}
+
+static int git_branch_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "color.branch")) {
+ branch_use_color = git_config_colorbool(var, value, -1);
+ return 0;
+ }
+ if (!prefixcmp(var, "color.branch.")) {
+ int slot = parse_branch_color_slot(var, 13);
+ if (slot < 0)
+ return 0;
+ if (!value)
+ return config_error_nonbool(var);
+ color_parse(value, var, branch_colors[slot]);
+ return 0;
+ }
+ return git_color_default_config(var, value, cb);
+}
+
+static const char *branch_get_color(enum color_branch ix)
+{
+ if (branch_use_color > 0)
+ return branch_colors[ix];
+ return "";
+}
+
+static int branch_merged(int kind, const char *name,
+ struct commit *rev, struct commit *head_rev)
+{
+ /*
+ * This checks whether the merge bases of branch and HEAD (or
+ * the other branch this branch builds upon) contains the
+ * branch, which means that the branch has already been merged
+ * safely to HEAD (or the other branch).
+ */
+ struct commit *reference_rev = NULL;
+ const char *reference_name = NULL;
+ int merged;
+
+ if (kind == REF_LOCAL_BRANCH) {
+ struct branch *branch = branch_get(name);
+ unsigned char sha1[20];
+
+ if (branch &&
+ branch->merge &&
+ branch->merge[0] &&
+ branch->merge[0]->dst &&
+ (reference_name =
+ resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+ reference_rev = lookup_commit_reference(sha1);
+ }
+ if (!reference_rev)
+ reference_rev = head_rev;
+
+ merged = in_merge_bases(rev, &reference_rev, 1);
+
+ /*
+ * After the safety valve is fully redefined to "check with
+ * upstream, if any, otherwise with HEAD", we should just
+ * return the result of the in_merge_bases() above without
+ * any of the following code, but during the transition period,
+ * a gentle reminder is in order.
+ */
+ if ((head_rev != reference_rev) &&
+ in_merge_bases(rev, &head_rev, 1) != merged) {
+ if (merged)
+ warning("deleting branch '%s' that has been merged to\n"
+ " '%s', but it is not yet merged to HEAD.",
+ name, reference_name);
+ else
+ warning("not deleting branch '%s' that is not yet merged to\n"
+ " '%s', even though it is merged to HEAD.",
+ name, reference_name);
+ }
+ return merged;
+}
+
+static int delete_branches(int argc, const char **argv, int force, int kinds)
+{
+ struct commit *rev, *head_rev = NULL;
+ unsigned char sha1[20];
+ char *name = NULL;
+ const char *fmt, *remote;
+ int i;
+ int ret = 0;
+ struct strbuf bname = STRBUF_INIT;
+
+ switch (kinds) {
+ case REF_REMOTE_BRANCH:
+ fmt = "refs/remotes/%s";
+ remote = "remote ";
+ force = 1;
+ break;
+ case REF_LOCAL_BRANCH:
+ fmt = "refs/heads/%s";
+ remote = "";
+ break;
+ default:
+ die("cannot use -a with -d");
+ }
+
+ if (!force) {
+ head_rev = lookup_commit_reference(head_sha1);
+ if (!head_rev)
+ die("Couldn't look up commit object for HEAD");
+ }
+ for (i = 0; i < argc; i++, strbuf_release(&bname)) {
+ strbuf_branchname(&bname, argv[i]);
+ if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
+ error("Cannot delete the branch '%s' "
+ "which you are currently on.", bname.buf);
+ ret = 1;
+ continue;
+ }
+
+ free(name);
+
+ name = xstrdup(mkpath(fmt, bname.buf));
+ if (!resolve_ref(name, sha1, 1, NULL)) {
+ error("%sbranch '%s' not found.",
+ remote, bname.buf);
+ ret = 1;
+ continue;
+ }
+
+ rev = lookup_commit_reference(sha1);
+ if (!rev) {
+ error("Couldn't look up commit object for '%s'", name);
+ ret = 1;
+ continue;
+ }
+
+ if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
+ error("The branch '%s' is not fully merged.\n"
+ "If you are sure you want to delete it, "
+ "run 'git branch -D %s'.", bname.buf, bname.buf);
+ ret = 1;
+ continue;
+ }
+
+ if (delete_ref(name, sha1, 0)) {
+ error("Error deleting %sbranch '%s'", remote,
+ bname.buf);
+ ret = 1;
+ } else {
+ struct strbuf buf = STRBUF_INIT;
+ printf("Deleted %sbranch %s (was %s).\n", remote,
+ bname.buf,
+ find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ strbuf_addf(&buf, "branch.%s", bname.buf);
+ if (git_config_rename_section(buf.buf, NULL) < 0)
+ warning("Update of config-file failed");
+ strbuf_release(&buf);
+ }
+ }
+
+ free(name);
+
+ return(ret);
+}
+
+struct ref_item {
+ char *name;
+ char *dest;
+ unsigned int kind, len;
+ struct commit *commit;
+};
+
+struct ref_list {
+ struct rev_info revs;
+ int index, alloc, maxwidth, verbose, abbrev;
+ struct ref_item *list;
+ struct commit_list *with_commit;
+ int kinds;
+};
+
+static char *resolve_symref(const char *src, const char *prefix)
+{
+ unsigned char sha1[20];
+ int flag;
+ const char *dst, *cp;
+
+ dst = resolve_ref(src, sha1, 0, &flag);
+ if (!(dst && (flag & REF_ISSYMREF)))
+ return NULL;
+ if (prefix && (cp = skip_prefix(dst, prefix)))
+ dst = cp;
+ return xstrdup(dst);
+}
+
+static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+{
+ struct ref_list *ref_list = (struct ref_list*)(cb_data);
+ struct ref_item *newitem;
+ struct commit *commit;
+ int kind, i;
+ const char *prefix, *orig_refname = refname;
+
+ static struct {
+ int kind;
+ const char *prefix;
+ int pfxlen;
+ } ref_kind[] = {
+ { REF_LOCAL_BRANCH, "refs/heads/", 11 },
+ { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
+ };
+
+ /* Detect kind */
+ for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+ prefix = ref_kind[i].prefix;
+ if (strncmp(refname, prefix, ref_kind[i].pfxlen))
+ continue;
+ kind = ref_kind[i].kind;
+ refname += ref_kind[i].pfxlen;
+ break;
+ }
+ if (ARRAY_SIZE(ref_kind) <= i)
+ return 0;
+
+ /* Don't add types the caller doesn't want */
+ if ((kind & ref_list->kinds) == 0)
+ return 0;
+
+ commit = NULL;
+ if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
+ commit = lookup_commit_reference_gently(sha1, 1);
+ if (!commit)
+ return error("branch '%s' does not point at a commit", refname);
+
+ /* Filter with with_commit if specified */
+ if (!is_descendant_of(commit, ref_list->with_commit))
+ return 0;
+
+ if (merge_filter != NO_FILTER)
+ add_pending_object(&ref_list->revs,
+ (struct object *)commit, refname);
+ }
+
+ /* Resize buffer */
+ if (ref_list->index >= ref_list->alloc) {
+ ref_list->alloc = alloc_nr(ref_list->alloc);
+ ref_list->list = xrealloc(ref_list->list,
+ ref_list->alloc * sizeof(struct ref_item));
+ }
+
+ /* Record the new item */
+ newitem = &(ref_list->list[ref_list->index++]);
+ newitem->name = xstrdup(refname);
+ newitem->kind = kind;
+ newitem->commit = commit;
+ newitem->len = strlen(refname);
+ newitem->dest = resolve_symref(orig_refname, prefix);
+ /* adjust for "remotes/" */
+ if (newitem->kind == REF_REMOTE_BRANCH &&
+ ref_list->kinds != REF_REMOTE_BRANCH)
+ newitem->len += 8;
+ if (newitem->len > ref_list->maxwidth)
+ ref_list->maxwidth = newitem->len;
+
+ return 0;
+}
+
+static void free_ref_list(struct ref_list *ref_list)
+{
+ int i;
+
+ for (i = 0; i < ref_list->index; i++) {
+ free(ref_list->list[i].name);
+ free(ref_list->list[i].dest);
+ }
+ free(ref_list->list);
+}
+
+static int ref_cmp(const void *r1, const void *r2)
+{
+ struct ref_item *c1 = (struct ref_item *)(r1);
+ struct ref_item *c2 = (struct ref_item *)(r2);
+
+ if (c1->kind != c2->kind)
+ return c1->kind - c2->kind;
+ return strcmp(c1->name, c2->name);
+}
+
+static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
+ int show_upstream_ref)
+{
+ int ours, theirs;
+ struct branch *branch = branch_get(branch_name);
+
+ if (!stat_tracking_info(branch, &ours, &theirs)) {
+ if (branch && branch->merge && branch->merge[0]->dst &&
+ show_upstream_ref)
+ strbuf_addf(stat, "[%s] ",
+ shorten_unambiguous_ref(branch->merge[0]->dst, 0));
+ return;
+ }
+
+ strbuf_addch(stat, '[');
+ if (show_upstream_ref)
+ strbuf_addf(stat, "%s: ",
+ shorten_unambiguous_ref(branch->merge[0]->dst, 0));
+ if (!ours)
+ strbuf_addf(stat, "behind %d] ", theirs);
+ else if (!theirs)
+ strbuf_addf(stat, "ahead %d] ", ours);
+ else
+ strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs);
+}
+
+static int matches_merge_filter(struct commit *commit)
+{
+ int is_merged;
+
+ if (merge_filter == NO_FILTER)
+ return 1;
+
+ is_merged = !!(commit->object.flags & UNINTERESTING);
+ return (is_merged == (merge_filter == SHOW_MERGED));
+}
+
+static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
+ int abbrev, int current, char *prefix)
+{
+ char c;
+ int color;
+ struct commit *commit = item->commit;
+ struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
+
+ if (!matches_merge_filter(commit))
+ return;
+
+ switch (item->kind) {
+ case REF_LOCAL_BRANCH:
+ color = BRANCH_COLOR_LOCAL;
+ break;
+ case REF_REMOTE_BRANCH:
+ color = BRANCH_COLOR_REMOTE;
+ break;
+ default:
+ color = BRANCH_COLOR_PLAIN;
+ break;
+ }
+
+ c = ' ';
+ if (current) {
+ c = '*';
+ color = BRANCH_COLOR_CURRENT;
+ }
+
+ strbuf_addf(&name, "%s%s", prefix, item->name);
+ if (verbose)
+ strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
+ maxwidth, name.buf,
+ branch_get_color(BRANCH_COLOR_RESET));
+ else
+ strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
+ name.buf, branch_get_color(BRANCH_COLOR_RESET));
+
+ if (item->dest)
+ strbuf_addf(&out, " -> %s", item->dest);
+ else if (verbose) {
+ struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
+ const char *sub = " **** invalid ref ****";
+
+ commit = item->commit;
+ if (commit && !parse_commit(commit)) {
+ struct pretty_print_context ctx = {0};
+ pretty_print_commit(CMIT_FMT_ONELINE, commit,
+ &subject, &ctx);
+ sub = subject.buf;
+ }
+
+ if (item->kind == REF_LOCAL_BRANCH)
+ fill_tracking_info(&stat, item->name, verbose > 1);
+
+ strbuf_addf(&out, " %s %s%s",
+ find_unique_abbrev(item->commit->object.sha1, abbrev),
+ stat.buf, sub);
+ strbuf_release(&stat);
+ strbuf_release(&subject);
+ }
+ printf("%s\n", out.buf);
+ strbuf_release(&name);
+ strbuf_release(&out);
+}
+
+static int calc_maxwidth(struct ref_list *refs)
+{
+ int i, w = 0;
+ for (i = 0; i < refs->index; i++) {
+ if (!matches_merge_filter(refs->list[i].commit))
+ continue;
+ if (refs->list[i].len > w)
+ w = refs->list[i].len;
+ }
+ return w;
+}
+
+
+static void show_detached(struct ref_list *ref_list)
+{
+ struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
+
+ if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
+ struct ref_item item;
+ item.name = xstrdup("(no branch)");
+ item.len = strlen(item.name);
+ item.kind = REF_LOCAL_BRANCH;
+ item.dest = NULL;
+ item.commit = head_commit;
+ if (item.len > ref_list->maxwidth)
+ ref_list->maxwidth = item.len;
+ print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, "");
+ free(item.name);
+ }
+}
+
+static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
+{
+ int i;
+ struct ref_list ref_list;
+
+ memset(&ref_list, 0, sizeof(ref_list));
+ ref_list.kinds = kinds;
+ ref_list.verbose = verbose;
+ ref_list.abbrev = abbrev;
+ ref_list.with_commit = with_commit;
+ if (merge_filter != NO_FILTER)
+ init_revisions(&ref_list.revs, NULL);
+ for_each_rawref(append_ref, &ref_list);
+ if (merge_filter != NO_FILTER) {
+ struct commit *filter;
+ filter = lookup_commit_reference_gently(merge_filter_ref, 0);
+ filter->object.flags |= UNINTERESTING;
+ add_pending_object(&ref_list.revs,
+ (struct object *) filter, "");
+ ref_list.revs.limited = 1;
+ prepare_revision_walk(&ref_list.revs);
+ if (verbose)
+ ref_list.maxwidth = calc_maxwidth(&ref_list);
+ }
+
+ qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
+
+ detached = (detached && (kinds & REF_LOCAL_BRANCH));
+ if (detached)
+ show_detached(&ref_list);
+
+ for (i = 0; i < ref_list.index; i++) {
+ int current = !detached &&
+ (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
+ !strcmp(ref_list.list[i].name, head);
+ char *prefix = (kinds != REF_REMOTE_BRANCH &&
+ ref_list.list[i].kind == REF_REMOTE_BRANCH)
+ ? "remotes/" : "";
+ print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
+ abbrev, current, prefix);
+ }
+
+ free_ref_list(&ref_list);
+}
+
+static void rename_branch(const char *oldname, const char *newname, int force)
+{
+ struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
+ unsigned char sha1[20];
+ struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
+ int recovery = 0;
+
+ if (!oldname)
+ die("cannot rename the current branch while not on any.");
+
+ if (strbuf_check_branch_ref(&oldref, oldname)) {
+ /*
+ * Bad name --- this could be an attempt to rename a
+ * ref that we used to allow to be created by accident.
+ */
+ if (resolve_ref(oldref.buf, sha1, 1, NULL))
+ recovery = 1;
+ else
+ die("Invalid branch name: '%s'", oldname);
+ }
+
+ if (strbuf_check_branch_ref(&newref, newname))
+ die("Invalid branch name: '%s'", newname);
+
+ if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
+ die("A branch named '%s' already exists.", newref.buf + 11);
+
+ strbuf_addf(&logmsg, "Branch: renamed %s to %s",
+ oldref.buf, newref.buf);
+
+ if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
+ die("Branch rename failed");
+ strbuf_release(&logmsg);
+
+ if (recovery)
+ warning("Renamed a misnamed branch '%s' away", oldref.buf + 11);
+
+ /* no need to pass logmsg here as HEAD didn't really move */
+ if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
+ die("Branch renamed to %s, but HEAD is not updated!", newname);
+
+ strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
+ strbuf_release(&oldref);
+ strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
+ strbuf_release(&newref);
+ if (git_config_rename_section(oldsection.buf, newsection.buf) < 0)
+ die("Branch is renamed, but update of config-file failed");
+ strbuf_release(&oldsection);
+ strbuf_release(&newsection);
+}
+
+static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
+{
+ merge_filter = ((opt->long_name[0] == 'n')
+ ? SHOW_NOT_MERGED
+ : SHOW_MERGED);
+ if (unset)
+ merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
+ if (!arg)
+ arg = "HEAD";
+ if (get_sha1(arg, merge_filter_ref))
+ die("malformed object name %s", arg);
+ return 0;
+}
+
+int cmd_branch(int argc, const char **argv, const char *prefix)
+{
+ int delete = 0, rename = 0, force_create = 0;
+ int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
+ int reflog = 0;
+ enum branch_track track;
+ int kinds = REF_LOCAL_BRANCH;
+ struct commit_list *with_commit = NULL;
+
+ struct option options[] = {
+ OPT_GROUP("Generic options"),
+ OPT__VERBOSE(&verbose),
+ OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))",
+ BRANCH_TRACK_EXPLICIT),
+ OPT_SET_INT( 0, "set-upstream", &track, "change upstream info",
+ BRANCH_TRACK_OVERRIDE),
+ OPT__COLOR(&branch_use_color, "use colored output"),
+ OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches",
+ REF_REMOTE_BRANCH),
+ {
+ OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
+ "print only branches that contain the commit",
+ PARSE_OPT_LASTARG_DEFAULT,
+ parse_opt_with_commit, (intptr_t)"HEAD",
+ },
+ {
+ OPTION_CALLBACK, 0, "with", &with_commit, "commit",
+ "print only branches that contain the commit",
+ PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
+ parse_opt_with_commit, (intptr_t) "HEAD",
+ },
+ OPT__ABBREV(&abbrev),
+
+ OPT_GROUP("Specific git-branch actions:"),
+ OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
+ REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
+ OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
+ OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
+ OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
+ OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
+ OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
+ OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"),
+ {
+ OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
+ "commit", "print only not merged branches",
+ PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
+ opt_parse_merge_filter, (intptr_t) "HEAD",
+ },
+ {
+ OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
+ "commit", "print only merged branches",
+ PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
+ opt_parse_merge_filter, (intptr_t) "HEAD",
+ },
+ OPT_END(),
+ };
+
+ git_config(git_branch_config, NULL);
+
+ if (branch_use_color == -1)
+ branch_use_color = git_use_color_default;
+
+ track = git_branch_track;
+
+ head = resolve_ref("HEAD", head_sha1, 0, NULL);
+ if (!head)
+ die("Failed to resolve HEAD as a valid ref.");
+ head = xstrdup(head);
+ if (!strcmp(head, "HEAD")) {
+ detached = 1;
+ } else {
+ if (prefixcmp(head, "refs/heads/"))
+ die("HEAD not found below refs/heads!");
+ head += 11;
+ }
+ hashcpy(merge_filter_ref, head_sha1);
+
+ argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
+ 0);
+ if (!!delete + !!rename + !!force_create > 1)
+ usage_with_options(builtin_branch_usage, options);
+
+ if (delete)
+ return delete_branches(argc, argv, delete > 1, kinds);
+ else if (argc == 0)
+ print_ref_list(kinds, detached, verbose, abbrev, with_commit);
+ else if (rename && (argc == 1))
+ rename_branch(head, argv[0], rename > 1);
+ else if (rename && (argc == 2))
+ rename_branch(argv[0], argv[1], rename > 1);
+ else if (argc <= 2) {
+ if (kinds != REF_LOCAL_BRANCH)
+ die("-a and -r options to 'git branch' do not make sense with a branch name");
+ create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
+ force_create, reflog, track);
+ } else
+ usage_with_options(builtin_branch_usage, options);
+
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "bundle.h"
+
+/*
+ * Basic handler for bundle files to connect repositories via sneakernet.
+ * Invocation must include action.
+ * This function can create a bundle or provide information on an existing
+ * bundle supporting "fetch", "pull", and "ls-remote".
+ */
+
+static const char builtin_bundle_usage[] =
+ "git bundle create <file> <git-rev-list args>\n"
+ " or: git bundle verify <file>\n"
+ " or: git bundle list-heads <file> [refname...]\n"
+ " or: git bundle unbundle <file> [refname...]";
+
+int cmd_bundle(int argc, const char **argv, const char *prefix)
+{
+ struct bundle_header header;
+ int nongit;
+ const char *cmd, *bundle_file;
+ int bundle_fd = -1;
+ char buffer[PATH_MAX];
+
+ if (argc < 3)
+ usage(builtin_bundle_usage);
+
+ cmd = argv[1];
+ bundle_file = argv[2];
+ argc -= 2;
+ argv += 2;
+
+ prefix = setup_git_directory_gently(&nongit);
+ if (prefix && bundle_file[0] != '/') {
+ snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file);
+ bundle_file = buffer;
+ }
+
+ memset(&header, 0, sizeof(header));
+ if (strcmp(cmd, "create") && (bundle_fd =
+ read_bundle_header(bundle_file, &header)) < 0)
+ return 1;
+
+ if (!strcmp(cmd, "verify")) {
+ close(bundle_fd);
+ if (verify_bundle(&header, 1))
+ return 1;
+ fprintf(stderr, "%s is okay\n", bundle_file);
+ return 0;
+ }
+ if (!strcmp(cmd, "list-heads")) {
+ close(bundle_fd);
+ return !!list_bundle_refs(&header, argc, argv);
+ }
+ if (!strcmp(cmd, "create")) {
+ if (nongit)
+ die("Need a repository to create a bundle.");
+ return !!create_bundle(&header, bundle_file, argc, argv);
+ } else if (!strcmp(cmd, "unbundle")) {
+ if (nongit)
+ die("Need a repository to unbundle.");
+ return !!unbundle(&header, bundle_fd) ||
+ list_bundle_refs(&header, argc, argv);
+ } else
+ usage(builtin_bundle_usage);
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "exec_cmd.h"
+#include "tag.h"
+#include "tree.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+#define BATCH 1
+#define BATCH_CHECK 2
+
+static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
+{
+ /* the parser in tag.c is useless here. */
+ const char *endp = buf + size;
+ const char *cp = buf;
+
+ while (cp < endp) {
+ char c = *cp++;
+ if (c != '\n')
+ continue;
+ if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) {
+ const char *tagger = cp;
+
+ /* Found the tagger line. Copy out the contents
+ * of the buffer so far.
+ */
+ write_or_die(1, buf, cp - buf);
+
+ /*
+ * Do something intelligent, like pretty-printing
+ * the date.
+ */
+ while (cp < endp) {
+ if (*cp++ == '\n') {
+ /* tagger to cp is a line
+ * that has ident and time.
+ */
+ const char *sp = tagger;
+ char *ep;
+ unsigned long date;
+ long tz;
+ while (sp < cp && *sp != '>')
+ sp++;
+ if (sp == cp) {
+ /* give up */
+ write_or_die(1, tagger,
+ cp - tagger);
+ break;
+ }
+ while (sp < cp &&
+ !('0' <= *sp && *sp <= '9'))
+ sp++;
+ write_or_die(1, tagger, sp - tagger);
+ date = strtoul(sp, &ep, 10);
+ tz = strtol(ep, NULL, 10);
+ sp = show_date(date, tz, 0);
+ write_or_die(1, sp, strlen(sp));
+ xwrite(1, "\n", 1);
+ break;
+ }
+ }
+ break;
+ }
+ if (cp < endp && *cp == '\n')
+ /* end of header */
+ break;
+ }
+ /* At this point, we have copied out the header up to the end of
+ * the tagger line and cp points at one past \n. It could be the
+ * next header line after the tagger line, or it could be another
+ * \n that marks the end of the headers. We need to copy out the
+ * remainder as is.
+ */
+ if (cp < endp)
+ write_or_die(1, cp, endp - cp);
+}
+
+static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
+{
+ unsigned char sha1[20];
+ enum object_type type;
+ void *buf;
+ unsigned long size;
+
+ if (get_sha1(obj_name, sha1))
+ die("Not a valid object name %s", obj_name);
+
+ buf = NULL;
+ switch (opt) {
+ case 't':
+ type = sha1_object_info(sha1, NULL);
+ if (type > 0) {
+ printf("%s\n", typename(type));
+ return 0;
+ }
+ break;
+
+ case 's':
+ type = sha1_object_info(sha1, &size);
+ if (type > 0) {
+ printf("%lu\n", size);
+ return 0;
+ }
+ break;
+
+ case 'e':
+ return !has_sha1_file(sha1);
+
+ case 'p':
+ type = sha1_object_info(sha1, NULL);
+ if (type < 0)
+ die("Not a valid object name %s", obj_name);
+
+ /* custom pretty-print here */
+ if (type == OBJ_TREE) {
+ const char *ls_args[3] = {"ls-tree", obj_name, NULL};
+ return cmd_ls_tree(2, ls_args, NULL);
+ }
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ die("Cannot read object %s", obj_name);
+ if (type == OBJ_TAG) {
+ pprint_tag(sha1, buf, size);
+ return 0;
+ }
+
+ /* otherwise just spit out the data */
+ break;
+ case 0:
+ buf = read_object_with_reference(sha1, exp_type, &size, NULL);
+ break;
+
+ default:
+ die("git cat-file: unknown option: %s", exp_type);
+ }
+
+ if (!buf)
+ die("git cat-file %s: bad file", obj_name);
+
+ write_or_die(1, buf, size);
+ return 0;
+}
+
+static int batch_one_object(const char *obj_name, int print_contents)
+{
+ unsigned char sha1[20];
+ enum object_type type = 0;
+ unsigned long size;
+ void *contents = contents;
+
+ if (!obj_name)
+ return 1;
+
+ if (get_sha1(obj_name, sha1)) {
+ printf("%s missing\n", obj_name);
+ fflush(stdout);
+ return 0;
+ }
+
+ if (print_contents == BATCH)
+ contents = read_sha1_file(sha1, &type, &size);
+ else
+ type = sha1_object_info(sha1, &size);
+
+ if (type <= 0) {
+ printf("%s missing\n", obj_name);
+ fflush(stdout);
+ return 0;
+ }
+
+ printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size);
+ fflush(stdout);
+
+ if (print_contents == BATCH) {
+ write_or_die(1, contents, size);
+ printf("\n");
+ fflush(stdout);
+ free(contents);
+ }
+
+ return 0;
+}
+
+static int batch_objects(int print_contents)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+ int error = batch_one_object(buf.buf, print_contents);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static const char * const cat_file_usage[] = {
+ "git cat-file (-t|-s|-e|-p|<type>) <object>",
+ "git cat-file (--batch|--batch-check) < <list_of_objects>",
+ NULL
+};
+
+int cmd_cat_file(int argc, const char **argv, const char *prefix)
+{
+ int opt = 0, batch = 0;
+ const char *exp_type = NULL, *obj_name = NULL;
+
+ const struct option options[] = {
+ OPT_GROUP("<type> can be one of: blob, tree, commit, tag"),
+ OPT_SET_INT('t', NULL, &opt, "show object type", 't'),
+ OPT_SET_INT('s', NULL, &opt, "show object size", 's'),
+ OPT_SET_INT('e', NULL, &opt,
+ "exit with zero when there's no error", 'e'),
+ OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'),
+ OPT_SET_INT(0, "batch", &batch,
+ "show info and content of objects fed from the standard input",
+ BATCH),
+ OPT_SET_INT(0, "batch-check", &batch,
+ "show info about objects fed from the standard input",
+ BATCH_CHECK),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+
+ if (argc != 3 && argc != 2)
+ usage_with_options(cat_file_usage, options);
+
+ argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);
+
+ if (opt) {
+ if (argc == 1)
+ obj_name = argv[0];
+ else
+ usage_with_options(cat_file_usage, options);
+ }
+ if (!opt && !batch) {
+ if (argc == 2) {
+ exp_type = argv[0];
+ obj_name = argv[1];
+ } else
+ usage_with_options(cat_file_usage, options);
+ }
+ if (batch && (opt || argc)) {
+ usage_with_options(cat_file_usage, options);
+ }
+
+ if (batch)
+ return batch_objects(batch);
+
+ return cat_one_file(opt, exp_type, obj_name);
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "attr.h"
+#include "quote.h"
+#include "parse-options.h"
+
+static int stdin_paths;
+static const char * const check_attr_usage[] = {
+"git check-attr attr... [--] pathname...",
+"git check-attr --stdin attr... < <list-of-paths>",
+NULL
+};
+
+static int null_term_line;
+
+static const struct option check_attr_options[] = {
+ OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"),
+ OPT_BOOLEAN('z', NULL, &null_term_line,
+ "input paths are terminated by a null character"),
+ OPT_END()
+};
+
+static void check_attr(int cnt, struct git_attr_check *check,
+ const char** name, const char *file)
+{
+ int j;
+ if (git_checkattr(file, cnt, check))
+ die("git_checkattr died");
+ for (j = 0; j < cnt; j++) {
+ const char *value = check[j].value;
+
+ if (ATTR_TRUE(value))
+ value = "set";
+ else if (ATTR_FALSE(value))
+ value = "unset";
+ else if (ATTR_UNSET(value))
+ value = "unspecified";
+
+ quote_c_style(file, NULL, stdout, 0);
+ printf(": %s: %s\n", name[j], value);
+ }
+}
+
+static void check_attr_stdin_paths(int cnt, struct git_attr_check *check,
+ const char** name)
+{
+ struct strbuf buf, nbuf;
+ int line_termination = null_term_line ? 0 : '\n';
+
+ strbuf_init(&buf, 0);
+ strbuf_init(&nbuf, 0);
+ while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+ if (line_termination && buf.buf[0] == '"') {
+ strbuf_reset(&nbuf);
+ if (unquote_c_style(&nbuf, buf.buf, NULL))
+ die("line is badly quoted");
+ strbuf_swap(&buf, &nbuf);
+ }
+ check_attr(cnt, check, name, buf.buf);
+ maybe_flush_or_die(stdout, "attribute to stdout");
+ }
+ strbuf_release(&buf);
+ strbuf_release(&nbuf);
+}
+
+int cmd_check_attr(int argc, const char **argv, const char *prefix)
+{
+ struct git_attr_check *check;
+ int cnt, i, doubledash;
+ const char *errstr = NULL;
+
+ argc = parse_options(argc, argv, prefix, check_attr_options,
+ check_attr_usage, PARSE_OPT_KEEP_DASHDASH);
+ if (!argc)
+ usage_with_options(check_attr_usage, check_attr_options);
+
+ if (read_cache() < 0) {
+ die("invalid cache");
+ }
+
+ doubledash = -1;
+ for (i = 0; doubledash < 0 && i < argc; i++) {
+ if (!strcmp(argv[i], "--"))
+ doubledash = i;
+ }
+
+ /* If there is no double dash, we handle only one attribute */
+ if (doubledash < 0) {
+ cnt = 1;
+ doubledash = 0;
+ } else
+ cnt = doubledash;
+ doubledash++;
+
+ if (cnt <= 0)
+ errstr = "No attribute specified";
+ else if (stdin_paths && doubledash < argc)
+ errstr = "Can't specify files with --stdin";
+ if (errstr) {
+ error("%s", errstr);
+ usage_with_options(check_attr_usage, check_attr_options);
+ }
+
+ check = xcalloc(cnt, sizeof(*check));
+ for (i = 0; i < cnt; i++) {
+ const char *name;
+ struct git_attr *a;
+ name = argv[i];
+ a = git_attr(name);
+ if (!a)
+ return error("%s: not a valid attribute name", name);
+ check[i].attr = a;
+ }
+
+ if (stdin_paths)
+ check_attr_stdin_paths(cnt, check, argv);
+ else {
+ for (i = doubledash; i < argc; i++)
+ check_attr(cnt, check, argv, argv[i]);
+ maybe_flush_or_die(stdout, "attribute to stdout");
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "builtin.h"
+#include "strbuf.h"
+
+static const char builtin_check_ref_format_usage[] =
+"git check-ref-format [--print] <refname>\n"
+" or: git check-ref-format --branch <branchname-shorthand>";
+
+/*
+ * Replace each run of adjacent slashes in src with a single slash,
+ * and write the result to dst.
+ *
+ * This function is similar to normalize_path_copy(), but stripped down
+ * to meet check_ref_format's simpler needs.
+ */
+static void collapse_slashes(char *dst, const char *src)
+{
+ char ch;
+ char prev = '\0';
+
+ while ((ch = *src++) != '\0') {
+ if (prev == '/' && ch == prev)
+ continue;
+
+ *dst++ = ch;
+ prev = ch;
+ }
+ *dst = '\0';
+}
+
+int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
+{
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(builtin_check_ref_format_usage);
+
+ if (argc == 3 && !strcmp(argv[1], "--branch")) {
+ struct strbuf sb = STRBUF_INIT;
+
+ if (strbuf_check_branch_ref(&sb, argv[2]))
+ die("'%s' is not a valid branch name", argv[2]);
+ printf("%s\n", sb.buf + 11);
+ exit(0);
+ }
+ if (argc == 3 && !strcmp(argv[1], "--print")) {
+ char *refname = xmalloc(strlen(argv[2]) + 1);
+
+ if (check_ref_format(argv[2]))
+ exit(1);
+ collapse_slashes(refname, argv[2]);
+ printf("%s\n", refname);
+ exit(0);
+ }
+ if (argc != 2)
+ usage(builtin_check_ref_format_usage);
+ return !!check_ref_format(argv[1]);
+}
--- /dev/null
+/*
+ * Check-out files from the "current cache directory"
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ *
+ * Careful: order of argument flags does matter. For example,
+ *
+ * git checkout-index -a -f file.c
+ *
+ * Will first check out all files listed in the cache (but not
+ * overwrite any old ones), and then force-checkout "file.c" a
+ * second time (ie that one _will_ overwrite any old contents
+ * with the same filename).
+ *
+ * Also, just doing "git checkout-index" does nothing. You probably
+ * meant "git checkout-index -a". And if you want to force it, you
+ * want "git checkout-index -f -a".
+ *
+ * Intuitiveness is not the goal here. Repeatability is. The
+ * reason for the "no arguments means no work" thing is that
+ * from scripts you are supposed to be able to do things like
+ *
+ * find . -name '*.h' -print0 | xargs -0 git checkout-index -f --
+ *
+ * or:
+ *
+ * find . -name '*.h' -print0 | git checkout-index -f -z --stdin
+ *
+ * which will force all existing *.h files to be replaced with
+ * their cached copies. If an empty command line implied "all",
+ * then this would force-refresh everything in the cache, which
+ * was not the point.
+ *
+ * Oh, and the "--" is just a good idea when you know the rest
+ * will be filenames. Just so that you wouldn't have a filename
+ * of "-a" causing problems (not possible in the above example,
+ * but get used to it in scripting!).
+ */
+#include "builtin.h"
+#include "cache.h"
+#include "quote.h"
+#include "cache-tree.h"
+#include "parse-options.h"
+
+#define CHECKOUT_ALL 4
+static int line_termination = '\n';
+static int checkout_stage; /* default to checkout stage0 */
+static int to_tempfile;
+static char topath[4][PATH_MAX + 1];
+
+static struct checkout state;
+
+static void write_tempfile_record(const char *name, int prefix_length)
+{
+ int i;
+
+ if (CHECKOUT_ALL == checkout_stage) {
+ for (i = 1; i < 4; i++) {
+ if (i > 1)
+ putchar(' ');
+ if (topath[i][0])
+ fputs(topath[i], stdout);
+ else
+ putchar('.');
+ }
+ } else
+ fputs(topath[checkout_stage], stdout);
+
+ putchar('\t');
+ write_name_quoted(name + prefix_length, stdout, line_termination);
+
+ for (i = 0; i < 4; i++) {
+ topath[i][0] = 0;
+ }
+}
+
+static int checkout_file(const char *name, int prefix_length)
+{
+ int namelen = strlen(name);
+ int pos = cache_name_pos(name, namelen);
+ int has_same_name = 0;
+ int did_checkout = 0;
+ int errs = 0;
+
+ if (pos < 0)
+ pos = -pos - 1;
+
+ while (pos < active_nr) {
+ struct cache_entry *ce = active_cache[pos];
+ if (ce_namelen(ce) != namelen ||
+ memcmp(ce->name, name, namelen))
+ break;
+ has_same_name = 1;
+ pos++;
+ if (ce_stage(ce) != checkout_stage
+ && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
+ continue;
+ did_checkout = 1;
+ if (checkout_entry(ce, &state,
+ to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
+ errs++;
+ }
+
+ if (did_checkout) {
+ if (to_tempfile)
+ write_tempfile_record(name, prefix_length);
+ return errs > 0 ? -1 : 0;
+ }
+
+ if (!state.quiet) {
+ fprintf(stderr, "git checkout-index: %s ", name);
+ if (!has_same_name)
+ fprintf(stderr, "is not in the cache");
+ else if (checkout_stage)
+ fprintf(stderr, "does not exist at stage %d",
+ checkout_stage);
+ else
+ fprintf(stderr, "is unmerged");
+ fputc('\n', stderr);
+ }
+ return -1;
+}
+
+static void checkout_all(const char *prefix, int prefix_length)
+{
+ int i, errs = 0;
+ struct cache_entry *last_ce = NULL;
+
+ for (i = 0; i < active_nr ; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (ce_stage(ce) != checkout_stage
+ && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
+ continue;
+ if (prefix && *prefix &&
+ (ce_namelen(ce) <= prefix_length ||
+ memcmp(prefix, ce->name, prefix_length)))
+ continue;
+ if (last_ce && to_tempfile) {
+ if (ce_namelen(last_ce) != ce_namelen(ce)
+ || memcmp(last_ce->name, ce->name, ce_namelen(ce)))
+ write_tempfile_record(last_ce->name, prefix_length);
+ }
+ if (checkout_entry(ce, &state,
+ to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
+ errs++;
+ last_ce = ce;
+ }
+ if (last_ce && to_tempfile)
+ write_tempfile_record(last_ce->name, prefix_length);
+ if (errs)
+ /* we have already done our error reporting.
+ * exit with the same code as die().
+ */
+ exit(128);
+}
+
+static const char * const builtin_checkout_index_usage[] = {
+ "git checkout-index [options] [--] <file>...",
+ NULL
+};
+
+static struct lock_file lock_file;
+
+static int option_parse_u(const struct option *opt,
+ const char *arg, int unset)
+{
+ int *newfd = opt->value;
+
+ state.refresh_cache = 1;
+ if (*newfd < 0)
+ *newfd = hold_locked_index(&lock_file, 1);
+ return 0;
+}
+
+static int option_parse_z(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ line_termination = '\n';
+ else
+ line_termination = 0;
+ return 0;
+}
+
+static int option_parse_prefix(const struct option *opt,
+ const char *arg, int unset)
+{
+ state.base_dir = arg;
+ state.base_dir_len = strlen(arg);
+ return 0;
+}
+
+static int option_parse_stage(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (!strcmp(arg, "all")) {
+ to_tempfile = 1;
+ checkout_stage = CHECKOUT_ALL;
+ } else {
+ int ch = arg[0];
+ if ('1' <= ch && ch <= '3')
+ checkout_stage = arg[0] - '0';
+ else
+ die("stage should be between 1 and 3 or all");
+ }
+ return 0;
+}
+
+int cmd_checkout_index(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ int newfd = -1;
+ int all = 0;
+ int read_from_stdin = 0;
+ int prefix_length;
+ int force = 0, quiet = 0, not_new = 0;
+ struct option builtin_checkout_index_options[] = {
+ OPT_BOOLEAN('a', "all", &all,
+ "checks out all files in the index"),
+ OPT_BOOLEAN('f', "force", &force,
+ "forces overwrite of existing files"),
+ OPT__QUIET(&quiet),
+ OPT_BOOLEAN('n', "no-create", ¬_new,
+ "don't checkout new files"),
+ { OPTION_CALLBACK, 'u', "index", &newfd, NULL,
+ "update stat information in the index file",
+ PARSE_OPT_NOARG, option_parse_u },
+ { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+ "paths are separated with NUL character",
+ PARSE_OPT_NOARG, option_parse_z },
+ OPT_BOOLEAN(0, "stdin", &read_from_stdin,
+ "read list of paths from the standard input"),
+ OPT_BOOLEAN(0, "temp", &to_tempfile,
+ "write the content to temporary files"),
+ OPT_CALLBACK(0, "prefix", NULL, "string",
+ "when creating files, prepend <string>",
+ option_parse_prefix),
+ OPT_CALLBACK(0, "stage", NULL, NULL,
+ "copy out the files from named stage",
+ option_parse_stage),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+ state.base_dir = "";
+ prefix_length = prefix ? strlen(prefix) : 0;
+
+ if (read_cache() < 0) {
+ die("invalid cache");
+ }
+
+ argc = parse_options(argc, argv, prefix, builtin_checkout_index_options,
+ builtin_checkout_index_usage, 0);
+ state.force = force;
+ state.quiet = quiet;
+ state.not_new = not_new;
+
+ if (state.base_dir_len || to_tempfile) {
+ /* when --prefix is specified we do not
+ * want to update cache.
+ */
+ if (state.refresh_cache) {
+ rollback_lock_file(&lock_file);
+ newfd = -1;
+ }
+ state.refresh_cache = 0;
+ }
+
+ /* Check out named files first */
+ for (i = 0; i < argc; i++) {
+ const char *arg = argv[i];
+ const char *p;
+
+ if (all)
+ die("git checkout-index: don't mix '--all' and explicit filenames");
+ if (read_from_stdin)
+ die("git checkout-index: don't mix '--stdin' and explicit filenames");
+ p = prefix_path(prefix, prefix_length, arg);
+ checkout_file(p, prefix_length);
+ if (p < arg || p > arg + strlen(arg))
+ free((char *)p);
+ }
+
+ if (read_from_stdin) {
+ struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
+
+ if (all)
+ die("git checkout-index: don't mix '--all' and '--stdin'");
+
+ while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+ const char *p;
+ if (line_termination && buf.buf[0] == '"') {
+ strbuf_reset(&nbuf);
+ if (unquote_c_style(&nbuf, buf.buf, NULL))
+ die("line is badly quoted");
+ strbuf_swap(&buf, &nbuf);
+ }
+ p = prefix_path(prefix, prefix_length, buf.buf);
+ checkout_file(p, prefix_length);
+ if (p < buf.buf || p > buf.buf + buf.len)
+ free((char *)p);
+ }
+ strbuf_release(&nbuf);
+ strbuf_release(&buf);
+ }
+
+ if (all)
+ checkout_all(prefix, prefix_length);
+
+ if (0 <= newfd &&
+ (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(&lock_file)))
+ die("Unable to write new index file");
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "commit.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "cache-tree.h"
+#include "unpack-trees.h"
+#include "dir.h"
+#include "run-command.h"
+#include "merge-recursive.h"
+#include "branch.h"
+#include "diff.h"
+#include "revision.h"
+#include "remote.h"
+#include "blob.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
+#include "resolve-undo.h"
+
+static const char * const checkout_usage[] = {
+ "git checkout [options] <branch>",
+ "git checkout [options] [<branch>] -- <file>...",
+ NULL,
+};
+
+struct checkout_opts {
+ int quiet;
+ int merge;
+ int force;
+ int writeout_stage;
+ int writeout_error;
+
+ const char *new_branch;
+ int new_branch_log;
+ enum branch_track track;
+};
+
+static int post_checkout_hook(struct commit *old, struct commit *new,
+ int changed)
+{
+ return run_hook(NULL, "post-checkout",
+ sha1_to_hex(old ? old->object.sha1 : null_sha1),
+ sha1_to_hex(new ? new->object.sha1 : null_sha1),
+ changed ? "1" : "0", NULL);
+ /* "new" can be NULL when checking out from the index before
+ a commit exists. */
+
+}
+
+static int update_some(const unsigned char *sha1, const char *base, int baselen,
+ const char *pathname, unsigned mode, int stage, void *context)
+{
+ int len;
+ struct cache_entry *ce;
+
+ if (S_ISDIR(mode))
+ return READ_TREE_RECURSIVE;
+
+ len = baselen + strlen(pathname);
+ ce = xcalloc(1, cache_entry_size(len));
+ hashcpy(ce->sha1, sha1);
+ memcpy(ce->name, base, baselen);
+ memcpy(ce->name + baselen, pathname, len - baselen);
+ ce->ce_flags = create_ce_flags(len, 0);
+ ce->ce_mode = create_ce_mode(mode);
+ add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+ return 0;
+}
+
+static int read_tree_some(struct tree *tree, const char **pathspec)
+{
+ read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
+
+ /* update the index with the given tree's info
+ * for all args, expanding wildcards, and exit
+ * with any non-zero return code.
+ */
+ return 0;
+}
+
+static int skip_same_name(struct cache_entry *ce, int pos)
+{
+ while (++pos < active_nr &&
+ !strcmp(active_cache[pos]->name, ce->name))
+ ; /* skip */
+ return pos;
+}
+
+static int check_stage(int stage, struct cache_entry *ce, int pos)
+{
+ while (pos < active_nr &&
+ !strcmp(active_cache[pos]->name, ce->name)) {
+ if (ce_stage(active_cache[pos]) == stage)
+ return 0;
+ pos++;
+ }
+ return error("path '%s' does not have %s version",
+ ce->name,
+ (stage == 2) ? "our" : "their");
+}
+
+static int check_all_stages(struct cache_entry *ce, int pos)
+{
+ if (ce_stage(ce) != 1 ||
+ active_nr <= pos + 2 ||
+ strcmp(active_cache[pos+1]->name, ce->name) ||
+ ce_stage(active_cache[pos+1]) != 2 ||
+ strcmp(active_cache[pos+2]->name, ce->name) ||
+ ce_stage(active_cache[pos+2]) != 3)
+ return error("path '%s' does not have all three versions",
+ ce->name);
+ return 0;
+}
+
+static int checkout_stage(int stage, struct cache_entry *ce, int pos,
+ struct checkout *state)
+{
+ while (pos < active_nr &&
+ !strcmp(active_cache[pos]->name, ce->name)) {
+ if (ce_stage(active_cache[pos]) == stage)
+ return checkout_entry(active_cache[pos], state, NULL);
+ pos++;
+ }
+ return error("path '%s' does not have %s version",
+ ce->name,
+ (stage == 2) ? "our" : "their");
+}
+
+static int checkout_merged(int pos, struct checkout *state)
+{
+ struct cache_entry *ce = active_cache[pos];
+ const char *path = ce->name;
+ mmfile_t ancestor, ours, theirs;
+ int status;
+ unsigned char sha1[20];
+ mmbuffer_t result_buf;
+
+ if (ce_stage(ce) != 1 ||
+ active_nr <= pos + 2 ||
+ strcmp(active_cache[pos+1]->name, path) ||
+ ce_stage(active_cache[pos+1]) != 2 ||
+ strcmp(active_cache[pos+2]->name, path) ||
+ ce_stage(active_cache[pos+2]) != 3)
+ return error("path '%s' does not have all 3 versions", path);
+
+ read_mmblob(&ancestor, active_cache[pos]->sha1);
+ read_mmblob(&ours, active_cache[pos+1]->sha1);
+ read_mmblob(&theirs, active_cache[pos+2]->sha1);
+
+ status = ll_merge(&result_buf, path, &ancestor,
+ &ours, "ours", &theirs, "theirs", 0);
+ free(ancestor.ptr);
+ free(ours.ptr);
+ free(theirs.ptr);
+ if (status < 0 || !result_buf.ptr) {
+ free(result_buf.ptr);
+ return error("path '%s': cannot merge", path);
+ }
+
+ /*
+ * NEEDSWORK:
+ * There is absolutely no reason to write this as a blob object
+ * and create a phony cache entry just to leak. This hack is
+ * primarily to get to the write_entry() machinery that massages
+ * the contents to work-tree format and writes out which only
+ * allows it for a cache entry. The code in write_entry() needs
+ * to be refactored to allow us to feed a <buffer, size, mode>
+ * instead of a cache entry. Such a refactoring would help
+ * merge_recursive as well (it also writes the merge result to the
+ * object database even when it may contain conflicts).
+ */
+ if (write_sha1_file(result_buf.ptr, result_buf.size,
+ blob_type, sha1))
+ die("Unable to add merge result for '%s'", path);
+ ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
+ sha1,
+ path, 2, 0);
+ if (!ce)
+ die("make_cache_entry failed for path '%s'", path);
+ status = checkout_entry(ce, state, NULL);
+ return status;
+}
+
+static int checkout_paths(struct tree *source_tree, const char **pathspec,
+ struct checkout_opts *opts)
+{
+ int pos;
+ struct checkout state;
+ static char *ps_matched;
+ unsigned char rev[20];
+ int flag;
+ struct commit *head;
+ int errs = 0;
+ int stage = opts->writeout_stage;
+ int merge = opts->merge;
+ int newfd;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ newfd = hold_locked_index(lock_file, 1);
+ if (read_cache_preload(pathspec) < 0)
+ return error("corrupt index file");
+
+ if (source_tree)
+ read_tree_some(source_tree, pathspec);
+
+ for (pos = 0; pathspec[pos]; pos++)
+ ;
+ ps_matched = xcalloc(1, pos);
+
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+ match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
+ }
+
+ if (report_path_error(ps_matched, pathspec, 0))
+ return 1;
+
+ /* "checkout -m path" to recreate conflicted state */
+ if (opts->merge)
+ unmerge_cache(pathspec);
+
+ /* Any unmerged paths? */
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+ if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
+ if (!ce_stage(ce))
+ continue;
+ if (opts->force) {
+ warning("path '%s' is unmerged", ce->name);
+ } else if (stage) {
+ errs |= check_stage(stage, ce, pos);
+ } else if (opts->merge) {
+ errs |= check_all_stages(ce, pos);
+ } else {
+ errs = 1;
+ error("path '%s' is unmerged", ce->name);
+ }
+ pos = skip_same_name(ce, pos) - 1;
+ }
+ }
+ if (errs)
+ return 1;
+
+ /* Now we are committed to check them out */
+ memset(&state, 0, sizeof(state));
+ state.force = 1;
+ state.refresh_cache = 1;
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+ if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
+ if (!ce_stage(ce)) {
+ errs |= checkout_entry(ce, &state, NULL);
+ continue;
+ }
+ if (stage)
+ errs |= checkout_stage(stage, ce, pos, &state);
+ else if (merge)
+ errs |= checkout_merged(pos, &state);
+ pos = skip_same_name(ce, pos) - 1;
+ }
+ }
+
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+
+ resolve_ref("HEAD", rev, 0, &flag);
+ head = lookup_commit_reference_gently(rev, 1);
+
+ errs |= post_checkout_hook(head, head, 0);
+ return errs;
+}
+
+static void show_local_changes(struct object *head)
+{
+ struct rev_info rev;
+ /* I think we want full paths, even if we're in a subdirectory. */
+ init_revisions(&rev, NULL);
+ rev.abbrev = 0;
+ rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+ if (diff_setup_done(&rev.diffopt) < 0)
+ die("diff_setup_done failed");
+ add_pending_object(&rev, head, NULL);
+ run_diff_index(&rev, 0);
+}
+
+static void describe_detached_head(char *msg, struct commit *commit)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ parse_commit(commit);
+ pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx);
+ fprintf(stderr, "%s %s... %s\n", msg,
+ find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
+ strbuf_release(&sb);
+}
+
+static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
+{
+ struct unpack_trees_options opts;
+ struct tree_desc tree_desc;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = -1;
+ opts.update = worktree;
+ opts.skip_unmerged = !worktree;
+ opts.reset = 1;
+ opts.merge = 1;
+ opts.fn = oneway_merge;
+ opts.verbose_update = !o->quiet;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ parse_tree(tree);
+ init_tree_desc(&tree_desc, tree->buffer, tree->size);
+ switch (unpack_trees(1, &tree_desc, &opts)) {
+ case -2:
+ o->writeout_error = 1;
+ /*
+ * We return 0 nevertheless, as the index is all right
+ * and more importantly we have made best efforts to
+ * update paths in the work tree, and we cannot revert
+ * them.
+ */
+ case 0:
+ return 0;
+ default:
+ return 128;
+ }
+}
+
+struct branch_info {
+ const char *name; /* The short name used */
+ const char *path; /* The full name of a real branch */
+ struct commit *commit; /* The named commit */
+};
+
+static void setup_branch_path(struct branch_info *branch)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_branchname(&buf, branch->name);
+ if (strcmp(buf.buf, branch->name))
+ branch->name = xstrdup(buf.buf);
+ strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+ branch->path = strbuf_detach(&buf, NULL);
+}
+
+static int merge_working_tree(struct checkout_opts *opts,
+ struct branch_info *old, struct branch_info *new)
+{
+ int ret;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+ int newfd = hold_locked_index(lock_file, 1);
+
+ if (read_cache_preload(NULL) < 0)
+ return error("corrupt index file");
+
+ resolve_undo_clear();
+ if (opts->force) {
+ ret = reset_tree(new->commit->tree, opts, 1);
+ if (ret)
+ return ret;
+ } else {
+ struct tree_desc trees[2];
+ struct tree *tree;
+ struct unpack_trees_options topts;
+
+ memset(&topts, 0, sizeof(topts));
+ topts.head_idx = -1;
+ topts.src_index = &the_index;
+ topts.dst_index = &the_index;
+
+ topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches.";
+
+ refresh_cache(REFRESH_QUIET);
+
+ if (unmerged_cache()) {
+ error("you need to resolve your current index first");
+ return 1;
+ }
+
+ /* 2-way merge to the new branch */
+ topts.initial_checkout = is_cache_unborn();
+ topts.update = 1;
+ topts.merge = 1;
+ topts.gently = opts->merge && old->commit;
+ topts.verbose_update = !opts->quiet;
+ topts.fn = twoway_merge;
+ topts.dir = xcalloc(1, sizeof(*topts.dir));
+ topts.dir->flags |= DIR_SHOW_IGNORED;
+ topts.dir->exclude_per_dir = ".gitignore";
+ tree = parse_tree_indirect(old->commit ?
+ old->commit->object.sha1 :
+ (unsigned char *)EMPTY_TREE_SHA1_BIN);
+ init_tree_desc(&trees[0], tree->buffer, tree->size);
+ tree = parse_tree_indirect(new->commit->object.sha1);
+ init_tree_desc(&trees[1], tree->buffer, tree->size);
+
+ ret = unpack_trees(2, trees, &topts);
+ if (ret == -1) {
+ /*
+ * Unpack couldn't do a trivial merge; either
+ * give up or do a real merge, depending on
+ * whether the merge flag was used.
+ */
+ struct tree *result;
+ struct tree *work;
+ struct merge_options o;
+ if (!opts->merge)
+ return 1;
+
+ /*
+ * Without old->commit, the below is the same as
+ * the two-tree unpack we already tried and failed.
+ */
+ if (!old->commit)
+ return 1;
+
+ /* Do more real merge */
+
+ /*
+ * We update the index fully, then write the
+ * tree from the index, then merge the new
+ * branch with the current tree, with the old
+ * branch as the base. Then we reset the index
+ * (but not the working tree) to the new
+ * branch, leaving the working tree as the
+ * merged version, but skipping unmerged
+ * entries in the index.
+ */
+
+ add_files_to_cache(NULL, NULL, 0);
+ init_merge_options(&o);
+ o.verbosity = 0;
+ work = write_tree_from_memory(&o);
+
+ ret = reset_tree(new->commit->tree, opts, 1);
+ if (ret)
+ return ret;
+ o.branch1 = new->name;
+ o.branch2 = "local";
+ merge_trees(&o, new->commit->tree, work,
+ old->commit->tree, &result);
+ ret = reset_tree(new->commit->tree, opts, 0);
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+
+ if (!opts->force && !opts->quiet)
+ show_local_changes(&new->commit->object);
+
+ return 0;
+}
+
+static void report_tracking(struct branch_info *new)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct branch *branch = branch_get(new->name);
+
+ if (!format_tracking_info(branch, &sb))
+ return;
+ fputs(sb.buf, stdout);
+ strbuf_release(&sb);
+}
+
+static void detach_advice(const char *old_path, const char *new_name)
+{
+ const char fmt[] =
+ "Note: checking out '%s'.\n\n"
+ "You are in 'detached HEAD' state. You can look around, make experimental\n"
+ "changes and commit them, and you can discard any commits you make in this\n"
+ "state without impacting any branches by performing another checkout.\n\n"
+ "If you want to create a new branch to retain commits you create, you may\n"
+ "do so (now or later) by using -b with the checkout command again. Example:\n\n"
+ " git checkout -b new_branch_name\n\n";
+
+ fprintf(stderr, fmt, new_name);
+}
+
+static void update_refs_for_switch(struct checkout_opts *opts,
+ struct branch_info *old,
+ struct branch_info *new)
+{
+ struct strbuf msg = STRBUF_INIT;
+ const char *old_desc;
+ if (opts->new_branch) {
+ create_branch(old->name, opts->new_branch, new->name, 0,
+ opts->new_branch_log, opts->track);
+ new->name = opts->new_branch;
+ setup_branch_path(new);
+ }
+
+ old_desc = old->name;
+ if (!old_desc && old->commit)
+ old_desc = sha1_to_hex(old->commit->object.sha1);
+ strbuf_addf(&msg, "checkout: moving from %s to %s",
+ old_desc ? old_desc : "(invalid)", new->name);
+
+ if (new->path) {
+ create_symref("HEAD", new->path, msg.buf);
+ if (!opts->quiet) {
+ if (old->path && !strcmp(new->path, old->path))
+ fprintf(stderr, "Already on '%s'\n",
+ new->name);
+ else
+ fprintf(stderr, "Switched to%s branch '%s'\n",
+ opts->new_branch ? " a new" : "",
+ new->name);
+ }
+ } else if (strcmp(new->name, "HEAD")) {
+ update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
+ REF_NODEREF, DIE_ON_ERR);
+ if (!opts->quiet) {
+ if (old->path && advice_detached_head)
+ detach_advice(old->path, new->name);
+ describe_detached_head("HEAD is now at", new->commit);
+ }
+ }
+ remove_branch_state();
+ strbuf_release(&msg);
+ if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
+ report_tracking(new);
+}
+
+static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
+{
+ int ret = 0;
+ struct branch_info old;
+ unsigned char rev[20];
+ int flag;
+ memset(&old, 0, sizeof(old));
+ old.path = resolve_ref("HEAD", rev, 0, &flag);
+ old.commit = lookup_commit_reference_gently(rev, 1);
+ if (!(flag & REF_ISSYMREF))
+ old.path = NULL;
+
+ if (old.path && !prefixcmp(old.path, "refs/heads/"))
+ old.name = old.path + strlen("refs/heads/");
+
+ if (!new->name) {
+ new->name = "HEAD";
+ new->commit = old.commit;
+ if (!new->commit)
+ die("You are on a branch yet to be born");
+ parse_commit(new->commit);
+ }
+
+ ret = merge_working_tree(opts, &old, new);
+ if (ret)
+ return ret;
+
+ /*
+ * If we were on a detached HEAD, but have now moved to
+ * a new commit, we want to mention the old commit once more
+ * to remind the user that it might be lost.
+ */
+ if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
+ describe_detached_head("Previous HEAD position was", old.commit);
+
+ update_refs_for_switch(opts, &old, new);
+
+ ret = post_checkout_hook(old.commit, new->commit, 1);
+ return ret || opts->writeout_error;
+}
+
+static int git_checkout_config(const char *var, const char *value, void *cb)
+{
+ return git_xmerge_config(var, value, cb);
+}
+
+static int interactive_checkout(const char *revision, const char **pathspec,
+ struct checkout_opts *opts)
+{
+ return run_add_interactive(revision, "--patch=checkout", pathspec);
+}
+
+struct tracking_name_data {
+ const char *name;
+ char *remote;
+ int unique;
+};
+
+static int check_tracking_name(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct tracking_name_data *cb = cb_data;
+ const char *slash;
+
+ if (prefixcmp(refname, "refs/remotes/"))
+ return 0;
+ slash = strchr(refname + 13, '/');
+ if (!slash || strcmp(slash + 1, cb->name))
+ return 0;
+ if (cb->remote) {
+ cb->unique = 0;
+ return 0;
+ }
+ cb->remote = xstrdup(refname);
+ return 0;
+}
+
+static const char *unique_tracking_name(const char *name)
+{
+ struct tracking_name_data cb_data = { name, NULL, 1 };
+ for_each_ref(check_tracking_name, &cb_data);
+ if (cb_data.unique)
+ return cb_data.remote;
+ free(cb_data.remote);
+ return NULL;
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+ struct checkout_opts opts;
+ unsigned char rev[20];
+ const char *arg;
+ struct branch_info new;
+ struct tree *source_tree = NULL;
+ char *conflict_style = NULL;
+ int patch_mode = 0;
+ int dwim_new_local_branch = 1;
+ struct option options[] = {
+ OPT__QUIET(&opts.quiet),
+ OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
+ OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
+ OPT_SET_INT('t', "track", &opts.track, "track",
+ BRANCH_TRACK_EXPLICIT),
+ OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
+ 2),
+ OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
+ 3),
+ OPT_BOOLEAN('f', "force", &opts.force, "force"),
+ OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
+ OPT_STRING(0, "conflict", &conflict_style, "style",
+ "conflict style (merge or diff3)"),
+ OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
+ { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL,
+ "second guess 'git checkout no-such-branch'",
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ OPT_END(),
+ };
+ int has_dash_dash;
+
+ memset(&opts, 0, sizeof(opts));
+ memset(&new, 0, sizeof(new));
+
+ git_config(git_checkout_config, NULL);
+
+ opts.track = BRANCH_TRACK_UNSPECIFIED;
+
+ argc = parse_options(argc, argv, prefix, options, checkout_usage,
+ PARSE_OPT_KEEP_DASHDASH);
+
+ if (patch_mode && (opts.track > 0 || opts.new_branch
+ || opts.new_branch_log || opts.merge || opts.force))
+ die ("--patch is incompatible with all other options");
+
+ /* --track without -b should DWIM */
+ if (0 < opts.track && !opts.new_branch) {
+ const char *argv0 = argv[0];
+ if (!argc || !strcmp(argv0, "--"))
+ die ("--track needs a branch name");
+ if (!prefixcmp(argv0, "refs/"))
+ argv0 += 5;
+ if (!prefixcmp(argv0, "remotes/"))
+ argv0 += 8;
+ argv0 = strchr(argv0, '/');
+ if (!argv0 || !argv0[1])
+ die ("Missing branch name; try -b");
+ opts.new_branch = argv0 + 1;
+ }
+
+ if (conflict_style) {
+ opts.merge = 1; /* implied */
+ git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+ }
+
+ if (opts.force && opts.merge)
+ die("git checkout: -f and -m are incompatible");
+
+ /*
+ * case 1: git checkout <ref> -- [<paths>]
+ *
+ * <ref> must be a valid tree, everything after the '--' must be
+ * a path.
+ *
+ * case 2: git checkout -- [<paths>]
+ *
+ * everything after the '--' must be paths.
+ *
+ * case 3: git checkout <something> [<paths>]
+ *
+ * With no paths, if <something> is a commit, that is to
+ * switch to the branch or detach HEAD at it. As a special case,
+ * if <something> is A...B (missing A or B means HEAD but you can
+ * omit at most one side), and if there is a unique merge base
+ * between A and B, A...B names that merge base.
+ *
+ * With no paths, if <something> is _not_ a commit, no -t nor -b
+ * was given, and there is a tracking branch whose name is
+ * <something> in one and only one remote, then this is a short-hand
+ * to fork local <something> from that remote tracking branch.
+ *
+ * Otherwise <something> shall not be ambiguous.
+ * - If it's *only* a reference, treat it like case (1).
+ * - If it's only a path, treat it like case (2).
+ * - else: fail.
+ *
+ */
+ if (argc) {
+ if (!strcmp(argv[0], "--")) { /* case (2) */
+ argv++;
+ argc--;
+ goto no_reference;
+ }
+
+ arg = argv[0];
+ has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+
+ if (!strcmp(arg, "-"))
+ arg = "@{-1}";
+
+ if (get_sha1_mb(arg, rev)) {
+ if (has_dash_dash) /* case (1) */
+ die("invalid reference: %s", arg);
+ if (!patch_mode &&
+ dwim_new_local_branch &&
+ opts.track == BRANCH_TRACK_UNSPECIFIED &&
+ !opts.new_branch &&
+ !check_filename(NULL, arg) &&
+ argc == 1) {
+ const char *remote = unique_tracking_name(arg);
+ if (!remote || get_sha1(remote, rev))
+ goto no_reference;
+ opts.new_branch = arg;
+ arg = remote;
+ /* DWIMmed to create local branch */
+ }
+ else
+ goto no_reference;
+ }
+
+ /* we can't end up being in (2) anymore, eat the argument */
+ argv++;
+ argc--;
+
+ new.name = arg;
+ if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
+ setup_branch_path(&new);
+
+ if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&
+ resolve_ref(new.path, rev, 1, NULL))
+ ;
+ else
+ new.path = NULL;
+ parse_commit(new.commit);
+ source_tree = new.commit->tree;
+ } else
+ source_tree = parse_tree_indirect(rev);
+
+ if (!source_tree) /* case (1): want a tree */
+ die("reference is not a tree: %s", arg);
+ if (!has_dash_dash) {/* case (3 -> 1) */
+ /*
+ * Do not complain the most common case
+ * git checkout branch
+ * even if there happen to be a file called 'branch';
+ * it would be extremely annoying.
+ */
+ if (argc)
+ verify_non_filename(NULL, arg);
+ }
+ else {
+ argv++;
+ argc--;
+ }
+ }
+
+no_reference:
+
+ if (opts.track == BRANCH_TRACK_UNSPECIFIED)
+ opts.track = git_branch_track;
+
+ if (argc) {
+ const char **pathspec = get_pathspec(prefix, argv);
+
+ if (!pathspec)
+ die("invalid path specification");
+
+ if (patch_mode)
+ return interactive_checkout(new.name, pathspec, &opts);
+
+ /* Checkout paths */
+ if (opts.new_branch) {
+ if (argc == 1) {
+ die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
+ } else {
+ die("git checkout: updating paths is incompatible with switching branches.");
+ }
+ }
+
+ if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+ die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
+
+ return checkout_paths(source_tree, pathspec, &opts);
+ }
+
+ if (patch_mode)
+ return interactive_checkout(new.name, NULL, &opts);
+
+ if (opts.new_branch) {
+ struct strbuf buf = STRBUF_INIT;
+ if (strbuf_check_branch_ref(&buf, opts.new_branch))
+ die("git checkout: we do not like '%s' as a branch name.",
+ opts.new_branch);
+ if (!get_sha1(buf.buf, rev))
+ die("git checkout: branch %s already exists", opts.new_branch);
+ strbuf_release(&buf);
+ }
+
+ if (new.name && !new.commit) {
+ die("Cannot switch branch to a non-commit.");
+ }
+ if (opts.writeout_stage)
+ die("--ours/--theirs is incompatible with switching branches.");
+
+ return switch_branches(&opts, &new);
+}
--- /dev/null
+/*
+ * "git clean" builtin command
+ *
+ * Copyright (C) 2007 Shawn Bohrer
+ *
+ * Based on git-clean.sh by Pavel Roskin
+ */
+
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "parse-options.h"
+#include "quote.h"
+
+static int force = -1; /* unset */
+
+static const char *const builtin_clean_usage[] = {
+ "git clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...",
+ NULL
+};
+
+static int git_clean_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "clean.requireforce"))
+ force = !git_config_bool(var, value);
+ return git_default_config(var, value, cb);
+}
+
+int cmd_clean(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
+ int ignored_only = 0, baselen = 0, config_set = 0, errors = 0;
+ int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
+ struct strbuf directory = STRBUF_INIT;
+ struct dir_struct dir;
+ static const char **pathspec;
+ struct strbuf buf = STRBUF_INIT;
+ const char *qname;
+ char *seen = NULL;
+ struct option options[] = {
+ OPT__QUIET(&quiet),
+ OPT__DRY_RUN(&show_only),
+ OPT_BOOLEAN('f', "force", &force, "force"),
+ OPT_BOOLEAN('d', NULL, &remove_directories,
+ "remove whole directories"),
+ OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
+ OPT_BOOLEAN('X', NULL, &ignored_only,
+ "remove only ignored files"),
+ OPT_END()
+ };
+
+ git_config(git_clean_config, NULL);
+ if (force < 0)
+ force = 0;
+ else
+ config_set = 1;
+
+ argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
+ 0);
+
+ memset(&dir, 0, sizeof(dir));
+ if (ignored_only)
+ dir.flags |= DIR_SHOW_IGNORED;
+
+ if (ignored && ignored_only)
+ die("-x and -X cannot be used together");
+
+ if (!show_only && !force)
+ die("clean.requireForce %s to true and neither -n nor -f given; "
+ "refusing to clean", config_set ? "set" : "defaults");
+
+ if (force > 1)
+ rm_flags = 0;
+
+ dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
+
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ if (!ignored)
+ setup_standard_excludes(&dir);
+
+ pathspec = get_pathspec(prefix, argv);
+
+ fill_directory(&dir, pathspec);
+
+ if (pathspec)
+ seen = xmalloc(argc > 0 ? argc : 1);
+
+ for (i = 0; i < dir.nr; i++) {
+ struct dir_entry *ent = dir.entries[i];
+ int len, pos;
+ int matches = 0;
+ struct cache_entry *ce;
+ struct stat st;
+
+ /*
+ * Remove the '/' at the end that directory
+ * walking adds for directory entries.
+ */
+ len = ent->len;
+ if (len && ent->name[len-1] == '/')
+ len--;
+ pos = cache_name_pos(ent->name, len);
+ if (0 <= pos)
+ continue; /* exact match */
+ pos = -pos - 1;
+ if (pos < active_nr) {
+ ce = active_cache[pos];
+ if (ce_namelen(ce) == len &&
+ !memcmp(ce->name, ent->name, len))
+ continue; /* Yup, this one exists unmerged */
+ }
+
+ /*
+ * we might have removed this as part of earlier
+ * recursive directory removal, so lstat() here could
+ * fail with ENOENT.
+ */
+ if (lstat(ent->name, &st))
+ continue;
+
+ if (pathspec) {
+ memset(seen, 0, argc > 0 ? argc : 1);
+ matches = match_pathspec(pathspec, ent->name, len,
+ baselen, seen);
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ strbuf_addstr(&directory, ent->name);
+ qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
+ if (show_only && (remove_directories ||
+ (matches == MATCHED_EXACTLY))) {
+ printf("Would remove %s\n", qname);
+ } else if (remove_directories ||
+ (matches == MATCHED_EXACTLY)) {
+ if (!quiet)
+ printf("Removing %s\n", qname);
+ if (remove_dir_recursively(&directory,
+ rm_flags) != 0) {
+ warning("failed to remove '%s'", qname);
+ errors++;
+ }
+ } else if (show_only) {
+ printf("Would not remove %s\n", qname);
+ } else {
+ printf("Not removing %s\n", qname);
+ }
+ strbuf_reset(&directory);
+ } else {
+ if (pathspec && !matches)
+ continue;
+ qname = quote_path_relative(ent->name, -1, &buf, prefix);
+ if (show_only) {
+ printf("Would remove %s\n", qname);
+ continue;
+ } else if (!quiet) {
+ printf("Removing %s\n", qname);
+ }
+ if (unlink(ent->name) != 0) {
+ warning("failed to remove '%s'", qname);
+ errors++;
+ }
+ }
+ }
+ free(seen);
+
+ strbuf_release(&directory);
+ return (errors != 0);
+}
--- /dev/null
+/*
+ * Builtin "git clone"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
+ * 2008 Daniel Barkalow <barkalow@iabervon.org>
+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
+ *
+ * Clone a repository into a different directory that does not yet exist.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "fetch-pack.h"
+#include "refs.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "unpack-trees.h"
+#include "transport.h"
+#include "strbuf.h"
+#include "dir.h"
+#include "pack-refs.h"
+#include "sigchain.h"
+#include "branch.h"
+#include "remote.h"
+#include "run-command.h"
+
+/*
+ * Overall FIXMEs:
+ * - respect DB_ENVIRONMENT for .git/objects.
+ *
+ * Implementation notes:
+ * - dropping use-separate-remote and no-separate-remote compatibility
+ *
+ */
+static const char * const builtin_clone_usage[] = {
+ "git clone [options] [--] <repo> [<dir>]",
+ NULL
+};
+
+static int option_quiet, option_no_checkout, option_bare, option_mirror;
+static int option_local, option_no_hardlinks, option_shared, option_recursive;
+static char *option_template, *option_reference, *option_depth;
+static char *option_origin = NULL;
+static char *option_branch = NULL;
+static char *option_upload_pack = "git-upload-pack";
+static int option_verbose;
+static int option_progress;
+
+static struct option builtin_clone_options[] = {
+ OPT__QUIET(&option_quiet),
+ OPT__VERBOSE(&option_verbose),
+ OPT_BOOLEAN(0, "progress", &option_progress,
+ "force progress reporting"),
+ OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
+ "don't create a checkout"),
+ OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
+ { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL,
+ "create a bare repository",
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ OPT_BOOLEAN(0, "mirror", &option_mirror,
+ "create a mirror repository (implies bare)"),
+ OPT_BOOLEAN('l', "local", &option_local,
+ "to clone from a local repository"),
+ OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
+ "don't use local hardlinks, always copy"),
+ OPT_BOOLEAN('s', "shared", &option_shared,
+ "setup as shared repository"),
+ OPT_BOOLEAN(0, "recursive", &option_recursive,
+ "initialize submodules in the clone"),
+ OPT_STRING(0, "template", &option_template, "path",
+ "path the template repository"),
+ OPT_STRING(0, "reference", &option_reference, "repo",
+ "reference repository"),
+ OPT_STRING('o', "origin", &option_origin, "branch",
+ "use <branch> instead of 'origin' to track upstream"),
+ OPT_STRING('b', "branch", &option_branch, "branch",
+ "checkout <branch> instead of the remote's HEAD"),
+ OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
+ "path to git-upload-pack on the remote"),
+ OPT_STRING(0, "depth", &option_depth, "depth",
+ "create a shallow clone of that depth"),
+
+ OPT_END()
+};
+
+static const char *argv_submodule[] = {
+ "submodule", "update", "--init", "--recursive", NULL
+};
+
+static char *get_repo_path(const char *repo, int *is_bundle)
+{
+ static char *suffix[] = { "/.git", ".git", "" };
+ static char *bundle_suffix[] = { ".bundle", "" };
+ struct stat st;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(suffix); i++) {
+ const char *path;
+ path = mkpath("%s%s", repo, suffix[i]);
+ if (is_directory(path)) {
+ *is_bundle = 0;
+ return xstrdup(make_nonrelative_path(path));
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {
+ const char *path;
+ path = mkpath("%s%s", repo, bundle_suffix[i]);
+ if (!stat(path, &st) && S_ISREG(st.st_mode)) {
+ *is_bundle = 1;
+ return xstrdup(make_nonrelative_path(path));
+ }
+ }
+
+ return NULL;
+}
+
+static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
+{
+ const char *end = repo + strlen(repo), *start;
+ char *dir;
+
+ /*
+ * Strip trailing spaces, slashes and /.git
+ */
+ while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
+ end--;
+ if (end - repo > 5 && is_dir_sep(end[-5]) &&
+ !strncmp(end - 4, ".git", 4)) {
+ end -= 5;
+ while (repo < end && is_dir_sep(end[-1]))
+ end--;
+ }
+
+ /*
+ * Find last component, but be prepared that repo could have
+ * the form "remote.example.com:foo.git", i.e. no slash
+ * in the directory part.
+ */
+ start = end;
+ while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':')
+ start--;
+
+ /*
+ * Strip .{bundle,git}.
+ */
+ if (is_bundle) {
+ if (end - start > 7 && !strncmp(end - 7, ".bundle", 7))
+ end -= 7;
+ } else {
+ if (end - start > 4 && !strncmp(end - 4, ".git", 4))
+ end -= 4;
+ }
+
+ if (is_bare) {
+ struct strbuf result = STRBUF_INIT;
+ strbuf_addf(&result, "%.*s.git", (int)(end - start), start);
+ dir = strbuf_detach(&result, NULL);
+ } else
+ dir = xstrndup(start, end - start);
+ /*
+ * Replace sequences of 'control' characters and whitespace
+ * with one ascii space, remove leading and trailing spaces.
+ */
+ if (*dir) {
+ char *out = dir;
+ int prev_space = 1 /* strip leading whitespace */;
+ for (end = dir; *end; ++end) {
+ char ch = *end;
+ if ((unsigned char)ch < '\x20')
+ ch = '\x20';
+ if (isspace(ch)) {
+ if (prev_space)
+ continue;
+ prev_space = 1;
+ } else
+ prev_space = 0;
+ *out++ = ch;
+ }
+ *out = '\0';
+ if (out > dir && prev_space)
+ out[-1] = '\0';
+ }
+ return dir;
+}
+
+static void strip_trailing_slashes(char *dir)
+{
+ char *end = dir + strlen(dir);
+
+ while (dir < end - 1 && is_dir_sep(end[-1]))
+ end--;
+ *end = '\0';
+}
+
+static void setup_reference(const char *repo)
+{
+ const char *ref_git;
+ char *ref_git_copy;
+
+ struct remote *remote;
+ struct transport *transport;
+ const struct ref *extra;
+
+ ref_git = make_absolute_path(option_reference);
+
+ if (is_directory(mkpath("%s/.git/objects", ref_git)))
+ ref_git = mkpath("%s/.git", ref_git);
+ else if (!is_directory(mkpath("%s/objects", ref_git)))
+ die("reference repository '%s' is not a local directory.",
+ option_reference);
+
+ ref_git_copy = xstrdup(ref_git);
+
+ add_to_alternates_file(ref_git_copy);
+
+ remote = remote_get(ref_git_copy);
+ transport = transport_get(remote, ref_git_copy);
+ for (extra = transport_get_remote_refs(transport); extra;
+ extra = extra->next)
+ add_extra_ref(extra->name, extra->old_sha1, 0);
+
+ transport_disconnect(transport);
+
+ free(ref_git_copy);
+}
+
+static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
+{
+ struct dirent *de;
+ struct stat buf;
+ int src_len, dest_len;
+ DIR *dir;
+
+ dir = opendir(src->buf);
+ if (!dir)
+ die_errno("failed to open '%s'", src->buf);
+
+ if (mkdir(dest->buf, 0777)) {
+ if (errno != EEXIST)
+ die_errno("failed to create directory '%s'", dest->buf);
+ else if (stat(dest->buf, &buf))
+ die_errno("failed to stat '%s'", dest->buf);
+ else if (!S_ISDIR(buf.st_mode))
+ die("%s exists and is not a directory", dest->buf);
+ }
+
+ strbuf_addch(src, '/');
+ src_len = src->len;
+ strbuf_addch(dest, '/');
+ dest_len = dest->len;
+
+ while ((de = readdir(dir)) != NULL) {
+ strbuf_setlen(src, src_len);
+ strbuf_addstr(src, de->d_name);
+ strbuf_setlen(dest, dest_len);
+ strbuf_addstr(dest, de->d_name);
+ if (stat(src->buf, &buf)) {
+ warning ("failed to stat %s\n", src->buf);
+ continue;
+ }
+ if (S_ISDIR(buf.st_mode)) {
+ if (de->d_name[0] != '.')
+ copy_or_link_directory(src, dest);
+ continue;
+ }
+
+ if (unlink(dest->buf) && errno != ENOENT)
+ die_errno("failed to unlink '%s'", dest->buf);
+ if (!option_no_hardlinks) {
+ if (!link(src->buf, dest->buf))
+ continue;
+ if (option_local)
+ die_errno("failed to create link '%s'", dest->buf);
+ option_no_hardlinks = 1;
+ }
+ if (copy_file_with_time(dest->buf, src->buf, 0666))
+ die_errno("failed to copy file to '%s'", dest->buf);
+ }
+ closedir(dir);
+}
+
+static const struct ref *clone_local(const char *src_repo,
+ const char *dest_repo)
+{
+ const struct ref *ret;
+ struct strbuf src = STRBUF_INIT;
+ struct strbuf dest = STRBUF_INIT;
+ struct remote *remote;
+ struct transport *transport;
+
+ if (option_shared)
+ add_to_alternates_file(src_repo);
+ else {
+ strbuf_addf(&src, "%s/objects", src_repo);
+ strbuf_addf(&dest, "%s/objects", dest_repo);
+ copy_or_link_directory(&src, &dest);
+ strbuf_release(&src);
+ strbuf_release(&dest);
+ }
+
+ remote = remote_get(src_repo);
+ transport = transport_get(remote, src_repo);
+ ret = transport_get_remote_refs(transport);
+ transport_disconnect(transport);
+ return ret;
+}
+
+static const char *junk_work_tree;
+static const char *junk_git_dir;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ if (getpid() != junk_pid)
+ return;
+ if (junk_git_dir) {
+ strbuf_addstr(&sb, junk_git_dir);
+ remove_dir_recursively(&sb, 0);
+ strbuf_reset(&sb);
+ }
+ if (junk_work_tree) {
+ strbuf_addstr(&sb, junk_work_tree);
+ remove_dir_recursively(&sb, 0);
+ strbuf_reset(&sb);
+ }
+}
+
+static void remove_junk_on_signal(int signo)
+{
+ remove_junk();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
+static struct ref *wanted_peer_refs(const struct ref *refs,
+ struct refspec *refspec)
+{
+ struct ref *local_refs = NULL;
+ struct ref **tail = &local_refs;
+
+ get_fetch_map(refs, refspec, &tail, 0);
+ if (!option_mirror)
+ get_fetch_map(refs, tag_refspec, &tail, 0);
+
+ return local_refs;
+}
+
+static void write_remote_refs(const struct ref *local_refs)
+{
+ const struct ref *r;
+
+ for (r = local_refs; r; r = r->next)
+ add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
+
+ pack_refs(PACK_REFS_ALL);
+ clear_extra_refs();
+}
+
+int cmd_clone(int argc, const char **argv, const char *prefix)
+{
+ int is_bundle = 0;
+ struct stat buf;
+ const char *repo_name, *repo, *work_tree, *git_dir;
+ char *path, *dir;
+ int dest_exists;
+ const struct ref *refs, *remote_head;
+ const struct ref *remote_head_points_at;
+ const struct ref *our_head_points_at;
+ struct ref *mapped_refs;
+ struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
+ struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
+ struct transport *transport = NULL;
+ char *src_ref_prefix = "refs/heads/";
+ int err = 0;
+
+ struct refspec *refspec;
+ const char *fetch_pattern;
+
+ junk_pid = getpid();
+
+ argc = parse_options(argc, argv, prefix, builtin_clone_options,
+ builtin_clone_usage, 0);
+
+ if (argc > 2)
+ usage_msg_opt("Too many arguments.",
+ builtin_clone_usage, builtin_clone_options);
+
+ if (argc == 0)
+ usage_msg_opt("You must specify a repository to clone.",
+ builtin_clone_usage, builtin_clone_options);
+
+ if (option_mirror)
+ option_bare = 1;
+
+ if (option_bare) {
+ if (option_origin)
+ die("--bare and --origin %s options are incompatible.",
+ option_origin);
+ option_no_checkout = 1;
+ }
+
+ if (!option_origin)
+ option_origin = "origin";
+
+ repo_name = argv[0];
+
+ path = get_repo_path(repo_name, &is_bundle);
+ if (path)
+ repo = xstrdup(make_nonrelative_path(repo_name));
+ else if (!strchr(repo_name, ':'))
+ repo = xstrdup(make_absolute_path(repo_name));
+ else
+ repo = repo_name;
+
+ if (argc == 2)
+ dir = xstrdup(argv[1]);
+ else
+ dir = guess_dir_name(repo_name, is_bundle, option_bare);
+ strip_trailing_slashes(dir);
+
+ dest_exists = !stat(dir, &buf);
+ if (dest_exists && !is_empty_dir(dir))
+ die("destination path '%s' already exists and is not "
+ "an empty directory.", dir);
+
+ strbuf_addf(&reflog_msg, "clone: from %s", repo);
+
+ if (option_bare)
+ work_tree = NULL;
+ else {
+ work_tree = getenv("GIT_WORK_TREE");
+ if (work_tree && !stat(work_tree, &buf))
+ die("working tree '%s' already exists.", work_tree);
+ }
+
+ if (option_bare || work_tree)
+ git_dir = xstrdup(dir);
+ else {
+ work_tree = dir;
+ git_dir = xstrdup(mkpath("%s/.git", dir));
+ }
+
+ if (!option_bare) {
+ junk_work_tree = work_tree;
+ if (safe_create_leading_directories_const(work_tree) < 0)
+ die_errno("could not create leading directories of '%s'",
+ work_tree);
+ if (!dest_exists && mkdir(work_tree, 0755))
+ die_errno("could not create work tree dir '%s'.",
+ work_tree);
+ set_git_work_tree(work_tree);
+ }
+ junk_git_dir = git_dir;
+ atexit(remove_junk);
+ sigchain_push_common(remove_junk_on_signal);
+
+ setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1);
+
+ if (safe_create_leading_directories_const(git_dir) < 0)
+ die("could not create leading directories of '%s'", git_dir);
+ set_git_dir(make_absolute_path(git_dir));
+
+ init_db(option_template, option_quiet ? INIT_DB_QUIET : 0);
+
+ /*
+ * At this point, the config exists, so we do not need the
+ * environment variable. We actually need to unset it, too, to
+ * re-enable parsing of the global configs.
+ */
+ unsetenv(CONFIG_ENVIRONMENT);
+
+ if (option_reference)
+ setup_reference(git_dir);
+
+ git_config(git_default_config, NULL);
+
+ if (option_bare) {
+ if (option_mirror)
+ src_ref_prefix = "refs/";
+ strbuf_addstr(&branch_top, src_ref_prefix);
+
+ git_config_set("core.bare", "true");
+ } else {
+ strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
+ }
+
+ strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
+
+ if (option_mirror || !option_bare) {
+ /* Configure the remote */
+ strbuf_addf(&key, "remote.%s.fetch", option_origin);
+ git_config_set_multivar(key.buf, value.buf, "^$", 0);
+ strbuf_reset(&key);
+
+ if (option_mirror) {
+ strbuf_addf(&key, "remote.%s.mirror", option_origin);
+ git_config_set(key.buf, "true");
+ strbuf_reset(&key);
+ }
+
+ strbuf_addf(&key, "remote.%s.url", option_origin);
+ git_config_set(key.buf, repo);
+ strbuf_reset(&key);
+ }
+
+ fetch_pattern = value.buf;
+ refspec = parse_fetch_refspec(1, &fetch_pattern);
+
+ strbuf_reset(&value);
+
+ if (path && !is_bundle) {
+ refs = clone_local(path, git_dir);
+ mapped_refs = wanted_peer_refs(refs, refspec);
+ } else {
+ struct remote *remote = remote_get(argv[0]);
+ transport = transport_get(remote, remote->url[0]);
+
+ if (!transport->get_refs_list || !transport->fetch)
+ die("Don't know how to clone %s", transport->url);
+
+ transport_set_option(transport, TRANS_OPT_KEEP, "yes");
+
+ if (option_depth)
+ transport_set_option(transport, TRANS_OPT_DEPTH,
+ option_depth);
+
+ if (option_quiet)
+ transport->verbose = -1;
+ else if (option_verbose)
+ transport->verbose = 1;
+
+ if (option_progress)
+ transport->progress = 1;
+
+ if (option_upload_pack)
+ transport_set_option(transport, TRANS_OPT_UPLOADPACK,
+ option_upload_pack);
+
+ refs = transport_get_remote_refs(transport);
+ if (refs) {
+ mapped_refs = wanted_peer_refs(refs, refspec);
+ transport_fetch_refs(transport, mapped_refs);
+ }
+ }
+
+ if (refs) {
+ clear_extra_refs();
+
+ write_remote_refs(mapped_refs);
+
+ remote_head = find_ref_by_name(refs, "HEAD");
+ remote_head_points_at =
+ guess_remote_head(remote_head, mapped_refs, 0);
+
+ if (option_branch) {
+ struct strbuf head = STRBUF_INIT;
+ strbuf_addstr(&head, src_ref_prefix);
+ strbuf_addstr(&head, option_branch);
+ our_head_points_at =
+ find_ref_by_name(mapped_refs, head.buf);
+ strbuf_release(&head);
+
+ if (!our_head_points_at) {
+ warning("Remote branch %s not found in "
+ "upstream %s, using HEAD instead",
+ option_branch, option_origin);
+ our_head_points_at = remote_head_points_at;
+ }
+ }
+ else
+ our_head_points_at = remote_head_points_at;
+ }
+ else {
+ warning("You appear to have cloned an empty repository.");
+ our_head_points_at = NULL;
+ remote_head_points_at = NULL;
+ remote_head = NULL;
+ option_no_checkout = 1;
+ if (!option_bare)
+ install_branch_config(0, "master", option_origin,
+ "refs/heads/master");
+ }
+
+ if (remote_head_points_at && !option_bare) {
+ struct strbuf head_ref = STRBUF_INIT;
+ strbuf_addstr(&head_ref, branch_top.buf);
+ strbuf_addstr(&head_ref, "HEAD");
+ create_symref(head_ref.buf,
+ remote_head_points_at->peer_ref->name,
+ reflog_msg.buf);
+ }
+
+ if (our_head_points_at) {
+ /* Local default branch link */
+ create_symref("HEAD", our_head_points_at->name, NULL);
+ if (!option_bare) {
+ const char *head = skip_prefix(our_head_points_at->name,
+ "refs/heads/");
+ update_ref(reflog_msg.buf, "HEAD",
+ our_head_points_at->old_sha1,
+ NULL, 0, DIE_ON_ERR);
+ install_branch_config(0, head, option_origin,
+ our_head_points_at->name);
+ }
+ } else if (remote_head) {
+ /* Source had detached HEAD pointing somewhere. */
+ if (!option_bare) {
+ update_ref(reflog_msg.buf, "HEAD",
+ remote_head->old_sha1,
+ NULL, REF_NODEREF, DIE_ON_ERR);
+ our_head_points_at = remote_head;
+ }
+ } else {
+ /* Nothing to checkout out */
+ if (!option_no_checkout)
+ warning("remote HEAD refers to nonexistent ref, "
+ "unable to checkout.\n");
+ option_no_checkout = 1;
+ }
+
+ if (transport) {
+ transport_unlock_pack(transport);
+ transport_disconnect(transport);
+ }
+
+ if (!option_no_checkout) {
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+ struct unpack_trees_options opts;
+ struct tree *tree;
+ struct tree_desc t;
+ int fd;
+
+ /* We need to be in the new work tree for the checkout */
+ setup_work_tree();
+
+ fd = hold_locked_index(lock_file, 1);
+
+ memset(&opts, 0, sizeof opts);
+ opts.update = 1;
+ opts.merge = 1;
+ opts.fn = oneway_merge;
+ opts.verbose_update = !option_quiet;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+
+ tree = parse_tree_indirect(our_head_points_at->old_sha1);
+ parse_tree(tree);
+ init_tree_desc(&t, tree->buffer, tree->size);
+ unpack_trees(1, &t, &opts);
+
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+
+ err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+ sha1_to_hex(our_head_points_at->old_sha1), "1",
+ NULL);
+
+ if (!err && option_recursive)
+ err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+ }
+
+ strbuf_release(&reflog_msg);
+ strbuf_release(&branch_top);
+ strbuf_release(&key);
+ strbuf_release(&value);
+ junk_pid = 0;
+ return err;
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "builtin.h"
+#include "utf8.h"
+
+/*
+ * FIXME! Share the code with "write-tree.c"
+ */
+static void check_valid(unsigned char *sha1, enum object_type expect)
+{
+ enum object_type type = sha1_object_info(sha1, NULL);
+ if (type < 0)
+ die("%s is not a valid object", sha1_to_hex(sha1));
+ if (type != expect)
+ die("%s is not a valid '%s' object", sha1_to_hex(sha1),
+ typename(expect));
+}
+
+static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog";
+
+static void new_parent(struct commit *parent, struct commit_list **parents_p)
+{
+ unsigned char *sha1 = parent->object.sha1;
+ struct commit_list *parents;
+ for (parents = *parents_p; parents; parents = parents->next) {
+ if (parents->item == parent) {
+ error("duplicate parent %s ignored", sha1_to_hex(sha1));
+ return;
+ }
+ parents_p = &parents->next;
+ }
+ commit_list_insert(parent, parents_p);
+}
+
+static const char commit_utf8_warn[] =
+"Warning: commit message does not conform to UTF-8.\n"
+"You may want to amend it after fixing the message, or set the config\n"
+"variable i18n.commitencoding to the encoding your project uses.\n";
+
+int commit_tree(const char *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret,
+ const char *author)
+{
+ int result;
+ int encoding_is_utf8;
+ struct strbuf buffer;
+
+ check_valid(tree, OBJ_TREE);
+
+ /* Not having i18n.commitencoding is the same as having utf-8 */
+ encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
+
+ strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
+ strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
+
+ /*
+ * NOTE! This ordering means that the same exact tree merged with a
+ * different order of parents will be a _different_ changeset even
+ * if everything else stays the same.
+ */
+ while (parents) {
+ struct commit_list *next = parents->next;
+ strbuf_addf(&buffer, "parent %s\n",
+ sha1_to_hex(parents->item->object.sha1));
+ free(parents);
+ parents = next;
+ }
+
+ /* Person/date information */
+ if (!author)
+ author = git_author_info(IDENT_ERROR_ON_NO_NAME);
+ strbuf_addf(&buffer, "author %s\n", author);
+ strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME));
+ if (!encoding_is_utf8)
+ strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
+ strbuf_addch(&buffer, '\n');
+
+ /* And add the comment */
+ strbuf_addstr(&buffer, msg);
+
+ /* And check the encoding */
+ if (encoding_is_utf8 && !is_utf8(buffer.buf))
+ fprintf(stderr, commit_utf8_warn);
+
+ result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+ strbuf_release(&buffer);
+ return result;
+}
+
+int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct commit_list *parents = NULL;
+ unsigned char tree_sha1[20];
+ unsigned char commit_sha1[20];
+ struct strbuf buffer = STRBUF_INIT;
+
+ git_config(git_default_config, NULL);
+
+ if (argc < 2 || !strcmp(argv[1], "-h"))
+ usage(commit_tree_usage);
+ if (get_sha1(argv[1], tree_sha1))
+ die("Not a valid object name %s", argv[1]);
+
+ for (i = 2; i < argc; i += 2) {
+ unsigned char sha1[20];
+ const char *a, *b;
+ a = argv[i]; b = argv[i+1];
+ if (!b || strcmp(a, "-p"))
+ usage(commit_tree_usage);
+
+ if (get_sha1(b, sha1))
+ die("Not a valid object name %s", b);
+ check_valid(sha1, OBJ_COMMIT);
+ new_parent(lookup_commit(sha1), &parents);
+ }
+
+ if (strbuf_read(&buffer, 0, 0) < 0)
+ die_errno("git commit-tree: failed to read");
+
+ if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+ printf("%s\n", sha1_to_hex(commit_sha1));
+ return 0;
+ }
+ else
+ return 1;
+}
--- /dev/null
+/*
+ * Builtin "git commit"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
+ */
+
+#include "cache.h"
+#include "cache-tree.h"
+#include "color.h"
+#include "dir.h"
+#include "builtin.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "commit.h"
+#include "revision.h"
+#include "wt-status.h"
+#include "run-command.h"
+#include "refs.h"
+#include "log-tree.h"
+#include "strbuf.h"
+#include "utf8.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "rerere.h"
+#include "unpack-trees.h"
+#include "quote.h"
+
+static const char * const builtin_commit_usage[] = {
+ "git commit [options] [--] <filepattern>...",
+ NULL
+};
+
+static const char * const builtin_status_usage[] = {
+ "git status [options] [--] <filepattern>...",
+ NULL
+};
+
+static const char implicit_ident_advice[] =
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"If the identity used for this commit is wrong, you can fix it with:\n"
+"\n"
+" git commit --amend --author='Your Name <you@example.com>'\n";
+
+static unsigned char head_sha1[20];
+
+static char *use_message_buffer;
+static const char commit_editmsg[] = "COMMIT_EDITMSG";
+static struct lock_file index_lock; /* real index */
+static struct lock_file false_lock; /* used only for partial commits */
+static enum {
+ COMMIT_AS_IS = 1,
+ COMMIT_NORMAL,
+ COMMIT_PARTIAL,
+} commit_style;
+
+static const char *logfile, *force_author;
+static const char *template_file;
+static char *edit_message, *use_message;
+static char *author_name, *author_email, *author_date;
+static int all, edit_flag, also, interactive, only, amend, signoff;
+static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static char *untracked_files_arg, *force_date;
+/*
+ * The default commit message cleanup mode will remove the lines
+ * beginning with # (shell comments) and leading and trailing
+ * whitespaces (empty lines or containing only whitespaces)
+ * if editor is used, and only the whitespaces if the message
+ * is specified explicitly.
+ */
+static enum {
+ CLEANUP_SPACE,
+ CLEANUP_NONE,
+ CLEANUP_ALL,
+} cleanup_mode;
+static char *cleanup_arg;
+
+static int use_editor = 1, initial_commit, in_merge, include_status = 1;
+static const char *only_include_assumed;
+static struct strbuf message;
+
+static int null_termination;
+static enum {
+ STATUS_FORMAT_LONG,
+ STATUS_FORMAT_SHORT,
+ STATUS_FORMAT_PORCELAIN,
+} status_format = STATUS_FORMAT_LONG;
+
+static int opt_parse_m(const struct option *opt, const char *arg, int unset)
+{
+ struct strbuf *buf = opt->value;
+ if (unset)
+ strbuf_setlen(buf, 0);
+ else {
+ strbuf_addstr(buf, arg);
+ strbuf_addstr(buf, "\n\n");
+ }
+ return 0;
+}
+
+static struct option builtin_commit_options[] = {
+ OPT__QUIET(&quiet),
+ OPT__VERBOSE(&verbose),
+
+ OPT_GROUP("Commit message options"),
+ OPT_FILENAME('F', "file", &logfile, "read log from file"),
+ OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
+ OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
+ OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
+ OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
+ OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
+ OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
+ OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
+ OPT_FILENAME('t', "template", &template_file, "use specified template file"),
+ OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
+ OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+ OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+ /* end commit message options */
+
+ OPT_GROUP("Commit contents options"),
+ OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
+ OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"),
+ OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
+ OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
+ OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
+ OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
+ OPT_SET_INT(0, "short", &status_format, "show status concisely",
+ STATUS_FORMAT_SHORT),
+ OPT_SET_INT(0, "porcelain", &status_format,
+ "show porcelain output format", STATUS_FORMAT_PORCELAIN),
+ OPT_BOOLEAN('z', "null", &null_termination,
+ "terminate entries with NUL"),
+ OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+ { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+ OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
+ /* end commit contents options */
+
+ OPT_END()
+};
+
+static void rollback_index_files(void)
+{
+ switch (commit_style) {
+ case COMMIT_AS_IS:
+ break; /* nothing to do */
+ case COMMIT_NORMAL:
+ rollback_lock_file(&index_lock);
+ break;
+ case COMMIT_PARTIAL:
+ rollback_lock_file(&index_lock);
+ rollback_lock_file(&false_lock);
+ break;
+ }
+}
+
+static int commit_index_files(void)
+{
+ int err = 0;
+
+ switch (commit_style) {
+ case COMMIT_AS_IS:
+ break; /* nothing to do */
+ case COMMIT_NORMAL:
+ err = commit_lock_file(&index_lock);
+ break;
+ case COMMIT_PARTIAL:
+ err = commit_lock_file(&index_lock);
+ rollback_lock_file(&false_lock);
+ break;
+ }
+
+ return err;
+}
+
+/*
+ * Take a union of paths in the index and the named tree (typically, "HEAD"),
+ * and return the paths that match the given pattern in list.
+ */
+static int list_paths(struct string_list *list, const char *with_tree,
+ const char *prefix, const char **pattern)
+{
+ int i;
+ char *m;
+
+ for (i = 0; pattern[i]; i++)
+ ;
+ m = xcalloc(1, i);
+
+ if (with_tree)
+ overlay_tree_on_cache(with_tree, prefix);
+
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ struct string_list_item *item;
+
+ if (ce->ce_flags & CE_UPDATE)
+ continue;
+ if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
+ continue;
+ item = string_list_insert(ce->name, list);
+ if (ce_skip_worktree(ce))
+ item->util = item; /* better a valid pointer than a fake one */
+ }
+
+ return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
+}
+
+static void add_remove_files(struct string_list *list)
+{
+ int i;
+ for (i = 0; i < list->nr; i++) {
+ struct stat st;
+ struct string_list_item *p = &(list->items[i]);
+
+ /* p->util is skip-worktree */
+ if (p->util)
+ continue;
+
+ if (!lstat(p->string, &st)) {
+ if (add_to_cache(p->string, &st, 0))
+ die("updating files failed");
+ } else
+ remove_file_from_cache(p->string);
+ }
+}
+
+static void create_base_index(void)
+{
+ struct tree *tree;
+ struct unpack_trees_options opts;
+ struct tree_desc t;
+
+ if (initial_commit) {
+ discard_cache();
+ return;
+ }
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.index_only = 1;
+ opts.merge = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+
+ opts.fn = oneway_merge;
+ tree = parse_tree_indirect(head_sha1);
+ if (!tree)
+ die("failed to unpack HEAD tree object");
+ parse_tree(tree);
+ init_tree_desc(&t, tree->buffer, tree->size);
+ if (unpack_trees(1, &t, &opts))
+ exit(128); /* We've already reported the error, finish dying */
+}
+
+static void refresh_cache_or_die(int refresh_flags)
+{
+ /*
+ * refresh_flags contains REFRESH_QUIET, so the only errors
+ * are for unmerged entries.
+ */
+ if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN))
+ die_resolve_conflict("commit");
+}
+
+static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
+{
+ int fd;
+ struct string_list partial;
+ const char **pathspec = NULL;
+ int refresh_flags = REFRESH_QUIET;
+
+ if (is_status)
+ refresh_flags |= REFRESH_UNMERGED;
+ if (interactive) {
+ if (interactive_add(argc, argv, prefix) != 0)
+ die("interactive add failed");
+ if (read_cache_preload(NULL) < 0)
+ die("index file corrupt");
+ commit_style = COMMIT_AS_IS;
+ return get_index_file();
+ }
+
+ if (*argv)
+ pathspec = get_pathspec(prefix, argv);
+
+ if (read_cache_preload(pathspec) < 0)
+ die("index file corrupt");
+
+ /*
+ * Non partial, non as-is commit.
+ *
+ * (1) get the real index;
+ * (2) update the_index as necessary;
+ * (3) write the_index out to the real index (still locked);
+ * (4) return the name of the locked index file.
+ *
+ * The caller should run hooks on the locked real index, and
+ * (A) if all goes well, commit the real index;
+ * (B) on failure, rollback the real index.
+ */
+ if (all || (also && pathspec && *pathspec)) {
+ int fd = hold_locked_index(&index_lock, 1);
+ add_files_to_cache(also ? prefix : NULL, pathspec, 0);
+ refresh_cache_or_die(refresh_flags);
+ if (write_cache(fd, active_cache, active_nr) ||
+ close_lock_file(&index_lock))
+ die("unable to write new_index file");
+ commit_style = COMMIT_NORMAL;
+ return index_lock.filename;
+ }
+
+ /*
+ * As-is commit.
+ *
+ * (1) return the name of the real index file.
+ *
+ * The caller should run hooks on the real index, and run
+ * hooks on the real index, and create commit from the_index.
+ * We still need to refresh the index here.
+ */
+ if (!pathspec || !*pathspec) {
+ fd = hold_locked_index(&index_lock, 1);
+ refresh_cache_or_die(refresh_flags);
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(&index_lock))
+ die("unable to write new_index file");
+ commit_style = COMMIT_AS_IS;
+ return get_index_file();
+ }
+
+ /*
+ * A partial commit.
+ *
+ * (0) find the set of affected paths;
+ * (1) get lock on the real index file;
+ * (2) update the_index with the given paths;
+ * (3) write the_index out to the real index (still locked);
+ * (4) get lock on the false index file;
+ * (5) reset the_index from HEAD;
+ * (6) update the_index the same way as (2);
+ * (7) write the_index out to the false index file;
+ * (8) return the name of the false index file (still locked);
+ *
+ * The caller should run hooks on the locked false index, and
+ * create commit from it. Then
+ * (A) if all goes well, commit the real index;
+ * (B) on failure, rollback the real index;
+ * In either case, rollback the false index.
+ */
+ commit_style = COMMIT_PARTIAL;
+
+ if (in_merge)
+ die("cannot do a partial commit during a merge.");
+
+ memset(&partial, 0, sizeof(partial));
+ partial.strdup_strings = 1;
+ if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
+ exit(1);
+
+ discard_cache();
+ if (read_cache() < 0)
+ die("cannot read the index");
+
+ fd = hold_locked_index(&index_lock, 1);
+ add_remove_files(&partial);
+ refresh_cache(REFRESH_QUIET);
+ if (write_cache(fd, active_cache, active_nr) ||
+ close_lock_file(&index_lock))
+ die("unable to write new_index file");
+
+ fd = hold_lock_file_for_update(&false_lock,
+ git_path("next-index-%"PRIuMAX,
+ (uintmax_t) getpid()),
+ LOCK_DIE_ON_ERROR);
+
+ create_base_index();
+ add_remove_files(&partial);
+ refresh_cache(REFRESH_QUIET);
+
+ if (write_cache(fd, active_cache, active_nr) ||
+ close_lock_file(&false_lock))
+ die("unable to write temporary index file");
+
+ discard_cache();
+ read_cache_from(false_lock.filename);
+
+ return false_lock.filename;
+}
+
+static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
+ struct wt_status *s)
+{
+ unsigned char sha1[20];
+
+ if (s->relative_paths)
+ s->prefix = prefix;
+
+ if (amend) {
+ s->amend = 1;
+ s->reference = "HEAD^1";
+ }
+ s->verbose = verbose;
+ s->index_file = index_file;
+ s->fp = fp;
+ s->nowarn = nowarn;
+ s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+
+ wt_status_collect(s);
+
+ switch (status_format) {
+ case STATUS_FORMAT_SHORT:
+ wt_shortstatus_print(s, null_termination);
+ break;
+ case STATUS_FORMAT_PORCELAIN:
+ wt_porcelain_print(s, null_termination);
+ break;
+ case STATUS_FORMAT_LONG:
+ wt_status_print(s);
+ break;
+ }
+
+ return s->commitable;
+}
+
+static int is_a_merge(const unsigned char *sha1)
+{
+ struct commit *commit = lookup_commit(sha1);
+ if (!commit || parse_commit(commit))
+ die("could not parse HEAD commit");
+ return !!(commit->parents && commit->parents->next);
+}
+
+static const char sign_off_header[] = "Signed-off-by: ";
+
+static void determine_author_info(void)
+{
+ char *name, *email, *date;
+
+ name = getenv("GIT_AUTHOR_NAME");
+ email = getenv("GIT_AUTHOR_EMAIL");
+ date = getenv("GIT_AUTHOR_DATE");
+
+ if (use_message && !renew_authorship) {
+ const char *a, *lb, *rb, *eol;
+
+ a = strstr(use_message_buffer, "\nauthor ");
+ if (!a)
+ die("invalid commit: %s", use_message);
+
+ lb = strstr(a + 8, " <");
+ rb = strstr(a + 8, "> ");
+ eol = strchr(a + 8, '\n');
+ if (!lb || !rb || !eol)
+ die("invalid commit: %s", use_message);
+
+ name = xstrndup(a + 8, lb - (a + 8));
+ email = xstrndup(lb + 2, rb - (lb + 2));
+ date = xstrndup(rb + 2, eol - (rb + 2));
+ }
+
+ if (force_author) {
+ const char *lb = strstr(force_author, " <");
+ const char *rb = strchr(force_author, '>');
+
+ if (!lb || !rb)
+ die("malformed --author parameter");
+ name = xstrndup(force_author, lb - force_author);
+ email = xstrndup(lb + 2, rb - (lb + 2));
+ }
+
+ if (force_date)
+ date = force_date;
+
+ author_name = name;
+ author_email = email;
+ author_date = date;
+}
+
+static int ends_rfc2822_footer(struct strbuf *sb)
+{
+ int ch;
+ int hit = 0;
+ int i, j, k;
+ int len = sb->len;
+ int first = 1;
+ const char *buf = sb->buf;
+
+ for (i = len - 1; i > 0; i--) {
+ if (hit && buf[i] == '\n')
+ break;
+ hit = (buf[i] == '\n');
+ }
+
+ while (i < len - 1 && buf[i] == '\n')
+ i++;
+
+ for (; i < len; i = k) {
+ for (k = i; k < len && buf[k] != '\n'; k++)
+ ; /* do nothing */
+ k++;
+
+ if ((buf[k] == ' ' || buf[k] == '\t') && !first)
+ continue;
+
+ first = 0;
+
+ for (j = 0; i + j < len; j++) {
+ ch = buf[i + j];
+ if (ch == ':')
+ break;
+ if (isalnum(ch) ||
+ (ch == '-'))
+ continue;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int prepare_to_commit(const char *index_file, const char *prefix,
+ struct wt_status *s)
+{
+ struct stat statbuf;
+ int commitable, saved_color_setting;
+ struct strbuf sb = STRBUF_INIT;
+ char *buffer;
+ FILE *fp;
+ const char *hook_arg1 = NULL;
+ const char *hook_arg2 = NULL;
+ int ident_shown = 0;
+
+ if (!no_verify && run_hook(index_file, "pre-commit", NULL))
+ return 0;
+
+ if (message.len) {
+ strbuf_addbuf(&sb, &message);
+ hook_arg1 = "message";
+ } else if (logfile && !strcmp(logfile, "-")) {
+ if (isatty(0))
+ fprintf(stderr, "(reading log message from standard input)\n");
+ if (strbuf_read(&sb, 0, 0) < 0)
+ die_errno("could not read log from standard input");
+ hook_arg1 = "message";
+ } else if (logfile) {
+ if (strbuf_read_file(&sb, logfile, 0) < 0)
+ die_errno("could not read log file '%s'",
+ logfile);
+ hook_arg1 = "message";
+ } else if (use_message) {
+ buffer = strstr(use_message_buffer, "\n\n");
+ if (!buffer || buffer[2] == '\0')
+ die("commit has empty message");
+ strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
+ hook_arg1 = "commit";
+ hook_arg2 = use_message;
+ } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
+ die_errno("could not read MERGE_MSG");
+ hook_arg1 = "merge";
+ } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
+ die_errno("could not read SQUASH_MSG");
+ hook_arg1 = "squash";
+ } else if (template_file && !stat(template_file, &statbuf)) {
+ if (strbuf_read_file(&sb, template_file, 0) < 0)
+ die_errno("could not read '%s'", template_file);
+ hook_arg1 = "template";
+ }
+
+ /*
+ * This final case does not modify the template message,
+ * it just sets the argument to the prepare-commit-msg hook.
+ */
+ else if (in_merge)
+ hook_arg1 = "merge";
+
+ fp = fopen(git_path(commit_editmsg), "w");
+ if (fp == NULL)
+ die_errno("could not open '%s'", git_path(commit_editmsg));
+
+ if (cleanup_mode != CLEANUP_NONE)
+ stripspace(&sb, 0);
+
+ if (signoff) {
+ struct strbuf sob = STRBUF_INIT;
+ int i;
+
+ strbuf_addstr(&sob, sign_off_header);
+ strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
+ getenv("GIT_COMMITTER_EMAIL")));
+ strbuf_addch(&sob, '\n');
+ for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
+ ; /* do nothing */
+ if (prefixcmp(sb.buf + i, sob.buf)) {
+ if (!i || !ends_rfc2822_footer(&sb))
+ strbuf_addch(&sb, '\n');
+ strbuf_addbuf(&sb, &sob);
+ }
+ strbuf_release(&sob);
+ }
+
+ if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
+ die_errno("could not write commit template");
+
+ strbuf_release(&sb);
+
+ determine_author_info();
+
+ /* This checks if committer ident is explicitly given */
+ git_committer_info(0);
+ if (use_editor && include_status) {
+ char *author_ident;
+ const char *committer_ident;
+
+ if (in_merge)
+ fprintf(fp,
+ "#\n"
+ "# It looks like you may be committing a MERGE.\n"
+ "# If this is not correct, please remove the file\n"
+ "# %s\n"
+ "# and try again.\n"
+ "#\n",
+ git_path("MERGE_HEAD"));
+
+ fprintf(fp,
+ "\n"
+ "# Please enter the commit message for your changes.");
+ if (cleanup_mode == CLEANUP_ALL)
+ fprintf(fp,
+ " Lines starting\n"
+ "# with '#' will be ignored, and an empty"
+ " message aborts the commit.\n");
+ else /* CLEANUP_SPACE, that is. */
+ fprintf(fp,
+ " Lines starting\n"
+ "# with '#' will be kept; you may remove them"
+ " yourself if you want to.\n"
+ "# An empty message aborts the commit.\n");
+ if (only_include_assumed)
+ fprintf(fp, "# %s\n", only_include_assumed);
+
+ author_ident = xstrdup(fmt_name(author_name, author_email));
+ committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"),
+ getenv("GIT_COMMITTER_EMAIL"));
+ if (strcmp(author_ident, committer_ident))
+ fprintf(fp,
+ "%s"
+ "# Author: %s\n",
+ ident_shown++ ? "" : "#\n",
+ author_ident);
+ free(author_ident);
+
+ if (!user_ident_sufficiently_given())
+ fprintf(fp,
+ "%s"
+ "# Committer: %s\n",
+ ident_shown++ ? "" : "#\n",
+ committer_ident);
+
+ if (ident_shown)
+ fprintf(fp, "#\n");
+
+ saved_color_setting = s->use_color;
+ s->use_color = 0;
+ commitable = run_status(fp, index_file, prefix, 1, s);
+ s->use_color = saved_color_setting;
+ } else {
+ unsigned char sha1[20];
+ const char *parent = "HEAD";
+
+ if (!active_nr && read_cache() < 0)
+ die("Cannot read index");
+
+ if (amend)
+ parent = "HEAD^1";
+
+ if (get_sha1(parent, sha1))
+ commitable = !!active_nr;
+ else
+ commitable = index_differs_from(parent, 0);
+ }
+
+ fclose(fp);
+
+ if (!commitable && !in_merge && !allow_empty &&
+ !(amend && is_a_merge(head_sha1))) {
+ run_status(stdout, index_file, prefix, 0, s);
+ return 0;
+ }
+
+ /*
+ * Re-read the index as pre-commit hook could have updated it,
+ * and write it out as a tree. We must do this before we invoke
+ * the editor and after we invoke run_status above.
+ */
+ discard_cache();
+ read_cache_from(index_file);
+ if (!active_cache_tree)
+ active_cache_tree = cache_tree();
+ if (cache_tree_update(active_cache_tree,
+ active_cache, active_nr, 0, 0) < 0) {
+ error("Error building trees");
+ return 0;
+ }
+
+ if (run_hook(index_file, "prepare-commit-msg",
+ git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
+ return 0;
+
+ if (use_editor) {
+ char index[PATH_MAX];
+ const char *env[2] = { index, NULL };
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+ if (launch_editor(git_path(commit_editmsg), NULL, env)) {
+ fprintf(stderr,
+ "Please supply the message using either -m or -F option.\n");
+ exit(1);
+ }
+ }
+
+ if (!no_verify &&
+ run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Find out if the message in the strbuf contains only whitespace and
+ * Signed-off-by lines.
+ */
+static int message_is_empty(struct strbuf *sb)
+{
+ struct strbuf tmpl = STRBUF_INIT;
+ const char *nl;
+ int eol, i, start = 0;
+
+ if (cleanup_mode == CLEANUP_NONE && sb->len)
+ return 0;
+
+ /* See if the template is just a prefix of the message. */
+ if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
+ stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
+ if (start + tmpl.len <= sb->len &&
+ memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
+ start += tmpl.len;
+ }
+ strbuf_release(&tmpl);
+
+ /* Check if the rest is just whitespace and Signed-of-by's. */
+ for (i = start; i < sb->len; i++) {
+ nl = memchr(sb->buf + i, '\n', sb->len - i);
+ if (nl)
+ eol = nl - sb->buf;
+ else
+ eol = sb->len;
+
+ if (strlen(sign_off_header) <= eol - i &&
+ !prefixcmp(sb->buf + i, sign_off_header)) {
+ i = eol;
+ continue;
+ }
+ while (i < eol)
+ if (!isspace(sb->buf[i++]))
+ return 0;
+ }
+
+ return 1;
+}
+
+static const char *find_author_by_nickname(const char *name)
+{
+ struct rev_info revs;
+ struct commit *commit;
+ struct strbuf buf = STRBUF_INIT;
+ const char *av[20];
+ int ac = 0;
+
+ init_revisions(&revs, NULL);
+ strbuf_addf(&buf, "--author=%s", name);
+ av[++ac] = "--all";
+ av[++ac] = "-i";
+ av[++ac] = buf.buf;
+ av[++ac] = NULL;
+ setup_revisions(ac, av, &revs, NULL);
+ prepare_revision_walk(&revs);
+ commit = get_revision(&revs);
+ if (commit) {
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode = DATE_NORMAL;
+ strbuf_release(&buf);
+ format_commit_message(commit, "%an <%ae>", &buf, &ctx);
+ return strbuf_detach(&buf, NULL);
+ }
+ die("No existing author found with '%s'", name);
+}
+
+
+static void handle_untracked_files_arg(struct wt_status *s)
+{
+ if (!untracked_files_arg)
+ ; /* default already initialized */
+ else if (!strcmp(untracked_files_arg, "no"))
+ s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
+ else if (!strcmp(untracked_files_arg, "normal"))
+ s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+ else if (!strcmp(untracked_files_arg, "all"))
+ s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+ else
+ die("Invalid untracked files mode '%s'", untracked_files_arg);
+}
+
+static int parse_and_validate_options(int argc, const char *argv[],
+ const char * const usage[],
+ const char *prefix,
+ struct wt_status *s)
+{
+ int f = 0;
+
+ argc = parse_options(argc, argv, prefix, builtin_commit_options, usage,
+ 0);
+
+ if (force_author && !strchr(force_author, '>'))
+ force_author = find_author_by_nickname(force_author);
+
+ if (force_author && renew_authorship)
+ die("Using both --reset-author and --author does not make sense");
+
+ if (logfile || message.len || use_message)
+ use_editor = 0;
+ if (edit_flag)
+ use_editor = 1;
+ if (!use_editor)
+ setenv("GIT_EDITOR", ":", 1);
+
+ if (get_sha1("HEAD", head_sha1))
+ initial_commit = 1;
+
+ /* Sanity check options */
+ if (amend && initial_commit)
+ die("You have nothing to amend.");
+ if (amend && in_merge)
+ die("You are in the middle of a merge -- cannot amend.");
+
+ if (use_message)
+ f++;
+ if (edit_message)
+ f++;
+ if (logfile)
+ f++;
+ if (f > 1)
+ die("Only one of -c/-C/-F can be used.");
+ if (message.len && f > 0)
+ die("Option -m cannot be combined with -c/-C/-F.");
+ if (edit_message)
+ use_message = edit_message;
+ if (amend && !use_message)
+ use_message = "HEAD";
+ if (!use_message && renew_authorship)
+ die("--reset-author can be used only with -C, -c or --amend.");
+ if (use_message) {
+ unsigned char sha1[20];
+ static char utf8[] = "UTF-8";
+ const char *out_enc;
+ char *enc, *end;
+ struct commit *commit;
+
+ if (get_sha1(use_message, sha1))
+ die("could not lookup commit %s", use_message);
+ commit = lookup_commit_reference(sha1);
+ if (!commit || parse_commit(commit))
+ die("could not parse commit %s", use_message);
+
+ enc = strstr(commit->buffer, "\nencoding");
+ if (enc) {
+ end = strchr(enc + 10, '\n');
+ enc = xstrndup(enc + 10, end - (enc + 10));
+ } else {
+ enc = utf8;
+ }
+ out_enc = git_commit_encoding ? git_commit_encoding : utf8;
+
+ if (strcmp(out_enc, enc))
+ use_message_buffer =
+ reencode_string(commit->buffer, out_enc, enc);
+
+ /*
+ * If we failed to reencode the buffer, just copy it
+ * byte for byte so the user can try to fix it up.
+ * This also handles the case where input and output
+ * encodings are identical.
+ */
+ if (use_message_buffer == NULL)
+ use_message_buffer = xstrdup(commit->buffer);
+ if (enc != utf8)
+ free(enc);
+ }
+
+ if (!!also + !!only + !!all + !!interactive > 1)
+ die("Only one of --include/--only/--all/--interactive can be used.");
+ if (argc == 0 && (also || (only && !amend)))
+ die("No paths with --include/--only does not make sense.");
+ if (argc == 0 && only && amend)
+ only_include_assumed = "Clever... amending the last one with dirty index.";
+ if (argc > 0 && !also && !only)
+ only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
+ if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
+ cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
+ else if (!strcmp(cleanup_arg, "verbatim"))
+ cleanup_mode = CLEANUP_NONE;
+ else if (!strcmp(cleanup_arg, "whitespace"))
+ cleanup_mode = CLEANUP_SPACE;
+ else if (!strcmp(cleanup_arg, "strip"))
+ cleanup_mode = CLEANUP_ALL;
+ else
+ die("Invalid cleanup mode %s", cleanup_arg);
+
+ handle_untracked_files_arg(s);
+
+ if (all && argc > 0)
+ die("Paths with -a does not make sense.");
+ else if (interactive && argc > 0)
+ die("Paths with --interactive does not make sense.");
+
+ if (null_termination && status_format == STATUS_FORMAT_LONG)
+ status_format = STATUS_FORMAT_PORCELAIN;
+ if (status_format != STATUS_FORMAT_LONG)
+ dry_run = 1;
+
+ return argc;
+}
+
+static int dry_run_commit(int argc, const char **argv, const char *prefix,
+ struct wt_status *s)
+{
+ int commitable;
+ const char *index_file;
+
+ index_file = prepare_index(argc, argv, prefix, 1);
+ commitable = run_status(stdout, index_file, prefix, 0, s);
+ rollback_index_files();
+
+ return commitable ? 0 : 1;
+}
+
+static int parse_status_slot(const char *var, int offset)
+{
+ if (!strcasecmp(var+offset, "header"))
+ return WT_STATUS_HEADER;
+ if (!strcasecmp(var+offset, "updated")
+ || !strcasecmp(var+offset, "added"))
+ return WT_STATUS_UPDATED;
+ if (!strcasecmp(var+offset, "changed"))
+ return WT_STATUS_CHANGED;
+ if (!strcasecmp(var+offset, "untracked"))
+ return WT_STATUS_UNTRACKED;
+ if (!strcasecmp(var+offset, "nobranch"))
+ return WT_STATUS_NOBRANCH;
+ if (!strcasecmp(var+offset, "unmerged"))
+ return WT_STATUS_UNMERGED;
+ return -1;
+}
+
+static int git_status_config(const char *k, const char *v, void *cb)
+{
+ struct wt_status *s = cb;
+
+ if (!strcmp(k, "status.submodulesummary")) {
+ int is_bool;
+ s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
+ if (is_bool && s->submodule_summary)
+ s->submodule_summary = -1;
+ return 0;
+ }
+ if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
+ s->use_color = git_config_colorbool(k, v, -1);
+ return 0;
+ }
+ if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
+ int slot = parse_status_slot(k, 13);
+ if (slot < 0)
+ return 0;
+ if (!v)
+ return config_error_nonbool(k);
+ color_parse(v, k, s->color_palette[slot]);
+ return 0;
+ }
+ if (!strcmp(k, "status.relativepaths")) {
+ s->relative_paths = git_config_bool(k, v);
+ return 0;
+ }
+ if (!strcmp(k, "status.showuntrackedfiles")) {
+ if (!v)
+ return config_error_nonbool(k);
+ else if (!strcmp(v, "no"))
+ s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
+ else if (!strcmp(v, "normal"))
+ s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+ else if (!strcmp(v, "all"))
+ s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+ else
+ return error("Invalid untracked files mode '%s'", v);
+ return 0;
+ }
+ return git_diff_ui_config(k, v, NULL);
+}
+
+int cmd_status(int argc, const char **argv, const char *prefix)
+{
+ struct wt_status s;
+ unsigned char sha1[20];
+ static struct option builtin_status_options[] = {
+ OPT__VERBOSE(&verbose),
+ OPT_SET_INT('s', "short", &status_format,
+ "show status concisely", STATUS_FORMAT_SHORT),
+ OPT_SET_INT(0, "porcelain", &status_format,
+ "show porcelain output format",
+ STATUS_FORMAT_PORCELAIN),
+ OPT_BOOLEAN('z', "null", &null_termination,
+ "terminate entries with NUL"),
+ { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
+ "mode",
+ "show untracked files, optional modes: all, normal, no. (Default: all)",
+ PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+ OPT_END(),
+ };
+
+ if (null_termination && status_format == STATUS_FORMAT_LONG)
+ status_format = STATUS_FORMAT_PORCELAIN;
+
+ wt_status_prepare(&s);
+ git_config(git_status_config, &s);
+ in_merge = file_exists(git_path("MERGE_HEAD"));
+ argc = parse_options(argc, argv, prefix,
+ builtin_status_options,
+ builtin_status_usage, 0);
+ handle_untracked_files_arg(&s);
+
+ if (*argv)
+ s.pathspec = get_pathspec(prefix, argv);
+
+ read_cache_preload(s.pathspec);
+ refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
+ s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
+ s.in_merge = in_merge;
+ wt_status_collect(&s);
+
+ if (s.relative_paths)
+ s.prefix = prefix;
+ if (s.use_color == -1)
+ s.use_color = git_use_color_default;
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ switch (status_format) {
+ case STATUS_FORMAT_SHORT:
+ wt_shortstatus_print(&s, null_termination);
+ break;
+ case STATUS_FORMAT_PORCELAIN:
+ wt_porcelain_print(&s, null_termination);
+ break;
+ case STATUS_FORMAT_LONG:
+ s.verbose = verbose;
+ wt_status_print(&s);
+ break;
+ }
+ return 0;
+}
+
+static void print_summary(const char *prefix, const unsigned char *sha1)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ struct strbuf format = STRBUF_INIT;
+ unsigned char junk_sha1[20];
+ const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
+ struct pretty_print_context pctx = {0};
+ struct strbuf author_ident = STRBUF_INIT;
+ struct strbuf committer_ident = STRBUF_INIT;
+
+ commit = lookup_commit(sha1);
+ if (!commit)
+ die("couldn't look up newly created commit");
+ if (!commit || parse_commit(commit))
+ die("could not parse newly created commit");
+
+ strbuf_addstr(&format, "format:%h] %s");
+
+ format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
+ format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
+ if (strbuf_cmp(&author_ident, &committer_ident)) {
+ strbuf_addstr(&format, "\n Author: ");
+ strbuf_addbuf_percentquote(&format, &author_ident);
+ }
+ if (!user_ident_sufficiently_given()) {
+ strbuf_addstr(&format, "\n Committer: ");
+ strbuf_addbuf_percentquote(&format, &committer_ident);
+ if (advice_implicit_identity) {
+ strbuf_addch(&format, '\n');
+ strbuf_addstr(&format, implicit_ident_advice);
+ }
+ }
+ strbuf_release(&author_ident);
+ strbuf_release(&committer_ident);
+
+ init_revisions(&rev, prefix);
+ setup_revisions(0, NULL, &rev, NULL);
+
+ rev.abbrev = 0;
+ rev.diff = 1;
+ rev.diffopt.output_format =
+ DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
+
+ rev.verbose_header = 1;
+ rev.show_root_diff = 1;
+ get_commit_format(format.buf, &rev);
+ rev.always_show_header = 0;
+ rev.diffopt.detect_rename = 1;
+ rev.diffopt.rename_limit = 100;
+ rev.diffopt.break_opt = 0;
+ diff_setup_done(&rev.diffopt);
+
+ printf("[%s%s ",
+ !prefixcmp(head, "refs/heads/") ?
+ head + 11 :
+ !strcmp(head, "HEAD") ?
+ "detached HEAD" :
+ head,
+ initial_commit ? " (root-commit)" : "");
+
+ if (!log_tree_commit(&rev, commit)) {
+ struct pretty_print_context ctx = {0};
+ struct strbuf buf = STRBUF_INIT;
+ ctx.date_mode = DATE_NORMAL;
+ format_commit_message(commit, format.buf + 7, &buf, &ctx);
+ printf("%s\n", buf.buf);
+ strbuf_release(&buf);
+ }
+ strbuf_release(&format);
+}
+
+static int git_commit_config(const char *k, const char *v, void *cb)
+{
+ struct wt_status *s = cb;
+
+ if (!strcmp(k, "commit.template"))
+ return git_config_pathname(&template_file, k, v);
+ if (!strcmp(k, "commit.status")) {
+ include_status = git_config_bool(k, v);
+ return 0;
+ }
+
+ return git_status_config(k, v, s);
+}
+
+int cmd_commit(int argc, const char **argv, const char *prefix)
+{
+ struct strbuf sb = STRBUF_INIT;
+ const char *index_file, *reflog_msg;
+ char *nl, *p;
+ unsigned char commit_sha1[20];
+ struct ref_lock *ref_lock;
+ struct commit_list *parents = NULL, **pptr = &parents;
+ struct stat statbuf;
+ int allow_fast_forward = 1;
+ struct wt_status s;
+
+ wt_status_prepare(&s);
+ git_config(git_commit_config, &s);
+ in_merge = file_exists(git_path("MERGE_HEAD"));
+ s.in_merge = in_merge;
+
+ if (s.use_color == -1)
+ s.use_color = git_use_color_default;
+ argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
+ prefix, &s);
+ if (dry_run) {
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+ return dry_run_commit(argc, argv, prefix, &s);
+ }
+ index_file = prepare_index(argc, argv, prefix, 0);
+
+ /* Set up everything for writing the commit object. This includes
+ running hooks, writing the trees, and interacting with the user. */
+ if (!prepare_to_commit(index_file, prefix, &s)) {
+ rollback_index_files();
+ return 1;
+ }
+
+ /* Determine parents */
+ if (initial_commit) {
+ reflog_msg = "commit (initial)";
+ } else if (amend) {
+ struct commit_list *c;
+ struct commit *commit;
+
+ reflog_msg = "commit (amend)";
+ commit = lookup_commit(head_sha1);
+ if (!commit || parse_commit(commit))
+ die("could not parse HEAD commit");
+
+ for (c = commit->parents; c; c = c->next)
+ pptr = &commit_list_insert(c->item, pptr)->next;
+ } else if (in_merge) {
+ struct strbuf m = STRBUF_INIT;
+ FILE *fp;
+
+ reflog_msg = "commit (merge)";
+ pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+ fp = fopen(git_path("MERGE_HEAD"), "r");
+ if (fp == NULL)
+ die_errno("could not open '%s' for reading",
+ git_path("MERGE_HEAD"));
+ while (strbuf_getline(&m, fp, '\n') != EOF) {
+ unsigned char sha1[20];
+ if (get_sha1_hex(m.buf, sha1) < 0)
+ die("Corrupt MERGE_HEAD file (%s)", m.buf);
+ pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
+ }
+ fclose(fp);
+ strbuf_release(&m);
+ if (!stat(git_path("MERGE_MODE"), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
+ die_errno("could not read MERGE_MODE");
+ if (!strcmp(sb.buf, "no-ff"))
+ allow_fast_forward = 0;
+ }
+ if (allow_fast_forward)
+ parents = reduce_heads(parents);
+ } else {
+ reflog_msg = "commit";
+ pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+ }
+
+ /* Finally, get the commit message */
+ strbuf_reset(&sb);
+ if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
+ int saved_errno = errno;
+ rollback_index_files();
+ die("could not read commit message: %s", strerror(saved_errno));
+ }
+
+ /* Truncate the message just before the diff, if any. */
+ if (verbose) {
+ p = strstr(sb.buf, "\ndiff --git ");
+ if (p != NULL)
+ strbuf_setlen(&sb, p - sb.buf + 1);
+ }
+
+ if (cleanup_mode != CLEANUP_NONE)
+ stripspace(&sb, cleanup_mode == CLEANUP_ALL);
+ if (message_is_empty(&sb)) {
+ rollback_index_files();
+ fprintf(stderr, "Aborting commit due to empty commit message.\n");
+ exit(1);
+ }
+
+ if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
+ fmt_ident(author_name, author_email, author_date,
+ IDENT_ERROR_ON_NO_NAME))) {
+ rollback_index_files();
+ die("failed to write commit object");
+ }
+
+ ref_lock = lock_any_ref_for_update("HEAD",
+ initial_commit ? NULL : head_sha1,
+ 0);
+
+ nl = strchr(sb.buf, '\n');
+ if (nl)
+ strbuf_setlen(&sb, nl + 1 - sb.buf);
+ else
+ strbuf_addch(&sb, '\n');
+ strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
+ strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
+
+ if (!ref_lock) {
+ rollback_index_files();
+ die("cannot lock HEAD ref");
+ }
+ if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
+ rollback_index_files();
+ die("cannot update HEAD ref");
+ }
+
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+ unlink(git_path("MERGE_MODE"));
+ unlink(git_path("SQUASH_MSG"));
+
+ if (commit_index_files())
+ die ("Repository has been updated, but unable to write\n"
+ "new_index file. Check that disk is not full or quota is\n"
+ "not exceeded, and then \"git reset HEAD\" to recover.");
+
+ rerere(0);
+ run_hook(get_index_file(), "post-commit", NULL);
+ if (!quiet)
+ print_summary(prefix, commit_sha1);
+
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "color.h"
+#include "parse-options.h"
+
+static const char *const builtin_config_usage[] = {
+ "git config [options]",
+ NULL
+};
+
+static char *key;
+static regex_t *key_regexp;
+static regex_t *regexp;
+static int show_keys;
+static int use_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 int use_global_config, use_system_config;
+static const char *given_config_file;
+static int actions, types;
+static const char *get_color_slot, *get_colorbool_slot;
+static int end_null;
+
+#define ACTION_GET (1<<0)
+#define ACTION_GET_ALL (1<<1)
+#define ACTION_GET_REGEXP (1<<2)
+#define ACTION_REPLACE_ALL (1<<3)
+#define ACTION_ADD (1<<4)
+#define ACTION_UNSET (1<<5)
+#define ACTION_UNSET_ALL (1<<6)
+#define ACTION_RENAME_SECTION (1<<7)
+#define ACTION_REMOVE_SECTION (1<<8)
+#define ACTION_LIST (1<<9)
+#define ACTION_EDIT (1<<10)
+#define ACTION_SET (1<<11)
+#define ACTION_SET_ALL (1<<12)
+#define ACTION_GET_COLOR (1<<13)
+#define ACTION_GET_COLORBOOL (1<<14)
+
+#define TYPE_BOOL (1<<0)
+#define TYPE_INT (1<<1)
+#define TYPE_BOOL_OR_INT (1<<2)
+#define TYPE_PATH (1<<3)
+
+static struct option builtin_config_options[] = {
+ OPT_GROUP("Config file location"),
+ OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"),
+ OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"),
+ OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"),
+ OPT_GROUP("Action"),
+ OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET),
+ OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL),
+ OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP),
+ OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL),
+ OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD),
+ OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET),
+ OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL),
+ OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION),
+ OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION),
+ OPT_BIT('l', "list", &actions, "list all", ACTION_LIST),
+ OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT),
+ OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"),
+ OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"),
+ OPT_GROUP("Type"),
+ OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL),
+ OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT),
+ OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT),
+ OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
+ OPT_GROUP("Other"),
+ OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
+ OPT_END(),
+};
+
+static void check_argc(int argc, int min, int max) {
+ if (argc >= min && argc <= max)
+ return;
+ error("wrong number of arguments");
+ usage_with_options(builtin_config_usage, builtin_config_options);
+}
+
+static int show_all_config(const char *key_, const char *value_, void *cb)
+{
+ if (value_)
+ printf("%s%c%s%c", key_, delim, value_, term);
+ else
+ printf("%s%c", key_, term);
+ return 0;
+}
+
+static int show_config(const char *key_, const char *value_, void *cb)
+{
+ char value[256];
+ const char *vptr = value;
+ int must_free_vptr = 0;
+ int dup_error = 0;
+
+ if (!use_key_regexp && strcmp(key_, key))
+ return 0;
+ if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
+ return 0;
+ if (regexp != NULL &&
+ (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
+ return 0;
+
+ if (show_keys) {
+ if (value_)
+ printf("%s%c", key_, key_delim);
+ else
+ printf("%s", key_);
+ }
+ if (seen && !do_all)
+ dup_error = 1;
+ if (types == TYPE_INT)
+ sprintf(value, "%d", git_config_int(key_, value_?value_:""));
+ else if (types == TYPE_BOOL)
+ vptr = git_config_bool(key_, value_) ? "true" : "false";
+ else if (types == TYPE_BOOL_OR_INT) {
+ int is_bool, v;
+ v = git_config_bool_or_int(key_, value_, &is_bool);
+ if (is_bool)
+ vptr = v ? "true" : "false";
+ else
+ sprintf(value, "%d", v);
+ } else if (types == TYPE_PATH) {
+ git_config_pathname(&vptr, key_, value_);
+ must_free_vptr = 1;
+ }
+ else
+ vptr = value_?value_:"";
+ seen++;
+ if (dup_error) {
+ error("More than one value for the key %s: %s",
+ key_, vptr);
+ }
+ else
+ printf("%s%c", vptr, term);
+ if (must_free_vptr)
+ /* If vptr must be freed, it's a pointer to a
+ * dynamically allocated buffer, it's safe to cast to
+ * const.
+ */
+ free((char *)vptr);
+
+ return 0;
+}
+
+static int get_value(const char *key_, const char *regex_)
+{
+ int ret = -1;
+ char *tl;
+ char *global = NULL, *repo_config = NULL;
+ const char *system_wide = NULL, *local;
+
+ local = config_exclusive_filename;
+ if (!local) {
+ const char *home = getenv("HOME");
+ local = repo_config = git_pathdup("config");
+ if (git_config_global() && home)
+ global = xstrdup(mkpath("%s/.gitconfig", home));
+ if (git_config_system())
+ system_wide = git_etc_gitconfig();
+ }
+
+ key = xstrdup(key_);
+ for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl)
+ *tl = tolower(*tl);
+ for (tl=key; *tl && *tl != '.'; ++tl)
+ *tl = tolower(*tl);
+
+ if (use_key_regexp) {
+ key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
+ if (regcomp(key_regexp, key, REG_EXTENDED)) {
+ fprintf(stderr, "Invalid key pattern: %s\n", key_);
+ goto free_strings;
+ }
+ }
+
+ if (regex_) {
+ if (regex_[0] == '!') {
+ do_not_match = 1;
+ regex_++;
+ }
+
+ regexp = (regex_t*)xmalloc(sizeof(regex_t));
+ if (regcomp(regexp, regex_, REG_EXTENDED)) {
+ fprintf(stderr, "Invalid pattern: %s\n", regex_);
+ goto free_strings;
+ }
+ }
+
+ if (do_all && system_wide)
+ git_config_from_file(show_config, system_wide, NULL);
+ if (do_all && global)
+ git_config_from_file(show_config, global, NULL);
+ git_config_from_file(show_config, local, NULL);
+ if (!do_all && !seen && global)
+ git_config_from_file(show_config, global, NULL);
+ if (!do_all && !seen && system_wide)
+ git_config_from_file(show_config, system_wide, NULL);
+
+ free(key);
+ if (regexp) {
+ regfree(regexp);
+ free(regexp);
+ }
+
+ if (do_all)
+ ret = !seen;
+ else
+ ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1;
+
+free_strings:
+ free(repo_config);
+ free(global);
+ return ret;
+}
+
+static char *normalize_value(const char *key, const char *value)
+{
+ char *normalized;
+
+ if (!value)
+ return NULL;
+
+ if (types == 0 || types == TYPE_PATH)
+ /*
+ * We don't do normalization for TYPE_PATH here: If
+ * the path is like ~/foobar/, we prefer to store
+ * "~/foobar/" in the config file, and to expand the ~
+ * when retrieving the value.
+ */
+ normalized = xstrdup(value);
+ else {
+ normalized = xmalloc(64);
+ if (types == TYPE_INT) {
+ int v = git_config_int(key, value);
+ sprintf(normalized, "%d", v);
+ }
+ else if (types == TYPE_BOOL)
+ sprintf(normalized, "%s",
+ git_config_bool(key, value) ? "true" : "false");
+ else if (types == TYPE_BOOL_OR_INT) {
+ int is_bool, v;
+ v = git_config_bool_or_int(key, value, &is_bool);
+ if (!is_bool)
+ sprintf(normalized, "%d", v);
+ else
+ sprintf(normalized, "%s", v ? "true" : "false");
+ }
+ }
+
+ return normalized;
+}
+
+static int get_color_found;
+static const char *get_color_slot;
+static const char *get_colorbool_slot;
+static char parsed_color[COLOR_MAXLEN];
+
+static int git_get_color_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, get_color_slot)) {
+ if (!value)
+ config_error_nonbool(var);
+ color_parse(value, var, parsed_color);
+ get_color_found = 1;
+ }
+ return 0;
+}
+
+static void get_color(const char *def_color)
+{
+ get_color_found = 0;
+ parsed_color[0] = '\0';
+ git_config(git_get_color_config, NULL);
+
+ if (!get_color_found && def_color)
+ color_parse(def_color, "command line", parsed_color);
+
+ fputs(parsed_color, stdout);
+}
+
+static int stdout_is_tty;
+static int get_colorbool_found;
+static int get_diff_color_found;
+static int git_get_colorbool_config(const char *var, const char *value,
+ void *cb)
+{
+ if (!strcmp(var, get_colorbool_slot)) {
+ get_colorbool_found =
+ git_config_colorbool(var, value, stdout_is_tty);
+ }
+ if (!strcmp(var, "diff.color")) {
+ get_diff_color_found =
+ git_config_colorbool(var, value, stdout_is_tty);
+ }
+ if (!strcmp(var, "color.ui")) {
+ git_use_color_default = git_config_colorbool(var, value, stdout_is_tty);
+ return 0;
+ }
+ return 0;
+}
+
+static int get_colorbool(int print)
+{
+ get_colorbool_found = -1;
+ get_diff_color_found = -1;
+ git_config(git_get_colorbool_config, NULL);
+
+ if (get_colorbool_found < 0) {
+ if (!strcmp(get_colorbool_slot, "color.diff"))
+ get_colorbool_found = get_diff_color_found;
+ if (get_colorbool_found < 0)
+ get_colorbool_found = git_use_color_default;
+ }
+
+ if (print) {
+ printf("%s\n", get_colorbool_found ? "true" : "false");
+ return 0;
+ } else
+ return get_colorbool_found ? 0 : 1;
+}
+
+int cmd_config(int argc, const char **argv, const char *unused_prefix)
+{
+ int nongit;
+ char *value;
+ const char *prefix = setup_git_directory_gently(&nongit);
+
+ config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
+
+ argc = parse_options(argc, argv, prefix, builtin_config_options,
+ builtin_config_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (use_global_config + use_system_config + !!given_config_file > 1) {
+ error("only one config file at a time.");
+ usage_with_options(builtin_config_usage, builtin_config_options);
+ }
+
+ if (use_global_config) {
+ char *home = getenv("HOME");
+ if (home) {
+ char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+ config_exclusive_filename = user_config;
+ } else {
+ die("$HOME not set");
+ }
+ }
+ else if (use_system_config)
+ config_exclusive_filename = git_etc_gitconfig();
+ else if (given_config_file) {
+ if (!is_absolute_path(given_config_file) && prefix)
+ config_exclusive_filename = prefix_filename(prefix,
+ strlen(prefix),
+ given_config_file);
+ else
+ config_exclusive_filename = given_config_file;
+ }
+
+ if (end_null) {
+ term = '\0';
+ delim = '\n';
+ key_delim = '\n';
+ }
+
+ if (HAS_MULTI_BITS(types)) {
+ error("only one type at a time.");
+ usage_with_options(builtin_config_usage, builtin_config_options);
+ }
+
+ if (get_color_slot)
+ actions |= ACTION_GET_COLOR;
+ if (get_colorbool_slot)
+ actions |= ACTION_GET_COLORBOOL;
+
+ if ((get_color_slot || get_colorbool_slot) && types) {
+ error("--get-color and variable type are incoherent");
+ usage_with_options(builtin_config_usage, builtin_config_options);
+ }
+
+ if (HAS_MULTI_BITS(actions)) {
+ error("only one action at a time.");
+ usage_with_options(builtin_config_usage, builtin_config_options);
+ }
+ if (actions == 0)
+ switch (argc) {
+ case 1: actions = ACTION_GET; break;
+ case 2: actions = ACTION_SET; break;
+ case 3: actions = ACTION_SET_ALL; break;
+ default:
+ usage_with_options(builtin_config_usage, builtin_config_options);
+ }
+
+ if (actions == ACTION_LIST) {
+ check_argc(argc, 0, 0);
+ if (git_config(show_all_config, NULL) < 0) {
+ if (config_exclusive_filename)
+ die_errno("unable to read config file '%s'",
+ config_exclusive_filename);
+ else
+ die("error processing config file(s)");
+ }
+ }
+ else if (actions == ACTION_EDIT) {
+ check_argc(argc, 0, 0);
+ if (!config_exclusive_filename && nongit)
+ die("not in a git directory");
+ git_config(git_default_config, NULL);
+ launch_editor(config_exclusive_filename ?
+ config_exclusive_filename : git_path("config"),
+ NULL, NULL);
+ }
+ else if (actions == ACTION_SET) {
+ check_argc(argc, 2, 2);
+ value = normalize_value(argv[0], argv[1]);
+ return git_config_set(argv[0], value);
+ }
+ else if (actions == ACTION_SET_ALL) {
+ check_argc(argc, 2, 3);
+ value = normalize_value(argv[0], argv[1]);
+ return git_config_set_multivar(argv[0], value, argv[2], 0);
+ }
+ else if (actions == ACTION_ADD) {
+ check_argc(argc, 2, 2);
+ value = normalize_value(argv[0], argv[1]);
+ return git_config_set_multivar(argv[0], value, "^$", 0);
+ }
+ else if (actions == ACTION_REPLACE_ALL) {
+ check_argc(argc, 2, 3);
+ value = normalize_value(argv[0], argv[1]);
+ return git_config_set_multivar(argv[0], value, argv[2], 1);
+ }
+ else if (actions == ACTION_GET) {
+ check_argc(argc, 1, 2);
+ return get_value(argv[0], argv[1]);
+ }
+ else if (actions == ACTION_GET_ALL) {
+ do_all = 1;
+ check_argc(argc, 1, 2);
+ return get_value(argv[0], argv[1]);
+ }
+ else if (actions == ACTION_GET_REGEXP) {
+ show_keys = 1;
+ use_key_regexp = 1;
+ do_all = 1;
+ check_argc(argc, 1, 2);
+ return get_value(argv[0], argv[1]);
+ }
+ else if (actions == ACTION_UNSET) {
+ check_argc(argc, 1, 2);
+ if (argc == 2)
+ return git_config_set_multivar(argv[0], NULL, argv[1], 0);
+ else
+ return git_config_set(argv[0], NULL);
+ }
+ else if (actions == ACTION_UNSET_ALL) {
+ check_argc(argc, 1, 2);
+ return git_config_set_multivar(argv[0], NULL, argv[1], 1);
+ }
+ else if (actions == ACTION_RENAME_SECTION) {
+ int ret;
+ check_argc(argc, 2, 2);
+ ret = git_config_rename_section(argv[0], argv[1]);
+ if (ret < 0)
+ return ret;
+ if (ret == 0)
+ die("No such section!");
+ }
+ else if (actions == ACTION_REMOVE_SECTION) {
+ int ret;
+ check_argc(argc, 1, 1);
+ ret = git_config_rename_section(argv[0], NULL);
+ if (ret < 0)
+ return ret;
+ if (ret == 0)
+ die("No such section!");
+ }
+ else if (actions == ACTION_GET_COLOR) {
+ get_color(argv[0]);
+ }
+ else if (actions == ACTION_GET_COLORBOOL) {
+ if (argc == 1)
+ stdout_is_tty = git_config_bool("command line", argv[0]);
+ else if (argc == 0)
+ stdout_is_tty = isatty(1);
+ return get_colorbool(argc != 0);
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * Builtin "git count-objects".
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "dir.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static void count_objects(DIR *d, char *path, int len, int verbose,
+ unsigned long *loose,
+ off_t *loose_size,
+ unsigned long *packed_loose,
+ unsigned long *garbage)
+{
+ struct dirent *ent;
+ while ((ent = readdir(d)) != NULL) {
+ char hex[41];
+ unsigned char sha1[20];
+ const char *cp;
+ int bad = 0;
+
+ if (is_dot_or_dotdot(ent->d_name))
+ continue;
+ for (cp = ent->d_name; *cp; cp++) {
+ int ch = *cp;
+ if (('0' <= ch && ch <= '9') ||
+ ('a' <= ch && ch <= 'f'))
+ continue;
+ bad = 1;
+ break;
+ }
+ if (cp - ent->d_name != 38)
+ bad = 1;
+ else {
+ struct stat st;
+ memcpy(path + len + 3, ent->d_name, 38);
+ path[len + 2] = '/';
+ path[len + 41] = 0;
+ if (lstat(path, &st) || !S_ISREG(st.st_mode))
+ bad = 1;
+ else
+ (*loose_size) += xsize_t(on_disk_bytes(st));
+ }
+ if (bad) {
+ if (verbose) {
+ error("garbage found: %.*s/%s",
+ len + 2, path, ent->d_name);
+ (*garbage)++;
+ }
+ continue;
+ }
+ (*loose)++;
+ if (!verbose)
+ continue;
+ memcpy(hex, path+len, 2);
+ memcpy(hex+2, ent->d_name, 38);
+ hex[40] = 0;
+ if (get_sha1_hex(hex, sha1))
+ die("internal error");
+ if (has_sha1_pack(sha1))
+ (*packed_loose)++;
+ }
+}
+
+static char const * const count_objects_usage[] = {
+ "git count-objects [-v]",
+ NULL
+};
+
+int cmd_count_objects(int argc, const char **argv, const char *prefix)
+{
+ int i, verbose = 0;
+ const char *objdir = get_object_directory();
+ int len = strlen(objdir);
+ char *path = xmalloc(len + 50);
+ unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
+ off_t loose_size = 0;
+ struct option opts[] = {
+ OPT__VERBOSE(&verbose),
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0);
+ /* we do not take arguments other than flags for now */
+ if (argc)
+ usage_with_options(count_objects_usage, opts);
+ memcpy(path, objdir, len);
+ if (len && objdir[len-1] != '/')
+ path[len++] = '/';
+ for (i = 0; i < 256; i++) {
+ DIR *d;
+ sprintf(path + len, "%02x", i);
+ d = opendir(path);
+ if (!d)
+ continue;
+ count_objects(d, path, len, verbose,
+ &loose, &loose_size, &packed_loose, &garbage);
+ closedir(d);
+ }
+ if (verbose) {
+ struct packed_git *p;
+ unsigned long num_pack = 0;
+ off_t size_pack = 0;
+ if (!packed_git)
+ prepare_packed_git();
+ for (p = packed_git; p; p = p->next) {
+ if (!p->pack_local)
+ continue;
+ if (open_pack_index(p))
+ continue;
+ packed += p->num_objects;
+ size_pack += p->pack_size + p->index_size;
+ num_pack++;
+ }
+ printf("count: %lu\n", loose);
+ printf("size: %lu\n", (unsigned long) (loose_size / 1024));
+ printf("in-pack: %lu\n", packed);
+ printf("packs: %lu\n", num_pack);
+ printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024));
+ printf("prune-packable: %lu\n", packed_loose);
+ printf("garbage: %lu\n", garbage);
+ }
+ else
+ printf("%lu objects, %lu kilobytes\n",
+ loose, (unsigned long) (loose_size / 1024));
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "parse-options.h"
+#include "diff.h"
+
+#define SEEN (1u<<0)
+#define MAX_TAGS (FLAG_BITS - 1)
+
+static const char * const describe_usage[] = {
+ "git describe [options] <committish>*",
+ "git describe [options] --dirty",
+ NULL
+};
+
+static int debug; /* Display lots of verbose info */
+static int all; /* Any valid ref can be used */
+static int tags; /* Allow lightweight tags */
+static int longformat;
+static int abbrev = DEFAULT_ABBREV;
+static int max_candidates = 10;
+static int found_names;
+static const char *pattern;
+static int always;
+static const char *dirty;
+
+/* diff-index command arguments to check if working tree is dirty. */
+static const char *diff_index_args[] = {
+ "diff-index", "--quiet", "HEAD", "--", NULL
+};
+
+
+struct commit_name {
+ struct tag *tag;
+ int prio; /* annotated tag = 2, tag = 1, head = 0 */
+ unsigned char sha1[20];
+ char path[FLEX_ARRAY]; /* more */
+};
+static const char *prio_names[] = {
+ "head", "lightweight", "annotated",
+};
+
+static void add_to_known_names(const char *path,
+ struct commit *commit,
+ int prio,
+ const unsigned char *sha1)
+{
+ struct commit_name *e = commit->util;
+ if (!e || e->prio < prio) {
+ size_t len = strlen(path)+1;
+ free(e);
+ e = xmalloc(sizeof(struct commit_name) + len);
+ e->tag = NULL;
+ e->prio = prio;
+ hashcpy(e->sha1, sha1);
+ memcpy(e->path, path, len);
+ commit->util = e;
+ }
+ found_names = 1;
+}
+
+static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+ int might_be_tag = !prefixcmp(path, "refs/tags/");
+ struct commit *commit;
+ struct object *object;
+ unsigned char peeled[20];
+ int is_tag, prio;
+
+ if (!all && !might_be_tag)
+ return 0;
+
+ if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
+ commit = lookup_commit_reference_gently(peeled, 1);
+ if (!commit)
+ return 0;
+ is_tag = !!hashcmp(sha1, commit->object.sha1);
+ } else {
+ commit = lookup_commit_reference_gently(sha1, 1);
+ object = parse_object(sha1);
+ if (!commit || !object)
+ return 0;
+ is_tag = object->type == OBJ_TAG;
+ }
+
+ /* If --all, then any refs are used.
+ * If --tags, then any tags are used.
+ * Otherwise only annotated tags are used.
+ */
+ if (might_be_tag) {
+ if (is_tag)
+ prio = 2;
+ else
+ prio = 1;
+
+ if (pattern && fnmatch(pattern, path + 10, 0))
+ prio = 0;
+ }
+ else
+ prio = 0;
+
+ if (!all) {
+ if (!prio)
+ return 0;
+ }
+ add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1);
+ return 0;
+}
+
+struct possible_tag {
+ struct commit_name *name;
+ int depth;
+ int found_order;
+ unsigned flag_within;
+};
+
+static int compare_pt(const void *a_, const void *b_)
+{
+ struct possible_tag *a = (struct possible_tag *)a_;
+ struct possible_tag *b = (struct possible_tag *)b_;
+ if (a->depth != b->depth)
+ return a->depth - b->depth;
+ if (a->found_order != b->found_order)
+ return a->found_order - b->found_order;
+ return 0;
+}
+
+static unsigned long finish_depth_computation(
+ struct commit_list **list,
+ struct possible_tag *best)
+{
+ unsigned long seen_commits = 0;
+ while (*list) {
+ struct commit *c = pop_commit(list);
+ struct commit_list *parents = c->parents;
+ seen_commits++;
+ if (c->object.flags & best->flag_within) {
+ struct commit_list *a = *list;
+ while (a) {
+ struct commit *i = a->item;
+ if (!(i->object.flags & best->flag_within))
+ break;
+ a = a->next;
+ }
+ if (!a)
+ break;
+ } else
+ best->depth++;
+ while (parents) {
+ struct commit *p = parents->item;
+ parse_commit(p);
+ if (!(p->object.flags & SEEN))
+ insert_by_date(p, list);
+ p->object.flags |= c->object.flags;
+ parents = parents->next;
+ }
+ }
+ return seen_commits;
+}
+
+static void display_name(struct commit_name *n)
+{
+ if (n->prio == 2 && !n->tag) {
+ n->tag = lookup_tag(n->sha1);
+ if (!n->tag || parse_tag(n->tag) || !n->tag->tag)
+ die("annotated tag %s not available", n->path);
+ if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
+ warning("tag '%s' is really '%s' here", n->tag->tag, n->path);
+ }
+
+ if (n->tag)
+ printf("%s", n->tag->tag);
+ else
+ printf("%s", n->path);
+}
+
+static void show_suffix(int depth, const unsigned char *sha1)
+{
+ printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev));
+}
+
+static void describe(const char *arg, int last_one)
+{
+ unsigned char sha1[20];
+ struct commit *cmit, *gave_up_on = NULL;
+ struct commit_list *list;
+ struct commit_name *n;
+ struct possible_tag all_matches[MAX_TAGS];
+ unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
+ unsigned long seen_commits = 0;
+ unsigned int unannotated_cnt = 0;
+
+ if (get_sha1(arg, sha1))
+ die("Not a valid object name %s", arg);
+ cmit = lookup_commit_reference(sha1);
+ if (!cmit)
+ die("%s is not a valid '%s' object", arg, commit_type);
+
+ n = cmit->util;
+ if (n && (tags || all || n->prio == 2)) {
+ /*
+ * Exact match to an existing ref.
+ */
+ display_name(n);
+ if (longformat)
+ show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1);
+ if (dirty)
+ printf("%s", dirty);
+ printf("\n");
+ return;
+ }
+
+ if (!max_candidates)
+ die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1));
+ if (debug)
+ fprintf(stderr, "searching to describe %s\n", arg);
+
+ list = NULL;
+ cmit->object.flags = SEEN;
+ commit_list_insert(cmit, &list);
+ while (list) {
+ struct commit *c = pop_commit(&list);
+ struct commit_list *parents = c->parents;
+ seen_commits++;
+ n = c->util;
+ if (n) {
+ if (!tags && !all && n->prio < 2) {
+ unannotated_cnt++;
+ } else if (match_cnt < max_candidates) {
+ struct possible_tag *t = &all_matches[match_cnt++];
+ t->name = n;
+ t->depth = seen_commits - 1;
+ t->flag_within = 1u << match_cnt;
+ t->found_order = match_cnt;
+ c->object.flags |= t->flag_within;
+ if (n->prio == 2)
+ annotated_cnt++;
+ }
+ else {
+ gave_up_on = c;
+ break;
+ }
+ }
+ for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+ struct possible_tag *t = &all_matches[cur_match];
+ if (!(c->object.flags & t->flag_within))
+ t->depth++;
+ }
+ if (annotated_cnt && !list) {
+ if (debug)
+ fprintf(stderr, "finished search at %s\n",
+ sha1_to_hex(c->object.sha1));
+ break;
+ }
+ while (parents) {
+ struct commit *p = parents->item;
+ parse_commit(p);
+ if (!(p->object.flags & SEEN))
+ insert_by_date(p, &list);
+ p->object.flags |= c->object.flags;
+ parents = parents->next;
+ }
+ }
+
+ if (!match_cnt) {
+ const unsigned char *sha1 = cmit->object.sha1;
+ if (always) {
+ printf("%s", find_unique_abbrev(sha1, abbrev));
+ if (dirty)
+ printf("%s", dirty);
+ printf("\n");
+ return;
+ }
+ if (unannotated_cnt)
+ die("No annotated tags can describe '%s'.\n"
+ "However, there were unannotated tags: try --tags.",
+ sha1_to_hex(sha1));
+ else
+ die("No tags can describe '%s'.\n"
+ "Try --always, or create some tags.",
+ sha1_to_hex(sha1));
+ }
+
+ qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
+
+ if (gave_up_on) {
+ insert_by_date(gave_up_on, &list);
+ seen_commits--;
+ }
+ seen_commits += finish_depth_computation(&list, &all_matches[0]);
+ free_commit_list(list);
+
+ if (debug) {
+ for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+ struct possible_tag *t = &all_matches[cur_match];
+ fprintf(stderr, " %-11s %8d %s\n",
+ prio_names[t->name->prio],
+ t->depth, t->name->path);
+ }
+ fprintf(stderr, "traversed %lu commits\n", seen_commits);
+ if (gave_up_on) {
+ fprintf(stderr,
+ "more than %i tags found; listed %i most recent\n"
+ "gave up search at %s\n",
+ max_candidates, max_candidates,
+ sha1_to_hex(gave_up_on->object.sha1));
+ }
+ }
+
+ display_name(all_matches[0].name);
+ if (abbrev)
+ show_suffix(all_matches[0].depth, cmit->object.sha1);
+ if (dirty)
+ printf("%s", dirty);
+ printf("\n");
+
+ if (!last_one)
+ clear_commit_marks(cmit, -1);
+}
+
+int cmd_describe(int argc, const char **argv, const char *prefix)
+{
+ int contains = 0;
+ struct option options[] = {
+ OPT_BOOLEAN(0, "contains", &contains, "find the tag that comes after the commit"),
+ OPT_BOOLEAN(0, "debug", &debug, "debug search strategy on stderr"),
+ OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"),
+ OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"),
+ OPT_BOOLEAN(0, "long", &longformat, "always use long format"),
+ OPT__ABBREV(&abbrev),
+ OPT_SET_INT(0, "exact-match", &max_candidates,
+ "only output exact matches", 0),
+ OPT_INTEGER(0, "candidates", &max_candidates,
+ "consider <n> most recent tags (default: 10)"),
+ OPT_STRING(0, "match", &pattern, "pattern",
+ "only consider tags matching <pattern>"),
+ OPT_BOOLEAN(0, "always", &always,
+ "show abbreviated commit object as fallback"),
+ {OPTION_STRING, 0, "dirty", &dirty, "mark",
+ "append <mark> on dirty working tree (default: \"-dirty\")",
+ PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"},
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, prefix, options, describe_usage, 0);
+ if (max_candidates < 0)
+ max_candidates = 0;
+ else if (max_candidates > MAX_TAGS)
+ max_candidates = MAX_TAGS;
+
+ save_commit_buffer = 0;
+
+ if (longformat && abbrev == 0)
+ die("--long is incompatible with --abbrev=0");
+
+ if (contains) {
+ const char **args = xmalloc((7 + argc) * sizeof(char *));
+ int i = 0;
+ args[i++] = "name-rev";
+ args[i++] = "--name-only";
+ args[i++] = "--no-undefined";
+ if (always)
+ args[i++] = "--always";
+ if (!all) {
+ args[i++] = "--tags";
+ if (pattern) {
+ char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1);
+ sprintf(s, "--refs=refs/tags/%s", pattern);
+ args[i++] = s;
+ }
+ }
+ memcpy(args + i, argv, argc * sizeof(char *));
+ args[i + argc] = NULL;
+ return cmd_name_rev(i + argc, args, prefix);
+ }
+
+ for_each_ref(get_name, NULL);
+ if (!found_names && !always)
+ die("No names found, cannot describe anything.");
+
+ if (argc == 0) {
+ if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix))
+ dirty = NULL;
+ describe("HEAD", 1);
+ } else if (dirty) {
+ die("--dirty is incompatible with committishes");
+ } else {
+ while (argc-- > 0) {
+ describe(*argv++, argc == 0);
+ }
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "revision.h"
+#include "builtin.h"
+
+static const char diff_files_usage[] =
+"git diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+int cmd_diff_files(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info rev;
+ int result;
+ unsigned options = 0;
+
+ init_revisions(&rev, prefix);
+ git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
+ rev.abbrev = 0;
+
+ argc = setup_revisions(argc, argv, &rev, NULL);
+ while (1 < argc && argv[1][0] == '-') {
+ if (!strcmp(argv[1], "--base"))
+ rev.max_count = 1;
+ else if (!strcmp(argv[1], "--ours"))
+ rev.max_count = 2;
+ else if (!strcmp(argv[1], "--theirs"))
+ rev.max_count = 3;
+ else if (!strcmp(argv[1], "-q"))
+ options |= DIFF_SILENT_ON_REMOVED;
+ else
+ usage(diff_files_usage);
+ argv++; argc--;
+ }
+ if (!rev.diffopt.output_format)
+ rev.diffopt.output_format = DIFF_FORMAT_RAW;
+
+ /*
+ * Make sure there are NO revision (i.e. pending object) parameter,
+ * rev.max_count is reasonable (0 <= n <= 3), and
+ * there is no other revision filtering parameters.
+ */
+ if (rev.pending.nr ||
+ rev.min_age != -1 || rev.max_age != -1 ||
+ 3 < rev.max_count)
+ usage(diff_files_usage);
+
+ /*
+ * "diff-files --base -p" should not combine merges because it
+ * was not asked to. "diff-files -c -p" should not densify
+ * (the user should ask with "diff-files --cc" explicitly).
+ */
+ if (rev.max_count == -1 && !rev.combine_merges &&
+ (rev.diffopt.output_format & DIFF_FORMAT_PATCH))
+ rev.combine_merges = rev.dense_combined_merges = 1;
+
+ if (read_cache_preload(rev.diffopt.paths) < 0) {
+ perror("read_cache_preload");
+ return -1;
+ }
+ result = run_diff_files(&rev, options);
+ return diff_result_code(&rev.diffopt, result);
+}
--- /dev/null
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "revision.h"
+#include "builtin.h"
+
+static const char diff_cache_usage[] =
+"git diff-index [-m] [--cached] "
+"[<common diff options>] <tree-ish> [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+int cmd_diff_index(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info rev;
+ int cached = 0;
+ int i;
+ int result;
+
+ init_revisions(&rev, prefix);
+ git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
+ rev.abbrev = 0;
+
+ 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
+ usage(diff_cache_usage);
+ }
+ if (!rev.diffopt.output_format)
+ rev.diffopt.output_format = DIFF_FORMAT_RAW;
+
+ /*
+ * Make sure there is one revision (i.e. pending object),
+ * and there is no revision filtering parameters.
+ */
+ if (rev.pending.nr != 1 ||
+ rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
+ usage(diff_cache_usage);
+ if (!cached)
+ setup_work_tree();
+ if (read_cache() < 0) {
+ perror("read_cache");
+ return -1;
+ }
+ result = run_diff_index(&rev, cached);
+ return diff_result_code(&rev.diffopt, result);
+}
--- /dev/null
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "log-tree.h"
+#include "builtin.h"
+
+static struct rev_info log_tree_opt;
+
+static int diff_tree_commit_sha1(const unsigned char *sha1)
+{
+ struct commit *commit = lookup_commit_reference(sha1);
+ if (!commit)
+ return -1;
+ return log_tree_commit(&log_tree_opt, commit);
+}
+
+/* Diff one or more commits. */
+static int stdin_diff_commit(struct commit *commit, char *line, int len)
+{
+ unsigned char sha1[20];
+ if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) {
+ /* Graft the fake parents locally to the commit */
+ int pos = 41;
+ struct commit_list **pptr, *parents;
+
+ /* Free the real parent list */
+ for (parents = commit->parents; parents; ) {
+ struct commit_list *tmp = parents->next;
+ free(parents);
+ parents = tmp;
+ }
+ commit->parents = NULL;
+ pptr = &(commit->parents);
+ while (line[pos] && !get_sha1_hex(line + pos, sha1)) {
+ struct commit *parent = lookup_commit(sha1);
+ if (parent) {
+ pptr = &commit_list_insert(parent, pptr)->next;
+ }
+ pos += 41;
+ }
+ }
+ return log_tree_commit(&log_tree_opt, commit);
+}
+
+/* Diff two trees. */
+static int stdin_diff_trees(struct tree *tree1, char *line, int len)
+{
+ unsigned char sha1[20];
+ struct tree *tree2;
+ if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1))
+ return error("Need exactly two trees, separated by a space");
+ tree2 = lookup_tree(sha1);
+ if (!tree2 || parse_tree(tree2))
+ return -1;
+ printf("%s %s\n", sha1_to_hex(tree1->object.sha1),
+ sha1_to_hex(tree2->object.sha1));
+ diff_tree_sha1(tree1->object.sha1, tree2->object.sha1,
+ "", &log_tree_opt.diffopt);
+ log_tree_diff_flush(&log_tree_opt);
+ return 0;
+}
+
+static int diff_tree_stdin(char *line)
+{
+ int len = strlen(line);
+ unsigned char sha1[20];
+ struct object *obj;
+
+ if (!len || line[len-1] != '\n')
+ return -1;
+ line[len-1] = 0;
+ if (get_sha1_hex(line, sha1))
+ return -1;
+ obj = lookup_unknown_object(sha1);
+ if (!obj || !obj->parsed)
+ obj = parse_object(sha1);
+ if (!obj)
+ return -1;
+ if (obj->type == OBJ_COMMIT)
+ return stdin_diff_commit((struct commit *)obj, line, len);
+ if (obj->type == OBJ_TREE)
+ return stdin_diff_trees((struct tree *)obj, line, len);
+ error("Object %s is a %s, not a commit or tree",
+ sha1_to_hex(sha1), typename(obj->type));
+ return -1;
+}
+
+static const char diff_tree_usage[] =
+"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
+" -r diff recursively\n"
+" --root include the initial commit as diff against /dev/null\n"
+COMMON_DIFF_OPTIONS_HELP;
+
+int cmd_diff_tree(int argc, const char **argv, const char *prefix)
+{
+ int nr_sha1;
+ char line[1000];
+ struct object *tree1, *tree2;
+ static struct rev_info *opt = &log_tree_opt;
+ int read_stdin = 0;
+
+ init_revisions(opt, prefix);
+ git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
+ opt->abbrev = 0;
+ opt->diff = 1;
+ opt->disable_stdin = 1;
+ argc = setup_revisions(argc, argv, opt, NULL);
+
+ while (--argc > 0) {
+ const char *arg = *++argv;
+
+ if (!strcmp(arg, "--stdin")) {
+ read_stdin = 1;
+ continue;
+ }
+ usage(diff_tree_usage);
+ }
+
+ if (!opt->diffopt.output_format)
+ opt->diffopt.output_format = DIFF_FORMAT_RAW;
+
+ /*
+ * NOTE! We expect "a ^b" to be equal to "a..b", so we
+ * reverse the order of the objects if the second one
+ * is marked UNINTERESTING.
+ */
+ nr_sha1 = opt->pending.nr;
+ switch (nr_sha1) {
+ case 0:
+ if (!read_stdin)
+ usage(diff_tree_usage);
+ break;
+ case 1:
+ tree1 = opt->pending.objects[0].item;
+ diff_tree_commit_sha1(tree1->sha1);
+ break;
+ case 2:
+ tree1 = opt->pending.objects[0].item;
+ tree2 = opt->pending.objects[1].item;
+ if (tree2->flags & UNINTERESTING) {
+ struct object *tmp = tree2;
+ tree2 = tree1;
+ tree1 = tmp;
+ }
+ diff_tree_sha1(tree1->sha1,
+ tree2->sha1,
+ "", &opt->diffopt);
+ log_tree_diff_flush(opt);
+ break;
+ }
+
+ if (read_stdin) {
+ if (opt->diffopt.detect_rename)
+ opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
+ DIFF_SETUP_USE_CACHE);
+ while (fgets(line, sizeof(line), stdin)) {
+ unsigned char sha1[20];
+
+ if (get_sha1_hex(line, sha1)) {
+ fputs(line, stdout);
+ fflush(stdout);
+ }
+ else
+ diff_tree_stdin(line);
+ }
+ }
+
+ return diff_result_code(&opt->diffopt, 0);
+}
--- /dev/null
+/*
+ * Builtin "git diff"
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "color.h"
+#include "commit.h"
+#include "blob.h"
+#include "tag.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "builtin.h"
+
+struct blobinfo {
+ unsigned char sha1[20];
+ const char *name;
+ unsigned mode;
+};
+
+static const char builtin_diff_usage[] =
+"git diff <options> <rev>{0,2} -- <path>*";
+
+static void stuff_change(struct diff_options *opt,
+ unsigned old_mode, unsigned new_mode,
+ const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *old_name,
+ const char *new_name)
+{
+ struct diff_filespec *one, *two;
+
+ if (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) &&
+ !hashcmp(old_sha1, new_sha1) && (old_mode == new_mode))
+ return;
+
+ if (DIFF_OPT_TST(opt, REVERSE_DIFF)) {
+ unsigned tmp;
+ const unsigned char *tmp_u;
+ const char *tmp_c;
+ tmp = old_mode; old_mode = new_mode; new_mode = tmp;
+ tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;
+ tmp_c = old_name; old_name = new_name; new_name = tmp_c;
+ }
+
+ if (opt->prefix &&
+ (strncmp(old_name, opt->prefix, opt->prefix_length) ||
+ strncmp(new_name, opt->prefix, opt->prefix_length)))
+ return;
+
+ one = alloc_filespec(old_name);
+ two = alloc_filespec(new_name);
+ fill_filespec(one, old_sha1, old_mode);
+ fill_filespec(two, new_sha1, new_mode);
+
+ diff_queue(&diff_queued_diff, one, two);
+}
+
+static int builtin_diff_b_f(struct rev_info *revs,
+ int argc, const char **argv,
+ struct blobinfo *blob,
+ const char *path)
+{
+ /* Blob vs file in the working tree*/
+ struct stat st;
+
+ if (argc > 1)
+ usage(builtin_diff_usage);
+
+ if (lstat(path, &st))
+ die_errno("failed to stat '%s'", path);
+ if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
+ die("'%s': not a regular file or symlink", path);
+
+ diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
+
+ if (blob[0].mode == S_IFINVALID)
+ blob[0].mode = canon_mode(st.st_mode);
+
+ stuff_change(&revs->diffopt,
+ blob[0].mode, canon_mode(st.st_mode),
+ blob[0].sha1, null_sha1,
+ path, path);
+ diffcore_std(&revs->diffopt);
+ diff_flush(&revs->diffopt);
+ return 0;
+}
+
+static int builtin_diff_blobs(struct rev_info *revs,
+ int argc, const char **argv,
+ struct blobinfo *blob)
+{
+ unsigned mode = canon_mode(S_IFREG | 0644);
+
+ if (argc > 1)
+ usage(builtin_diff_usage);
+
+ if (blob[0].mode == S_IFINVALID)
+ blob[0].mode = mode;
+
+ if (blob[1].mode == S_IFINVALID)
+ blob[1].mode = mode;
+
+ stuff_change(&revs->diffopt,
+ blob[0].mode, blob[1].mode,
+ blob[0].sha1, blob[1].sha1,
+ blob[0].name, blob[1].name);
+ diffcore_std(&revs->diffopt);
+ diff_flush(&revs->diffopt);
+ return 0;
+}
+
+static int builtin_diff_index(struct rev_info *revs,
+ int argc, const char **argv)
+{
+ int cached = 0;
+ while (1 < argc) {
+ const char *arg = argv[1];
+ if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged"))
+ cached = 1;
+ else
+ usage(builtin_diff_usage);
+ argv++; argc--;
+ }
+ if (!cached)
+ setup_work_tree();
+ /*
+ * Make sure there is one revision (i.e. pending object),
+ * and there is no revision filtering parameters.
+ */
+ if (revs->pending.nr != 1 ||
+ revs->max_count != -1 || revs->min_age != -1 ||
+ revs->max_age != -1)
+ usage(builtin_diff_usage);
+ if (read_cache_preload(revs->diffopt.paths) < 0) {
+ perror("read_cache_preload");
+ return -1;
+ }
+ return run_diff_index(revs, cached);
+}
+
+static int builtin_diff_tree(struct rev_info *revs,
+ int argc, const char **argv,
+ struct object_array_entry *ent)
+{
+ const unsigned char *(sha1[2]);
+ int swap = 0;
+
+ if (argc > 1)
+ usage(builtin_diff_usage);
+
+ /* We saw two trees, ent[0] and ent[1].
+ * if ent[1] is uninteresting, they are swapped
+ */
+ if (ent[1].item->flags & UNINTERESTING)
+ swap = 1;
+ sha1[swap] = ent[0].item->sha1;
+ sha1[1-swap] = ent[1].item->sha1;
+ diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
+ log_tree_diff_flush(revs);
+ return 0;
+}
+
+static int builtin_diff_combined(struct rev_info *revs,
+ int argc, const char **argv,
+ struct object_array_entry *ent,
+ int ents)
+{
+ const unsigned char (*parent)[20];
+ int i;
+
+ if (argc > 1)
+ usage(builtin_diff_usage);
+
+ if (!revs->dense_combined_merges && !revs->combine_merges)
+ revs->dense_combined_merges = revs->combine_merges = 1;
+ parent = xmalloc(ents * sizeof(*parent));
+ for (i = 0; i < ents; i++)
+ hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
+ diff_tree_combined(parent[0], parent + 1, ents - 1,
+ revs->dense_combined_merges, revs);
+ return 0;
+}
+
+static void refresh_index_quietly(void)
+{
+ struct lock_file *lock_file;
+ int fd;
+
+ lock_file = xcalloc(1, sizeof(struct lock_file));
+ fd = hold_locked_index(lock_file, 0);
+ if (fd < 0)
+ return;
+ discard_cache();
+ read_cache();
+ refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
+
+ if (active_cache_changed &&
+ !write_cache(fd, active_cache, active_nr))
+ commit_locked_index(lock_file);
+
+ rollback_lock_file(lock_file);
+}
+
+static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv)
+{
+ int result;
+ unsigned int options = 0;
+
+ while (1 < argc && argv[1][0] == '-') {
+ if (!strcmp(argv[1], "--base"))
+ revs->max_count = 1;
+ else if (!strcmp(argv[1], "--ours"))
+ revs->max_count = 2;
+ else if (!strcmp(argv[1], "--theirs"))
+ revs->max_count = 3;
+ else if (!strcmp(argv[1], "-q"))
+ options |= DIFF_SILENT_ON_REMOVED;
+ else if (!strcmp(argv[1], "-h"))
+ usage(builtin_diff_usage);
+ else
+ return error("invalid option: %s", argv[1]);
+ argv++; argc--;
+ }
+
+ /*
+ * "diff --base" should not combine merges because it was not
+ * asked to. "diff -c" should not densify (if the user wants
+ * dense one, --cc can be explicitly asked for, or just rely
+ * on the default).
+ */
+ if (revs->max_count == -1 && !revs->combine_merges &&
+ (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
+ revs->combine_merges = revs->dense_combined_merges = 1;
+
+ setup_work_tree();
+ if (read_cache_preload(revs->diffopt.paths) < 0) {
+ perror("read_cache_preload");
+ return -1;
+ }
+ result = run_diff_files(revs, options);
+ return diff_result_code(&revs->diffopt, result);
+}
+
+int cmd_diff(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct rev_info rev;
+ struct object_array_entry ent[100];
+ int ents = 0, blobs = 0, paths = 0;
+ const char *path = NULL;
+ struct blobinfo blob[2];
+ int nongit;
+ int result = 0;
+
+ /*
+ * We could get N tree-ish in the rev.pending_objects list.
+ * Also there could be M blobs there, and P pathspecs.
+ *
+ * N=0, M=0:
+ * cache vs files (diff-files)
+ * N=0, M=2:
+ * compare two random blobs. P must be zero.
+ * N=0, M=1, P=1:
+ * compare a blob with a working tree file.
+ *
+ * N=1, M=0:
+ * tree vs cache (diff-index --cached)
+ *
+ * N=2, M=0:
+ * tree vs tree (diff-tree)
+ *
+ * N=0, M=0, P=2:
+ * compare two filesystem entities (aka --no-index).
+ *
+ * Other cases are errors.
+ */
+
+ prefix = setup_git_directory_gently(&nongit);
+ git_config(git_diff_ui_config, NULL);
+
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ init_revisions(&rev, prefix);
+
+ /* If this is a no-index diff, just run it and exit there. */
+ diff_no_index(&rev, argc, argv, nongit, prefix);
+
+ /* Otherwise, we are doing the usual "git" diff */
+ rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
+
+ /* Default to let external and textconv be used */
+ DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
+ DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
+
+ if (nongit)
+ die("Not a git repository");
+ argc = setup_revisions(argc, argv, &rev, NULL);
+ if (!rev.diffopt.output_format) {
+ rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+ if (diff_setup_done(&rev.diffopt) < 0)
+ die("diff_setup_done failed");
+ }
+
+ DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
+
+ /*
+ * If the user asked for our exit code then don't start a
+ * pager or we would end up reporting its exit code instead.
+ */
+ if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS) &&
+ check_pager_config("diff") != 0)
+ setup_pager();
+
+ /*
+ * Do we have --cached and not have a pending object, then
+ * default to HEAD by hand. Eek.
+ */
+ if (!rev.pending.nr) {
+ int i;
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "--"))
+ break;
+ else if (!strcmp(arg, "--cached") ||
+ !strcmp(arg, "--staged")) {
+ add_head_to_pending(&rev);
+ if (!rev.pending.nr)
+ die("No HEAD commit to compare with (yet)");
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < rev.pending.nr; i++) {
+ struct object_array_entry *list = rev.pending.objects+i;
+ struct object *obj = list->item;
+ const char *name = list->name;
+ int flags = (obj->flags & UNINTERESTING);
+ if (!obj->parsed)
+ obj = parse_object(obj->sha1);
+ obj = deref_tag(obj, NULL, 0);
+ if (!obj)
+ die("invalid object '%s' given.", name);
+ if (obj->type == OBJ_COMMIT)
+ obj = &((struct commit *)obj)->tree->object;
+ if (obj->type == OBJ_TREE) {
+ if (ARRAY_SIZE(ent) <= ents)
+ die("more than %d trees given: '%s'",
+ (int) ARRAY_SIZE(ent), name);
+ obj->flags |= flags;
+ ent[ents].item = obj;
+ ent[ents].name = name;
+ ents++;
+ continue;
+ }
+ if (obj->type == OBJ_BLOB) {
+ if (2 <= blobs)
+ die("more than two blobs given: '%s'", name);
+ hashcpy(blob[blobs].sha1, obj->sha1);
+ blob[blobs].name = name;
+ blob[blobs].mode = list->mode;
+ blobs++;
+ continue;
+
+ }
+ die("unhandled object '%s' given.", name);
+ }
+ if (rev.prune_data) {
+ const char **pathspec = rev.prune_data;
+ while (*pathspec) {
+ if (!path)
+ path = *pathspec;
+ paths++;
+ pathspec++;
+ }
+ }
+
+ /*
+ * Now, do the arguments look reasonable?
+ */
+ if (!ents) {
+ switch (blobs) {
+ case 0:
+ result = builtin_diff_files(&rev, argc, argv);
+ break;
+ case 1:
+ if (paths != 1)
+ usage(builtin_diff_usage);
+ result = builtin_diff_b_f(&rev, argc, argv, blob, path);
+ break;
+ case 2:
+ if (paths)
+ usage(builtin_diff_usage);
+ result = builtin_diff_blobs(&rev, argc, argv, blob);
+ break;
+ default:
+ usage(builtin_diff_usage);
+ }
+ }
+ else if (blobs)
+ usage(builtin_diff_usage);
+ else if (ents == 1)
+ result = builtin_diff_index(&rev, argc, argv);
+ else if (ents == 2)
+ result = builtin_diff_tree(&rev, argc, argv, ent);
+ else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) {
+ /* diff A...B where there is one sane merge base between
+ * A and B. We have ent[0] == merge-base, ent[1] == A,
+ * and ent[2] == B. Show diff between the base and B.
+ */
+ ent[1] = ent[2];
+ result = builtin_diff_tree(&rev, argc, argv, ent);
+ }
+ else
+ result = builtin_diff_combined(&rev, argc, argv,
+ ent, ents);
+ result = diff_result_code(&rev.diffopt, result);
+ if (1 < rev.diffopt.skip_stat_unmatch)
+ refresh_index_quietly();
+ return result;
+}
--- /dev/null
+/*
+ * "git fast-export" builtin command
+ *
+ * Copyright (C) 2007 Johannes E. Schindelin
+ */
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "object.h"
+#include "tag.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "log-tree.h"
+#include "revision.h"
+#include "decorate.h"
+#include "string-list.h"
+#include "utf8.h"
+#include "parse-options.h"
+
+static const char *fast_export_usage[] = {
+ "git fast-export [rev-list-opts]",
+ NULL
+};
+
+static int progress;
+static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
+static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
+static int fake_missing_tagger;
+static int no_data;
+
+static int parse_opt_signed_tag_mode(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset || !strcmp(arg, "abort"))
+ signed_tag_mode = ABORT;
+ else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore"))
+ signed_tag_mode = VERBATIM;
+ else if (!strcmp(arg, "warn"))
+ signed_tag_mode = WARN;
+ else if (!strcmp(arg, "strip"))
+ signed_tag_mode = STRIP;
+ else
+ return error("Unknown signed-tag mode: %s", arg);
+ return 0;
+}
+
+static int parse_opt_tag_of_filtered_mode(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset || !strcmp(arg, "abort"))
+ tag_of_filtered_mode = ABORT;
+ else if (!strcmp(arg, "drop"))
+ tag_of_filtered_mode = DROP;
+ else if (!strcmp(arg, "rewrite"))
+ tag_of_filtered_mode = REWRITE;
+ else
+ return error("Unknown tag-of-filtered mode: %s", arg);
+ return 0;
+}
+
+static struct decoration idnums;
+static uint32_t last_idnum;
+
+static int has_unshown_parent(struct commit *commit)
+{
+ struct commit_list *parent;
+
+ for (parent = commit->parents; parent; parent = parent->next)
+ if (!(parent->item->object.flags & SHOWN) &&
+ !(parent->item->object.flags & UNINTERESTING))
+ return 1;
+ return 0;
+}
+
+/* Since intptr_t is C99, we do not use it here */
+static inline uint32_t *mark_to_ptr(uint32_t mark)
+{
+ return ((uint32_t *)NULL) + mark;
+}
+
+static inline uint32_t ptr_to_mark(void * mark)
+{
+ return (uint32_t *)mark - (uint32_t *)NULL;
+}
+
+static inline void mark_object(struct object *object, uint32_t mark)
+{
+ add_decoration(&idnums, object, mark_to_ptr(mark));
+}
+
+static inline void mark_next_object(struct object *object)
+{
+ mark_object(object, ++last_idnum);
+}
+
+static int get_object_mark(struct object *object)
+{
+ void *decoration = lookup_decoration(&idnums, object);
+ if (!decoration)
+ return 0;
+ return ptr_to_mark(decoration);
+}
+
+static void show_progress(void)
+{
+ static int counter = 0;
+ if (!progress)
+ return;
+ if ((++counter % progress) == 0)
+ printf("progress %d objects\n", counter);
+}
+
+static void handle_object(const unsigned char *sha1)
+{
+ unsigned long size;
+ enum object_type type;
+ char *buf;
+ struct object *object;
+
+ if (no_data)
+ return;
+
+ if (is_null_sha1(sha1))
+ return;
+
+ object = parse_object(sha1);
+ if (!object)
+ die ("Could not read blob %s", sha1_to_hex(sha1));
+
+ if (object->flags & SHOWN)
+ return;
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ die ("Could not read blob %s", sha1_to_hex(sha1));
+
+ mark_next_object(object);
+
+ printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size);
+ if (size && fwrite(buf, size, 1, stdout) != 1)
+ die_errno ("Could not write blob '%s'", sha1_to_hex(sha1));
+ printf("\n");
+
+ show_progress();
+
+ object->flags |= SHOWN;
+ free(buf);
+}
+
+static void show_filemodify(struct diff_queue_struct *q,
+ struct diff_options *options, void *data)
+{
+ int i;
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filespec *ospec = q->queue[i]->one;
+ struct diff_filespec *spec = q->queue[i]->two;
+
+ switch (q->queue[i]->status) {
+ case DIFF_STATUS_DELETED:
+ printf("D %s\n", spec->path);
+ break;
+
+ case DIFF_STATUS_COPIED:
+ case DIFF_STATUS_RENAMED:
+ printf("%c \"%s\" \"%s\"\n", q->queue[i]->status,
+ ospec->path, spec->path);
+
+ if (!hashcmp(ospec->sha1, spec->sha1) &&
+ ospec->mode == spec->mode)
+ break;
+ /* fallthrough */
+
+ case DIFF_STATUS_TYPE_CHANGED:
+ case DIFF_STATUS_MODIFIED:
+ case DIFF_STATUS_ADDED:
+ /*
+ * Links refer to objects in another repositories;
+ * output the SHA-1 verbatim.
+ */
+ if (no_data || S_ISGITLINK(spec->mode))
+ printf("M %06o %s %s\n", spec->mode,
+ sha1_to_hex(spec->sha1), spec->path);
+ else {
+ struct object *object = lookup_object(spec->sha1);
+ printf("M %06o :%d %s\n", spec->mode,
+ get_object_mark(object), spec->path);
+ }
+ break;
+
+ default:
+ die("Unexpected comparison status '%c' for %s, %s",
+ q->queue[i]->status,
+ ospec->path ? ospec->path : "none",
+ spec->path ? spec->path : "none");
+ }
+ }
+}
+
+static const char *find_encoding(const char *begin, const char *end)
+{
+ const char *needle = "\nencoding ";
+ char *bol, *eol;
+
+ bol = memmem(begin, end ? end - begin : strlen(begin),
+ needle, strlen(needle));
+ if (!bol)
+ return git_commit_encoding;
+ bol += strlen(needle);
+ eol = strchrnul(bol, '\n');
+ *eol = '\0';
+ return bol;
+}
+
+static void handle_commit(struct commit *commit, struct rev_info *rev)
+{
+ int saved_output_format = rev->diffopt.output_format;
+ const char *author, *author_end, *committer, *committer_end;
+ const char *encoding, *message;
+ char *reencoded = NULL;
+ struct commit_list *p;
+ int i;
+
+ rev->diffopt.output_format = DIFF_FORMAT_CALLBACK;
+
+ parse_commit(commit);
+ author = strstr(commit->buffer, "\nauthor ");
+ if (!author)
+ die ("Could not find author in commit %s",
+ sha1_to_hex(commit->object.sha1));
+ author++;
+ author_end = strchrnul(author, '\n');
+ committer = strstr(author_end, "\ncommitter ");
+ if (!committer)
+ die ("Could not find committer in commit %s",
+ sha1_to_hex(commit->object.sha1));
+ committer++;
+ committer_end = strchrnul(committer, '\n');
+ message = strstr(committer_end, "\n\n");
+ encoding = find_encoding(committer_end, message);
+ if (message)
+ message += 2;
+
+ if (commit->parents &&
+ get_object_mark(&commit->parents->item->object) != 0) {
+ parse_commit(commit->parents->item);
+ diff_tree_sha1(commit->parents->item->tree->object.sha1,
+ commit->tree->object.sha1, "", &rev->diffopt);
+ }
+ else
+ diff_root_tree_sha1(commit->tree->object.sha1,
+ "", &rev->diffopt);
+
+ /* Export the referenced blobs, and remember the marks. */
+ for (i = 0; i < diff_queued_diff.nr; i++)
+ if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode))
+ handle_object(diff_queued_diff.queue[i]->two->sha1);
+
+ mark_next_object(&commit->object);
+ if (!is_encoding_utf8(encoding))
+ reencoded = reencode_string(message, "UTF-8", encoding);
+ if (!commit->parents)
+ printf("reset %s\n", (const char*)commit->util);
+ printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s",
+ (const char *)commit->util, last_idnum,
+ (int)(author_end - author), author,
+ (int)(committer_end - committer), committer,
+ (unsigned)(reencoded
+ ? strlen(reencoded) : message
+ ? strlen(message) : 0),
+ reencoded ? reencoded : message ? message : "");
+ free(reencoded);
+
+ for (i = 0, p = commit->parents; p; p = p->next) {
+ int mark = get_object_mark(&p->item->object);
+ if (!mark)
+ continue;
+ if (i == 0)
+ printf("from :%d\n", mark);
+ else
+ printf("merge :%d\n", mark);
+ i++;
+ }
+
+ log_tree_diff_flush(rev);
+ rev->diffopt.output_format = saved_output_format;
+
+ printf("\n");
+
+ show_progress();
+}
+
+static void handle_tail(struct object_array *commits, struct rev_info *revs)
+{
+ struct commit *commit;
+ while (commits->nr) {
+ commit = (struct commit *)commits->objects[commits->nr - 1].item;
+ if (has_unshown_parent(commit))
+ return;
+ handle_commit(commit, revs);
+ commits->nr--;
+ }
+}
+
+static void handle_tag(const char *name, struct tag *tag)
+{
+ unsigned long size;
+ enum object_type type;
+ char *buf;
+ const char *tagger, *tagger_end, *message;
+ size_t message_size = 0;
+ struct object *tagged;
+ int tagged_mark;
+ struct commit *p;
+
+ /* Trees have no identifer in fast-export output, thus we have no way
+ * to output tags of trees, tags of tags of trees, etc. Simply omit
+ * such tags.
+ */
+ tagged = tag->tagged;
+ while (tagged->type == OBJ_TAG) {
+ tagged = ((struct tag *)tagged)->tagged;
+ }
+ if (tagged->type == OBJ_TREE) {
+ warning("Omitting tag %s,\nsince tags of trees (or tags of tags of trees, etc.) are not supported.",
+ sha1_to_hex(tag->object.sha1));
+ return;
+ }
+
+ buf = read_sha1_file(tag->object.sha1, &type, &size);
+ if (!buf)
+ die ("Could not read tag %s", sha1_to_hex(tag->object.sha1));
+ message = memmem(buf, size, "\n\n", 2);
+ if (message) {
+ message += 2;
+ message_size = strlen(message);
+ }
+ tagger = memmem(buf, message ? message - buf : size, "\ntagger ", 8);
+ if (!tagger) {
+ if (fake_missing_tagger)
+ tagger = "tagger Unspecified Tagger "
+ "<unspecified-tagger> 0 +0000";
+ else
+ tagger = "";
+ tagger_end = tagger + strlen(tagger);
+ } else {
+ tagger++;
+ tagger_end = strchrnul(tagger, '\n');
+ }
+
+ /* handle signed tags */
+ if (message) {
+ const char *signature = strstr(message,
+ "\n-----BEGIN PGP SIGNATURE-----\n");
+ if (signature)
+ switch(signed_tag_mode) {
+ case ABORT:
+ die ("Encountered signed tag %s; use "
+ "--signed-tag=<mode> to handle it.",
+ sha1_to_hex(tag->object.sha1));
+ case WARN:
+ warning ("Exporting signed tag %s",
+ sha1_to_hex(tag->object.sha1));
+ /* fallthru */
+ case VERBATIM:
+ break;
+ case STRIP:
+ message_size = signature + 1 - message;
+ break;
+ }
+ }
+
+ /* handle tag->tagged having been filtered out due to paths specified */
+ tagged = tag->tagged;
+ tagged_mark = get_object_mark(tagged);
+ if (!tagged_mark) {
+ switch(tag_of_filtered_mode) {
+ case ABORT:
+ die ("Tag %s tags unexported object; use "
+ "--tag-of-filtered-object=<mode> to handle it.",
+ sha1_to_hex(tag->object.sha1));
+ case DROP:
+ /* Ignore this tag altogether */
+ return;
+ case REWRITE:
+ if (tagged->type != OBJ_COMMIT) {
+ die ("Tag %s tags unexported %s!",
+ sha1_to_hex(tag->object.sha1),
+ typename(tagged->type));
+ }
+ p = (struct commit *)tagged;
+ for (;;) {
+ if (p->parents && p->parents->next)
+ break;
+ if (p->object.flags & UNINTERESTING)
+ break;
+ if (!(p->object.flags & TREESAME))
+ break;
+ if (!p->parents)
+ die ("Can't find replacement commit for tag %s\n",
+ sha1_to_hex(tag->object.sha1));
+ p = p->parents->item;
+ }
+ tagged_mark = get_object_mark(&p->object);
+ }
+ }
+
+ if (!prefixcmp(name, "refs/tags/"))
+ name += 10;
+ printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n",
+ name, tagged_mark,
+ (int)(tagger_end - tagger), tagger,
+ tagger == tagger_end ? "" : "\n",
+ (int)message_size, (int)message_size, message ? message : "");
+}
+
+static void get_tags_and_duplicates(struct object_array *pending,
+ struct string_list *extra_refs)
+{
+ struct tag *tag;
+ int i;
+
+ for (i = 0; i < pending->nr; i++) {
+ struct object_array_entry *e = pending->objects + i;
+ unsigned char sha1[20];
+ struct commit *commit = commit;
+ char *full_name;
+
+ if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
+ continue;
+
+ switch (e->item->type) {
+ case OBJ_COMMIT:
+ commit = (struct commit *)e->item;
+ break;
+ case OBJ_TAG:
+ tag = (struct tag *)e->item;
+
+ /* handle nested tags */
+ while (tag && tag->object.type == OBJ_TAG) {
+ parse_object(tag->object.sha1);
+ string_list_append(full_name, extra_refs)->util = tag;
+ tag = (struct tag *)tag->tagged;
+ }
+ if (!tag)
+ die ("Tag %s points nowhere?", e->name);
+ switch(tag->object.type) {
+ case OBJ_COMMIT:
+ commit = (struct commit *)tag;
+ break;
+ case OBJ_BLOB:
+ handle_object(tag->object.sha1);
+ continue;
+ default: /* OBJ_TAG (nested tags) is already handled */
+ warning("Tag points to object of unexpected type %s, skipping.",
+ typename(tag->object.type));
+ continue;
+ }
+ break;
+ default:
+ warning("%s: Unexpected object of type %s, skipping.",
+ e->name,
+ typename(e->item->type));
+ continue;
+ }
+ if (commit->util)
+ /* more than one name for the same object */
+ string_list_append(full_name, extra_refs)->util = commit;
+ else
+ commit->util = full_name;
+ }
+}
+
+static void handle_tags_and_duplicates(struct string_list *extra_refs)
+{
+ struct commit *commit;
+ int i;
+
+ for (i = extra_refs->nr - 1; i >= 0; i--) {
+ const char *name = extra_refs->items[i].string;
+ struct object *object = extra_refs->items[i].util;
+ switch (object->type) {
+ case OBJ_TAG:
+ handle_tag(name, (struct tag *)object);
+ break;
+ case OBJ_COMMIT:
+ /* create refs pointing to already seen commits */
+ commit = (struct commit *)object;
+ printf("reset %s\nfrom :%d\n\n", name,
+ get_object_mark(&commit->object));
+ show_progress();
+ break;
+ }
+ }
+}
+
+static void export_marks(char *file)
+{
+ unsigned int i;
+ uint32_t mark;
+ struct object_decoration *deco = idnums.hash;
+ FILE *f;
+ int e = 0;
+
+ f = fopen(file, "w");
+ if (!f)
+ error("Unable to open marks file %s for writing.", file);
+
+ for (i = 0; i < idnums.size; i++) {
+ if (deco->base && deco->base->type == 1) {
+ mark = ptr_to_mark(deco->decoration);
+ if (fprintf(f, ":%"PRIu32" %s\n", mark,
+ sha1_to_hex(deco->base->sha1)) < 0) {
+ e = 1;
+ break;
+ }
+ }
+ deco++;
+ }
+
+ e |= ferror(f);
+ e |= fclose(f);
+ if (e)
+ error("Unable to write marks file %s.", file);
+}
+
+static void import_marks(char *input_file)
+{
+ char line[512];
+ FILE *f = fopen(input_file, "r");
+ if (!f)
+ die_errno("cannot read '%s'", input_file);
+
+ while (fgets(line, sizeof(line), f)) {
+ uint32_t mark;
+ char *line_end, *mark_end;
+ unsigned char sha1[20];
+ struct object *object;
+
+ line_end = strchr(line, '\n');
+ if (line[0] != ':' || !line_end)
+ die("corrupt mark line: %s", line);
+ *line_end = '\0';
+
+ mark = strtoumax(line + 1, &mark_end, 10);
+ if (!mark || mark_end == line + 1
+ || *mark_end != ' ' || get_sha1(mark_end + 1, sha1))
+ die("corrupt mark line: %s", line);
+
+ object = parse_object(sha1);
+ if (!object)
+ die ("Could not read blob %s", sha1_to_hex(sha1));
+
+ if (object->flags & SHOWN)
+ error("Object %s already has a mark", sha1);
+
+ mark_object(object, mark);
+ if (last_idnum < mark)
+ last_idnum = mark;
+
+ object->flags |= SHOWN;
+ }
+ fclose(f);
+}
+
+int cmd_fast_export(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info revs;
+ struct object_array commits = { 0, 0, NULL };
+ struct string_list extra_refs = { NULL, 0, 0, 0 };
+ struct commit *commit;
+ char *export_filename = NULL, *import_filename = NULL;
+ struct option options[] = {
+ OPT_INTEGER(0, "progress", &progress,
+ "show progress after <n> objects"),
+ OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode",
+ "select handling of signed tags",
+ parse_opt_signed_tag_mode),
+ OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode",
+ "select handling of tags that tag filtered objects",
+ parse_opt_tag_of_filtered_mode),
+ OPT_STRING(0, "export-marks", &export_filename, "FILE",
+ "Dump marks to this file"),
+ OPT_STRING(0, "import-marks", &import_filename, "FILE",
+ "Import marks from this file"),
+ OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
+ "Fake a tagger when tags lack one"),
+ { OPTION_NEGBIT, 0, "data", &no_data, NULL,
+ "Skip output of blob data",
+ PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
+ OPT_END()
+ };
+
+ if (argc == 1)
+ usage_with_options (fast_export_usage, options);
+
+ /* we handle encodings */
+ git_config(git_default_config, NULL);
+
+ init_revisions(&revs, prefix);
+ revs.topo_order = 1;
+ revs.show_source = 1;
+ revs.rewrite_parents = 1;
+ argc = setup_revisions(argc, argv, &revs, NULL);
+ argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0);
+ if (argc > 1)
+ usage_with_options (fast_export_usage, options);
+
+ if (import_filename)
+ import_marks(import_filename);
+
+ get_tags_and_duplicates(&revs.pending, &extra_refs);
+
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ revs.diffopt.format_callback = show_filemodify;
+ DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
+ while ((commit = get_revision(&revs))) {
+ if (has_unshown_parent(commit)) {
+ add_object_array(&commit->object, NULL, &commits);
+ }
+ else {
+ handle_commit(commit, &revs);
+ handle_tail(&commits, &revs);
+ }
+ }
+
+ handle_tags_and_duplicates(&extra_refs);
+
+ if (export_filename)
+ export_marks(export_filename);
+
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "commit.h"
+#include "tag.h"
+#include "exec_cmd.h"
+#include "pack.h"
+#include "sideband.h"
+#include "fetch-pack.h"
+#include "remote.h"
+#include "run-command.h"
+
+static int transfer_unpack_limit = -1;
+static int fetch_unpack_limit = -1;
+static int unpack_limit = 100;
+static int prefer_ofs_delta = 1;
+static struct fetch_pack_args args = {
+ /* .uploadpack = */ "git-upload-pack",
+};
+
+static const char fetch_pack_usage[] =
+"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
+
+#define COMPLETE (1U << 0)
+#define COMMON (1U << 1)
+#define COMMON_REF (1U << 2)
+#define SEEN (1U << 3)
+#define POPPED (1U << 4)
+
+static int marked;
+
+/*
+ * After sending this many "have"s if we do not get any new ACK , we
+ * give up traversing our history.
+ */
+#define MAX_IN_VAIN 256
+
+static struct commit_list *rev_list;
+static int non_common_revs, multi_ack, use_sideband;
+
+static void rev_list_push(struct commit *commit, int mark)
+{
+ if (!(commit->object.flags & mark)) {
+ commit->object.flags |= mark;
+
+ if (!(commit->object.parsed))
+ if (parse_commit(commit))
+ return;
+
+ insert_by_date(commit, &rev_list);
+
+ if (!(commit->object.flags & COMMON))
+ non_common_revs++;
+ }
+}
+
+static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+ struct object *o = deref_tag(parse_object(sha1), path, 0);
+
+ if (o && o->type == OBJ_COMMIT)
+ rev_list_push((struct commit *)o, SEEN);
+
+ return 0;
+}
+
+static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+ struct object *o = deref_tag(parse_object(sha1), path, 0);
+
+ if (o && o->type == OBJ_COMMIT)
+ clear_commit_marks((struct commit *)o,
+ COMMON | COMMON_REF | SEEN | POPPED);
+ return 0;
+}
+
+/*
+ This function marks a rev and its ancestors as common.
+ In some cases, it is desirable to mark only the ancestors (for example
+ when only the server does not yet know that they are common).
+*/
+
+static void mark_common(struct commit *commit,
+ int ancestors_only, int dont_parse)
+{
+ if (commit != NULL && !(commit->object.flags & COMMON)) {
+ struct object *o = (struct object *)commit;
+
+ if (!ancestors_only)
+ o->flags |= COMMON;
+
+ if (!(o->flags & SEEN))
+ rev_list_push(commit, SEEN);
+ else {
+ struct commit_list *parents;
+
+ if (!ancestors_only && !(o->flags & POPPED))
+ non_common_revs--;
+ if (!o->parsed && !dont_parse)
+ if (parse_commit(commit))
+ return;
+
+ for (parents = commit->parents;
+ parents;
+ parents = parents->next)
+ mark_common(parents->item, 0, dont_parse);
+ }
+ }
+}
+
+/*
+ Get the next rev to send, ignoring the common.
+*/
+
+static const unsigned char *get_rev(void)
+{
+ struct commit *commit = NULL;
+
+ while (commit == NULL) {
+ unsigned int mark;
+ struct commit_list *parents;
+
+ if (rev_list == NULL || non_common_revs == 0)
+ return NULL;
+
+ commit = rev_list->item;
+ if (!commit->object.parsed)
+ parse_commit(commit);
+ parents = commit->parents;
+
+ commit->object.flags |= POPPED;
+ if (!(commit->object.flags & COMMON))
+ non_common_revs--;
+
+ if (commit->object.flags & COMMON) {
+ /* do not send "have", and ignore ancestors */
+ commit = NULL;
+ mark = COMMON | SEEN;
+ } else if (commit->object.flags & COMMON_REF)
+ /* send "have", and ignore ancestors */
+ mark = COMMON | SEEN;
+ else
+ /* send "have", also for its ancestors */
+ mark = SEEN;
+
+ while (parents) {
+ if (!(parents->item->object.flags & SEEN))
+ rev_list_push(parents->item, mark);
+ if (mark & COMMON)
+ mark_common(parents->item, 1, 0);
+ parents = parents->next;
+ }
+
+ rev_list = rev_list->next;
+ }
+
+ return commit->object.sha1;
+}
+
+enum ack_type {
+ NAK = 0,
+ ACK,
+ ACK_continue,
+ ACK_common,
+ ACK_ready
+};
+
+static void consume_shallow_list(int fd)
+{
+ if (args.stateless_rpc && args.depth > 0) {
+ /* If we sent a depth we will get back "duplicate"
+ * shallow and unshallow commands every time there
+ * is a block of have lines exchanged.
+ */
+ char line[1000];
+ while (packet_read_line(fd, line, sizeof(line))) {
+ if (!prefixcmp(line, "shallow "))
+ continue;
+ if (!prefixcmp(line, "unshallow "))
+ continue;
+ die("git fetch-pack: expected shallow list");
+ }
+ }
+}
+
+static enum ack_type get_ack(int fd, unsigned char *result_sha1)
+{
+ static char line[1000];
+ int len = packet_read_line(fd, line, sizeof(line));
+
+ if (!len)
+ die("git fetch-pack: expected ACK/NAK, got EOF");
+ if (line[len-1] == '\n')
+ line[--len] = 0;
+ if (!strcmp(line, "NAK"))
+ return NAK;
+ if (!prefixcmp(line, "ACK ")) {
+ if (!get_sha1_hex(line+4, result_sha1)) {
+ if (strstr(line+45, "continue"))
+ return ACK_continue;
+ if (strstr(line+45, "common"))
+ return ACK_common;
+ if (strstr(line+45, "ready"))
+ return ACK_ready;
+ return ACK;
+ }
+ }
+ die("git fetch_pack: expected ACK/NAK, got '%s'", line);
+}
+
+static void send_request(int fd, struct strbuf *buf)
+{
+ if (args.stateless_rpc) {
+ send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX);
+ packet_flush(fd);
+ } else
+ safe_write(fd, buf->buf, buf->len);
+}
+
+static int find_common(int fd[2], unsigned char *result_sha1,
+ struct ref *refs)
+{
+ int fetching;
+ int count = 0, flushes = 0, retval;
+ const unsigned char *sha1;
+ unsigned in_vain = 0;
+ int got_continue = 0;
+ struct strbuf req_buf = STRBUF_INIT;
+ size_t state_len = 0;
+
+ if (args.stateless_rpc && multi_ack == 1)
+ die("--stateless-rpc requires multi_ack_detailed");
+ if (marked)
+ for_each_ref(clear_marks, NULL);
+ marked = 1;
+
+ for_each_ref(rev_list_insert_ref, NULL);
+
+ fetching = 0;
+ for ( ; refs ; refs = refs->next) {
+ unsigned char *remote = refs->old_sha1;
+ const char *remote_hex;
+ struct object *o;
+
+ /*
+ * If that object is complete (i.e. it is an ancestor of a
+ * local ref), we tell them we have it but do not have to
+ * tell them about its ancestors, which they already know
+ * about.
+ *
+ * We use lookup_object here because we are only
+ * interested in the case we *know* the object is
+ * reachable and we have already scanned it.
+ */
+ if (((o = lookup_object(remote)) != NULL) &&
+ (o->flags & COMPLETE)) {
+ continue;
+ }
+
+ remote_hex = sha1_to_hex(remote);
+ if (!fetching) {
+ struct strbuf c = STRBUF_INIT;
+ if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed");
+ if (multi_ack == 1) strbuf_addstr(&c, " multi_ack");
+ if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k");
+ if (use_sideband == 1) strbuf_addstr(&c, " side-band");
+ if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack");
+ if (args.no_progress) strbuf_addstr(&c, " no-progress");
+ if (args.include_tag) strbuf_addstr(&c, " include-tag");
+ if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta");
+ packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf);
+ strbuf_release(&c);
+ } else
+ packet_buf_write(&req_buf, "want %s\n", remote_hex);
+ fetching++;
+ }
+
+ if (!fetching) {
+ strbuf_release(&req_buf);
+ packet_flush(fd[1]);
+ return 1;
+ }
+
+ if (is_repository_shallow())
+ write_shallow_commits(&req_buf, 1);
+ if (args.depth > 0)
+ packet_buf_write(&req_buf, "deepen %d", args.depth);
+ packet_buf_flush(&req_buf);
+ state_len = req_buf.len;
+
+ if (args.depth > 0) {
+ char line[1024];
+ unsigned char sha1[20];
+
+ send_request(fd[1], &req_buf);
+ while (packet_read_line(fd[0], line, sizeof(line))) {
+ if (!prefixcmp(line, "shallow ")) {
+ if (get_sha1_hex(line + 8, sha1))
+ die("invalid shallow line: %s", line);
+ register_shallow(sha1);
+ continue;
+ }
+ if (!prefixcmp(line, "unshallow ")) {
+ if (get_sha1_hex(line + 10, sha1))
+ die("invalid unshallow line: %s", line);
+ if (!lookup_object(sha1))
+ die("object not found: %s", line);
+ /* make sure that it is parsed as shallow */
+ if (!parse_object(sha1))
+ die("error in object: %s", line);
+ if (unregister_shallow(sha1))
+ die("no shallow found: %s", line);
+ continue;
+ }
+ die("expected shallow/unshallow, got %s", line);
+ }
+ } else if (!args.stateless_rpc)
+ send_request(fd[1], &req_buf);
+
+ if (!args.stateless_rpc) {
+ /* If we aren't using the stateless-rpc interface
+ * we don't need to retain the headers.
+ */
+ strbuf_setlen(&req_buf, 0);
+ state_len = 0;
+ }
+
+ flushes = 0;
+ retval = -1;
+ while ((sha1 = get_rev())) {
+ packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1));
+ if (args.verbose)
+ fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
+ in_vain++;
+ if (!(31 & ++count)) {
+ int ack;
+
+ packet_buf_flush(&req_buf);
+ send_request(fd[1], &req_buf);
+ strbuf_setlen(&req_buf, state_len);
+ flushes++;
+
+ /*
+ * We keep one window "ahead" of the other side, and
+ * will wait for an ACK only on the next one
+ */
+ if (!args.stateless_rpc && count == 32)
+ continue;
+
+ consume_shallow_list(fd[0]);
+ do {
+ ack = get_ack(fd[0], result_sha1);
+ if (args.verbose && ack)
+ fprintf(stderr, "got ack %d %s\n", ack,
+ sha1_to_hex(result_sha1));
+ switch (ack) {
+ case ACK:
+ flushes = 0;
+ multi_ack = 0;
+ retval = 0;
+ goto done;
+ case ACK_common:
+ case ACK_ready:
+ case ACK_continue: {
+ struct commit *commit =
+ lookup_commit(result_sha1);
+ if (args.stateless_rpc
+ && ack == ACK_common
+ && !(commit->object.flags & COMMON)) {
+ /* We need to replay the have for this object
+ * on the next RPC request so the peer knows
+ * it is in common with us.
+ */
+ const char *hex = sha1_to_hex(result_sha1);
+ packet_buf_write(&req_buf, "have %s\n", hex);
+ state_len = req_buf.len;
+ }
+ mark_common(commit, 0, 1);
+ retval = 0;
+ in_vain = 0;
+ got_continue = 1;
+ break;
+ }
+ }
+ } while (ack);
+ flushes--;
+ if (got_continue && MAX_IN_VAIN < in_vain) {
+ if (args.verbose)
+ fprintf(stderr, "giving up\n");
+ break; /* give up */
+ }
+ }
+ }
+done:
+ packet_buf_write(&req_buf, "done\n");
+ send_request(fd[1], &req_buf);
+ if (args.verbose)
+ fprintf(stderr, "done\n");
+ if (retval != 0) {
+ multi_ack = 0;
+ flushes++;
+ }
+ strbuf_release(&req_buf);
+
+ consume_shallow_list(fd[0]);
+ while (flushes || multi_ack) {
+ int ack = get_ack(fd[0], result_sha1);
+ if (ack) {
+ if (args.verbose)
+ fprintf(stderr, "got ack (%d) %s\n", ack,
+ sha1_to_hex(result_sha1));
+ if (ack == ACK)
+ return 0;
+ multi_ack = 1;
+ continue;
+ }
+ flushes--;
+ }
+ /* it is no error to fetch into a completely empty repo */
+ return count ? retval : 0;
+}
+
+static struct commit_list *complete;
+
+static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+ struct object *o = parse_object(sha1);
+
+ while (o && o->type == OBJ_TAG) {
+ struct tag *t = (struct tag *) o;
+ if (!t->tagged)
+ break; /* broken repository */
+ o->flags |= COMPLETE;
+ o = parse_object(t->tagged->sha1);
+ }
+ if (o && o->type == OBJ_COMMIT) {
+ struct commit *commit = (struct commit *)o;
+ commit->object.flags |= COMPLETE;
+ insert_by_date(commit, &complete);
+ }
+ return 0;
+}
+
+static void mark_recent_complete_commits(unsigned long cutoff)
+{
+ while (complete && cutoff <= complete->item->date) {
+ if (args.verbose)
+ fprintf(stderr, "Marking %s as complete\n",
+ sha1_to_hex(complete->item->object.sha1));
+ pop_most_recent_commit(&complete, COMPLETE);
+ }
+}
+
+static void filter_refs(struct ref **refs, int nr_match, char **match)
+{
+ struct ref **return_refs;
+ struct ref *newlist = NULL;
+ struct ref **newtail = &newlist;
+ struct ref *ref, *next;
+ struct ref *fastarray[32];
+
+ if (nr_match && !args.fetch_all) {
+ if (ARRAY_SIZE(fastarray) < nr_match)
+ return_refs = xcalloc(nr_match, sizeof(struct ref *));
+ else {
+ return_refs = fastarray;
+ memset(return_refs, 0, sizeof(struct ref *) * nr_match);
+ }
+ }
+ else
+ return_refs = NULL;
+
+ for (ref = *refs; ref; ref = next) {
+ next = ref->next;
+ if (!memcmp(ref->name, "refs/", 5) &&
+ check_ref_format(ref->name + 5))
+ ; /* trash */
+ else if (args.fetch_all &&
+ (!args.depth || prefixcmp(ref->name, "refs/tags/") )) {
+ *newtail = ref;
+ ref->next = NULL;
+ newtail = &ref->next;
+ continue;
+ }
+ else {
+ int order = path_match(ref->name, nr_match, match);
+ if (order) {
+ return_refs[order-1] = ref;
+ continue; /* we will link it later */
+ }
+ }
+ free(ref);
+ }
+
+ if (!args.fetch_all) {
+ int i;
+ for (i = 0; i < nr_match; i++) {
+ ref = return_refs[i];
+ if (ref) {
+ *newtail = ref;
+ ref->next = NULL;
+ newtail = &ref->next;
+ }
+ }
+ if (return_refs != fastarray)
+ free(return_refs);
+ }
+ *refs = newlist;
+}
+
+static int everything_local(struct ref **refs, int nr_match, char **match)
+{
+ struct ref *ref;
+ int retval;
+ unsigned long cutoff = 0;
+
+ save_commit_buffer = 0;
+
+ for (ref = *refs; ref; ref = ref->next) {
+ struct object *o;
+
+ o = parse_object(ref->old_sha1);
+ if (!o)
+ continue;
+
+ /* We already have it -- which may mean that we were
+ * in sync with the other side at some time after
+ * that (it is OK if we guess wrong here).
+ */
+ if (o->type == OBJ_COMMIT) {
+ struct commit *commit = (struct commit *)o;
+ if (!cutoff || cutoff < commit->date)
+ cutoff = commit->date;
+ }
+ }
+
+ if (!args.depth) {
+ for_each_ref(mark_complete, NULL);
+ if (cutoff)
+ mark_recent_complete_commits(cutoff);
+ }
+
+ /*
+ * Mark all complete remote refs as common refs.
+ * Don't mark them common yet; the server has to be told so first.
+ */
+ for (ref = *refs; ref; ref = ref->next) {
+ struct object *o = deref_tag(lookup_object(ref->old_sha1),
+ NULL, 0);
+
+ if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
+ continue;
+
+ if (!(o->flags & SEEN)) {
+ rev_list_push((struct commit *)o, COMMON_REF | SEEN);
+
+ mark_common((struct commit *)o, 1, 1);
+ }
+ }
+
+ filter_refs(refs, nr_match, match);
+
+ for (retval = 1, ref = *refs; ref ; ref = ref->next) {
+ const unsigned char *remote = ref->old_sha1;
+ unsigned char local[20];
+ struct object *o;
+
+ o = lookup_object(remote);
+ if (!o || !(o->flags & COMPLETE)) {
+ retval = 0;
+ if (!args.verbose)
+ continue;
+ fprintf(stderr,
+ "want %s (%s)\n", sha1_to_hex(remote),
+ ref->name);
+ continue;
+ }
+
+ hashcpy(ref->new_sha1, local);
+ if (!args.verbose)
+ continue;
+ fprintf(stderr,
+ "already have %s (%s)\n", sha1_to_hex(remote),
+ ref->name);
+ }
+ return retval;
+}
+
+static int sideband_demux(int in, int out, void *data)
+{
+ int *xd = data;
+
+ int ret = recv_sideband("fetch-pack", xd[0], out);
+ close(out);
+ return ret;
+}
+
+static int get_pack(int xd[2], char **pack_lockfile)
+{
+ struct async demux;
+ const char *argv[20];
+ char keep_arg[256];
+ char hdr_arg[256];
+ const char **av;
+ int do_keep = args.keep_pack;
+ struct child_process cmd;
+
+ memset(&demux, 0, sizeof(demux));
+ if (use_sideband) {
+ /* xd[] is talking with upload-pack; subprocess reads from
+ * xd[0], spits out band#2 to stderr, and feeds us band#1
+ * through demux->out.
+ */
+ demux.proc = sideband_demux;
+ demux.data = xd;
+ demux.out = -1;
+ if (start_async(&demux))
+ die("fetch-pack: unable to fork off sideband"
+ " demultiplexer");
+ }
+ else
+ demux.out = xd[0];
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.argv = argv;
+ av = argv;
+ *hdr_arg = 0;
+ if (!args.keep_pack && unpack_limit) {
+ struct pack_header header;
+
+ if (read_pack_header(demux.out, &header))
+ die("protocol error: bad pack header");
+ snprintf(hdr_arg, sizeof(hdr_arg),
+ "--pack_header=%"PRIu32",%"PRIu32,
+ ntohl(header.hdr_version), ntohl(header.hdr_entries));
+ if (ntohl(header.hdr_entries) < unpack_limit)
+ do_keep = 0;
+ else
+ do_keep = 1;
+ }
+
+ if (do_keep) {
+ if (pack_lockfile)
+ cmd.out = -1;
+ *av++ = "index-pack";
+ *av++ = "--stdin";
+ if (!args.quiet && !args.no_progress)
+ *av++ = "-v";
+ if (args.use_thin_pack)
+ *av++ = "--fix-thin";
+ if (args.lock_pack || unpack_limit) {
+ int s = sprintf(keep_arg,
+ "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid());
+ if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+ strcpy(keep_arg + s, "localhost");
+ *av++ = keep_arg;
+ }
+ }
+ else {
+ *av++ = "unpack-objects";
+ if (args.quiet)
+ *av++ = "-q";
+ }
+ if (*hdr_arg)
+ *av++ = hdr_arg;
+ *av++ = NULL;
+
+ cmd.in = demux.out;
+ cmd.git_cmd = 1;
+ if (start_command(&cmd))
+ die("fetch-pack: unable to fork off %s", argv[0]);
+ if (do_keep && pack_lockfile) {
+ *pack_lockfile = index_pack_lockfile(cmd.out);
+ close(cmd.out);
+ }
+
+ if (finish_command(&cmd))
+ die("%s failed", argv[0]);
+ if (use_sideband && finish_async(&demux))
+ die("error in sideband demultiplexer");
+ return 0;
+}
+
+static struct ref *do_fetch_pack(int fd[2],
+ const struct ref *orig_ref,
+ int nr_match,
+ char **match,
+ char **pack_lockfile)
+{
+ struct ref *ref = copy_ref_list(orig_ref);
+ unsigned char sha1[20];
+
+ if (is_repository_shallow() && !server_supports("shallow"))
+ die("Server does not support shallow clients");
+ if (server_supports("multi_ack_detailed")) {
+ if (args.verbose)
+ fprintf(stderr, "Server supports multi_ack_detailed\n");
+ multi_ack = 2;
+ }
+ else if (server_supports("multi_ack")) {
+ if (args.verbose)
+ fprintf(stderr, "Server supports multi_ack\n");
+ multi_ack = 1;
+ }
+ if (server_supports("side-band-64k")) {
+ if (args.verbose)
+ fprintf(stderr, "Server supports side-band-64k\n");
+ use_sideband = 2;
+ }
+ else if (server_supports("side-band")) {
+ if (args.verbose)
+ fprintf(stderr, "Server supports side-band\n");
+ use_sideband = 1;
+ }
+ if (server_supports("ofs-delta")) {
+ if (args.verbose)
+ fprintf(stderr, "Server supports ofs-delta\n");
+ } else
+ prefer_ofs_delta = 0;
+ if (everything_local(&ref, nr_match, match)) {
+ packet_flush(fd[1]);
+ goto all_done;
+ }
+ if (find_common(fd, sha1, ref) < 0)
+ if (!args.keep_pack)
+ /* When cloning, it is not unusual to have
+ * no common commit.
+ */
+ warning("no common commits");
+
+ if (args.stateless_rpc)
+ packet_flush(fd[1]);
+ if (get_pack(fd, pack_lockfile))
+ die("git fetch-pack: fetch failed.");
+
+ all_done:
+ return ref;
+}
+
+static int remove_duplicates(int nr_heads, char **heads)
+{
+ int src, dst;
+
+ for (src = dst = 0; src < nr_heads; src++) {
+ /* If heads[src] is different from any of
+ * heads[0..dst], push it in.
+ */
+ int i;
+ for (i = 0; i < dst; i++) {
+ if (!strcmp(heads[i], heads[src]))
+ break;
+ }
+ if (i < dst)
+ continue;
+ if (src != dst)
+ heads[dst] = heads[src];
+ dst++;
+ }
+ return dst;
+}
+
+static int fetch_pack_config(const char *var, const char *value, void *cb)
+{
+ if (strcmp(var, "fetch.unpacklimit") == 0) {
+ fetch_unpack_limit = git_config_int(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "transfer.unpacklimit") == 0) {
+ transfer_unpack_limit = git_config_int(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
+ prefer_ofs_delta = git_config_bool(var, value);
+ return 0;
+ }
+
+ return git_default_config(var, value, cb);
+}
+
+static struct lock_file lock;
+
+static void fetch_pack_setup(void)
+{
+ static int did_setup;
+ if (did_setup)
+ return;
+ git_config(fetch_pack_config, NULL);
+ if (0 <= transfer_unpack_limit)
+ unpack_limit = transfer_unpack_limit;
+ else if (0 <= fetch_unpack_limit)
+ unpack_limit = fetch_unpack_limit;
+ did_setup = 1;
+}
+
+int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
+{
+ int i, ret, nr_heads;
+ struct ref *ref = NULL;
+ char *dest = NULL, **heads;
+ int fd[2];
+ char *pack_lockfile = NULL;
+ char **pack_lockfile_ptr = NULL;
+ struct child_process *conn;
+
+ nr_heads = 0;
+ heads = NULL;
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!prefixcmp(arg, "--upload-pack=")) {
+ args.uploadpack = arg + 14;
+ continue;
+ }
+ if (!prefixcmp(arg, "--exec=")) {
+ args.uploadpack = arg + 7;
+ continue;
+ }
+ if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
+ args.quiet = 1;
+ continue;
+ }
+ if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
+ args.lock_pack = args.keep_pack;
+ args.keep_pack = 1;
+ continue;
+ }
+ if (!strcmp("--thin", arg)) {
+ args.use_thin_pack = 1;
+ continue;
+ }
+ if (!strcmp("--include-tag", arg)) {
+ args.include_tag = 1;
+ continue;
+ }
+ if (!strcmp("--all", arg)) {
+ args.fetch_all = 1;
+ continue;
+ }
+ if (!strcmp("-v", arg)) {
+ args.verbose = 1;
+ continue;
+ }
+ if (!prefixcmp(arg, "--depth=")) {
+ args.depth = strtol(arg + 8, NULL, 0);
+ continue;
+ }
+ if (!strcmp("--no-progress", arg)) {
+ args.no_progress = 1;
+ continue;
+ }
+ if (!strcmp("--stateless-rpc", arg)) {
+ args.stateless_rpc = 1;
+ continue;
+ }
+ if (!strcmp("--lock-pack", arg)) {
+ args.lock_pack = 1;
+ pack_lockfile_ptr = &pack_lockfile;
+ continue;
+ }
+ usage(fetch_pack_usage);
+ }
+ dest = (char *)arg;
+ heads = (char **)(argv + i + 1);
+ nr_heads = argc - i - 1;
+ break;
+ }
+ if (!dest)
+ usage(fetch_pack_usage);
+
+ if (args.stateless_rpc) {
+ conn = NULL;
+ fd[0] = 0;
+ fd[1] = 1;
+ } else {
+ conn = git_connect(fd, (char *)dest, args.uploadpack,
+ args.verbose ? CONNECT_VERBOSE : 0);
+ }
+
+ get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
+
+ ref = fetch_pack(&args, fd, conn, ref, dest,
+ nr_heads, heads, pack_lockfile_ptr);
+ if (pack_lockfile) {
+ printf("lock %s\n", pack_lockfile);
+ fflush(stdout);
+ }
+ close(fd[0]);
+ close(fd[1]);
+ if (finish_connect(conn))
+ ref = NULL;
+ ret = !ref;
+
+ if (!ret && nr_heads) {
+ /* If the heads to pull were given, we should have
+ * consumed all of them by matching the remote.
+ * Otherwise, 'git fetch remote no-such-ref' would
+ * silently succeed without issuing an error.
+ */
+ for (i = 0; i < nr_heads; i++)
+ if (heads[i] && heads[i][0]) {
+ error("no such remote ref %s", heads[i]);
+ ret = 1;
+ }
+ }
+ while (ref) {
+ printf("%s %s\n",
+ sha1_to_hex(ref->old_sha1), ref->name);
+ ref = ref->next;
+ }
+
+ return ret;
+}
+
+struct ref *fetch_pack(struct fetch_pack_args *my_args,
+ int fd[], struct child_process *conn,
+ const struct ref *ref,
+ const char *dest,
+ int nr_heads,
+ char **heads,
+ char **pack_lockfile)
+{
+ struct stat st;
+ struct ref *ref_cpy;
+
+ fetch_pack_setup();
+ if (&args != my_args)
+ memcpy(&args, my_args, sizeof(args));
+ if (args.depth > 0) {
+ if (stat(git_path("shallow"), &st))
+ st.st_mtime = 0;
+ }
+
+ if (heads && nr_heads)
+ nr_heads = remove_duplicates(nr_heads, heads);
+ if (!ref) {
+ packet_flush(fd[1]);
+ die("no matching remote head");
+ }
+ ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile);
+
+ if (args.depth > 0) {
+ struct cache_time mtime;
+ struct strbuf sb = STRBUF_INIT;
+ char *shallow = git_path("shallow");
+ int fd;
+
+ mtime.sec = st.st_mtime;
+ mtime.nsec = ST_MTIME_NSEC(st);
+ if (stat(shallow, &st)) {
+ if (mtime.sec)
+ die("shallow file was removed during fetch");
+ } else if (st.st_mtime != mtime.sec
+#ifdef USE_NSEC
+ || ST_MTIME_NSEC(st) != mtime.nsec
+#endif
+ )
+ die("shallow file was changed during fetch");
+
+ fd = hold_lock_file_for_update(&lock, shallow,
+ LOCK_DIE_ON_ERROR);
+ if (!write_shallow_commits(&sb, 0)
+ || write_in_full(fd, sb.buf, sb.len) != sb.len) {
+ unlink_or_warn(shallow);
+ rollback_lock_file(&lock);
+ } else {
+ commit_lock_file(&lock);
+ }
+ strbuf_release(&sb);
+ }
+
+ reprepare_packed_git();
+ return ref_cpy;
+}
--- /dev/null
+/*
+ * "git fetch"
+ */
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+#include "string-list.h"
+#include "remote.h"
+#include "transport.h"
+#include "run-command.h"
+#include "parse-options.h"
+#include "sigchain.h"
+#include "transport.h"
+
+static const char * const builtin_fetch_usage[] = {
+ "git fetch [options] [<repository> <refspec>...]",
+ "git fetch [options] <group>",
+ "git fetch --multiple [options] [<repository> | <group>]...",
+ "git fetch --all [options]",
+ NULL
+};
+
+enum {
+ TAGS_UNSET = 0,
+ TAGS_DEFAULT = 1,
+ TAGS_SET = 2
+};
+
+static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
+static int tags = TAGS_DEFAULT;
+static const char *depth;
+static const char *upload_pack;
+static struct strbuf default_rla = STRBUF_INIT;
+static struct transport *transport;
+
+static struct option builtin_fetch_options[] = {
+ OPT__VERBOSITY(&verbosity),
+ OPT_BOOLEAN(0, "all", &all,
+ "fetch from all remotes"),
+ OPT_BOOLEAN('a', "append", &append,
+ "append to .git/FETCH_HEAD instead of overwriting"),
+ OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
+ "path to upload pack on remote end"),
+ OPT_BOOLEAN('f', "force", &force,
+ "force overwrite of local branch"),
+ OPT_BOOLEAN('m', "multiple", &multiple,
+ "fetch from multiple remotes"),
+ OPT_SET_INT('t', "tags", &tags,
+ "fetch all tags and associated objects", TAGS_SET),
+ OPT_SET_INT('n', NULL, &tags,
+ "do not fetch all tags (--no-tags)", TAGS_UNSET),
+ OPT_BOOLEAN('p', "prune", &prune,
+ "prune tracking branches no longer on remote"),
+ OPT_BOOLEAN(0, "dry-run", &dry_run,
+ "dry run"),
+ OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
+ OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
+ "allow updating of HEAD ref"),
+ OPT_STRING(0, "depth", &depth, "DEPTH",
+ "deepen history of shallow clone"),
+ OPT_END()
+};
+
+static void unlock_pack(void)
+{
+ if (transport)
+ transport_unlock_pack(transport);
+}
+
+static void unlock_pack_on_signal(int signo)
+{
+ unlock_pack();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
+static void add_merge_config(struct ref **head,
+ const struct ref *remote_refs,
+ struct branch *branch,
+ struct ref ***tail)
+{
+ int i;
+
+ for (i = 0; i < branch->merge_nr; i++) {
+ struct ref *rm, **old_tail = *tail;
+ struct refspec refspec;
+
+ for (rm = *head; rm; rm = rm->next) {
+ if (branch_merge_matches(branch, i, rm->name)) {
+ rm->merge = 1;
+ break;
+ }
+ }
+ if (rm)
+ continue;
+
+ /*
+ * Not fetched to a tracking branch? We need to fetch
+ * it anyway to allow this branch's "branch.$name.merge"
+ * to be honored by 'git pull', but we do not have to
+ * fail if branch.$name.merge is misconfigured to point
+ * at a nonexisting branch. If we were indeed called by
+ * 'git pull', it will notice the misconfiguration because
+ * there is no entry in the resulting FETCH_HEAD marked
+ * for merging.
+ */
+ refspec.src = branch->merge[i]->src;
+ refspec.dst = NULL;
+ refspec.pattern = 0;
+ refspec.force = 0;
+ get_fetch_map(remote_refs, &refspec, tail, 1);
+ for (rm = *old_tail; rm; rm = rm->next)
+ rm->merge = 1;
+ }
+}
+
+static void find_non_local_tags(struct transport *transport,
+ struct ref **head,
+ struct ref ***tail);
+
+static struct ref *get_ref_map(struct transport *transport,
+ struct refspec *refs, int ref_count, int tags,
+ int *autotags)
+{
+ int i;
+ struct ref *rm;
+ struct ref *ref_map = NULL;
+ struct ref **tail = &ref_map;
+
+ const struct ref *remote_refs = transport_get_remote_refs(transport);
+
+ if (ref_count || tags == TAGS_SET) {
+ for (i = 0; i < ref_count; i++) {
+ get_fetch_map(remote_refs, &refs[i], &tail, 0);
+ if (refs[i].dst && refs[i].dst[0])
+ *autotags = 1;
+ }
+ /* Merge everything on the command line, but not --tags */
+ for (rm = ref_map; rm; rm = rm->next)
+ rm->merge = 1;
+ if (tags == TAGS_SET)
+ get_fetch_map(remote_refs, tag_refspec, &tail, 0);
+ } else {
+ /* Use the defaults */
+ struct remote *remote = transport->remote;
+ struct branch *branch = branch_get(NULL);
+ int has_merge = branch_has_merge_config(branch);
+ if (remote && (remote->fetch_refspec_nr || has_merge)) {
+ for (i = 0; i < remote->fetch_refspec_nr; i++) {
+ get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0);
+ if (remote->fetch[i].dst &&
+ remote->fetch[i].dst[0])
+ *autotags = 1;
+ if (!i && !has_merge && ref_map &&
+ !remote->fetch[0].pattern)
+ ref_map->merge = 1;
+ }
+ /*
+ * if the remote we're fetching from is the same
+ * as given in branch.<name>.remote, we add the
+ * ref given in branch.<name>.merge, too.
+ */
+ if (has_merge &&
+ !strcmp(branch->remote_name, remote->name))
+ add_merge_config(&ref_map, remote_refs, branch, &tail);
+ } else {
+ ref_map = get_remote_ref(remote_refs, "HEAD");
+ if (!ref_map)
+ die("Couldn't find remote ref HEAD");
+ ref_map->merge = 1;
+ tail = &ref_map->next;
+ }
+ }
+ if (tags == TAGS_DEFAULT && *autotags)
+ find_non_local_tags(transport, &ref_map, &tail);
+ ref_remove_duplicates(ref_map);
+
+ return ref_map;
+}
+
+#define STORE_REF_ERROR_OTHER 1
+#define STORE_REF_ERROR_DF_CONFLICT 2
+
+static int s_update_ref(const char *action,
+ struct ref *ref,
+ int check_old)
+{
+ char msg[1024];
+ char *rla = getenv("GIT_REFLOG_ACTION");
+ static struct ref_lock *lock;
+
+ if (dry_run)
+ return 0;
+ if (!rla)
+ rla = default_rla.buf;
+ snprintf(msg, sizeof(msg), "%s: %s", rla, action);
+ lock = lock_any_ref_for_update(ref->name,
+ check_old ? ref->old_sha1 : NULL, 0);
+ if (!lock)
+ return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
+ STORE_REF_ERROR_OTHER;
+ if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
+ return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
+ STORE_REF_ERROR_OTHER;
+ return 0;
+}
+
+#define REFCOL_WIDTH 10
+
+static int update_local_ref(struct ref *ref,
+ const char *remote,
+ char *display)
+{
+ struct commit *current = NULL, *updated;
+ enum object_type type;
+ struct branch *current_branch = branch_get(NULL);
+ const char *pretty_ref = prettify_refname(ref->name);
+
+ *display = 0;
+ type = sha1_object_info(ref->new_sha1, NULL);
+ if (type < 0)
+ die("object %s not found", sha1_to_hex(ref->new_sha1));
+
+ if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
+ if (verbosity > 0)
+ sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH,
+ "[up to date]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 0;
+ }
+
+ if (current_branch &&
+ !strcmp(ref->name, current_branch->name) &&
+ !(update_head_ok || is_bare_repository()) &&
+ !is_null_sha1(ref->old_sha1)) {
+ /*
+ * If this is the head, and it's not okay to update
+ * the head, and the old value of the head isn't empty...
+ */
+ sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)",
+ TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 1;
+ }
+
+ if (!is_null_sha1(ref->old_sha1) &&
+ !prefixcmp(ref->name, "refs/tags/")) {
+ int r;
+ r = s_update_ref("updating tag", ref, 0);
+ sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-',
+ TRANSPORT_SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
+ pretty_ref, r ? " (unable to update local ref)" : "");
+ return r;
+ }
+
+ current = lookup_commit_reference_gently(ref->old_sha1, 1);
+ updated = lookup_commit_reference_gently(ref->new_sha1, 1);
+ if (!current || !updated) {
+ const char *msg;
+ const char *what;
+ int r;
+ if (!strncmp(ref->name, "refs/tags/", 10)) {
+ msg = "storing tag";
+ what = "[new tag]";
+ }
+ else {
+ msg = "storing head";
+ what = "[new branch]";
+ }
+
+ r = s_update_ref(msg, ref, 0);
+ sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
+ TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
+ r ? " (unable to update local ref)" : "");
+ return r;
+ }
+
+ if (in_merge_bases(current, &updated, 1)) {
+ char quickref[83];
+ int r;
+ strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+ strcat(quickref, "..");
+ strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ r = s_update_ref("fast-forward", ref, 1);
+ sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
+ TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
+ pretty_ref, r ? " (unable to update local ref)" : "");
+ return r;
+ } else if (force || ref->force) {
+ char quickref[84];
+ int r;
+ strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+ strcat(quickref, "...");
+ strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ r = s_update_ref("forced-update", ref, 1);
+ sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+',
+ TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
+ pretty_ref,
+ r ? "unable to update local ref" : "forced update");
+ return r;
+ } else {
+ sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)",
+ TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 1;
+ }
+}
+
+static int store_updated_refs(const char *raw_url, const char *remote_name,
+ struct ref *ref_map)
+{
+ FILE *fp;
+ struct commit *commit;
+ int url_len, i, note_len, shown_url = 0, rc = 0;
+ char note[1024];
+ const char *what, *kind;
+ struct ref *rm;
+ char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+
+ fp = fopen(filename, "a");
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
+
+ if (raw_url)
+ url = transport_anonymize_url(raw_url);
+ else
+ url = xstrdup("foreign");
+ for (rm = ref_map; rm; rm = rm->next) {
+ struct ref *ref = NULL;
+
+ if (rm->peer_ref) {
+ ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
+ strcpy(ref->name, rm->peer_ref->name);
+ hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
+ hashcpy(ref->new_sha1, rm->old_sha1);
+ ref->force = rm->peer_ref->force;
+ }
+
+ commit = lookup_commit_reference_gently(rm->old_sha1, 1);
+ if (!commit)
+ rm->merge = 0;
+
+ if (!strcmp(rm->name, "HEAD")) {
+ kind = "";
+ what = "";
+ }
+ else if (!prefixcmp(rm->name, "refs/heads/")) {
+ kind = "branch";
+ what = rm->name + 11;
+ }
+ else if (!prefixcmp(rm->name, "refs/tags/")) {
+ kind = "tag";
+ what = rm->name + 10;
+ }
+ else if (!prefixcmp(rm->name, "refs/remotes/")) {
+ kind = "remote branch";
+ what = rm->name + 13;
+ }
+ else {
+ kind = "";
+ what = rm->name;
+ }
+
+ url_len = strlen(url);
+ for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
+ ;
+ url_len = i + 1;
+ if (4 < i && !strncmp(".git", url + i - 3, 4))
+ url_len = i - 3;
+
+ note_len = 0;
+ if (*what) {
+ if (*kind)
+ note_len += sprintf(note + note_len, "%s ",
+ kind);
+ note_len += sprintf(note + note_len, "'%s' of ", what);
+ }
+ note[note_len] = '\0';
+ fprintf(fp, "%s\t%s\t%s",
+ sha1_to_hex(commit ? commit->object.sha1 :
+ rm->old_sha1),
+ rm->merge ? "" : "not-for-merge",
+ note);
+ for (i = 0; i < url_len; ++i)
+ if ('\n' == url[i])
+ fputs("\\n", fp);
+ else
+ fputc(url[i], fp);
+ fputc('\n', fp);
+
+ if (ref)
+ rc |= update_local_ref(ref, what, note);
+ else
+ sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
+ TRANSPORT_SUMMARY_WIDTH, *kind ? kind : "branch",
+ REFCOL_WIDTH, *what ? what : "HEAD");
+ if (*note) {
+ if (verbosity >= 0 && !shown_url) {
+ fprintf(stderr, "From %.*s\n",
+ url_len, url);
+ shown_url = 1;
+ }
+ if (verbosity >= 0)
+ fprintf(stderr, " %s\n", note);
+ }
+ }
+ free(url);
+ fclose(fp);
+ if (rc & STORE_REF_ERROR_DF_CONFLICT)
+ error("some local refs could not be updated; try running\n"
+ " 'git remote prune %s' to remove any old, conflicting "
+ "branches", remote_name);
+ return rc;
+}
+
+/*
+ * We would want to bypass the object transfer altogether if
+ * everything we are going to fetch already exists and is connected
+ * locally.
+ *
+ * The refs we are going to fetch are in ref_map. If running
+ *
+ * $ git rev-list --objects --stdin --not --all
+ *
+ * (feeding all the refs in ref_map on its standard input)
+ * does not error out, that means everything reachable from the
+ * refs we are going to fetch exists and is connected to some of
+ * our existing refs.
+ */
+static int quickfetch(struct ref *ref_map)
+{
+ struct child_process revlist;
+ struct ref *ref;
+ int err;
+ const char *argv[] = {"rev-list",
+ "--quiet", "--objects", "--stdin", "--not", "--all", NULL};
+
+ /*
+ * If we are deepening a shallow clone we already have these
+ * objects reachable. Running rev-list here will return with
+ * a good (0) exit status and we'll bypass the fetch that we
+ * really need to perform. Claiming failure now will ensure
+ * we perform the network exchange to deepen our history.
+ */
+ if (depth)
+ return -1;
+
+ if (!ref_map)
+ return 0;
+
+ memset(&revlist, 0, sizeof(revlist));
+ revlist.argv = argv;
+ revlist.git_cmd = 1;
+ revlist.no_stdout = 1;
+ revlist.no_stderr = 1;
+ revlist.in = -1;
+
+ err = start_command(&revlist);
+ if (err) {
+ error("could not run rev-list");
+ return err;
+ }
+
+ /*
+ * If rev-list --stdin encounters an unknown commit, it terminates,
+ * which will cause SIGPIPE in the write loop below.
+ */
+ sigchain_push(SIGPIPE, SIG_IGN);
+
+ for (ref = ref_map; ref; ref = ref->next) {
+ if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
+ write_str_in_full(revlist.in, "\n") < 0) {
+ if (errno != EPIPE && errno != EINVAL)
+ error("failed write to rev-list: %s", strerror(errno));
+ err = -1;
+ break;
+ }
+ }
+
+ if (close(revlist.in)) {
+ error("failed to close rev-list's stdin: %s", strerror(errno));
+ err = -1;
+ }
+
+ sigchain_pop(SIGPIPE);
+
+ return finish_command(&revlist) || err;
+}
+
+static int fetch_refs(struct transport *transport, struct ref *ref_map)
+{
+ int ret = quickfetch(ref_map);
+ if (ret)
+ ret = transport_fetch_refs(transport, ref_map);
+ if (!ret)
+ ret |= store_updated_refs(transport->url,
+ transport->remote->name,
+ ref_map);
+ transport_unlock_pack(transport);
+ return ret;
+}
+
+static int prune_refs(struct transport *transport, struct ref *ref_map)
+{
+ int result = 0;
+ struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+ const char *dangling_msg = dry_run
+ ? " (%s will become dangling)\n"
+ : " (%s has become dangling)\n";
+
+ for (ref = stale_refs; ref; ref = ref->next) {
+ if (!dry_run)
+ result |= delete_ref(ref->name, NULL, 0);
+ if (verbosity >= 0) {
+ fprintf(stderr, " x %-*s %-*s -> %s\n",
+ TRANSPORT_SUMMARY_WIDTH, "[deleted]",
+ REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
+ warn_dangling_symref(stderr, dangling_msg, ref->name);
+ }
+ }
+ free_refs(stale_refs);
+ return result;
+}
+
+static int add_existing(const char *refname, const unsigned char *sha1,
+ int flag, void *cbdata)
+{
+ struct string_list *list = (struct string_list *)cbdata;
+ struct string_list_item *item = string_list_insert(refname, list);
+ item->util = (void *)sha1;
+ return 0;
+}
+
+static int will_fetch(struct ref **head, const unsigned char *sha1)
+{
+ struct ref *rm = *head;
+ while (rm) {
+ if (!hashcmp(rm->old_sha1, sha1))
+ return 1;
+ rm = rm->next;
+ }
+ return 0;
+}
+
+struct tag_data {
+ struct ref **head;
+ struct ref ***tail;
+};
+
+static int add_to_tail(struct string_list_item *item, void *cb_data)
+{
+ struct tag_data *data = (struct tag_data *)cb_data;
+ struct ref *rm = NULL;
+
+ /* We have already decided to ignore this item */
+ if (!item->util)
+ return 0;
+
+ rm = alloc_ref(item->string);
+ rm->peer_ref = alloc_ref(item->string);
+ hashcpy(rm->old_sha1, item->util);
+
+ **data->tail = rm;
+ *data->tail = &rm->next;
+
+ return 0;
+}
+
+static void find_non_local_tags(struct transport *transport,
+ struct ref **head,
+ struct ref ***tail)
+{
+ struct string_list existing_refs = { NULL, 0, 0, 0 };
+ struct string_list remote_refs = { NULL, 0, 0, 0 };
+ struct tag_data data = {head, tail};
+ const struct ref *ref;
+ struct string_list_item *item = NULL;
+
+ for_each_ref(add_existing, &existing_refs);
+ for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
+ if (prefixcmp(ref->name, "refs/tags"))
+ continue;
+
+ /*
+ * The peeled ref always follows the matching base
+ * ref, so if we see a peeled ref that we don't want
+ * to fetch then we can mark the ref entry in the list
+ * as one to ignore by setting util to NULL.
+ */
+ if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) {
+ if (item && !has_sha1_file(ref->old_sha1) &&
+ !will_fetch(head, ref->old_sha1) &&
+ !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+ item = NULL;
+ continue;
+ }
+
+ /*
+ * If item is non-NULL here, then we previously saw a
+ * ref not followed by a peeled reference, so we need
+ * to check if it is a lightweight tag that we want to
+ * fetch.
+ */
+ if (item && !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+
+ item = NULL;
+
+ /* skip duplicates and refs that we already have */
+ if (string_list_has_string(&remote_refs, ref->name) ||
+ string_list_has_string(&existing_refs, ref->name))
+ continue;
+
+ item = string_list_insert(ref->name, &remote_refs);
+ item->util = (void *)ref->old_sha1;
+ }
+ string_list_clear(&existing_refs, 0);
+
+ /*
+ * We may have a final lightweight tag that needs to be
+ * checked to see if it needs fetching.
+ */
+ if (item && !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+
+ /*
+ * For all the tags in the remote_refs string list, call
+ * add_to_tail to add them to the list of refs to be fetched
+ */
+ for_each_string_list(add_to_tail, &remote_refs, &data);
+
+ string_list_clear(&remote_refs, 0);
+}
+
+static void check_not_current_branch(struct ref *ref_map)
+{
+ struct branch *current_branch = branch_get(NULL);
+
+ if (is_bare_repository() || !current_branch)
+ return;
+
+ for (; ref_map; ref_map = ref_map->next)
+ if (ref_map->peer_ref && !strcmp(current_branch->refname,
+ ref_map->peer_ref->name))
+ die("Refusing to fetch into current branch %s "
+ "of non-bare repository", current_branch->refname);
+}
+
+static int truncate_fetch_head(void)
+{
+ char *filename = git_path("FETCH_HEAD");
+ FILE *fp = fopen(filename, "w");
+
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
+ fclose(fp);
+ return 0;
+}
+
+static int do_fetch(struct transport *transport,
+ struct refspec *refs, int ref_count)
+{
+ struct string_list existing_refs = { NULL, 0, 0, 0 };
+ struct string_list_item *peer_item = NULL;
+ struct ref *ref_map;
+ struct ref *rm;
+ int autotags = (transport->remote->fetch_tags == 1);
+
+ for_each_ref(add_existing, &existing_refs);
+
+ if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
+ tags = TAGS_SET;
+ if (transport->remote->fetch_tags == -1)
+ tags = TAGS_UNSET;
+
+ if (!transport->get_refs_list || !transport->fetch)
+ die("Don't know how to fetch from %s", transport->url);
+
+ /* if not appending, truncate FETCH_HEAD */
+ if (!append && !dry_run) {
+ int errcode = truncate_fetch_head();
+ if (errcode)
+ return errcode;
+ }
+
+ ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
+ if (!update_head_ok)
+ check_not_current_branch(ref_map);
+
+ for (rm = ref_map; rm; rm = rm->next) {
+ if (rm->peer_ref) {
+ peer_item = string_list_lookup(rm->peer_ref->name,
+ &existing_refs);
+ if (peer_item)
+ hashcpy(rm->peer_ref->old_sha1,
+ peer_item->util);
+ }
+ }
+
+ if (tags == TAGS_DEFAULT && autotags)
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
+ if (fetch_refs(transport, ref_map)) {
+ free_refs(ref_map);
+ return 1;
+ }
+ if (prune)
+ prune_refs(transport, ref_map);
+ free_refs(ref_map);
+
+ /* if neither --no-tags nor --tags was specified, do automated tag
+ * following ... */
+ if (tags == TAGS_DEFAULT && autotags) {
+ struct ref **tail = &ref_map;
+ ref_map = NULL;
+ find_non_local_tags(transport, &ref_map, &tail);
+ if (ref_map) {
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
+ transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+ fetch_refs(transport, ref_map);
+ }
+ free_refs(ref_map);
+ }
+
+ return 0;
+}
+
+static void set_option(const char *name, const char *value)
+{
+ int r = transport_set_option(transport, name, value);
+ if (r < 0)
+ die("Option \"%s\" value \"%s\" is not valid for %s",
+ name, value, transport->url);
+ if (r > 0)
+ warning("Option \"%s\" is ignored for %s\n",
+ name, transport->url);
+}
+
+static int get_one_remote_for_fetch(struct remote *remote, void *priv)
+{
+ struct string_list *list = priv;
+ if (!remote->skip_default_update)
+ string_list_append(remote->name, list);
+ return 0;
+}
+
+struct remote_group_data {
+ const char *name;
+ struct string_list *list;
+};
+
+static int get_remote_group(const char *key, const char *value, void *priv)
+{
+ struct remote_group_data *g = priv;
+
+ if (!prefixcmp(key, "remotes.") &&
+ !strcmp(key + 8, g->name)) {
+ /* split list by white space */
+ int space = strcspn(value, " \t\n");
+ while (*value) {
+ if (space > 1) {
+ string_list_append(xstrndup(value, space),
+ g->list);
+ }
+ value += space + (value[space] != '\0');
+ space = strcspn(value, " \t\n");
+ }
+ }
+
+ return 0;
+}
+
+static int add_remote_or_group(const char *name, struct string_list *list)
+{
+ int prev_nr = list->nr;
+ struct remote_group_data g = { name, list };
+
+ git_config(get_remote_group, &g);
+ if (list->nr == prev_nr) {
+ struct remote *remote;
+ if (!remote_is_configured(name))
+ return 0;
+ remote = remote_get(name);
+ string_list_append(remote->name, list);
+ }
+ return 1;
+}
+
+static int fetch_multiple(struct string_list *list)
+{
+ int i, result = 0;
+ const char *argv[11] = { "fetch", "--append" };
+ int argc = 2;
+
+ if (dry_run)
+ argv[argc++] = "--dry-run";
+ if (prune)
+ argv[argc++] = "--prune";
+ if (update_head_ok)
+ argv[argc++] = "--update-head-ok";
+ if (force)
+ argv[argc++] = "--force";
+ if (keep)
+ argv[argc++] = "--keep";
+ if (verbosity >= 2)
+ argv[argc++] = "-v";
+ if (verbosity >= 1)
+ argv[argc++] = "-v";
+ else if (verbosity < 0)
+ argv[argc++] = "-q";
+
+ if (!append && !dry_run) {
+ int errcode = truncate_fetch_head();
+ if (errcode)
+ return errcode;
+ }
+
+ for (i = 0; i < list->nr; i++) {
+ const char *name = list->items[i].string;
+ argv[argc] = name;
+ argv[argc + 1] = NULL;
+ if (verbosity >= 0)
+ printf("Fetching %s\n", name);
+ if (run_command_v_opt(argv, RUN_GIT_CMD)) {
+ error("Could not fetch %s", name);
+ result = 1;
+ }
+ }
+
+ return result;
+}
+
+static int fetch_one(struct remote *remote, int argc, const char **argv)
+{
+ int i;
+ static const char **refs = NULL;
+ int ref_nr = 0;
+ int exit_code;
+
+ if (!remote)
+ die("Where do you want to fetch from today?");
+
+ transport = transport_get(remote, NULL);
+ if (verbosity >= 2)
+ transport->verbose = verbosity <= 3 ? verbosity : 3;
+ if (verbosity < 0)
+ transport->verbose = -1;
+ if (upload_pack)
+ set_option(TRANS_OPT_UPLOADPACK, upload_pack);
+ if (keep)
+ set_option(TRANS_OPT_KEEP, "yes");
+ if (depth)
+ set_option(TRANS_OPT_DEPTH, depth);
+
+ if (argc > 0) {
+ int j = 0;
+ refs = xcalloc(argc + 1, sizeof(const char *));
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "tag")) {
+ char *ref;
+ i++;
+ if (i >= argc)
+ die("You need to specify a tag name.");
+ ref = xmalloc(strlen(argv[i]) * 2 + 22);
+ strcpy(ref, "refs/tags/");
+ strcat(ref, argv[i]);
+ strcat(ref, ":refs/tags/");
+ strcat(ref, argv[i]);
+ refs[j++] = ref;
+ } else
+ refs[j++] = argv[i];
+ }
+ refs[j] = NULL;
+ ref_nr = j;
+ }
+
+ sigchain_push_common(unlock_pack_on_signal);
+ atexit(unlock_pack);
+ exit_code = do_fetch(transport,
+ parse_fetch_refspec(ref_nr, refs), ref_nr);
+ transport_disconnect(transport);
+ transport = NULL;
+ return exit_code;
+}
+
+int cmd_fetch(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct string_list list = { NULL, 0, 0, 0 };
+ struct remote *remote;
+ int result = 0;
+
+ /* Record the command line for the reflog */
+ strbuf_addstr(&default_rla, "fetch");
+ for (i = 1; i < argc; i++)
+ strbuf_addf(&default_rla, " %s", argv[i]);
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_fetch_options, builtin_fetch_usage, 0);
+
+ if (all) {
+ if (argc == 1)
+ die("fetch --all does not take a repository argument");
+ else if (argc > 1)
+ die("fetch --all does not make sense with refspecs");
+ (void) for_each_remote(get_one_remote_for_fetch, &list);
+ result = fetch_multiple(&list);
+ } else if (argc == 0) {
+ /* No arguments -- use default remote */
+ remote = remote_get(NULL);
+ result = fetch_one(remote, argc, argv);
+ } else if (multiple) {
+ /* All arguments are assumed to be remotes or groups */
+ for (i = 0; i < argc; i++)
+ if (!add_remote_or_group(argv[i], &list))
+ die("No such remote or remote group: %s", argv[i]);
+ result = fetch_multiple(&list);
+ } else {
+ /* Single remote or group */
+ (void) add_remote_or_group(argv[0], &list);
+ if (list.nr > 1) {
+ /* More than one remote */
+ if (argc > 1)
+ die("Fetching a group and specifying refspecs does not make sense");
+ result = fetch_multiple(&list);
+ } else {
+ /* Zero or one remotes */
+ remote = remote_get(argv[0]);
+ result = fetch_one(remote, argc-1, argv+1);
+ }
+ }
+
+ /* All names were strdup()ed or strndup()ed */
+ list.strdup_strings = 1;
+ string_list_clear(&list, 0);
+
+ return result;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "tag.h"
+
+static const char * const fmt_merge_msg_usage[] = {
+ "git fmt-merge-msg [--log|--no-log] [--file <file>]",
+ NULL
+};
+
+static int merge_summary;
+
+static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
+{
+ static int found_merge_log = 0;
+ if (!strcmp("merge.log", key)) {
+ found_merge_log = 1;
+ merge_summary = git_config_bool(key, value);
+ }
+ if (!found_merge_log && !strcmp("merge.summary", key))
+ merge_summary = git_config_bool(key, value);
+ return 0;
+}
+
+struct list {
+ char **list;
+ void **payload;
+ unsigned nr, alloc;
+};
+
+static void append_to_list(struct list *list, char *value, void *payload)
+{
+ if (list->nr == list->alloc) {
+ list->alloc += 32;
+ list->list = xrealloc(list->list, sizeof(char *) * list->alloc);
+ list->payload = xrealloc(list->payload,
+ sizeof(char *) * list->alloc);
+ }
+ list->payload[list->nr] = payload;
+ list->list[list->nr++] = value;
+}
+
+static int find_in_list(struct list *list, char *value)
+{
+ int i;
+
+ for (i = 0; i < list->nr; i++)
+ if (!strcmp(list->list[i], value))
+ return i;
+
+ return -1;
+}
+
+static void free_list(struct list *list)
+{
+ int i;
+
+ if (list->alloc == 0)
+ return;
+
+ for (i = 0; i < list->nr; i++) {
+ free(list->list[i]);
+ free(list->payload[i]);
+ }
+ free(list->list);
+ free(list->payload);
+ list->nr = list->alloc = 0;
+}
+
+struct src_data {
+ struct list branch, tag, r_branch, generic;
+ int head_status;
+};
+
+static struct list srcs = { NULL, NULL, 0, 0};
+static struct list origins = { NULL, NULL, 0, 0};
+
+static int handle_line(char *line)
+{
+ int i, len = strlen(line);
+ unsigned char *sha1;
+ char *src, *origin;
+ struct src_data *src_data;
+ int pulling_head = 0;
+
+ if (len < 43 || line[40] != '\t')
+ return 1;
+
+ if (!prefixcmp(line + 41, "not-for-merge"))
+ return 0;
+
+ if (line[41] != '\t')
+ return 2;
+
+ line[40] = 0;
+ sha1 = xmalloc(20);
+ i = get_sha1(line, sha1);
+ line[40] = '\t';
+ if (i)
+ return 3;
+
+ if (line[len - 1] == '\n')
+ line[len - 1] = 0;
+ line += 42;
+
+ src = strstr(line, " of ");
+ if (src) {
+ *src = 0;
+ src += 4;
+ pulling_head = 0;
+ } else {
+ src = line;
+ pulling_head = 1;
+ }
+
+ i = find_in_list(&srcs, src);
+ if (i < 0) {
+ i = srcs.nr;
+ append_to_list(&srcs, xstrdup(src),
+ xcalloc(1, sizeof(struct src_data)));
+ }
+ src_data = srcs.payload[i];
+
+ if (pulling_head) {
+ origin = xstrdup(src);
+ src_data->head_status |= 1;
+ } else if (!prefixcmp(line, "branch ")) {
+ origin = xstrdup(line + 7);
+ append_to_list(&src_data->branch, origin, NULL);
+ src_data->head_status |= 2;
+ } else if (!prefixcmp(line, "tag ")) {
+ origin = line;
+ append_to_list(&src_data->tag, xstrdup(origin + 4), NULL);
+ src_data->head_status |= 2;
+ } else if (!prefixcmp(line, "remote branch ")) {
+ origin = xstrdup(line + 14);
+ append_to_list(&src_data->r_branch, origin, NULL);
+ src_data->head_status |= 2;
+ } else {
+ origin = xstrdup(src);
+ append_to_list(&src_data->generic, xstrdup(line), NULL);
+ src_data->head_status |= 2;
+ }
+
+ if (!strcmp(".", src) || !strcmp(src, origin)) {
+ int len = strlen(origin);
+ if (origin[0] == '\'' && origin[len - 1] == '\'') {
+ origin = xmemdupz(origin + 1, len - 2);
+ } else {
+ origin = xstrdup(origin);
+ }
+ } else {
+ char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5);
+ sprintf(new_origin, "%s of %s", origin, src);
+ origin = new_origin;
+ }
+ append_to_list(&origins, origin, sha1);
+ return 0;
+}
+
+static void print_joined(const char *singular, const char *plural,
+ struct list *list, struct strbuf *out)
+{
+ if (list->nr == 0)
+ return;
+ if (list->nr == 1) {
+ strbuf_addf(out, "%s%s", singular, list->list[0]);
+ } else {
+ int i;
+ strbuf_addstr(out, plural);
+ for (i = 0; i < list->nr - 1; i++)
+ strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]);
+ strbuf_addf(out, " and %s", list->list[list->nr - 1]);
+ }
+}
+
+static void shortlog(const char *name, unsigned char *sha1,
+ struct commit *head, struct rev_info *rev, int limit,
+ struct strbuf *out)
+{
+ int i, count = 0;
+ struct commit *commit;
+ struct object *branch;
+ struct list subjects = { NULL, NULL, 0, 0 };
+ int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
+
+ branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
+ if (!branch || branch->type != OBJ_COMMIT)
+ return;
+
+ setup_revisions(0, NULL, rev, NULL);
+ rev->ignore_merges = 1;
+ add_pending_object(rev, branch, name);
+ add_pending_object(rev, &head->object, "^HEAD");
+ head->object.flags |= UNINTERESTING;
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
+ while ((commit = get_revision(rev)) != NULL) {
+ char *oneline, *bol, *eol;
+
+ /* ignore merges */
+ if (commit->parents && commit->parents->next)
+ continue;
+
+ count++;
+ if (subjects.nr > limit)
+ continue;
+
+ bol = strstr(commit->buffer, "\n\n");
+ if (bol) {
+ unsigned char c;
+ do {
+ c = *++bol;
+ } while (isspace(c));
+ if (!c)
+ bol = NULL;
+ }
+
+ if (!bol) {
+ append_to_list(&subjects, xstrdup(sha1_to_hex(
+ commit->object.sha1)),
+ NULL);
+ continue;
+ }
+
+ eol = strchr(bol, '\n');
+ if (eol) {
+ oneline = xmemdupz(bol, eol - bol);
+ } else {
+ oneline = xstrdup(bol);
+ }
+ append_to_list(&subjects, oneline, NULL);
+ }
+
+ if (count > limit)
+ strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
+ else
+ strbuf_addf(out, "\n* %s:\n", name);
+
+ for (i = 0; i < subjects.nr; i++)
+ if (i >= limit)
+ strbuf_addf(out, " ...\n");
+ else
+ strbuf_addf(out, " %s\n", subjects.list[i]);
+
+ clear_commit_marks((struct commit *)branch, flags);
+ clear_commit_marks(head, flags);
+ free_commit_list(rev->commits);
+ rev->commits = NULL;
+ rev->pending.nr = 0;
+
+ free_list(&subjects);
+}
+
+int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
+ int limit = 20, i = 0, pos = 0;
+ char *sep = "";
+ unsigned char head_sha1[20];
+ const char *current_branch;
+
+ /* get current branch */
+ current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
+ if (!current_branch)
+ die("No current branch");
+ if (!prefixcmp(current_branch, "refs/heads/"))
+ current_branch += 11;
+
+ /* get a line */
+ while (pos < in->len) {
+ int len;
+ char *newline, *p = in->buf + pos;
+
+ newline = strchr(p, '\n');
+ len = newline ? newline - p : strlen(p);
+ pos += len + !!newline;
+ i++;
+ p[len] = 0;
+ if (handle_line(p))
+ die ("Error in line %d: %.*s", i, len, p);
+ }
+
+ strbuf_addstr(out, "Merge ");
+ for (i = 0; i < srcs.nr; i++) {
+ struct src_data *src_data = srcs.payload[i];
+ const char *subsep = "";
+
+ strbuf_addstr(out, sep);
+ sep = "; ";
+
+ if (src_data->head_status == 1) {
+ strbuf_addstr(out, srcs.list[i]);
+ continue;
+ }
+ if (src_data->head_status == 3) {
+ subsep = ", ";
+ strbuf_addstr(out, "HEAD");
+ }
+ if (src_data->branch.nr) {
+ strbuf_addstr(out, subsep);
+ subsep = ", ";
+ print_joined("branch ", "branches ", &src_data->branch,
+ out);
+ }
+ if (src_data->r_branch.nr) {
+ strbuf_addstr(out, subsep);
+ subsep = ", ";
+ print_joined("remote branch ", "remote branches ",
+ &src_data->r_branch, out);
+ }
+ if (src_data->tag.nr) {
+ strbuf_addstr(out, subsep);
+ subsep = ", ";
+ print_joined("tag ", "tags ", &src_data->tag, out);
+ }
+ if (src_data->generic.nr) {
+ strbuf_addstr(out, subsep);
+ print_joined("commit ", "commits ", &src_data->generic,
+ out);
+ }
+ if (strcmp(".", srcs.list[i]))
+ strbuf_addf(out, " of %s", srcs.list[i]);
+ }
+
+ if (!strcmp("master", current_branch))
+ strbuf_addch(out, '\n');
+ else
+ strbuf_addf(out, " into %s\n", current_branch);
+
+ if (merge_summary) {
+ struct commit *head;
+ struct rev_info rev;
+
+ head = lookup_commit(head_sha1);
+ init_revisions(&rev, NULL);
+ rev.commit_format = CMIT_FMT_ONELINE;
+ rev.ignore_merges = 1;
+ rev.limited = 1;
+
+ for (i = 0; i < origins.nr; i++)
+ shortlog(origins.list[i], origins.payload[i],
+ head, &rev, limit, out);
+ }
+ return 0;
+}
+
+int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
+{
+ const char *inpath = NULL;
+ struct option options[] = {
+ OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"),
+ OPT_BOOLEAN(0, "summary", &merge_summary, "alias for --log"),
+ OPT_FILENAME('F', "file", &inpath, "file to read from"),
+ OPT_END()
+ };
+
+ FILE *in = stdin;
+ struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
+ int ret;
+
+ git_config(fmt_merge_msg_config, NULL);
+ argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
+ 0);
+ if (argc > 0)
+ usage_with_options(fmt_merge_msg_usage, options);
+
+ if (inpath && strcmp(inpath, "-")) {
+ in = fopen(inpath, "r");
+ if (!in)
+ die_errno("cannot open '%s'", inpath);
+ }
+
+ if (strbuf_read(&input, fileno(in), 0) < 0)
+ die_errno("could not read input file");
+ ret = fmt_merge_msg(merge_summary, &input, &output);
+ if (ret)
+ return ret;
+ write_in_full(STDOUT_FILENO, output.buf, output.len);
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "quote.h"
+#include "parse-options.h"
+#include "remote.h"
+
+/* Quoting styles */
+#define QUOTE_NONE 0
+#define QUOTE_SHELL 1
+#define QUOTE_PERL 2
+#define QUOTE_PYTHON 4
+#define QUOTE_TCL 8
+
+typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+
+struct atom_value {
+ const char *s;
+ unsigned long ul; /* used for sorting when not FIELD_STR */
+};
+
+struct ref_sort {
+ struct ref_sort *next;
+ int atom; /* index into used_atom array */
+ unsigned reverse : 1;
+};
+
+struct refinfo {
+ char *refname;
+ unsigned char objectname[20];
+ int flag;
+ const char *symref;
+ struct atom_value *value;
+};
+
+static struct {
+ const char *name;
+ cmp_type cmp_type;
+} valid_atom[] = {
+ { "refname" },
+ { "objecttype" },
+ { "objectsize", FIELD_ULONG },
+ { "objectname" },
+ { "tree" },
+ { "parent" },
+ { "numparent", FIELD_ULONG },
+ { "object" },
+ { "type" },
+ { "tag" },
+ { "author" },
+ { "authorname" },
+ { "authoremail" },
+ { "authordate", FIELD_TIME },
+ { "committer" },
+ { "committername" },
+ { "committeremail" },
+ { "committerdate", FIELD_TIME },
+ { "tagger" },
+ { "taggername" },
+ { "taggeremail" },
+ { "taggerdate", FIELD_TIME },
+ { "creator" },
+ { "creatordate", FIELD_TIME },
+ { "subject" },
+ { "body" },
+ { "contents" },
+ { "upstream" },
+ { "symref" },
+ { "flag" },
+};
+
+/*
+ * An atom is a valid field atom listed above, possibly prefixed with
+ * a "*" to denote deref_tag().
+ *
+ * We parse given format string and sort specifiers, and make a list
+ * of properties that we need to extract out of objects. refinfo
+ * structure will hold an array of values extracted that can be
+ * indexed with the "atom number", which is an index into this
+ * array.
+ */
+static const char **used_atom;
+static cmp_type *used_atom_type;
+static int used_atom_cnt, sort_atom_limit, need_tagged, need_symref;
+
+/*
+ * Used to parse format string and sort specifiers
+ */
+static int parse_atom(const char *atom, const char *ep)
+{
+ const char *sp;
+ int i, at;
+
+ sp = atom;
+ if (*sp == '*' && sp < ep)
+ sp++; /* deref */
+ if (ep <= sp)
+ die("malformed field name: %.*s", (int)(ep-atom), atom);
+
+ /* Do we have the atom already used elsewhere? */
+ for (i = 0; i < used_atom_cnt; i++) {
+ int len = strlen(used_atom[i]);
+ if (len == ep - atom && !memcmp(used_atom[i], atom, len))
+ return i;
+ }
+
+ /* Is the atom a valid one? */
+ for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
+ int len = strlen(valid_atom[i].name);
+ /*
+ * If the atom name has a colon, strip it and everything after
+ * it off - it specifies the format for this entry, and
+ * shouldn't be used for checking against the valid_atom
+ * table.
+ */
+ const char *formatp = strchr(sp, ':');
+ if (!formatp || ep < formatp)
+ formatp = ep;
+ if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len))
+ break;
+ }
+
+ if (ARRAY_SIZE(valid_atom) <= i)
+ die("unknown field name: %.*s", (int)(ep-atom), atom);
+
+ /* Add it in, including the deref prefix */
+ at = used_atom_cnt;
+ used_atom_cnt++;
+ used_atom = xrealloc(used_atom,
+ (sizeof *used_atom) * used_atom_cnt);
+ used_atom_type = xrealloc(used_atom_type,
+ (sizeof(*used_atom_type) * used_atom_cnt));
+ used_atom[at] = xmemdupz(atom, ep - atom);
+ used_atom_type[at] = valid_atom[i].cmp_type;
+ if (*atom == '*')
+ need_tagged = 1;
+ if (!strcmp(used_atom[at], "symref"))
+ need_symref = 1;
+ return at;
+}
+
+/*
+ * In a format string, find the next occurrence of %(atom).
+ */
+static const char *find_next(const char *cp)
+{
+ while (*cp) {
+ if (*cp == '%') {
+ /*
+ * %( is the start of an atom;
+ * %% is a quoted per-cent.
+ */
+ if (cp[1] == '(')
+ return cp;
+ else if (cp[1] == '%')
+ cp++; /* skip over two % */
+ /* otherwise this is a singleton, literal % */
+ }
+ cp++;
+ }
+ return NULL;
+}
+
+/*
+ * Make sure the format string is well formed, and parse out
+ * the used atoms.
+ */
+static int verify_format(const char *format)
+{
+ const char *cp, *sp;
+ for (cp = format; *cp && (sp = find_next(cp)); ) {
+ const char *ep = strchr(sp, ')');
+ if (!ep)
+ return error("malformed format string %s", sp);
+ /* sp points at "%(" and ep points at the closing ")" */
+ parse_atom(sp + 2, ep);
+ cp = ep + 1;
+ }
+ return 0;
+}
+
+/*
+ * Given an object name, read the object data and size, and return a
+ * "struct object". If the object data we are returning is also borrowed
+ * by the "struct object" representation, set *eaten as well---it is a
+ * signal from parse_object_buffer to us not to free the buffer.
+ */
+static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
+{
+ enum object_type type;
+ void *buf = read_sha1_file(sha1, &type, sz);
+
+ if (buf)
+ *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
+ else
+ *obj = NULL;
+ return buf;
+}
+
+/* See grab_values */
+static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "objecttype"))
+ v->s = typename(obj->type);
+ else if (!strcmp(name, "objectsize")) {
+ char *s = xmalloc(40);
+ sprintf(s, "%lu", sz);
+ v->ul = sz;
+ v->s = s;
+ }
+ else if (!strcmp(name, "objectname")) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(obj->sha1));
+ v->s = s;
+ }
+ }
+}
+
+/* See grab_values */
+static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ struct tag *tag = (struct tag *) obj;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "tag"))
+ v->s = tag->tag;
+ else if (!strcmp(name, "type") && tag->tagged)
+ v->s = typename(tag->tagged->type);
+ else if (!strcmp(name, "object") && tag->tagged) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(tag->tagged->sha1));
+ v->s = s;
+ }
+ }
+}
+
+static int num_parents(struct commit *commit)
+{
+ struct commit_list *parents;
+ int i;
+
+ for (i = 0, parents = commit->parents;
+ parents;
+ parents = parents->next)
+ i++;
+ return i;
+}
+
+/* See grab_values */
+static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ struct commit *commit = (struct commit *) obj;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "tree")) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(commit->tree->object.sha1));
+ v->s = s;
+ }
+ if (!strcmp(name, "numparent")) {
+ char *s = xmalloc(40);
+ v->ul = num_parents(commit);
+ sprintf(s, "%lu", v->ul);
+ v->s = s;
+ }
+ else if (!strcmp(name, "parent")) {
+ int num = num_parents(commit);
+ int i;
+ struct commit_list *parents;
+ char *s = xmalloc(41 * num + 1);
+ v->s = s;
+ for (i = 0, parents = commit->parents;
+ parents;
+ parents = parents->next, i = i + 41) {
+ struct commit *parent = parents->item;
+ strcpy(s+i, sha1_to_hex(parent->object.sha1));
+ if (parents->next)
+ s[i+40] = ' ';
+ }
+ if (!i)
+ *s = '\0';
+ }
+ }
+}
+
+static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
+{
+ const char *eol;
+ while (*buf) {
+ if (!strncmp(buf, who, wholen) &&
+ buf[wholen] == ' ')
+ return buf + wholen + 1;
+ eol = strchr(buf, '\n');
+ if (!eol)
+ return "";
+ eol++;
+ if (*eol == '\n')
+ return ""; /* end of header */
+ buf = eol;
+ }
+ return "";
+}
+
+static const char *copy_line(const char *buf)
+{
+ const char *eol = strchrnul(buf, '\n');
+ return xmemdupz(buf, eol - buf);
+}
+
+static const char *copy_name(const char *buf)
+{
+ const char *cp;
+ for (cp = buf; *cp && *cp != '\n'; cp++) {
+ if (!strncmp(cp, " <", 2))
+ return xmemdupz(buf, cp - buf);
+ }
+ return "";
+}
+
+static const char *copy_email(const char *buf)
+{
+ const char *email = strchr(buf, '<');
+ const char *eoemail;
+ if (!email)
+ return "";
+ eoemail = strchr(email, '>');
+ if (!eoemail)
+ return "";
+ return xmemdupz(email, eoemail + 1 - email);
+}
+
+static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
+{
+ const char *eoemail = strstr(buf, "> ");
+ char *zone;
+ unsigned long timestamp;
+ long tz;
+ enum date_mode date_mode = DATE_NORMAL;
+ const char *formatp;
+
+ /*
+ * We got here because atomname ends in "date" or "date<something>";
+ * it's not possible that <something> is not ":<format>" because
+ * parse_atom() wouldn't have allowed it, so we can assume that no
+ * ":" means no format is specified, and use the default.
+ */
+ formatp = strchr(atomname, ':');
+ if (formatp != NULL) {
+ formatp++;
+ date_mode = parse_date_format(formatp);
+ }
+
+ if (!eoemail)
+ goto bad;
+ timestamp = strtoul(eoemail + 2, &zone, 10);
+ if (timestamp == ULONG_MAX)
+ goto bad;
+ tz = strtol(zone, NULL, 10);
+ if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
+ goto bad;
+ v->s = xstrdup(show_date(timestamp, tz, date_mode));
+ v->ul = timestamp;
+ return;
+ bad:
+ v->s = "";
+ v->ul = 0;
+}
+
+/* See grab_values */
+static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ int wholen = strlen(who);
+ const char *wholine = NULL;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (strncmp(who, name, wholen))
+ continue;
+ if (name[wholen] != 0 &&
+ strcmp(name + wholen, "name") &&
+ strcmp(name + wholen, "email") &&
+ prefixcmp(name + wholen, "date"))
+ continue;
+ if (!wholine)
+ wholine = find_wholine(who, wholen, buf, sz);
+ if (!wholine)
+ return; /* no point looking for it */
+ if (name[wholen] == 0)
+ v->s = copy_line(wholine);
+ else if (!strcmp(name + wholen, "name"))
+ v->s = copy_name(wholine);
+ else if (!strcmp(name + wholen, "email"))
+ v->s = copy_email(wholine);
+ else if (!prefixcmp(name + wholen, "date"))
+ grab_date(wholine, v, name);
+ }
+
+ /*
+ * For a tag or a commit object, if "creator" or "creatordate" is
+ * requested, do something special.
+ */
+ if (strcmp(who, "tagger") && strcmp(who, "committer"))
+ return; /* "author" for commit object is not wanted */
+ if (!wholine)
+ wholine = find_wholine(who, wholen, buf, sz);
+ if (!wholine)
+ return;
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+
+ if (!prefixcmp(name, "creatordate"))
+ grab_date(wholine, v, name);
+ else if (!strcmp(name, "creator"))
+ v->s = copy_line(wholine);
+ }
+}
+
+static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+{
+ while (*buf) {
+ const char *eol = strchr(buf, '\n');
+ if (!eol)
+ return;
+ if (eol[1] == '\n') {
+ buf = eol + 1;
+ break; /* found end of header */
+ }
+ buf = eol + 1;
+ }
+ while (*buf == '\n')
+ buf++;
+ if (!*buf)
+ return;
+ *sub = buf; /* first non-empty line */
+ buf = strchr(buf, '\n');
+ if (!buf) {
+ *body = "";
+ return; /* no body */
+ }
+ while (*buf == '\n')
+ buf++; /* skip blank between subject and body */
+ *body = buf;
+}
+
+/* See grab_values */
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ const char *subpos = NULL, *bodypos = NULL;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (strcmp(name, "subject") &&
+ strcmp(name, "body") &&
+ strcmp(name, "contents"))
+ continue;
+ if (!subpos)
+ find_subpos(buf, sz, &subpos, &bodypos);
+ if (!subpos)
+ return;
+
+ if (!strcmp(name, "subject"))
+ v->s = copy_line(subpos);
+ else if (!strcmp(name, "body"))
+ v->s = xstrdup(bodypos);
+ else if (!strcmp(name, "contents"))
+ v->s = xstrdup(subpos);
+ }
+}
+
+/*
+ * We want to have empty print-string for field requests
+ * that do not apply (e.g. "authordate" for a tag object)
+ */
+static void fill_missing_values(struct atom_value *val)
+{
+ int i;
+ for (i = 0; i < used_atom_cnt; i++) {
+ struct atom_value *v = &val[i];
+ if (v->s == NULL)
+ v->s = "";
+ }
+}
+
+/*
+ * val is a list of atom_value to hold returned values. Extract
+ * the values for atoms in used_atom array out of (obj, buf, sz).
+ * when deref is false, (obj, buf, sz) is the object that is
+ * pointed at by the ref itself; otherwise it is the object the
+ * ref (which is a tag) refers to.
+ */
+static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ grab_common_values(val, deref, obj, buf, sz);
+ switch (obj->type) {
+ case OBJ_TAG:
+ grab_tag_values(val, deref, obj, buf, sz);
+ grab_sub_body_contents(val, deref, obj, buf, sz);
+ grab_person("tagger", val, deref, obj, buf, sz);
+ break;
+ case OBJ_COMMIT:
+ grab_commit_values(val, deref, obj, buf, sz);
+ grab_sub_body_contents(val, deref, obj, buf, sz);
+ grab_person("author", val, deref, obj, buf, sz);
+ grab_person("committer", val, deref, obj, buf, sz);
+ break;
+ case OBJ_TREE:
+ // grab_tree_values(val, deref, obj, buf, sz);
+ break;
+ case OBJ_BLOB:
+ // grab_blob_values(val, deref, obj, buf, sz);
+ break;
+ default:
+ die("Eh? Object of type %d?", obj->type);
+ }
+}
+
+static inline char *copy_advance(char *dst, const char *src)
+{
+ while (*src)
+ *dst++ = *src++;
+ return dst;
+}
+
+/*
+ * Parse the object referred by ref, and grab needed value.
+ */
+static void populate_value(struct refinfo *ref)
+{
+ void *buf;
+ struct object *obj;
+ int eaten, i;
+ unsigned long size;
+ const unsigned char *tagged;
+
+ ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
+
+ if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
+ unsigned char unused1[20];
+ const char *symref;
+ symref = resolve_ref(ref->refname, unused1, 1, NULL);
+ if (symref)
+ ref->symref = xstrdup(symref);
+ else
+ ref->symref = "";
+ }
+
+ /* Fill in specials first */
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &ref->value[i];
+ int deref = 0;
+ const char *refname;
+ const char *formatp;
+
+ if (*name == '*') {
+ deref = 1;
+ name++;
+ }
+
+ if (!prefixcmp(name, "refname"))
+ refname = ref->refname;
+ else if (!prefixcmp(name, "symref"))
+ refname = ref->symref ? ref->symref : "";
+ else if (!prefixcmp(name, "upstream")) {
+ struct branch *branch;
+ /* only local branches may have an upstream */
+ if (prefixcmp(ref->refname, "refs/heads/"))
+ continue;
+ branch = branch_get(ref->refname + 11);
+
+ if (!branch || !branch->merge || !branch->merge[0] ||
+ !branch->merge[0]->dst)
+ continue;
+ refname = branch->merge[0]->dst;
+ }
+ else if (!strcmp(name, "flag")) {
+ char buf[256], *cp = buf;
+ if (ref->flag & REF_ISSYMREF)
+ cp = copy_advance(cp, ",symref");
+ if (ref->flag & REF_ISPACKED)
+ cp = copy_advance(cp, ",packed");
+ if (cp == buf)
+ v->s = "";
+ else {
+ *cp = '\0';
+ v->s = xstrdup(buf + 1);
+ }
+ continue;
+ }
+ else
+ continue;
+
+ formatp = strchr(name, ':');
+ /* look for "short" refname format */
+ if (formatp) {
+ formatp++;
+ if (!strcmp(formatp, "short"))
+ refname = shorten_unambiguous_ref(refname,
+ warn_ambiguous_refs);
+ else
+ die("unknown %.*s format %s",
+ (int)(formatp - name), name, formatp);
+ }
+
+ if (!deref)
+ v->s = refname;
+ else {
+ int len = strlen(refname);
+ char *s = xmalloc(len + 4);
+ sprintf(s, "%s^{}", refname);
+ v->s = s;
+ }
+ }
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ struct atom_value *v = &ref->value[i];
+ if (v->s == NULL)
+ goto need_obj;
+ }
+ return;
+
+ need_obj:
+ buf = get_obj(ref->objectname, &obj, &size, &eaten);
+ if (!buf)
+ die("missing object %s for %s",
+ sha1_to_hex(ref->objectname), ref->refname);
+ if (!obj)
+ die("parse_object_buffer failed on %s for %s",
+ sha1_to_hex(ref->objectname), ref->refname);
+
+ grab_values(ref->value, 0, obj, buf, size);
+ if (!eaten)
+ free(buf);
+
+ /*
+ * If there is no atom that wants to know about tagged
+ * object, we are done.
+ */
+ if (!need_tagged || (obj->type != OBJ_TAG))
+ return;
+
+ /*
+ * If it is a tag object, see if we use a value that derefs
+ * the object, and if we do grab the object it refers to.
+ */
+ tagged = ((struct tag *)obj)->tagged->sha1;
+
+ /*
+ * NEEDSWORK: This derefs tag only once, which
+ * is good to deal with chains of trust, but
+ * is not consistent with what deref_tag() does
+ * which peels the onion to the core.
+ */
+ buf = get_obj(tagged, &obj, &size, &eaten);
+ if (!buf)
+ die("missing object %s for %s",
+ sha1_to_hex(tagged), ref->refname);
+ if (!obj)
+ die("parse_object_buffer failed on %s for %s",
+ sha1_to_hex(tagged), ref->refname);
+ grab_values(ref->value, 1, obj, buf, size);
+ if (!eaten)
+ free(buf);
+}
+
+/*
+ * Given a ref, return the value for the atom. This lazily gets value
+ * out of the object by calling populate value.
+ */
+static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
+{
+ if (!ref->value) {
+ populate_value(ref);
+ fill_missing_values(ref->value);
+ }
+ *v = &ref->value[atom];
+}
+
+struct grab_ref_cbdata {
+ struct refinfo **grab_array;
+ const char **grab_pattern;
+ int grab_cnt;
+};
+
+/*
+ * A call-back given to for_each_ref(). Filter refs and keep them for
+ * later object processing.
+ */
+static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ struct grab_ref_cbdata *cb = cb_data;
+ struct refinfo *ref;
+ int cnt;
+
+ if (*cb->grab_pattern) {
+ const char **pattern;
+ int namelen = strlen(refname);
+ for (pattern = cb->grab_pattern; *pattern; pattern++) {
+ const char *p = *pattern;
+ int plen = strlen(p);
+
+ if ((plen <= namelen) &&
+ !strncmp(refname, p, plen) &&
+ (refname[plen] == '\0' ||
+ refname[plen] == '/' ||
+ p[plen-1] == '/'))
+ break;
+ if (!fnmatch(p, refname, FNM_PATHNAME))
+ break;
+ }
+ if (!*pattern)
+ return 0;
+ }
+
+ /*
+ * We do not open the object yet; sort may only need refname
+ * to do its job and the resulting list may yet to be pruned
+ * by maxcount logic.
+ */
+ ref = xcalloc(1, sizeof(*ref));
+ ref->refname = xstrdup(refname);
+ hashcpy(ref->objectname, sha1);
+ ref->flag = flag;
+
+ cnt = cb->grab_cnt;
+ cb->grab_array = xrealloc(cb->grab_array,
+ sizeof(*cb->grab_array) * (cnt + 1));
+ cb->grab_array[cnt++] = ref;
+ cb->grab_cnt = cnt;
+ return 0;
+}
+
+static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
+{
+ struct atom_value *va, *vb;
+ int cmp;
+ cmp_type cmp_type = used_atom_type[s->atom];
+
+ get_value(a, s->atom, &va);
+ get_value(b, s->atom, &vb);
+ switch (cmp_type) {
+ case FIELD_STR:
+ cmp = strcmp(va->s, vb->s);
+ break;
+ default:
+ if (va->ul < vb->ul)
+ cmp = -1;
+ else if (va->ul == vb->ul)
+ cmp = 0;
+ else
+ cmp = 1;
+ break;
+ }
+ return (s->reverse) ? -cmp : cmp;
+}
+
+static struct ref_sort *ref_sort;
+static int compare_refs(const void *a_, const void *b_)
+{
+ struct refinfo *a = *((struct refinfo **)a_);
+ struct refinfo *b = *((struct refinfo **)b_);
+ struct ref_sort *s;
+
+ for (s = ref_sort; s; s = s->next) {
+ int cmp = cmp_ref_sort(s, a, b);
+ if (cmp)
+ return cmp;
+ }
+ return 0;
+}
+
+static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
+{
+ ref_sort = sort;
+ qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
+}
+
+static void print_value(struct refinfo *ref, int atom, int quote_style)
+{
+ struct atom_value *v;
+ get_value(ref, atom, &v);
+ switch (quote_style) {
+ case QUOTE_NONE:
+ fputs(v->s, stdout);
+ break;
+ case QUOTE_SHELL:
+ sq_quote_print(stdout, v->s);
+ break;
+ case QUOTE_PERL:
+ perl_quote_print(stdout, v->s);
+ break;
+ case QUOTE_PYTHON:
+ python_quote_print(stdout, v->s);
+ break;
+ case QUOTE_TCL:
+ tcl_quote_print(stdout, v->s);
+ break;
+ }
+}
+
+static int hex1(char ch)
+{
+ if ('0' <= ch && ch <= '9')
+ return ch - '0';
+ else if ('a' <= ch && ch <= 'f')
+ return ch - 'a' + 10;
+ else if ('A' <= ch && ch <= 'F')
+ return ch - 'A' + 10;
+ return -1;
+}
+static int hex2(const char *cp)
+{
+ if (cp[0] && cp[1])
+ return (hex1(cp[0]) << 4) | hex1(cp[1]);
+ else
+ return -1;
+}
+
+static void emit(const char *cp, const char *ep)
+{
+ while (*cp && (!ep || cp < ep)) {
+ if (*cp == '%') {
+ if (cp[1] == '%')
+ cp++;
+ else {
+ int ch = hex2(cp + 1);
+ if (0 <= ch) {
+ putchar(ch);
+ cp += 3;
+ continue;
+ }
+ }
+ }
+ putchar(*cp);
+ cp++;
+ }
+}
+
+static void show_ref(struct refinfo *info, const char *format, int quote_style)
+{
+ const char *cp, *sp, *ep;
+
+ for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+ ep = strchr(sp, ')');
+ if (cp < sp)
+ emit(cp, sp);
+ print_value(info, parse_atom(sp + 2, ep), quote_style);
+ }
+ if (*cp) {
+ sp = cp + strlen(cp);
+ emit(cp, sp);
+ }
+ putchar('\n');
+}
+
+static struct ref_sort *default_sort(void)
+{
+ static const char cstr_name[] = "refname";
+
+ struct ref_sort *sort = xcalloc(1, sizeof(*sort));
+
+ sort->next = NULL;
+ sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
+ return sort;
+}
+
+static int opt_parse_sort(const struct option *opt, const char *arg, int unset)
+{
+ struct ref_sort **sort_tail = opt->value;
+ struct ref_sort *s;
+ int len;
+
+ if (!arg) /* should --no-sort void the list ? */
+ return -1;
+
+ *sort_tail = s = xcalloc(1, sizeof(*s));
+
+ if (*arg == '-') {
+ s->reverse = 1;
+ arg++;
+ }
+ len = strlen(arg);
+ s->atom = parse_atom(arg, arg+len);
+ return 0;
+}
+
+static char const * const for_each_ref_usage[] = {
+ "git for-each-ref [options] [<pattern>]",
+ NULL
+};
+
+int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
+{
+ int i, num_refs;
+ const char *format = "%(objectname) %(objecttype)\t%(refname)";
+ struct ref_sort *sort = NULL, **sort_tail = &sort;
+ int maxcount = 0, quote_style = 0;
+ struct refinfo **refs;
+ struct grab_ref_cbdata cbdata;
+
+ struct option opts[] = {
+ OPT_BIT('s', "shell", "e_style,
+ "quote placeholders suitably for shells", QUOTE_SHELL),
+ OPT_BIT('p', "perl", "e_style,
+ "quote placeholders suitably for perl", QUOTE_PERL),
+ OPT_BIT(0 , "python", "e_style,
+ "quote placeholders suitably for python", QUOTE_PYTHON),
+ OPT_BIT(0 , "tcl", "e_style,
+ "quote placeholders suitably for tcl", QUOTE_TCL),
+
+ OPT_GROUP(""),
+ OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"),
+ OPT_STRING( 0 , "format", &format, "format", "format to use for the output"),
+ OPT_CALLBACK(0 , "sort", sort_tail, "key",
+ "field name to sort on", &opt_parse_sort),
+ OPT_END(),
+ };
+
+ parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
+ if (maxcount < 0) {
+ error("invalid --count argument: `%d'", maxcount);
+ usage_with_options(for_each_ref_usage, opts);
+ }
+ if (HAS_MULTI_BITS(quote_style)) {
+ error("more than one quoting style?");
+ usage_with_options(for_each_ref_usage, opts);
+ }
+ if (verify_format(format))
+ usage_with_options(for_each_ref_usage, opts);
+
+ if (!sort)
+ sort = default_sort();
+ sort_atom_limit = used_atom_cnt;
+
+ /* for warn_ambiguous_refs */
+ git_config(git_default_config, NULL);
+
+ memset(&cbdata, 0, sizeof(cbdata));
+ cbdata.grab_pattern = argv;
+ for_each_rawref(grab_single_ref, &cbdata);
+ refs = cbdata.grab_array;
+ num_refs = cbdata.grab_cnt;
+
+ sort_refs(sort, refs, num_refs);
+
+ if (!maxcount || num_refs < maxcount)
+ maxcount = num_refs;
+ for (i = 0; i < maxcount; i++)
+ show_ref(refs[i], format, quote_style);
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tag.h"
+#include "refs.h"
+#include "pack.h"
+#include "cache-tree.h"
+#include "tree-walk.h"
+#include "fsck.h"
+#include "parse-options.h"
+#include "dir.h"
+
+#define REACHABLE 0x0001
+#define SEEN 0x0002
+
+static int show_root;
+static int show_tags;
+static int show_unreachable;
+static int include_reflogs = 1;
+static int check_full = 1;
+static int check_strict;
+static int keep_cache_objects;
+static unsigned char head_sha1[20];
+static const char *head_points_at;
+static int errors_found;
+static int write_lost_and_found;
+static int verbose;
+#define ERROR_OBJECT 01
+#define ERROR_REACHABLE 02
+
+#ifdef NO_D_INO_IN_DIRENT
+#define SORT_DIRENT 0
+#define DIRENT_SORT_HINT(de) 0
+#else
+#define SORT_DIRENT 1
+#define DIRENT_SORT_HINT(de) ((de)->d_ino)
+#endif
+
+static void objreport(struct object *obj, const char *severity,
+ const char *err, va_list params)
+{
+ fprintf(stderr, "%s in %s %s: ",
+ severity, typename(obj->type), sha1_to_hex(obj->sha1));
+ vfprintf(stderr, err, params);
+ fputs("\n", stderr);
+}
+
+__attribute__((format (printf, 2, 3)))
+static int objerror(struct object *obj, const char *err, ...)
+{
+ va_list params;
+ va_start(params, err);
+ errors_found |= ERROR_OBJECT;
+ objreport(obj, "error", err, params);
+ va_end(params);
+ return -1;
+}
+
+__attribute__((format (printf, 3, 4)))
+static int fsck_error_func(struct object *obj, int type, const char *err, ...)
+{
+ va_list params;
+ va_start(params, err);
+ objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params);
+ va_end(params);
+ return (type == FSCK_WARN) ? 0 : 1;
+}
+
+static struct object_array pending;
+
+static int mark_object(struct object *obj, int type, void *data)
+{
+ struct object *parent = data;
+
+ if (!obj) {
+ printf("broken link from %7s %s\n",
+ typename(parent->type), sha1_to_hex(parent->sha1));
+ printf("broken link from %7s %s\n",
+ (type == OBJ_ANY ? "unknown" : typename(type)), "unknown");
+ errors_found |= ERROR_REACHABLE;
+ return 1;
+ }
+
+ if (type != OBJ_ANY && obj->type != type)
+ objerror(parent, "wrong object type in link");
+
+ if (obj->flags & REACHABLE)
+ return 0;
+ obj->flags |= REACHABLE;
+ if (!obj->parsed) {
+ if (parent && !has_sha1_file(obj->sha1)) {
+ printf("broken link from %7s %s\n",
+ typename(parent->type), sha1_to_hex(parent->sha1));
+ printf(" to %7s %s\n",
+ typename(obj->type), sha1_to_hex(obj->sha1));
+ errors_found |= ERROR_REACHABLE;
+ }
+ return 1;
+ }
+
+ add_object_array(obj, (void *) parent, &pending);
+ return 0;
+}
+
+static void mark_object_reachable(struct object *obj)
+{
+ mark_object(obj, OBJ_ANY, NULL);
+}
+
+static int traverse_one_object(struct object *obj, struct object *parent)
+{
+ int result;
+ struct tree *tree = NULL;
+
+ if (obj->type == OBJ_TREE) {
+ obj->parsed = 0;
+ tree = (struct tree *)obj;
+ if (parse_tree(tree) < 0)
+ return 1; /* error already displayed */
+ }
+ result = fsck_walk(obj, mark_object, obj);
+ if (tree) {
+ free(tree->buffer);
+ tree->buffer = NULL;
+ }
+ return result;
+}
+
+static int traverse_reachable(void)
+{
+ int result = 0;
+ while (pending.nr) {
+ struct object_array_entry *entry;
+ struct object *obj, *parent;
+
+ entry = pending.objects + --pending.nr;
+ obj = entry->item;
+ parent = (struct object *) entry->name;
+ result |= traverse_one_object(obj, parent);
+ }
+ return !!result;
+}
+
+static int mark_used(struct object *obj, int type, void *data)
+{
+ if (!obj)
+ return 1;
+ obj->used = 1;
+ return 0;
+}
+
+/*
+ * Check a single reachable object
+ */
+static void check_reachable_object(struct object *obj)
+{
+ /*
+ * We obviously want the object to be parsed,
+ * except if it was in a pack-file and we didn't
+ * do a full fsck
+ */
+ if (!obj->parsed) {
+ if (has_sha1_pack(obj->sha1))
+ return; /* it is in pack - forget about it */
+ printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
+ errors_found |= ERROR_REACHABLE;
+ return;
+ }
+}
+
+/*
+ * Check a single unreachable object
+ */
+static void check_unreachable_object(struct object *obj)
+{
+ /*
+ * Missing unreachable object? Ignore it. It's not like
+ * we miss it (since it can't be reached), nor do we want
+ * to complain about it being unreachable (since it does
+ * not exist).
+ */
+ if (!obj->parsed)
+ return;
+
+ /*
+ * Unreachable object that exists? Show it if asked to,
+ * since this is something that is prunable.
+ */
+ if (show_unreachable) {
+ printf("unreachable %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
+ return;
+ }
+
+ /*
+ * "!used" means that nothing at all points to it, including
+ * other unreachable objects. In other words, it's the "tip"
+ * of some set of unreachable objects, usually a commit that
+ * got dropped.
+ *
+ * Such starting points are more interesting than some random
+ * set of unreachable objects, so we show them even if the user
+ * hasn't asked for _all_ unreachable objects. If you have
+ * deleted a branch by mistake, this is a prime candidate to
+ * start looking at, for example.
+ */
+ 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_errno("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) {
+ if (fwrite(buf, size, 1, f) != 1)
+ die_errno("Could not write '%s'",
+ filename);
+ free(buf);
+ }
+ } else
+ fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
+ if (fclose(f))
+ die_errno("Could not finish '%s'",
+ filename);
+ }
+ return;
+ }
+
+ /*
+ * Otherwise? It's there, it's unreachable, and some other unreachable
+ * object points to it. Ignore it - it's not interesting, and we showed
+ * all the interesting cases above.
+ */
+}
+
+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
+ check_unreachable_object(obj);
+}
+
+static void check_connectivity(void)
+{
+ int i, max;
+
+ /* Traverse the pending reachable objects */
+ traverse_reachable();
+
+ /* 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);
+
+ if (obj)
+ check_object(obj);
+ }
+}
+
+static int fsck_sha1(const unsigned char *sha1)
+{
+ struct object *obj = parse_object(sha1);
+ if (!obj) {
+ errors_found |= ERROR_OBJECT;
+ return error("%s: object corrupt or missing",
+ sha1_to_hex(sha1));
+ }
+ if (obj->flags & SEEN)
+ return 0;
+ obj->flags |= SEEN;
+
+ if (verbose)
+ fprintf(stderr, "Checking %s %s\n",
+ typename(obj->type), sha1_to_hex(obj->sha1));
+
+ if (fsck_walk(obj, mark_used, NULL))
+ objerror(obj, "broken links");
+ if (fsck_object(obj, check_strict, fsck_error_func))
+ return -1;
+
+ if (obj->type == OBJ_TREE) {
+ struct tree *item = (struct tree *) obj;
+
+ free(item->buffer);
+ item->buffer = NULL;
+ }
+
+ if (obj->type == OBJ_COMMIT) {
+ struct commit *commit = (struct commit *) obj;
+
+ free(commit->buffer);
+ commit->buffer = NULL;
+
+ if (!commit->parents && show_root)
+ printf("root %s\n", sha1_to_hex(commit->object.sha1));
+ }
+
+ if (obj->type == OBJ_TAG) {
+ struct tag *tag = (struct tag *) obj;
+
+ if (show_tags && tag->tagged) {
+ printf("tagged %s %s", typename(tag->tagged->type), sha1_to_hex(tag->tagged->sha1));
+ printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1));
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * This is the sorting chunk size: make it reasonably
+ * big so that we can sort well..
+ */
+#define MAX_SHA1_ENTRIES (1024)
+
+struct sha1_entry {
+ unsigned long ino;
+ unsigned char sha1[20];
+};
+
+static struct {
+ unsigned long nr;
+ struct sha1_entry *entry[MAX_SHA1_ENTRIES];
+} sha1_list;
+
+static int ino_compare(const void *_a, const void *_b)
+{
+ const struct sha1_entry *a = _a, *b = _b;
+ unsigned long ino1 = a->ino, ino2 = b->ino;
+ return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0;
+}
+
+static void fsck_sha1_list(void)
+{
+ int i, nr = sha1_list.nr;
+
+ if (SORT_DIRENT)
+ qsort(sha1_list.entry, nr,
+ sizeof(struct sha1_entry *), ino_compare);
+ for (i = 0; i < nr; i++) {
+ struct sha1_entry *entry = sha1_list.entry[i];
+ unsigned char *sha1 = entry->sha1;
+
+ sha1_list.entry[i] = NULL;
+ fsck_sha1(sha1);
+ free(entry);
+ }
+ sha1_list.nr = 0;
+}
+
+static void add_sha1_list(unsigned char *sha1, unsigned long ino)
+{
+ struct sha1_entry *entry = xmalloc(sizeof(*entry));
+ int nr;
+
+ entry->ino = ino;
+ hashcpy(entry->sha1, sha1);
+ nr = sha1_list.nr;
+ if (nr == MAX_SHA1_ENTRIES) {
+ fsck_sha1_list();
+ nr = 0;
+ }
+ sha1_list.entry[nr] = entry;
+ sha1_list.nr = ++nr;
+}
+
+static void fsck_dir(int i, char *path)
+{
+ DIR *dir = opendir(path);
+ struct dirent *de;
+
+ if (!dir)
+ return;
+
+ if (verbose)
+ fprintf(stderr, "Checking directory %s\n", path);
+
+ while ((de = readdir(dir)) != NULL) {
+ char name[100];
+ unsigned char sha1[20];
+
+ if (is_dot_or_dotdot(de->d_name))
+ continue;
+ if (strlen(de->d_name) == 38) {
+ sprintf(name, "%02x", i);
+ memcpy(name+2, de->d_name, 39);
+ if (get_sha1_hex(name, sha1) < 0)
+ break;
+ add_sha1_list(sha1, DIRENT_SORT_HINT(de));
+ continue;
+ }
+ if (!prefixcmp(de->d_name, "tmp_obj_"))
+ continue;
+ fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+ }
+ closedir(dir);
+}
+
+static int default_refs;
+
+static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ 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) {
+ obj->used = 1;
+ mark_object_reachable(obj);
+ }
+ }
+ obj = lookup_object(nsha1);
+ if (obj) {
+ obj->used = 1;
+ mark_object_reachable(obj);
+ }
+ return 0;
+}
+
+static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL);
+ return 0;
+}
+
+static int is_branch(const char *refname)
+{
+ return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/");
+}
+
+static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ struct object *obj;
+
+ obj = parse_object(sha1);
+ if (!obj) {
+ error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
+ /* We'll continue with the rest despite the error.. */
+ return 0;
+ }
+ if (obj->type != OBJ_COMMIT && is_branch(refname))
+ error("%s: not a commit", refname);
+ default_refs++;
+ obj->used = 1;
+ mark_object_reachable(obj);
+
+ return 0;
+}
+
+static void get_default_heads(void)
+{
+ if (head_points_at && !is_null_sha1(head_sha1))
+ fsck_handle_ref("HEAD", head_sha1, 0, NULL);
+ for_each_ref(fsck_handle_ref, NULL);
+ if (include_reflogs)
+ for_each_reflog(fsck_handle_reflog, NULL);
+
+ /*
+ * Not having any default heads isn't really fatal, but
+ * it does mean that "--unreachable" no longer makes any
+ * sense (since in this case everything will obviously
+ * be unreachable by definition.
+ *
+ * Showing dangling objects is valid, though (as those
+ * dangling objects are likely lost heads).
+ *
+ * So we just print a warning about it, and clear the
+ * "show_unreachable" flag.
+ */
+ if (!default_refs) {
+ fprintf(stderr, "notice: No default references\n");
+ show_unreachable = 0;
+ }
+}
+
+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);
+ fsck_dir(i, dir);
+ }
+ fsck_sha1_list();
+}
+
+static int fsck_head_link(void)
+{
+ int flag;
+ int null_is_error = 0;
+
+ if (verbose)
+ fprintf(stderr, "Checking HEAD link\n");
+
+ head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
+ if (!head_points_at)
+ return error("Invalid HEAD");
+ if (!strcmp(head_points_at, "HEAD"))
+ /* detached HEAD */
+ null_is_error = 1;
+ else if (prefixcmp(head_points_at, "refs/heads/"))
+ return error("HEAD points to something strange (%s)",
+ head_points_at);
+ if (is_null_sha1(head_sha1)) {
+ if (null_is_error)
+ return error("HEAD: detached HEAD points at nothing");
+ fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
+ head_points_at + 11);
+ }
+ return 0;
+}
+
+static int fsck_cache_tree(struct cache_tree *it)
+{
+ 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) {
+ error("%s: invalid sha1 pointer in cache-tree",
+ sha1_to_hex(it->sha1));
+ return 1;
+ }
+ mark_object_reachable(obj);
+ obj->used = 1;
+ if (obj->type != OBJ_TREE)
+ err |= objerror(obj, "non-tree in cache-tree");
+ }
+ for (i = 0; i < it->subtree_nr; i++)
+ err |= fsck_cache_tree(it->down[i]->cache_tree);
+ return err;
+}
+
+static char const * const fsck_usage[] = {
+ "git fsck [options] [<object>...]",
+ NULL
+};
+
+static struct option fsck_opts[] = {
+ OPT__VERBOSE(&verbose),
+ OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"),
+ OPT_BOOLEAN(0, "tags", &show_tags, "report tags"),
+ OPT_BOOLEAN(0, "root", &show_root, "report root nodes"),
+ OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"),
+ OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"),
+ OPT_BOOLEAN(0, "full", &check_full, "also consider packs and alternate objects"),
+ OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"),
+ OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
+ "write dangling objects in .git/lost-found"),
+ OPT_END(),
+};
+
+int cmd_fsck(int argc, const char **argv, const char *prefix)
+{
+ int i, heads;
+ struct alternate_object_database *alt;
+
+ errors_found = 0;
+ read_replace_refs = 0;
+
+ argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);
+ if (write_lost_and_found) {
+ check_full = 1;
+ include_reflogs = 0;
+ }
+
+ fsck_head_link();
+ fsck_object_dir(get_object_directory());
+
+ prepare_alt_odb();
+ for (alt = alt_odb_list; alt; alt = alt->next) {
+ char namebuf[PATH_MAX];
+ int namelen = alt->name - alt->base;
+ memcpy(namebuf, alt->base, namelen);
+ namebuf[namelen - 1] = 0;
+ fsck_object_dir(namebuf);
+ }
+
+ if (check_full) {
+ struct packed_git *p;
+
+ prepare_packed_git();
+ for (p = packed_git; p; p = p->next)
+ /* verify gives error messages itself */
+ verify_pack(p);
+
+ for (p = packed_git; p; p = p->next) {
+ uint32_t j, num;
+ if (open_pack_index(p))
+ continue;
+ num = p->num_objects;
+ for (j = 0; j < num; j++)
+ fsck_sha1(nth_packed_object_sha1(p, j));
+ }
+ }
+
+ heads = 0;
+ for (i = 0; i < argc; i++) {
+ const char *arg = argv[i];
+ unsigned char sha1[20];
+ if (!get_sha1(arg, sha1)) {
+ struct object *obj = lookup_object(sha1);
+
+ /* Error is printed by lookup_object(). */
+ if (!obj)
+ continue;
+
+ obj->used = 1;
+ mark_object_reachable(obj);
+ heads++;
+ continue;
+ }
+ error("invalid parameter: expected sha1, got '%s'", arg);
+ }
+
+ /*
+ * If we've not been given any explicit head information, do the
+ * default ones from .git/refs. We also consider the index file
+ * in this case (ie this implies --cache).
+ */
+ if (!heads) {
+ get_default_heads();
+ keep_cache_objects = 1;
+ }
+
+ if (keep_cache_objects) {
+ read_cache();
+ for (i = 0; i < active_nr; i++) {
+ unsigned int mode;
+ struct blob *blob;
+ struct object *obj;
+
+ mode = active_cache[i]->ce_mode;
+ if (S_ISGITLINK(mode))
+ continue;
+ blob = lookup_blob(active_cache[i]->sha1);
+ if (!blob)
+ continue;
+ obj = &blob->object;
+ obj->used = 1;
+ mark_object_reachable(obj);
+ }
+ if (active_cache_tree)
+ fsck_cache_tree(active_cache_tree);
+ }
+
+ check_connectivity();
+ return errors_found;
+}
--- /dev/null
+/*
+ * git gc builtin command
+ *
+ * Cleanup unreachable files and optimize the repository.
+ *
+ * Copyright (c) 2007 James Bowes
+ *
+ * Based on git-gc.sh, which is
+ *
+ * Copyright (c) 2006 Shawn O. Pearce
+ */
+
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "run-command.h"
+
+#define FAILED_RUN "failed to run %s"
+
+static const char * const builtin_gc_usage[] = {
+ "git gc [options]",
+ NULL
+};
+
+static int pack_refs = 1;
+static int aggressive_window = 250;
+static int gc_auto_threshold = 6700;
+static int gc_auto_pack_limit = 50;
+static const char *prune_expire = "2.weeks.ago";
+
+#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[MAX_ADD] = {"repack", "-d", "-l", NULL};
+static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
+static const char *argv_rerere[] = {"rerere", "gc", NULL};
+
+static int gc_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "gc.packrefs")) {
+ if (value && !strcmp(value, "notbare"))
+ pack_refs = -1;
+ else
+ pack_refs = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "gc.aggressivewindow")) {
+ aggressive_window = git_config_int(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "gc.auto")) {
+ gc_auto_threshold = git_config_int(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "gc.autopacklimit")) {
+ gc_auto_pack_limit = git_config_int(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "gc.pruneexpire")) {
+ if (value && strcmp(value, "now")) {
+ unsigned long now = approxidate("now");
+ if (approxidate(value) >= now)
+ return error("Invalid %s: '%s'", var, value);
+ }
+ return git_config_string(&prune_expire, var, value);
+ }
+ return git_default_config(var, value, cb);
+}
+
+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;
+}
+
+static int too_many_loose_objects(void)
+{
+ /*
+ * Quickly check if a "gc" is needed, by estimating how
+ * many loose objects there are. Because SHA-1 is evenly
+ * distributed, we can check only one and get a reasonable
+ * estimate.
+ */
+ char path[PATH_MAX];
+ const char *objdir = get_object_directory();
+ DIR *dir;
+ struct dirent *ent;
+ int auto_threshold;
+ int num_loose = 0;
+ int needed = 0;
+
+ if (gc_auto_threshold <= 0)
+ return 0;
+
+ if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) {
+ warning("insanely long object directory %.*s", 50, objdir);
+ return 0;
+ }
+ dir = opendir(path);
+ if (!dir)
+ return 0;
+
+ auto_threshold = (gc_auto_threshold + 255) / 256;
+ while ((ent = readdir(dir)) != NULL) {
+ if (strspn(ent->d_name, "0123456789abcdef") != 38 ||
+ ent->d_name[38] != '\0')
+ continue;
+ if (++num_loose > auto_threshold) {
+ needed = 1;
+ break;
+ }
+ }
+ closedir(dir);
+ return needed;
+}
+
+static int too_many_packs(void)
+{
+ struct packed_git *p;
+ int cnt;
+
+ if (gc_auto_pack_limit <= 0)
+ return 0;
+
+ prepare_packed_git();
+ for (cnt = 0, p = packed_git; p; p = p->next) {
+ if (!p->pack_local)
+ continue;
+ if (p->pack_keep)
+ continue;
+ /*
+ * Perhaps check the size of the pack and count only
+ * very small ones here?
+ */
+ cnt++;
+ }
+ return gc_auto_pack_limit <= cnt;
+}
+
+static int need_to_gc(void)
+{
+ /*
+ * Setting gc.auto to 0 or negative can disable the
+ * automatic gc.
+ */
+ if (gc_auto_threshold <= 0)
+ return 0;
+
+ /*
+ * If there are too many loose objects, but not too many
+ * packs, we run "repack -d -l". If there are too many packs,
+ * we run "repack -A -d -l". Otherwise we tell the caller
+ * there is no need.
+ */
+ if (too_many_packs())
+ append_option(argv_repack,
+ prune_expire && !strcmp(prune_expire, "now") ?
+ "-a" : "-A",
+ MAX_ADD);
+ else if (!too_many_loose_objects())
+ return 0;
+
+ if (run_hook(NULL, "pre-auto-gc", NULL))
+ return 0;
+ return 1;
+}
+
+int cmd_gc(int argc, const char **argv, const char *prefix)
+{
+ int aggressive = 0;
+ int auto_gc = 0;
+ int quiet = 0;
+ char buf[80];
+
+ struct option builtin_gc_options[] = {
+ OPT__QUIET(&quiet),
+ { OPTION_STRING, 0, "prune", &prune_expire, "date",
+ "prune unreferenced objects",
+ PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
+ OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
+ OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
+ OPT_END()
+ };
+
+ git_config(gc_config, NULL);
+
+ if (pack_refs < 0)
+ pack_refs = !is_bare_repository();
+
+ argc = parse_options(argc, argv, prefix, builtin_gc_options,
+ builtin_gc_usage, 0);
+ if (argc > 0)
+ usage_with_options(builtin_gc_usage, builtin_gc_options);
+
+ if (aggressive) {
+ append_option(argv_repack, "-f", MAX_ADD);
+ append_option(argv_repack, "--depth=250", MAX_ADD);
+ if (aggressive_window > 0) {
+ sprintf(buf, "--window=%d", aggressive_window);
+ append_option(argv_repack, buf, MAX_ADD);
+ }
+ }
+ if (quiet)
+ append_option(argv_repack, "-q", MAX_ADD);
+
+ if (auto_gc) {
+ /*
+ * Auto-gc should be least intrusive as possible.
+ */
+ if (!need_to_gc())
+ return 0;
+ fprintf(stderr,
+ "Auto packing the repository for optimum performance.%s\n",
+ quiet
+ ? ""
+ : (" You may also\n"
+ "run \"git gc\" manually. See "
+ "\"git help gc\" for more information."));
+ } else
+ append_option(argv_repack,
+ prune_expire && !strcmp(prune_expire, "now")
+ ? "-a" : "-A",
+ MAX_ADD);
+
+ if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
+ return error(FAILED_RUN, argv_pack_refs[0]);
+
+ if (run_command_v_opt(argv_reflog, RUN_GIT_CMD))
+ return error(FAILED_RUN, argv_reflog[0]);
+
+ if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
+ return error(FAILED_RUN, argv_repack[0]);
+
+ if (prune_expire) {
+ argv_prune[2] = prune_expire;
+ if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
+ return error(FAILED_RUN, argv_prune[0]);
+ }
+
+ if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
+ return error(FAILED_RUN, argv_rerere[0]);
+
+ if (auto_gc && too_many_loose_objects())
+ warning("There are too many unreachable loose objects; "
+ "run 'git prune' to remove them.");
+
+ return 0;
+}
--- /dev/null
+/*
+ * Builtin "git grep"
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree-walk.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "userdiff.h"
+#include "grep.h"
+#include "quote.h"
+#include "dir.h"
+
+#ifndef NO_PTHREADS
+#include "thread-utils.h"
+#include <pthread.h>
+#endif
+
+static char const * const grep_usage[] = {
+ "git grep [options] [-e] <pattern> [<rev>...] [[--] path...]",
+ NULL
+};
+
+static int use_threads = 1;
+
+#ifndef NO_PTHREADS
+#define THREADS 8
+static pthread_t threads[THREADS];
+
+static void *load_sha1(const unsigned char *sha1, unsigned long *size,
+ const char *name);
+static void *load_file(const char *filename, size_t *sz);
+
+enum work_type {WORK_SHA1, WORK_FILE};
+
+/* We use one producer thread and THREADS consumer
+ * threads. The producer adds struct work_items to 'todo' and the
+ * consumers pick work items from the same array.
+ */
+struct work_item
+{
+ enum work_type type;
+ char *name;
+
+ /* if type == WORK_SHA1, then 'identifier' is a SHA1,
+ * otherwise type == WORK_FILE, and 'identifier' is a NUL
+ * terminated filename.
+ */
+ void *identifier;
+ char done;
+ struct strbuf out;
+};
+
+/* In the range [todo_done, todo_start) in 'todo' we have work_items
+ * that have been or are processed by a consumer thread. We haven't
+ * written the result for these to stdout yet.
+ *
+ * The work_items in [todo_start, todo_end) are waiting to be picked
+ * up by a consumer thread.
+ *
+ * The ranges are modulo TODO_SIZE.
+ */
+#define TODO_SIZE 128
+static struct work_item todo[TODO_SIZE];
+static int todo_start;
+static int todo_end;
+static int todo_done;
+
+/* Has all work items been added? */
+static int all_work_added;
+
+/* This lock protects all the variables above. */
+static pthread_mutex_t grep_mutex;
+
+/* Used to serialize calls to read_sha1_file. */
+static pthread_mutex_t read_sha1_mutex;
+
+#define grep_lock() pthread_mutex_lock(&grep_mutex)
+#define grep_unlock() pthread_mutex_unlock(&grep_mutex)
+#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex)
+#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex)
+
+/* Signalled when a new work_item is added to todo. */
+static pthread_cond_t cond_add;
+
+/* Signalled when the result from one work_item is written to
+ * stdout.
+ */
+static pthread_cond_t cond_write;
+
+/* Signalled when we are finished with everything. */
+static pthread_cond_t cond_result;
+
+static void add_work(enum work_type type, char *name, void *id)
+{
+ grep_lock();
+
+ while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) {
+ pthread_cond_wait(&cond_write, &grep_mutex);
+ }
+
+ todo[todo_end].type = type;
+ todo[todo_end].name = name;
+ todo[todo_end].identifier = id;
+ todo[todo_end].done = 0;
+ strbuf_reset(&todo[todo_end].out);
+ todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
+
+ pthread_cond_signal(&cond_add);
+ grep_unlock();
+}
+
+static struct work_item *get_work(void)
+{
+ struct work_item *ret;
+
+ grep_lock();
+ while (todo_start == todo_end && !all_work_added) {
+ pthread_cond_wait(&cond_add, &grep_mutex);
+ }
+
+ if (todo_start == todo_end && all_work_added) {
+ ret = NULL;
+ } else {
+ ret = &todo[todo_start];
+ todo_start = (todo_start + 1) % ARRAY_SIZE(todo);
+ }
+ grep_unlock();
+ return ret;
+}
+
+static void grep_sha1_async(struct grep_opt *opt, char *name,
+ const unsigned char *sha1)
+{
+ unsigned char *s;
+ s = xmalloc(20);
+ memcpy(s, sha1, 20);
+ add_work(WORK_SHA1, name, s);
+}
+
+static void grep_file_async(struct grep_opt *opt, char *name,
+ const char *filename)
+{
+ add_work(WORK_FILE, name, xstrdup(filename));
+}
+
+static void work_done(struct work_item *w)
+{
+ int old_done;
+
+ grep_lock();
+ w->done = 1;
+ old_done = todo_done;
+ for(; todo[todo_done].done && todo_done != todo_start;
+ todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
+ w = &todo[todo_done];
+ write_or_die(1, w->out.buf, w->out.len);
+ free(w->name);
+ free(w->identifier);
+ }
+
+ if (old_done != todo_done)
+ pthread_cond_signal(&cond_write);
+
+ if (all_work_added && todo_done == todo_end)
+ pthread_cond_signal(&cond_result);
+
+ grep_unlock();
+}
+
+static void *run(void *arg)
+{
+ int hit = 0;
+ struct grep_opt *opt = arg;
+
+ while (1) {
+ struct work_item *w = get_work();
+ if (!w)
+ break;
+
+ opt->output_priv = w;
+ if (w->type == WORK_SHA1) {
+ unsigned long sz;
+ void* data = load_sha1(w->identifier, &sz, w->name);
+
+ if (data) {
+ hit |= grep_buffer(opt, w->name, data, sz);
+ free(data);
+ }
+ } else if (w->type == WORK_FILE) {
+ size_t sz;
+ void* data = load_file(w->identifier, &sz);
+ if (data) {
+ hit |= grep_buffer(opt, w->name, data, sz);
+ free(data);
+ }
+ } else {
+ assert(0);
+ }
+
+ work_done(w);
+ }
+ free_grep_patterns(arg);
+ free(arg);
+
+ return (void*) (intptr_t) hit;
+}
+
+static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size)
+{
+ struct work_item *w = opt->output_priv;
+ strbuf_add(&w->out, buf, size);
+}
+
+static void start_threads(struct grep_opt *opt)
+{
+ int i;
+
+ pthread_mutex_init(&grep_mutex, NULL);
+ pthread_mutex_init(&read_sha1_mutex, NULL);
+ pthread_cond_init(&cond_add, NULL);
+ pthread_cond_init(&cond_write, NULL);
+ pthread_cond_init(&cond_result, NULL);
+
+ for (i = 0; i < ARRAY_SIZE(todo); i++) {
+ strbuf_init(&todo[i].out, 0);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(threads); i++) {
+ int err;
+ struct grep_opt *o = grep_opt_dup(opt);
+ o->output = strbuf_out;
+ compile_grep_patterns(o);
+ err = pthread_create(&threads[i], NULL, run, o);
+
+ if (err)
+ die("grep: failed to create thread: %s",
+ strerror(err));
+ }
+}
+
+static int wait_all(void)
+{
+ int hit = 0;
+ int i;
+
+ grep_lock();
+ all_work_added = 1;
+
+ /* Wait until all work is done. */
+ while (todo_done != todo_end)
+ pthread_cond_wait(&cond_result, &grep_mutex);
+
+ /* Wake up all the consumer threads so they can see that there
+ * is no more work to do.
+ */
+ pthread_cond_broadcast(&cond_add);
+ grep_unlock();
+
+ for (i = 0; i < ARRAY_SIZE(threads); i++) {
+ void *h;
+ pthread_join(threads[i], &h);
+ hit |= (int) (intptr_t) h;
+ }
+
+ pthread_mutex_destroy(&grep_mutex);
+ pthread_mutex_destroy(&read_sha1_mutex);
+ pthread_cond_destroy(&cond_add);
+ pthread_cond_destroy(&cond_write);
+ pthread_cond_destroy(&cond_result);
+
+ return hit;
+}
+#else /* !NO_PTHREADS */
+#define read_sha1_lock()
+#define read_sha1_unlock()
+
+static int wait_all(void)
+{
+ return 0;
+}
+#endif
+
+static int grep_config(const char *var, const char *value, void *cb)
+{
+ struct grep_opt *opt = cb;
+
+ switch (userdiff_config(var, value)) {
+ case 0: break;
+ case -1: return -1;
+ default: return 0;
+ }
+
+ if (!strcmp(var, "color.grep")) {
+ opt->color = git_config_colorbool(var, value, -1);
+ return 0;
+ }
+ if (!strcmp(var, "color.grep.match")) {
+ if (!value)
+ return config_error_nonbool(var);
+ color_parse(value, var, opt->color_match);
+ return 0;
+ }
+ return git_color_default_config(var, value, cb);
+}
+
+/*
+ * Return non-zero if max_depth is negative or path has no more then max_depth
+ * slashes.
+ */
+static int accept_subdir(const char *path, int max_depth)
+{
+ if (max_depth < 0)
+ return 1;
+
+ while ((path = strchr(path, '/')) != NULL) {
+ max_depth--;
+ if (max_depth < 0)
+ return 0;
+ path++;
+ }
+ return 1;
+}
+
+/*
+ * Return non-zero if name is a subdirectory of match and is not too deep.
+ */
+static int is_subdir(const char *name, int namelen,
+ const char *match, int matchlen, int max_depth)
+{
+ if (matchlen > namelen || strncmp(name, match, matchlen))
+ return 0;
+
+ if (name[matchlen] == '\0') /* exact match */
+ return 1;
+
+ if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
+ return accept_subdir(name + matchlen + 1, max_depth);
+
+ return 0;
+}
+
+/*
+ * git grep pathspecs are somewhat different from diff-tree pathspecs;
+ * pathname wildcards are allowed.
+ */
+static int pathspec_matches(const char **paths, const char *name, int max_depth)
+{
+ int namelen, i;
+ if (!paths || !*paths)
+ return accept_subdir(name, max_depth);
+ namelen = strlen(name);
+ for (i = 0; paths[i]; i++) {
+ const char *match = paths[i];
+ int matchlen = strlen(match);
+ const char *cp, *meta;
+
+ if (is_subdir(name, namelen, match, matchlen, max_depth))
+ return 1;
+ if (!fnmatch(match, name, 0))
+ return 1;
+ if (name[namelen-1] != '/')
+ continue;
+
+ /* We are being asked if the directory ("name") is worth
+ * descending into.
+ *
+ * Find the longest leading directory name that does
+ * not have metacharacter in the pathspec; the name
+ * we are looking at must overlap with that directory.
+ */
+ for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
+ char ch = *cp;
+ if (ch == '*' || ch == '[' || ch == '?') {
+ meta = cp;
+ break;
+ }
+ }
+ if (!meta)
+ meta = cp; /* fully literal */
+
+ if (namelen <= meta - match) {
+ /* Looking at "Documentation/" and
+ * the pattern says "Documentation/howto/", or
+ * "Documentation/diff*.txt". The name we
+ * have should match prefix.
+ */
+ if (!memcmp(match, name, namelen))
+ return 1;
+ continue;
+ }
+
+ if (meta - match < namelen) {
+ /* Looking at "Documentation/howto/" and
+ * the pattern says "Documentation/h*";
+ * match up to "Do.../h"; this avoids descending
+ * into "Documentation/technical/".
+ */
+ if (!memcmp(match, name, meta - match))
+ return 1;
+ continue;
+ }
+ }
+ return 0;
+}
+
+static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
+{
+ void *data;
+
+ if (use_threads) {
+ read_sha1_lock();
+ data = read_sha1_file(sha1, type, size);
+ read_sha1_unlock();
+ } else {
+ data = read_sha1_file(sha1, type, size);
+ }
+ return data;
+}
+
+static void *load_sha1(const unsigned char *sha1, unsigned long *size,
+ const char *name)
+{
+ enum object_type type;
+ void *data = lock_and_read_sha1_file(sha1, &type, size);
+
+ if (!data)
+ error("'%s': unable to read %s", name, sha1_to_hex(sha1));
+
+ return data;
+}
+
+static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
+ const char *filename, int tree_name_len)
+{
+ struct strbuf pathbuf = STRBUF_INIT;
+ char *name;
+
+ if (opt->relative && opt->prefix_length) {
+ quote_path_relative(filename + tree_name_len, -1, &pathbuf,
+ opt->prefix);
+ strbuf_insert(&pathbuf, 0, filename, tree_name_len);
+ } else {
+ strbuf_addstr(&pathbuf, filename);
+ }
+
+ name = strbuf_detach(&pathbuf, NULL);
+
+#ifndef NO_PTHREADS
+ if (use_threads) {
+ grep_sha1_async(opt, name, sha1);
+ return 0;
+ } else
+#endif
+ {
+ int hit;
+ unsigned long sz;
+ void *data = load_sha1(sha1, &sz, name);
+ if (!data)
+ hit = 0;
+ else
+ hit = grep_buffer(opt, name, data, sz);
+
+ free(data);
+ free(name);
+ return hit;
+ }
+}
+
+static void *load_file(const char *filename, size_t *sz)
+{
+ struct stat st;
+ char *data;
+ int i;
+
+ if (lstat(filename, &st) < 0) {
+ err_ret:
+ if (errno != ENOENT)
+ error("'%s': %s", filename, strerror(errno));
+ return 0;
+ }
+ if (!S_ISREG(st.st_mode))
+ return 0;
+ *sz = xsize_t(st.st_size);
+ i = open(filename, O_RDONLY);
+ if (i < 0)
+ goto err_ret;
+ data = xmalloc(*sz + 1);
+ if (st.st_size != read_in_full(i, data, *sz)) {
+ error("'%s': short read %s", filename, strerror(errno));
+ close(i);
+ free(data);
+ return 0;
+ }
+ close(i);
+ data[*sz] = 0;
+ return data;
+}
+
+static int grep_file(struct grep_opt *opt, const char *filename)
+{
+ struct strbuf buf = STRBUF_INIT;
+ char *name;
+
+ if (opt->relative && opt->prefix_length)
+ quote_path_relative(filename, -1, &buf, opt->prefix);
+ else
+ strbuf_addstr(&buf, filename);
+ name = strbuf_detach(&buf, NULL);
+
+#ifndef NO_PTHREADS
+ if (use_threads) {
+ grep_file_async(opt, name, filename);
+ return 0;
+ } else
+#endif
+ {
+ int hit;
+ size_t sz;
+ void *data = load_file(filename, &sz);
+ if (!data)
+ hit = 0;
+ else
+ hit = grep_buffer(opt, name, data, sz);
+
+ free(data);
+ free(name);
+ return hit;
+ }
+}
+
+static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
+{
+ int hit = 0;
+ int nr;
+ read_cache();
+
+ for (nr = 0; nr < active_nr; nr++) {
+ struct cache_entry *ce = active_cache[nr];
+ if (!S_ISREG(ce->ce_mode))
+ continue;
+ if (!pathspec_matches(paths, ce->name, opt->max_depth))
+ continue;
+ /*
+ * If CE_VALID is on, we assume worktree file and its cache entry
+ * are identical, even if worktree file has been modified, so use
+ * cache version instead
+ */
+ if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
+ if (ce_stage(ce))
+ continue;
+ hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
+ }
+ else
+ hit |= grep_file(opt, ce->name);
+ if (ce_stage(ce)) {
+ do {
+ nr++;
+ } while (nr < active_nr &&
+ !strcmp(ce->name, active_cache[nr]->name));
+ nr--; /* compensate for loop control */
+ }
+ if (hit && opt->status_only)
+ break;
+ }
+ free_grep_patterns(opt);
+ return hit;
+}
+
+static int grep_tree(struct grep_opt *opt, const char **paths,
+ struct tree_desc *tree,
+ const char *tree_name, const char *base)
+{
+ int len;
+ int hit = 0;
+ struct name_entry entry;
+ char *down;
+ int tn_len = strlen(tree_name);
+ struct strbuf pathbuf;
+
+ strbuf_init(&pathbuf, PATH_MAX + tn_len);
+
+ if (tn_len) {
+ strbuf_add(&pathbuf, tree_name, tn_len);
+ strbuf_addch(&pathbuf, ':');
+ tn_len = pathbuf.len;
+ }
+ strbuf_addstr(&pathbuf, base);
+ len = pathbuf.len;
+
+ while (tree_entry(tree, &entry)) {
+ int te_len = tree_entry_len(entry.path, entry.sha1);
+ pathbuf.len = len;
+ strbuf_add(&pathbuf, entry.path, te_len);
+
+ if (S_ISDIR(entry.mode))
+ /* Match "abc/" against pathspec to
+ * decide if we want to descend into "abc"
+ * directory.
+ */
+ strbuf_addch(&pathbuf, '/');
+
+ down = pathbuf.buf + tn_len;
+ if (!pathspec_matches(paths, down, opt->max_depth))
+ ;
+ else if (S_ISREG(entry.mode))
+ hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
+ else if (S_ISDIR(entry.mode)) {
+ enum object_type type;
+ struct tree_desc sub;
+ void *data;
+ unsigned long size;
+
+ data = lock_and_read_sha1_file(entry.sha1, &type, &size);
+ if (!data)
+ die("unable to read tree (%s)",
+ sha1_to_hex(entry.sha1));
+ init_tree_desc(&sub, data, size);
+ hit |= grep_tree(opt, paths, &sub, tree_name, down);
+ free(data);
+ }
+ if (hit && opt->status_only)
+ break;
+ }
+ strbuf_release(&pathbuf);
+ return hit;
+}
+
+static int grep_object(struct grep_opt *opt, const char **paths,
+ struct object *obj, const char *name)
+{
+ if (obj->type == OBJ_BLOB)
+ return grep_sha1(opt, obj->sha1, name, 0);
+ if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
+ struct tree_desc tree;
+ void *data;
+ unsigned long size;
+ int hit;
+ data = read_object_with_reference(obj->sha1, tree_type,
+ &size, NULL);
+ if (!data)
+ die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
+ init_tree_desc(&tree, data, size);
+ hit = grep_tree(opt, paths, &tree, name, "");
+ free(data);
+ return hit;
+ }
+ die("unable to grep from object of type %s", typename(obj->type));
+}
+
+static int grep_directory(struct grep_opt *opt, const char **paths)
+{
+ struct dir_struct dir;
+ int i, hit = 0;
+
+ memset(&dir, 0, sizeof(dir));
+ setup_standard_excludes(&dir);
+
+ fill_directory(&dir, paths);
+ for (i = 0; i < dir.nr; i++) {
+ hit |= grep_file(opt, dir.entries[i]->name);
+ if (hit && opt->status_only)
+ break;
+ }
+ free_grep_patterns(opt);
+ return hit;
+}
+
+static int context_callback(const struct option *opt, const char *arg,
+ int unset)
+{
+ struct grep_opt *grep_opt = opt->value;
+ int value;
+ const char *endp;
+
+ if (unset) {
+ grep_opt->pre_context = grep_opt->post_context = 0;
+ return 0;
+ }
+ value = strtol(arg, (char **)&endp, 10);
+ if (*endp) {
+ return error("switch `%c' expects a numerical value",
+ opt->short_name);
+ }
+ grep_opt->pre_context = grep_opt->post_context = value;
+ return 0;
+}
+
+static int file_callback(const struct option *opt, const char *arg, int unset)
+{
+ struct grep_opt *grep_opt = opt->value;
+ FILE *patterns;
+ int lno = 0;
+ struct strbuf sb = STRBUF_INIT;
+
+ patterns = fopen(arg, "r");
+ if (!patterns)
+ die_errno("cannot open '%s'", arg);
+ while (strbuf_getline(&sb, patterns, '\n') == 0) {
+ /* ignore empty line like grep does */
+ if (sb.len == 0)
+ continue;
+ append_grep_pattern(grep_opt, strbuf_detach(&sb, NULL), arg,
+ ++lno, GREP_PATTERN);
+ }
+ fclose(patterns);
+ strbuf_release(&sb);
+ return 0;
+}
+
+static int not_callback(const struct option *opt, const char *arg, int unset)
+{
+ struct grep_opt *grep_opt = opt->value;
+ append_grep_pattern(grep_opt, "--not", "command line", 0, GREP_NOT);
+ return 0;
+}
+
+static int and_callback(const struct option *opt, const char *arg, int unset)
+{
+ struct grep_opt *grep_opt = opt->value;
+ append_grep_pattern(grep_opt, "--and", "command line", 0, GREP_AND);
+ return 0;
+}
+
+static int open_callback(const struct option *opt, const char *arg, int unset)
+{
+ struct grep_opt *grep_opt = opt->value;
+ append_grep_pattern(grep_opt, "(", "command line", 0, GREP_OPEN_PAREN);
+ return 0;
+}
+
+static int close_callback(const struct option *opt, const char *arg, int unset)
+{
+ struct grep_opt *grep_opt = opt->value;
+ append_grep_pattern(grep_opt, ")", "command line", 0, GREP_CLOSE_PAREN);
+ return 0;
+}
+
+static int pattern_callback(const struct option *opt, const char *arg,
+ int unset)
+{
+ struct grep_opt *grep_opt = opt->value;
+ append_grep_pattern(grep_opt, arg, "-e option", 0, GREP_PATTERN);
+ return 0;
+}
+
+static int help_callback(const struct option *opt, const char *arg, int unset)
+{
+ return -1;
+}
+
+int cmd_grep(int argc, const char **argv, const char *prefix)
+{
+ int hit = 0;
+ int cached = 0;
+ int seen_dashdash = 0;
+ int external_grep_allowed__ignored;
+ struct grep_opt opt;
+ struct object_array list = { 0, 0, NULL };
+ const char **paths = NULL;
+ int i;
+ int dummy;
+ int nongit = 0, use_index = 1;
+ struct option options[] = {
+ OPT_BOOLEAN(0, "cached", &cached,
+ "search in index instead of in the work tree"),
+ OPT_BOOLEAN(0, "index", &use_index,
+ "--no-index finds in contents not managed by git"),
+ OPT_GROUP(""),
+ OPT_BOOLEAN('v', "invert-match", &opt.invert,
+ "show non-matching lines"),
+ OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case,
+ "case insensitive matching"),
+ OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp,
+ "match patterns only at word boundaries"),
+ OPT_SET_INT('a', "text", &opt.binary,
+ "process binary files as text", GREP_BINARY_TEXT),
+ OPT_SET_INT('I', NULL, &opt.binary,
+ "don't match patterns in binary files",
+ GREP_BINARY_NOMATCH),
+ { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth",
+ "descend at most <depth> levels", PARSE_OPT_NONEG,
+ NULL, 1 },
+ OPT_GROUP(""),
+ OPT_BIT('E', "extended-regexp", &opt.regflags,
+ "use extended POSIX regular expressions", REG_EXTENDED),
+ OPT_NEGBIT('G', "basic-regexp", &opt.regflags,
+ "use basic POSIX regular expressions (default)",
+ REG_EXTENDED),
+ OPT_BOOLEAN('F', "fixed-strings", &opt.fixed,
+ "interpret patterns as fixed strings"),
+ OPT_GROUP(""),
+ OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"),
+ OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1),
+ OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1),
+ OPT_NEGBIT(0, "full-name", &opt.relative,
+ "show filenames relative to top directory", 1),
+ OPT_BOOLEAN('l', "files-with-matches", &opt.name_only,
+ "show only filenames instead of matching lines"),
+ OPT_BOOLEAN(0, "name-only", &opt.name_only,
+ "synonym for --files-with-matches"),
+ OPT_BOOLEAN('L', "files-without-match",
+ &opt.unmatch_name_only,
+ "show only the names of files without match"),
+ OPT_BOOLEAN('z', "null", &opt.null_following_name,
+ "print NUL after filenames"),
+ OPT_BOOLEAN('c', "count", &opt.count,
+ "show the number of matches instead of matching lines"),
+ OPT__COLOR(&opt.color, "highlight matches"),
+ OPT_GROUP(""),
+ OPT_CALLBACK('C', NULL, &opt, "n",
+ "show <n> context lines before and after matches",
+ context_callback),
+ OPT_INTEGER('B', NULL, &opt.pre_context,
+ "show <n> context lines before matches"),
+ OPT_INTEGER('A', NULL, &opt.post_context,
+ "show <n> context lines after matches"),
+ OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM",
+ context_callback),
+ OPT_BOOLEAN('p', "show-function", &opt.funcname,
+ "show a line with the function name before matches"),
+ OPT_GROUP(""),
+ OPT_CALLBACK('f', NULL, &opt, "file",
+ "read patterns from file", file_callback),
+ { OPTION_CALLBACK, 'e', NULL, &opt, "pattern",
+ "match <pattern>", PARSE_OPT_NONEG, pattern_callback },
+ { OPTION_CALLBACK, 0, "and", &opt, NULL,
+ "combine patterns specified with -e",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
+ OPT_BOOLEAN(0, "or", &dummy, ""),
+ { OPTION_CALLBACK, 0, "not", &opt, NULL, "",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback },
+ { OPTION_CALLBACK, '(', NULL, &opt, NULL, "",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+ open_callback },
+ { OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+ close_callback },
+ OPT_BOOLEAN('q', "quiet", &opt.status_only,
+ "indicate hit with exit status without output"),
+ OPT_BOOLEAN(0, "all-match", &opt.all_match,
+ "show only matches from files that match all patterns"),
+ OPT_GROUP(""),
+ OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored,
+ "allow calling of grep(1) (ignored by this build)"),
+ { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage",
+ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
+ OPT_END()
+ };
+
+ prefix = setup_git_directory_gently(&nongit);
+
+ /*
+ * 'git grep -h', unlike 'git grep -h <pattern>', is a request
+ * to show usage information and exit.
+ */
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(grep_usage, options);
+
+ memset(&opt, 0, sizeof(opt));
+ opt.prefix = prefix;
+ opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
+ opt.relative = 1;
+ opt.pathname = 1;
+ opt.pattern_tail = &opt.pattern_list;
+ opt.header_tail = &opt.header_list;
+ opt.regflags = REG_NEWLINE;
+ opt.max_depth = -1;
+
+ strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
+ opt.color = -1;
+ git_config(grep_config, &opt);
+ if (opt.color == -1)
+ opt.color = git_use_color_default;
+
+ /*
+ * If there is no -- then the paths must exist in the working
+ * tree. If there is no explicit pattern specified with -e or
+ * -f, we take the first unrecognized non option to be the
+ * pattern, but then what follows it must be zero or more
+ * valid refs up to the -- (if exists), and then existing
+ * paths. If there is an explicit pattern, then the first
+ * unrecognized non option is the beginning of the refs list
+ * that continues up to the -- (if exists), and then paths.
+ */
+ argc = parse_options(argc, argv, prefix, options, grep_usage,
+ PARSE_OPT_KEEP_DASHDASH |
+ PARSE_OPT_STOP_AT_NON_OPTION |
+ PARSE_OPT_NO_INTERNAL_HELP);
+
+ if (use_index && nongit)
+ /* die the same way as if we did it at the beginning */
+ setup_git_directory();
+
+ /*
+ * skip a -- separator; we know it cannot be
+ * separating revisions from pathnames if
+ * we haven't even had any patterns yet
+ */
+ if (argc > 0 && !opt.pattern_list && !strcmp(argv[0], "--")) {
+ argv++;
+ argc--;
+ }
+
+ /* First unrecognized non-option token */
+ if (argc > 0 && !opt.pattern_list) {
+ append_grep_pattern(&opt, argv[0], "command line", 0,
+ GREP_PATTERN);
+ argv++;
+ argc--;
+ }
+
+ if (!opt.pattern_list)
+ die("no pattern given.");
+ if (!opt.fixed && opt.ignore_case)
+ opt.regflags |= REG_ICASE;
+ if ((opt.regflags != REG_NEWLINE) && opt.fixed)
+ die("cannot mix --fixed-strings and regexp");
+
+#ifndef NO_PTHREADS
+ if (online_cpus() == 1 || !grep_threads_ok(&opt))
+ use_threads = 0;
+
+ if (use_threads)
+ start_threads(&opt);
+#else
+ use_threads = 0;
+#endif
+
+ compile_grep_patterns(&opt);
+
+ /* Check revs and then paths */
+ for (i = 0; i < argc; i++) {
+ const char *arg = argv[i];
+ unsigned char sha1[20];
+ /* Is it a rev? */
+ if (!get_sha1(arg, sha1)) {
+ struct object *object = parse_object(sha1);
+ if (!object)
+ die("bad object %s", arg);
+ add_object_array(object, arg, &list);
+ continue;
+ }
+ if (!strcmp(arg, "--")) {
+ i++;
+ seen_dashdash = 1;
+ }
+ break;
+ }
+
+ /* The rest are paths */
+ if (!seen_dashdash) {
+ int j;
+ for (j = i; j < argc; j++)
+ verify_filename(prefix, argv[j]);
+ }
+
+ if (i < argc)
+ paths = get_pathspec(prefix, argv + i);
+ else if (prefix) {
+ paths = xcalloc(2, sizeof(const char *));
+ paths[0] = prefix;
+ paths[1] = NULL;
+ }
+
+ if (!use_index) {
+ int hit;
+ if (cached)
+ die("--cached cannot be used with --no-index.");
+ if (list.nr)
+ die("--no-index cannot be used with revs.");
+ hit = grep_directory(&opt, paths);
+ if (use_threads)
+ hit |= wait_all();
+ return !hit;
+ }
+
+ if (!list.nr) {
+ int hit;
+ if (!cached)
+ setup_work_tree();
+
+ hit = grep_cache(&opt, paths, cached);
+ if (use_threads)
+ hit |= wait_all();
+ return !hit;
+ }
+
+ if (cached)
+ die("both --cached and trees are given.");
+
+ for (i = 0; i < list.nr; i++) {
+ struct object *real_obj;
+ real_obj = deref_tag(list.objects[i].item, NULL, 0);
+ if (grep_object(&opt, paths, real_obj, list.objects[i].name)) {
+ hit = 1;
+ if (opt.status_only)
+ break;
+ }
+ }
+
+ if (use_threads)
+ hit |= wait_all();
+ free_grep_patterns(&opt);
+ return !hit;
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Junio C Hamano, 2005
+ */
+#include "cache.h"
+#include "blob.h"
+#include "quote.h"
+#include "parse-options.h"
+#include "exec_cmd.h"
+
+static void hash_fd(int fd, const char *type, int write_object, const char *path)
+{
+ struct stat st;
+ unsigned char sha1[20];
+ if (fstat(fd, &st) < 0 ||
+ index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
+ die(write_object
+ ? "Unable to add %s to database"
+ : "Unable to hash %s", path);
+ printf("%s\n", sha1_to_hex(sha1));
+ maybe_flush_or_die(stdout, "hash to stdout");
+}
+
+static void hash_object(const char *path, const char *type, int write_object,
+ const char *vpath)
+{
+ int fd;
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ die_errno("Cannot open '%s'", path);
+ hash_fd(fd, type, write_object, vpath);
+}
+
+static int no_filters;
+
+static void hash_stdin_paths(const char *type, int write_objects)
+{
+ struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
+
+ while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+ if (buf.buf[0] == '"') {
+ strbuf_reset(&nbuf);
+ if (unquote_c_style(&nbuf, buf.buf, NULL))
+ die("line is badly quoted");
+ strbuf_swap(&buf, &nbuf);
+ }
+ hash_object(buf.buf, type, write_objects,
+ no_filters ? NULL : buf.buf);
+ }
+ strbuf_release(&buf);
+ strbuf_release(&nbuf);
+}
+
+static const char * const hash_object_usage[] = {
+ "git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...",
+ "git hash-object --stdin-paths < <list-of-paths>",
+ NULL
+};
+
+static const char *type;
+static int write_object;
+static int hashstdin;
+static int stdin_paths;
+static const char *vpath;
+
+static const struct option hash_object_options[] = {
+ OPT_STRING('t', NULL, &type, "type", "object type"),
+ OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"),
+ OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"),
+ OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"),
+ OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"),
+ OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"),
+ OPT_END()
+};
+
+int cmd_hash_object(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ int prefix_length = -1;
+ const char *errstr = NULL;
+
+ type = blob_type;
+
+ argc = parse_options(argc, argv, NULL, hash_object_options,
+ hash_object_usage, 0);
+
+ if (write_object) {
+ prefix = setup_git_directory();
+ prefix_length = prefix ? strlen(prefix) : 0;
+ if (vpath && prefix)
+ vpath = prefix_filename(prefix, prefix_length, vpath);
+ }
+
+ git_config(git_default_config, NULL);
+
+ if (stdin_paths) {
+ if (hashstdin)
+ errstr = "Can't use --stdin-paths with --stdin";
+ else if (argc)
+ errstr = "Can't specify files with --stdin-paths";
+ else if (vpath)
+ errstr = "Can't use --stdin-paths with --path";
+ }
+ else {
+ if (hashstdin > 1)
+ errstr = "Multiple --stdin arguments are not supported";
+ if (vpath && no_filters)
+ errstr = "Can't use --path with --no-filters";
+ }
+
+ if (errstr) {
+ error("%s", errstr);
+ usage_with_options(hash_object_usage, hash_object_options);
+ }
+
+ if (hashstdin)
+ hash_fd(0, type, write_object, vpath);
+
+ for (i = 0 ; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (0 <= prefix_length)
+ arg = prefix_filename(prefix, prefix_length, arg);
+ hash_object(arg, type, write_object,
+ no_filters ? NULL : vpath ? vpath : arg);
+ }
+
+ if (stdin_paths)
+ hash_stdin_paths(type, write_object);
+
+ return 0;
+}
--- /dev/null
+/*
+ * builtin-help.c
+ *
+ * Builtin help command
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "common-cmds.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "help.h"
+
+static struct man_viewer_list {
+ struct man_viewer_list *next;
+ char name[FLEX_ARRAY];
+} *man_viewer_list;
+
+static struct man_viewer_info_list {
+ struct man_viewer_info_list *next;
+ const char *info;
+ char name[FLEX_ARRAY];
+} *man_viewer_info_list;
+
+enum help_format {
+ HELP_FORMAT_NONE,
+ HELP_FORMAT_MAN,
+ HELP_FORMAT_INFO,
+ HELP_FORMAT_WEB,
+};
+
+static int show_all = 0;
+static enum help_format help_format = HELP_FORMAT_NONE;
+static struct option builtin_help_options[] = {
+ OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
+ OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
+ OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
+ HELP_FORMAT_WEB),
+ OPT_SET_INT('i', "info", &help_format, "show info page",
+ HELP_FORMAT_INFO),
+ OPT_END(),
+};
+
+static const char * const builtin_help_usage[] = {
+ "git help [--all] [--man|--web|--info] [command]",
+ NULL
+};
+
+static enum help_format parse_help_format(const char *format)
+{
+ if (!strcmp(format, "man"))
+ return HELP_FORMAT_MAN;
+ if (!strcmp(format, "info"))
+ return HELP_FORMAT_INFO;
+ if (!strcmp(format, "web") || !strcmp(format, "html"))
+ return HELP_FORMAT_WEB;
+ die("unrecognized help format '%s'", format);
+}
+
+static const char *get_man_viewer_info(const char *name)
+{
+ struct man_viewer_info_list *viewer;
+
+ for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
+ {
+ if (!strcasecmp(name, viewer->name))
+ return viewer->info;
+ }
+ return NULL;
+}
+
+static int check_emacsclient_version(void)
+{
+ struct strbuf buffer = STRBUF_INIT;
+ struct child_process ec_process;
+ const char *argv_ec[] = { "emacsclient", "--version", NULL };
+ int version;
+
+ /* emacsclient prints its version number on stderr */
+ memset(&ec_process, 0, sizeof(ec_process));
+ ec_process.argv = argv_ec;
+ ec_process.err = -1;
+ ec_process.stdout_to_stderr = 1;
+ if (start_command(&ec_process))
+ return error("Failed to start emacsclient.");
+
+ strbuf_read(&buffer, ec_process.err, 20);
+ close(ec_process.err);
+
+ /*
+ * Don't bother checking return value, because "emacsclient --version"
+ * seems to always exits with code 1.
+ */
+ finish_command(&ec_process);
+
+ if (prefixcmp(buffer.buf, "emacsclient")) {
+ strbuf_release(&buffer);
+ return error("Failed to parse emacsclient version.");
+ }
+
+ strbuf_remove(&buffer, 0, strlen("emacsclient"));
+ version = atoi(buffer.buf);
+
+ if (version < 22) {
+ strbuf_release(&buffer);
+ return error("emacsclient version '%d' too old (< 22).",
+ version);
+ }
+
+ strbuf_release(&buffer);
+ return 0;
+}
+
+static void exec_woman_emacs(const char *path, const char *page)
+{
+ if (!check_emacsclient_version()) {
+ /* This works only with emacsclient version >= 22. */
+ struct strbuf man_page = STRBUF_INIT;
+
+ if (!path)
+ path = "emacsclient";
+ strbuf_addf(&man_page, "(woman \"%s\")", page);
+ execlp(path, "emacsclient", "-e", man_page.buf, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+ }
+}
+
+static void exec_man_konqueror(const char *path, const char *page)
+{
+ const char *display = getenv("DISPLAY");
+ if (display && *display) {
+ struct strbuf man_page = STRBUF_INIT;
+ const char *filename = "kfmclient";
+
+ /* It's simpler to launch konqueror using kfmclient. */
+ if (path) {
+ const char *file = strrchr(path, '/');
+ if (file && !strcmp(file + 1, "konqueror")) {
+ char *new = xstrdup(path);
+ char *dest = strrchr(new, '/');
+
+ /* strlen("konqueror") == strlen("kfmclient") */
+ strcpy(dest + 1, "kfmclient");
+ path = new;
+ }
+ if (file)
+ filename = file;
+ } else
+ path = "kfmclient";
+ strbuf_addf(&man_page, "man:%s(1)", page);
+ execlp(path, filename, "newTab", man_page.buf, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+ }
+}
+
+static void exec_man_man(const char *path, const char *page)
+{
+ if (!path)
+ path = "man";
+ execlp(path, "man", page, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+}
+
+static void exec_man_cmd(const char *cmd, const char *page)
+{
+ struct strbuf shell_cmd = STRBUF_INIT;
+ strbuf_addf(&shell_cmd, "%s %s", cmd, page);
+ execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL);
+ warning("failed to exec '%s': %s", cmd, strerror(errno));
+}
+
+static void add_man_viewer(const char *name)
+{
+ struct man_viewer_list **p = &man_viewer_list;
+ size_t len = strlen(name);
+
+ while (*p)
+ p = &((*p)->next);
+ *p = xcalloc(1, (sizeof(**p) + len + 1));
+ strncpy((*p)->name, name, len);
+}
+
+static int supported_man_viewer(const char *name, size_t len)
+{
+ return (!strncasecmp("man", name, len) ||
+ !strncasecmp("woman", name, len) ||
+ !strncasecmp("konqueror", name, len));
+}
+
+static void do_add_man_viewer_info(const char *name,
+ size_t len,
+ const char *value)
+{
+ struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
+
+ strncpy(new->name, name, len);
+ new->info = xstrdup(value);
+ new->next = man_viewer_info_list;
+ man_viewer_info_list = new;
+}
+
+static int add_man_viewer_path(const char *name,
+ size_t len,
+ const char *value)
+{
+ if (supported_man_viewer(name, len))
+ do_add_man_viewer_info(name, len, value);
+ else
+ warning("'%s': path for unsupported man viewer.\n"
+ "Please consider using 'man.<tool>.cmd' instead.",
+ name);
+
+ return 0;
+}
+
+static int add_man_viewer_cmd(const char *name,
+ size_t len,
+ const char *value)
+{
+ if (supported_man_viewer(name, len))
+ warning("'%s': cmd for supported man viewer.\n"
+ "Please consider using 'man.<tool>.path' instead.",
+ name);
+ else
+ do_add_man_viewer_info(name, len, value);
+
+ return 0;
+}
+
+static int add_man_viewer_info(const char *var, const char *value)
+{
+ const char *name = var + 4;
+ const char *subkey = strrchr(name, '.');
+
+ if (!subkey)
+ return 0;
+
+ if (!strcmp(subkey, ".path")) {
+ if (!value)
+ return config_error_nonbool(var);
+ return add_man_viewer_path(name, subkey - name, value);
+ }
+ if (!strcmp(subkey, ".cmd")) {
+ if (!value)
+ return config_error_nonbool(var);
+ return add_man_viewer_cmd(name, subkey - name, value);
+ }
+
+ return 0;
+}
+
+static int git_help_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "help.format")) {
+ if (!value)
+ return config_error_nonbool(var);
+ help_format = parse_help_format(value);
+ return 0;
+ }
+ if (!strcmp(var, "man.viewer")) {
+ if (!value)
+ return config_error_nonbool(var);
+ add_man_viewer(value);
+ return 0;
+ }
+ if (!prefixcmp(var, "man."))
+ return add_man_viewer_info(var, value);
+
+ return git_default_config(var, value, cb);
+}
+
+static struct cmdnames main_cmds, other_cmds;
+
+void list_common_cmds_help(void)
+{
+ int i, longest = 0;
+
+ for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ if (longest < strlen(common_cmds[i].name))
+ longest = strlen(common_cmds[i].name);
+ }
+
+ puts("The most commonly used git commands are:");
+ for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ printf(" %s ", common_cmds[i].name);
+ mput_char(' ', longest - strlen(common_cmds[i].name));
+ puts(common_cmds[i].help);
+ }
+}
+
+static int is_git_command(const char *s)
+{
+ return is_in_cmdlist(&main_cmds, s) ||
+ is_in_cmdlist(&other_cmds, s);
+}
+
+static const char *prepend(const char *prefix, const char *cmd)
+{
+ size_t pre_len = strlen(prefix);
+ size_t cmd_len = strlen(cmd);
+ char *p = xmalloc(pre_len + cmd_len + 1);
+ memcpy(p, prefix, pre_len);
+ strcpy(p + pre_len, cmd);
+ return p;
+}
+
+static const char *cmd_to_page(const char *git_cmd)
+{
+ if (!git_cmd)
+ return "git";
+ else if (!prefixcmp(git_cmd, "git"))
+ return git_cmd;
+ else if (is_git_command(git_cmd))
+ return prepend("git-", git_cmd);
+ else
+ return prepend("git", git_cmd);
+}
+
+static void setup_man_path(void)
+{
+ struct strbuf new_path = STRBUF_INIT;
+ const char *old_path = getenv("MANPATH");
+
+ /* We should always put ':' after our path. If there is no
+ * old_path, the ':' at the end will let 'man' to try
+ * system-wide paths after ours to find the manual page. If
+ * there is old_path, we need ':' as delimiter. */
+ strbuf_addstr(&new_path, system_path(GIT_MAN_PATH));
+ strbuf_addch(&new_path, ':');
+ if (old_path)
+ strbuf_addstr(&new_path, old_path);
+
+ setenv("MANPATH", new_path.buf, 1);
+
+ strbuf_release(&new_path);
+}
+
+static void exec_viewer(const char *name, const char *page)
+{
+ const char *info = get_man_viewer_info(name);
+
+ if (!strcasecmp(name, "man"))
+ exec_man_man(info, page);
+ else if (!strcasecmp(name, "woman"))
+ exec_woman_emacs(info, page);
+ else if (!strcasecmp(name, "konqueror"))
+ exec_man_konqueror(info, page);
+ else if (info)
+ exec_man_cmd(info, page);
+ else
+ warning("'%s': unknown man viewer.", name);
+}
+
+static void show_man_page(const char *git_cmd)
+{
+ struct man_viewer_list *viewer;
+ const char *page = cmd_to_page(git_cmd);
+ const char *fallback = getenv("GIT_MAN_VIEWER");
+
+ setup_man_path();
+ for (viewer = man_viewer_list; viewer; viewer = viewer->next)
+ {
+ exec_viewer(viewer->name, page); /* will return when unable */
+ }
+ if (fallback)
+ exec_viewer(fallback, page);
+ exec_viewer("man", page);
+ die("no man viewer handled the request");
+}
+
+static void show_info_page(const char *git_cmd)
+{
+ const char *page = cmd_to_page(git_cmd);
+ setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
+ execlp("info", "info", "gitman", page, NULL);
+ die("no info viewer handled the request");
+}
+
+static void get_html_page_path(struct strbuf *page_path, const char *page)
+{
+ struct stat st;
+ const char *html_path = system_path(GIT_HTML_PATH);
+
+ /* Check that we have a git documentation directory. */
+ if (stat(mkpath("%s/git.html", html_path), &st)
+ || !S_ISREG(st.st_mode))
+ die("'%s': not a documentation directory.", html_path);
+
+ strbuf_init(page_path, 0);
+ strbuf_addf(page_path, "%s/%s.html", html_path, page);
+}
+
+/*
+ * If open_html is not defined in a platform-specific way (see for
+ * example compat/mingw.h), we use the script web--browse to display
+ * HTML.
+ */
+#ifndef open_html
+static void open_html(const char *path)
+{
+ execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
+}
+#endif
+
+static void show_html_page(const char *git_cmd)
+{
+ const char *page = cmd_to_page(git_cmd);
+ struct strbuf page_path; /* it leaks but we exec bellow */
+
+ get_html_page_path(&page_path, page);
+
+ open_html(page_path.buf);
+}
+
+int cmd_help(int argc, const char **argv, const char *prefix)
+{
+ int nongit;
+ const char *alias;
+ enum help_format parsed_help_format;
+ load_command_list("git-", &main_cmds, &other_cmds);
+
+ argc = parse_options(argc, argv, prefix, builtin_help_options,
+ builtin_help_usage, 0);
+ parsed_help_format = help_format;
+
+ if (show_all) {
+ printf("usage: %s\n\n", git_usage_string);
+ list_commands("git commands", &main_cmds, &other_cmds);
+ printf("%s\n", git_more_info_string);
+ return 0;
+ }
+
+ if (!argv[0]) {
+ printf("usage: %s\n\n", git_usage_string);
+ list_common_cmds_help();
+ printf("\n%s\n", git_more_info_string);
+ return 0;
+ }
+
+ setup_git_directory_gently(&nongit);
+ git_config(git_help_config, NULL);
+
+ if (parsed_help_format != HELP_FORMAT_NONE)
+ help_format = parsed_help_format;
+
+ alias = alias_lookup(argv[0]);
+ if (alias && !is_git_command(argv[0])) {
+ printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+ return 0;
+ }
+
+ switch (help_format) {
+ case HELP_FORMAT_NONE:
+ case HELP_FORMAT_MAN:
+ show_man_page(argv[0]);
+ break;
+ case HELP_FORMAT_INFO:
+ show_info_page(argv[0]);
+ break;
+ case HELP_FORMAT_WEB:
+ show_html_page(argv[0]);
+ break;
+ }
+
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "delta.h"
+#include "pack.h"
+#include "csum-file.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
+#include "progress.h"
+#include "fsck.h"
+#include "exec_cmd.h"
+
+static const char index_pack_usage[] =
+"git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
+
+struct object_entry
+{
+ struct pack_idx_entry idx;
+ unsigned long size;
+ unsigned int hdr_size;
+ enum object_type type;
+ enum object_type real_type;
+};
+
+union delta_base {
+ unsigned char sha1[20];
+ off_t offset;
+};
+
+struct base_data {
+ struct base_data *base;
+ struct base_data *child;
+ struct object_entry *obj;
+ void *data;
+ unsigned long size;
+};
+
+/*
+ * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
+ * to memcmp() only the first 20 bytes.
+ */
+#define UNION_BASE_SZ 20
+
+#define FLAG_LINK (1u<<20)
+#define FLAG_CHECKED (1u<<21)
+
+struct delta_entry
+{
+ union delta_base base;
+ int obj_no;
+};
+
+static struct object_entry *objects;
+static struct delta_entry *deltas;
+static struct base_data *base_cache;
+static size_t base_cache_used;
+static int nr_objects;
+static int nr_deltas;
+static int nr_resolved_deltas;
+
+static int from_stdin;
+static int strict;
+static int verbose;
+
+static struct progress *progress;
+
+/* We always read in 4kB chunks. */
+static unsigned char input_buffer[4096];
+static unsigned int input_offset, input_len;
+static off_t consumed_bytes;
+static git_SHA_CTX input_ctx;
+static uint32_t input_crc32;
+static int input_fd, output_fd, pack_fd;
+
+static int mark_link(struct object *obj, int type, void *data)
+{
+ if (!obj)
+ return -1;
+
+ if (type != OBJ_ANY && obj->type != type)
+ die("object type mismatch at %s", sha1_to_hex(obj->sha1));
+
+ obj->flags |= FLAG_LINK;
+ return 0;
+}
+
+/* The content of each linked object must have been checked
+ or it must be already present in the object database */
+static void check_object(struct object *obj)
+{
+ if (!obj)
+ return;
+
+ if (!(obj->flags & FLAG_LINK))
+ return;
+
+ if (!(obj->flags & FLAG_CHECKED)) {
+ unsigned long size;
+ int type = sha1_object_info(obj->sha1, &size);
+ if (type != obj->type || type <= 0)
+ die("object of unexpected type");
+ obj->flags |= FLAG_CHECKED;
+ return;
+ }
+}
+
+static void check_objects(void)
+{
+ unsigned i, max;
+
+ max = get_max_object_index();
+ for (i = 0; i < max; i++)
+ check_object(get_indexed_object(i));
+}
+
+
+/* Discard current buffer used content. */
+static void flush(void)
+{
+ if (input_offset) {
+ if (output_fd >= 0)
+ write_or_die(output_fd, input_buffer, input_offset);
+ git_SHA1_Update(&input_ctx, input_buffer, input_offset);
+ memmove(input_buffer, input_buffer + input_offset, input_len);
+ input_offset = 0;
+ }
+}
+
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void *fill(int min)
+{
+ if (min <= input_len)
+ return input_buffer + input_offset;
+ if (min > sizeof(input_buffer))
+ die("cannot fill %d bytes", min);
+ flush();
+ do {
+ ssize_t ret = xread(input_fd, input_buffer + input_len,
+ sizeof(input_buffer) - input_len);
+ if (ret <= 0) {
+ if (!ret)
+ die("early EOF");
+ die_errno("read error on input");
+ }
+ input_len += ret;
+ if (from_stdin)
+ display_throughput(progress, consumed_bytes + input_len);
+ } while (input_len < min);
+ return input_buffer;
+}
+
+static void use(int bytes)
+{
+ if (bytes > input_len)
+ die("used more bytes than were available");
+ input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
+ input_len -= bytes;
+ input_offset += bytes;
+
+ /* make sure off_t is sufficiently large not to wrap */
+ if (consumed_bytes > consumed_bytes + bytes)
+ die("pack too large for current definition of off_t");
+ consumed_bytes += bytes;
+}
+
+static const char *open_pack_file(const char *pack_name)
+{
+ if (from_stdin) {
+ input_fd = 0;
+ if (!pack_name) {
+ static char tmpfile[PATH_MAX];
+ output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+ "pack/tmp_pack_XXXXXX");
+ pack_name = xstrdup(tmpfile);
+ } else
+ output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
+ if (output_fd < 0)
+ die_errno("unable to create '%s'", pack_name);
+ pack_fd = output_fd;
+ } else {
+ input_fd = open(pack_name, O_RDONLY);
+ if (input_fd < 0)
+ die_errno("cannot open packfile '%s'", pack_name);
+ output_fd = -1;
+ pack_fd = input_fd;
+ }
+ git_SHA1_Init(&input_ctx);
+ return pack_name;
+}
+
+static void parse_pack_header(void)
+{
+ struct pack_header *hdr = fill(sizeof(struct pack_header));
+
+ /* Header consistency check */
+ if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+ die("pack signature mismatch");
+ if (!pack_version_ok(hdr->hdr_version))
+ die("pack version %"PRIu32" unsupported",
+ ntohl(hdr->hdr_version));
+
+ nr_objects = ntohl(hdr->hdr_entries);
+ use(sizeof(struct pack_header));
+}
+
+static NORETURN void bad_object(unsigned long offset, const char *format,
+ ...) __attribute__((format (printf, 2, 3)));
+
+static void bad_object(unsigned long offset, const char *format, ...)
+{
+ va_list params;
+ char buf[1024];
+
+ va_start(params, format);
+ vsnprintf(buf, sizeof(buf), format, params);
+ va_end(params);
+ die("pack has bad object at offset %lu: %s", offset, buf);
+}
+
+static void free_base_data(struct base_data *c)
+{
+ if (c->data) {
+ free(c->data);
+ c->data = NULL;
+ base_cache_used -= c->size;
+ }
+}
+
+static void prune_base_data(struct base_data *retain)
+{
+ struct base_data *b;
+ for (b = base_cache;
+ base_cache_used > delta_base_cache_limit && b;
+ b = b->child) {
+ if (b->data && b != retain)
+ free_base_data(b);
+ }
+}
+
+static void link_base_data(struct base_data *base, struct base_data *c)
+{
+ if (base)
+ base->child = c;
+ else
+ base_cache = c;
+
+ c->base = base;
+ c->child = NULL;
+ if (c->data)
+ base_cache_used += c->size;
+ prune_base_data(c);
+}
+
+static void unlink_base_data(struct base_data *c)
+{
+ struct base_data *base = c->base;
+ if (base)
+ base->child = NULL;
+ else
+ base_cache = NULL;
+ free_base_data(c);
+}
+
+static void *unpack_entry_data(unsigned long offset, unsigned long size)
+{
+ z_stream stream;
+ void *buf = xmalloc(size);
+
+ memset(&stream, 0, sizeof(stream));
+ stream.next_out = buf;
+ stream.avail_out = size;
+ stream.next_in = fill(1);
+ stream.avail_in = input_len;
+ git_inflate_init(&stream);
+
+ for (;;) {
+ int ret = git_inflate(&stream, 0);
+ use(input_len - stream.avail_in);
+ if (stream.total_out == size && ret == Z_STREAM_END)
+ break;
+ if (ret != Z_OK)
+ bad_object(offset, "inflate returned %d", ret);
+ stream.next_in = fill(1);
+ stream.avail_in = input_len;
+ }
+ git_inflate_end(&stream);
+ return buf;
+}
+
+static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
+{
+ unsigned char *p;
+ unsigned long size, c;
+ off_t base_offset;
+ unsigned shift;
+ void *data;
+
+ obj->idx.offset = consumed_bytes;
+ input_crc32 = crc32(0, Z_NULL, 0);
+
+ p = fill(1);
+ c = *p;
+ use(1);
+ obj->type = (c >> 4) & 7;
+ size = (c & 15);
+ shift = 4;
+ while (c & 0x80) {
+ p = fill(1);
+ c = *p;
+ use(1);
+ size += (c & 0x7f) << shift;
+ shift += 7;
+ }
+ obj->size = size;
+
+ switch (obj->type) {
+ case OBJ_REF_DELTA:
+ hashcpy(delta_base->sha1, fill(20));
+ use(20);
+ break;
+ case OBJ_OFS_DELTA:
+ memset(delta_base, 0, sizeof(*delta_base));
+ p = fill(1);
+ c = *p;
+ use(1);
+ base_offset = c & 127;
+ while (c & 128) {
+ base_offset += 1;
+ if (!base_offset || MSB(base_offset, 7))
+ 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->idx.offset - base_offset;
+ if (delta_base->offset <= 0 || 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_BLOB:
+ case OBJ_TAG:
+ break;
+ default:
+ bad_object(obj->idx.offset, "unknown object type %d", obj->type);
+ }
+ obj->hdr_size = consumed_bytes - obj->idx.offset;
+
+ 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)
+{
+ off_t 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;
+ int st;
+
+ src = xmalloc(len);
+ data = src;
+ do {
+ ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy);
+ if (n < 0)
+ die_errno("cannot pread pack file");
+ if (!n)
+ die("premature end of pack file, %lu bytes missing",
+ len - rdy);
+ rdy += n;
+ } while (rdy < len);
+ data = xmalloc(obj->size);
+ memset(&stream, 0, sizeof(stream));
+ stream.next_out = data;
+ stream.avail_out = obj->size;
+ stream.next_in = src;
+ stream.avail_in = len;
+ git_inflate_init(&stream);
+ while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK);
+ git_inflate_end(&stream);
+ if (st != Z_STREAM_END || stream.total_out != obj->size)
+ die("serious inflate inconsistency");
+ free(src);
+ return data;
+}
+
+static int find_delta(const union delta_base *base)
+{
+ int first = 0, last = nr_deltas;
+
+ while (first < last) {
+ int next = (first + last) / 2;
+ struct delta_entry *delta = &deltas[next];
+ int cmp;
+
+ cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ return -first-1;
+}
+
+static void find_delta_children(const union delta_base *base,
+ int *first_index, int *last_index)
+{
+ int first = find_delta(base);
+ int last = first;
+ int end = nr_deltas - 1;
+
+ if (first < 0) {
+ *first_index = 0;
+ *last_index = -1;
+ return;
+ }
+ while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
+ --first;
+ while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
+ ++last;
+ *first_index = first;
+ *last_index = last;
+}
+
+static void sha1_object(const void *data, unsigned long size,
+ enum object_type type, unsigned char *sha1)
+{
+ hash_sha1_file(data, size, typename(type), sha1);
+ if (has_sha1_file(sha1)) {
+ void *has_data;
+ enum object_type has_type;
+ unsigned long has_size;
+ has_data = read_sha1_file(sha1, &has_type, &has_size);
+ if (!has_data)
+ die("cannot read existing object %s", sha1_to_hex(sha1));
+ if (size != has_size || type != has_type ||
+ memcmp(data, has_data, size) != 0)
+ die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
+ free(has_data);
+ }
+ if (strict) {
+ if (type == OBJ_BLOB) {
+ struct blob *blob = lookup_blob(sha1);
+ if (blob)
+ blob->object.flags |= FLAG_CHECKED;
+ else
+ die("invalid blob object %s", sha1_to_hex(sha1));
+ } else {
+ struct object *obj;
+ int eaten;
+ void *buf = (void *) data;
+
+ /*
+ * we do not need to free the memory here, as the
+ * buf is deleted by the caller.
+ */
+ obj = parse_object_buffer(sha1, type, size, buf, &eaten);
+ if (!obj)
+ die("invalid %s", typename(type));
+ if (fsck_object(obj, 1, fsck_error_function))
+ die("Error in object");
+ if (fsck_walk(obj, mark_link, NULL))
+ die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1));
+
+ if (obj->type == OBJ_TREE) {
+ struct tree *item = (struct tree *) obj;
+ item->buffer = NULL;
+ }
+ if (obj->type == OBJ_COMMIT) {
+ struct commit *commit = (struct commit *) obj;
+ commit->buffer = NULL;
+ }
+ obj->flags |= FLAG_CHECKED;
+ }
+ }
+}
+
+static void *get_base_data(struct base_data *c)
+{
+ if (!c->data) {
+ struct object_entry *obj = c->obj;
+
+ if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+ void *base = get_base_data(c->base);
+ void *raw = get_data_from_pack(obj);
+ c->data = patch_delta(
+ base, c->base->size,
+ raw, obj->size,
+ &c->size);
+ free(raw);
+ if (!c->data)
+ bad_object(obj->idx.offset, "failed to apply delta");
+ } else {
+ c->data = get_data_from_pack(obj);
+ c->size = obj->size;
+ }
+
+ base_cache_used += c->size;
+ prune_base_data(c);
+ }
+ return c->data;
+}
+
+static void resolve_delta(struct object_entry *delta_obj,
+ struct base_data *base, struct base_data *result)
+{
+ void *base_data, *delta_data;
+
+ delta_obj->real_type = base->obj->real_type;
+ delta_data = get_data_from_pack(delta_obj);
+ base_data = get_base_data(base);
+ result->obj = delta_obj;
+ result->data = patch_delta(base_data, base->size,
+ delta_data, delta_obj->size, &result->size);
+ free(delta_data);
+ if (!result->data)
+ bad_object(delta_obj->idx.offset, "failed to apply delta");
+ sha1_object(result->data, result->size, delta_obj->real_type,
+ delta_obj->idx.sha1);
+ nr_resolved_deltas++;
+}
+
+static void find_unresolved_deltas(struct base_data *base,
+ struct base_data *prev_base)
+{
+ int i, ref_first, ref_last, ofs_first, ofs_last;
+
+ /*
+ * This is a recursive function. Those brackets should help reducing
+ * stack usage by limiting the scope of the delta_base union.
+ */
+ {
+ union delta_base base_spec;
+
+ hashcpy(base_spec.sha1, base->obj->idx.sha1);
+ find_delta_children(&base_spec, &ref_first, &ref_last);
+
+ memset(&base_spec, 0, sizeof(base_spec));
+ base_spec.offset = base->obj->idx.offset;
+ find_delta_children(&base_spec, &ofs_first, &ofs_last);
+ }
+
+ if (ref_last == -1 && ofs_last == -1) {
+ free(base->data);
+ return;
+ }
+
+ link_base_data(prev_base, base);
+
+ for (i = ref_first; i <= ref_last; i++) {
+ struct object_entry *child = objects + deltas[i].obj_no;
+ if (child->real_type == OBJ_REF_DELTA) {
+ struct base_data result;
+ resolve_delta(child, base, &result);
+ if (i == ref_last && ofs_last == -1)
+ free_base_data(base);
+ find_unresolved_deltas(&result, base);
+ }
+ }
+
+ for (i = ofs_first; i <= ofs_last; i++) {
+ struct object_entry *child = objects + deltas[i].obj_no;
+ if (child->real_type == OBJ_OFS_DELTA) {
+ struct base_data result;
+ resolve_delta(child, base, &result);
+ if (i == ofs_last)
+ free_base_data(base);
+ find_unresolved_deltas(&result, base);
+ }
+ }
+
+ unlink_base_data(base);
+}
+
+static int compare_delta_entry(const void *a, const void *b)
+{
+ const struct delta_entry *delta_a = a;
+ const struct delta_entry *delta_b = b;
+ return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
+}
+
+/* Parse all objects and return the pack content SHA1 hash */
+static void parse_pack_objects(unsigned char *sha1)
+{
+ int i;
+ struct delta_entry *delta = deltas;
+ struct stat st;
+
+ /*
+ * First pass:
+ * - find locations of all objects;
+ * - calculate SHA1 of all non-delta objects;
+ * - remember base (SHA1 or offset) for all deltas.
+ */
+ if (verbose)
+ progress = start_progress(
+ from_stdin ? "Receiving objects" : "Indexing objects",
+ nr_objects);
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *obj = &objects[i];
+ void *data = unpack_raw_entry(obj, &delta->base);
+ obj->real_type = obj->type;
+ if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+ nr_deltas++;
+ delta->obj_no = i;
+ delta++;
+ } else
+ sha1_object(data, obj->size, obj->type, obj->idx.sha1);
+ free(data);
+ display_progress(progress, i+1);
+ }
+ objects[i].idx.offset = consumed_bytes;
+ stop_progress(&progress);
+
+ /* Check pack integrity */
+ flush();
+ git_SHA1_Final(sha1, &input_ctx);
+ if (hashcmp(fill(20), sha1))
+ die("pack is corrupted (SHA1 mismatch)");
+ use(20);
+
+ /* If input_fd is a file, we should have reached its end now. */
+ if (fstat(input_fd, &st))
+ die_errno("cannot fstat packfile");
+ if (S_ISREG(st.st_mode) &&
+ lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
+ die("pack has junk at the end");
+
+ if (!nr_deltas)
+ return;
+
+ /* Sort deltas by base SHA1/offset for fast searching */
+ qsort(deltas, nr_deltas, sizeof(struct delta_entry),
+ compare_delta_entry);
+
+ /*
+ * Second pass:
+ * - for all non-delta objects, look if it is used as a base for
+ * deltas;
+ * - if used as a base, uncompress the object and apply all deltas,
+ * recursively checking if the resulting object is used as a base
+ * for some more deltas.
+ */
+ if (verbose)
+ progress = start_progress("Resolving deltas", nr_deltas);
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *obj = &objects[i];
+ struct base_data base_obj;
+
+ if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
+ continue;
+ base_obj.obj = obj;
+ base_obj.data = NULL;
+ find_unresolved_deltas(&base_obj, NULL);
+ display_progress(progress, nr_resolved_deltas);
+ }
+}
+
+static int write_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);
+ deflateEnd(&stream);
+
+ size = stream.total_out;
+ sha1write(f, out, size);
+ free(out);
+ return size;
+}
+
+static struct object_entry *append_obj_to_pack(struct sha1file *f,
+ const unsigned char *sha1, void *buf,
+ unsigned long size, enum object_type type)
+{
+ struct object_entry *obj = &objects[nr_objects++];
+ unsigned char header[10];
+ unsigned long s = size;
+ int n = 0;
+ unsigned char c = (type << 4) | (s & 15);
+ s >>= 4;
+ while (s) {
+ header[n++] = c | 0x80;
+ c = s & 0x7f;
+ s >>= 7;
+ }
+ header[n++] = c;
+ crc32_begin(f);
+ sha1write(f, header, n);
+ obj[0].size = size;
+ obj[0].hdr_size = n;
+ obj[0].type = type;
+ obj[0].real_type = type;
+ obj[1].idx.offset = obj[0].idx.offset + n;
+ obj[1].idx.offset += write_compressed(f, buf, size);
+ obj[0].idx.crc32 = crc32_end(f);
+ sha1flush(f);
+ hashcpy(obj->idx.sha1, sha1);
+ return obj;
+}
+
+static int delta_pos_compare(const void *_a, const void *_b)
+{
+ struct delta_entry *a = *(struct delta_entry **)_a;
+ struct delta_entry *b = *(struct delta_entry **)_b;
+ return a->obj_no - b->obj_no;
+}
+
+static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
+{
+ struct delta_entry **sorted_by_pos;
+ int i, n = 0;
+
+ /*
+ * Since many unresolved deltas may well be themselves base objects
+ * for more unresolved deltas, we really want to include the
+ * smallest number of base objects that would cover as much delta
+ * as possible by picking the
+ * trunc deltas first, allowing for other deltas to resolve without
+ * additional base objects. Since most base objects are to be found
+ * before deltas depending on them, a good heuristic is to start
+ * resolving deltas in the same order as their position in the pack.
+ */
+ sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
+ for (i = 0; i < nr_deltas; i++) {
+ if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
+ continue;
+ sorted_by_pos[n++] = &deltas[i];
+ }
+ qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
+
+ for (i = 0; i < n; i++) {
+ struct delta_entry *d = sorted_by_pos[i];
+ enum object_type type;
+ struct base_data base_obj;
+
+ if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
+ continue;
+ base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
+ if (!base_obj.data)
+ continue;
+
+ if (check_sha1_signature(d->base.sha1, base_obj.data,
+ base_obj.size, typename(type)))
+ die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+ base_obj.obj = append_obj_to_pack(f, d->base.sha1,
+ base_obj.data, base_obj.size, type);
+ find_unresolved_deltas(&base_obj, NULL);
+ display_progress(progress, nr_resolved_deltas);
+ }
+ free(sorted_by_pos);
+}
+
+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,
+ unsigned char *sha1)
+{
+ const char *report = "pack";
+ char name[PATH_MAX];
+ int err;
+
+ if (!from_stdin) {
+ close(input_fd);
+ } else {
+ fsync_or_die(output_fd, curr_pack_name);
+ err = close(output_fd);
+ if (err)
+ die_errno("error while closing pack file");
+ }
+
+ if (keep_msg) {
+ int keep_fd, keep_msg_len = strlen(keep_msg);
+
+ if (!keep_name)
+ keep_fd = odb_pack_keep(name, sizeof(name), sha1);
+ else
+ keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+
+ if (keep_fd < 0) {
+ if (errno != EEXIST)
+ die_errno("cannot write keep file '%s'",
+ keep_name);
+ } else {
+ if (keep_msg_len > 0) {
+ write_or_die(keep_fd, keep_msg, keep_msg_len);
+ write_or_die(keep_fd, "\n", 1);
+ }
+ if (close(keep_fd) != 0)
+ die_errno("cannot close written keep file '%s'",
+ keep_name);
+ report = "keep";
+ }
+ }
+
+ if (final_pack_name != curr_pack_name) {
+ if (!final_pack_name) {
+ snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
+ get_object_directory(), sha1_to_hex(sha1));
+ final_pack_name = name;
+ }
+ if (move_temp_to_file(curr_pack_name, final_pack_name))
+ die("cannot store pack file");
+ } else if (from_stdin)
+ chmod(final_pack_name, 0444);
+
+ if (final_index_name != curr_index_name) {
+ if (!final_index_name) {
+ snprintf(name, sizeof(name), "%s/pack/pack-%s.idx",
+ get_object_directory(), sha1_to_hex(sha1));
+ final_index_name = name;
+ }
+ if (move_temp_to_file(curr_index_name, final_index_name))
+ die("cannot store index file");
+ } else
+ chmod(final_index_name, 0444);
+
+ if (!from_stdin) {
+ printf("%s\n", sha1_to_hex(sha1));
+ } else {
+ char buf[48];
+ int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
+ report, sha1_to_hex(sha1));
+ write_or_die(1, buf, len);
+
+ /*
+ * Let's just mimic git-unpack-objects here and write
+ * the last part of the input buffer to stdout.
+ */
+ while (input_len) {
+ err = xwrite(1, input_buffer + input_offset, input_len);
+ if (err <= 0)
+ break;
+ input_len -= err;
+ input_offset += err;
+ }
+ }
+}
+
+static int git_index_pack_config(const char *k, const char *v, void *cb)
+{
+ if (!strcmp(k, "pack.indexversion")) {
+ pack_idx_default_version = git_config_int(k, v);
+ if (pack_idx_default_version > 2)
+ die("bad pack.indexversion=%"PRIu32,
+ pack_idx_default_version);
+ return 0;
+ }
+ return git_default_config(k, v, cb);
+}
+
+int cmd_index_pack(int argc, const char **argv, const char *prefix)
+{
+ int i, fix_thin_pack = 0;
+ const char *curr_pack, *curr_index;
+ const char *index_name = NULL, *pack_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 pack_sha1[20];
+
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(index_pack_usage);
+
+ /*
+ * We wish to read the repository's config file if any, and
+ * for that it is necessary to call setup_git_directory_gently().
+ * However if the cwd was inside .git/objects/pack/ then we need
+ * to go back there or all the pack name arguments will be wrong.
+ * And in that case we cannot rely on any prefix returned by
+ * setup_git_directory_gently() either.
+ */
+ {
+ char cwd[PATH_MAX+1];
+ int nongit;
+
+ if (!getcwd(cwd, sizeof(cwd)-1))
+ die("Unable to get current working directory");
+ setup_git_directory_gently(&nongit);
+ git_config(git_index_pack_config, NULL);
+ if (chdir(cwd))
+ die("Cannot come back to cwd");
+ }
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!strcmp(arg, "--stdin")) {
+ from_stdin = 1;
+ } else if (!strcmp(arg, "--fix-thin")) {
+ fix_thin_pack = 1;
+ } else if (!strcmp(arg, "--strict")) {
+ strict = 1;
+ } else if (!strcmp(arg, "--keep")) {
+ keep_msg = "";
+ } else if (!prefixcmp(arg, "--keep=")) {
+ keep_msg = arg + 7;
+ } else if (!prefixcmp(arg, "--pack_header=")) {
+ struct pack_header *hdr;
+ char *c;
+
+ hdr = (struct pack_header *)input_buffer;
+ hdr->hdr_signature = htonl(PACK_SIGNATURE);
+ hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+ if (*c != ',')
+ die("bad %s", arg);
+ hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+ if (*c)
+ die("bad %s", arg);
+ input_len = sizeof(*hdr);
+ } else if (!strcmp(arg, "-v")) {
+ verbose = 1;
+ } else if (!strcmp(arg, "-o")) {
+ if (index_name || (i+1) >= argc)
+ usage(index_pack_usage);
+ index_name = argv[++i];
+ } else if (!prefixcmp(arg, "--index-version=")) {
+ char *c;
+ pack_idx_default_version = strtoul(arg + 16, &c, 10);
+ if (pack_idx_default_version > 2)
+ die("bad %s", arg);
+ if (*c == ',')
+ 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);
+ continue;
+ }
+
+ if (pack_name)
+ usage(index_pack_usage);
+ pack_name = arg;
+ }
+
+ if (!pack_name && !from_stdin)
+ usage(index_pack_usage);
+ if (fix_thin_pack && !from_stdin)
+ die("--fix-thin cannot be used without --stdin");
+ if (!index_name && pack_name) {
+ int len = strlen(pack_name);
+ if (!has_extension(pack_name, ".pack"))
+ die("packfile name '%s' does not end with '.pack'",
+ pack_name);
+ index_name_buf = xmalloc(len);
+ memcpy(index_name_buf, pack_name, len - 5);
+ strcpy(index_name_buf + len - 5, ".idx");
+ index_name = index_name_buf;
+ }
+ if (keep_msg && !keep_name && pack_name) {
+ int len = strlen(pack_name);
+ if (!has_extension(pack_name, ".pack"))
+ die("packfile name '%s' does not end with '.pack'",
+ pack_name);
+ keep_name_buf = xmalloc(len);
+ memcpy(keep_name_buf, pack_name, len - 5);
+ strcpy(keep_name_buf + len - 5, ".keep");
+ keep_name = keep_name_buf;
+ }
+
+ curr_pack = open_pack_file(pack_name);
+ parse_pack_header();
+ objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
+ deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
+ parse_pack_objects(pack_sha1);
+ if (nr_deltas == nr_resolved_deltas) {
+ stop_progress(&progress);
+ /* Flush remaining pack final 20-byte SHA1. */
+ flush();
+ } else {
+ if (fix_thin_pack) {
+ struct sha1file *f;
+ unsigned char read_sha1[20], tail_sha1[20];
+ char msg[48];
+ int nr_unresolved = nr_deltas - nr_resolved_deltas;
+ int nr_objects_initial = nr_objects;
+ if (nr_unresolved <= 0)
+ die("confusion beyond insanity");
+ objects = xrealloc(objects,
+ (nr_objects + nr_unresolved + 1)
+ * sizeof(*objects));
+ f = sha1fd(output_fd, curr_pack);
+ fix_unresolved_deltas(f, nr_unresolved);
+ sprintf(msg, "completed with %d local objects",
+ nr_objects - nr_objects_initial);
+ stop_progress_msg(&progress, msg);
+ sha1close(f, tail_sha1, 0);
+ hashcpy(read_sha1, pack_sha1);
+ fixup_pack_header_footer(output_fd, pack_sha1,
+ curr_pack, nr_objects,
+ read_sha1, consumed_bytes-20);
+ if (hashcmp(read_sha1, tail_sha1) != 0)
+ die("Unexpected tail checksum for %s "
+ "(disk corruption?)", curr_pack);
+ }
+ if (nr_deltas != nr_resolved_deltas)
+ die("pack has %d unresolved deltas",
+ nr_deltas - nr_resolved_deltas);
+ }
+ free(deltas);
+ if (strict)
+ check_objects();
+
+ 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, pack_sha1);
+ free(idx_objects);
+
+ final(pack_name, curr_pack,
+ index_name, curr_index,
+ keep_name, keep_msg,
+ pack_sha1);
+ free(objects);
+ free(index_name_buf);
+ free(keep_name_buf);
+ if (pack_name == NULL)
+ free((void *) curr_pack);
+ if (index_name == NULL)
+ free((void *) curr_index);
+
+ return 0;
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "parse-options.h"
+
+#ifndef DEFAULT_GIT_TEMPLATE_DIR
+#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
+#endif
+
+#ifdef NO_TRUSTABLE_FILEMODE
+#define TEST_FILEMODE 0
+#else
+#define TEST_FILEMODE 1
+#endif
+
+static int init_is_bare_repository = 0;
+static int init_shared_repository = -1;
+static const char *init_db_template_dir;
+
+static void safe_create_dir(const char *dir, int share)
+{
+ if (mkdir(dir, 0777) < 0) {
+ if (errno != EEXIST) {
+ perror(dir);
+ exit(1);
+ }
+ }
+ else if (share && adjust_shared_perm(dir))
+ die("Could not make %s writable by group", dir);
+}
+
+static void copy_templates_1(char *path, int baselen,
+ char *template, int template_baselen,
+ DIR *dir)
+{
+ struct dirent *de;
+
+ /* Note: if ".git/hooks" file exists in the repository being
+ * re-initialized, /etc/core-git/templates/hooks/update would
+ * cause "git init" to fail here. I think this is sane but
+ * it means that the set of templates we ship by default, along
+ * with the way the namespace under .git/ is organized, should
+ * be really carefully chosen.
+ */
+ safe_create_dir(path, 1);
+ while ((de = readdir(dir)) != NULL) {
+ struct stat st_git, st_template;
+ int namelen;
+ int exists = 0;
+
+ if (de->d_name[0] == '.')
+ continue;
+ namelen = strlen(de->d_name);
+ if ((PATH_MAX <= baselen + namelen) ||
+ (PATH_MAX <= template_baselen + namelen))
+ die("insanely long template name %s", de->d_name);
+ memcpy(path + baselen, de->d_name, namelen+1);
+ memcpy(template + template_baselen, de->d_name, namelen+1);
+ if (lstat(path, &st_git)) {
+ if (errno != ENOENT)
+ die_errno("cannot stat '%s'", path);
+ }
+ else
+ exists = 1;
+
+ if (lstat(template, &st_template))
+ die_errno("cannot stat template '%s'", template);
+
+ if (S_ISDIR(st_template.st_mode)) {
+ DIR *subdir = opendir(template);
+ int baselen_sub = baselen + namelen;
+ int template_baselen_sub = template_baselen + namelen;
+ if (!subdir)
+ die_errno("cannot opendir '%s'", template);
+ path[baselen_sub++] =
+ template[template_baselen_sub++] = '/';
+ path[baselen_sub] =
+ template[template_baselen_sub] = 0;
+ copy_templates_1(path, baselen_sub,
+ template, template_baselen_sub,
+ subdir);
+ closedir(subdir);
+ }
+ else if (exists)
+ continue;
+ else if (S_ISLNK(st_template.st_mode)) {
+ char lnk[256];
+ int len;
+ len = readlink(template, lnk, sizeof(lnk));
+ if (len < 0)
+ die_errno("cannot readlink '%s'", template);
+ if (sizeof(lnk) <= len)
+ die("insanely long symlink %s", template);
+ lnk[len] = 0;
+ if (symlink(lnk, path))
+ die_errno("cannot symlink '%s' '%s'", lnk, path);
+ }
+ else if (S_ISREG(st_template.st_mode)) {
+ if (copy_file(path, template, st_template.st_mode))
+ die_errno("cannot copy '%s' to '%s'", template,
+ path);
+ }
+ else
+ error("ignoring template %s", template);
+ }
+}
+
+static void copy_templates(const char *template_dir)
+{
+ char path[PATH_MAX];
+ char template_path[PATH_MAX];
+ int template_len;
+ DIR *dir;
+ const char *git_dir = get_git_dir();
+ int len = strlen(git_dir);
+
+ if (!template_dir)
+ template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
+ if (!template_dir)
+ template_dir = init_db_template_dir;
+ if (!template_dir)
+ template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
+ if (!template_dir[0])
+ return;
+ template_len = strlen(template_dir);
+ if (PATH_MAX <= (template_len+strlen("/config")))
+ die("insanely long template path %s", template_dir);
+ strcpy(template_path, template_dir);
+ if (template_path[template_len-1] != '/') {
+ template_path[template_len++] = '/';
+ template_path[template_len] = 0;
+ }
+ dir = opendir(template_path);
+ if (!dir) {
+ warning("templates not found %s", template_dir);
+ return;
+ }
+
+ /* Make sure that template is from the correct vintage */
+ strcpy(template_path + template_len, "config");
+ repository_format_version = 0;
+ git_config_from_file(check_repository_format_version,
+ template_path, NULL);
+ template_path[template_len] = 0;
+
+ if (repository_format_version &&
+ repository_format_version != GIT_REPO_VERSION) {
+ warning("not copying templates of "
+ "a wrong format version %d from '%s'",
+ repository_format_version,
+ template_dir);
+ closedir(dir);
+ return;
+ }
+
+ memcpy(path, git_dir, len);
+ if (len && path[len - 1] != '/')
+ path[len++] = '/';
+ path[len] = 0;
+ copy_templates_1(path, len,
+ template_path, template_len,
+ dir);
+ closedir(dir);
+}
+
+static int git_init_db_config(const char *k, const char *v, void *cb)
+{
+ if (!strcmp(k, "init.templatedir"))
+ return git_config_pathname(&init_db_template_dir, k, v);
+
+ return 0;
+}
+
+static int create_default_files(const char *template_path)
+{
+ const char *git_dir = get_git_dir();
+ unsigned len = strlen(git_dir);
+ static char path[PATH_MAX];
+ struct stat st1;
+ char repo_version_string[10];
+ char junk[2];
+ int reinit;
+ int filemode;
+
+ if (len > sizeof(path)-50)
+ die("insane git directory %s", git_dir);
+ memcpy(path, git_dir, len);
+
+ if (len && path[len-1] != '/')
+ path[len++] = '/';
+
+ /*
+ * Create .git/refs/{heads,tags}
+ */
+ safe_create_dir(git_path("refs"), 1);
+ safe_create_dir(git_path("refs/heads"), 1);
+ safe_create_dir(git_path("refs/tags"), 1);
+
+ /* Just look for `init.templatedir` */
+ git_config(git_init_db_config, NULL);
+
+ /* First copy the templates -- we might have the default
+ * config file there, in which case we would want to read
+ * from it after installing.
+ */
+ copy_templates(template_path);
+
+ git_config(git_default_config, NULL);
+ is_bare_repository_cfg = init_is_bare_repository;
+
+ /* reading existing config may have overwrote it */
+ if (init_shared_repository != -1)
+ shared_repository = init_shared_repository;
+
+ /*
+ * We would have created the above under user's umask -- under
+ * shared-repository settings, we would need to fix them up.
+ */
+ if (shared_repository) {
+ adjust_shared_perm(get_git_dir());
+ adjust_shared_perm(git_path("refs"));
+ adjust_shared_perm(git_path("refs/heads"));
+ adjust_shared_perm(git_path("refs/tags"));
+ }
+
+ /*
+ * Create the default symlink from ".git/HEAD" to the "master"
+ * branch, if it does not exist yet.
+ */
+ strcpy(path + len, "HEAD");
+ reinit = (!access(path, R_OK)
+ || readlink(path, junk, sizeof(junk)-1) != -1);
+ if (!reinit) {
+ if (create_symref("HEAD", "refs/heads/master", NULL) < 0)
+ exit(1);
+ }
+
+ /* This forces creation of new config file */
+ sprintf(repo_version_string, "%d", GIT_REPO_VERSION);
+ git_config_set("core.repositoryformatversion", repo_version_string);
+
+ path[len] = 0;
+ strcpy(path + len, "config");
+
+ /* Check filemode trustability */
+ filemode = TEST_FILEMODE;
+ if (TEST_FILEMODE && !lstat(path, &st1)) {
+ struct stat st2;
+ filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) &&
+ !lstat(path, &st2) &&
+ st1.st_mode != st2.st_mode);
+ }
+ git_config_set("core.filemode", filemode ? "true" : "false");
+
+ 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 (prefixcmp(git_dir, work_tree) ||
+ strcmp(git_dir + strlen(work_tree), "/.git")) {
+ git_config_set("core.worktree", work_tree);
+ }
+ }
+
+ if (!reinit) {
+ /* Check if symlink is supported in the work tree */
+ path[len] = 0;
+ strcpy(path + len, "tXXXXXX");
+ if (!close(xmkstemp(path)) &&
+ !unlink(path) &&
+ !symlink("testing", path) &&
+ !lstat(path, &st1) &&
+ S_ISLNK(st1.st_mode))
+ unlink(path); /* good */
+ else
+ git_config_set("core.symlinks", "false");
+
+ /* Check if the filesystem is case-insensitive */
+ path[len] = 0;
+ strcpy(path + len, "CoNfIg");
+ if (!access(path, F_OK))
+ git_config_set("core.ignorecase", "true");
+ }
+
+ return reinit;
+}
+
+int init_db(const char *template_dir, unsigned int flags)
+{
+ const char *sha1_dir;
+ char *path;
+ int len, reinit;
+
+ safe_create_dir(get_git_dir(), 0);
+
+ init_is_bare_repository = is_bare_repository();
+
+ /* Check to see if the repository version is right.
+ * Note that a newly created repository does not have
+ * config file, so this will not fail. What we are catching
+ * is an attempt to reinitialize new repository with an old tool.
+ */
+ check_repository_format();
+
+ reinit = create_default_files(template_dir);
+
+ sha1_dir = get_object_directory();
+ len = strlen(sha1_dir);
+ path = xmalloc(len + 40);
+ memcpy(path, sha1_dir, len);
+
+ safe_create_dir(sha1_dir, 1);
+ strcpy(path+len, "/pack");
+ safe_create_dir(path, 1);
+ strcpy(path+len, "/info");
+ safe_create_dir(path, 1);
+
+ if (shared_repository) {
+ char buf[10];
+ /* We do not spell "group" and such, so that
+ * the configuration can be read by older version
+ * of git. Note, we use octal numbers for new share modes,
+ * and compatibility values for PERM_GROUP and
+ * PERM_EVERYBODY.
+ */
+ if (shared_repository < 0)
+ /* force to the mode value */
+ sprintf(buf, "0%o", -shared_repository);
+ else if (shared_repository == PERM_GROUP)
+ sprintf(buf, "%d", OLD_PERM_GROUP);
+ else if (shared_repository == PERM_EVERYBODY)
+ sprintf(buf, "%d", OLD_PERM_EVERYBODY);
+ else
+ die("oops");
+ git_config_set("core.sharedrepository", buf);
+ git_config_set("receive.denyNonFastforwards", "true");
+ }
+
+ if (!(flags & INIT_DB_QUIET)) {
+ const char *git_dir = get_git_dir();
+ int len = strlen(git_dir);
+ printf("%s%s Git repository in %s%s\n",
+ reinit ? "Reinitialized existing" : "Initialized empty",
+ shared_repository ? " shared" : "",
+ git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+ }
+
+ return 0;
+}
+
+static int guess_repository_type(const char *git_dir)
+{
+ char cwd[PATH_MAX];
+ const char *slash;
+
+ /*
+ * "GIT_DIR=. git init" is always bare.
+ * "GIT_DIR=`pwd` git init" too.
+ */
+ if (!strcmp(".", git_dir))
+ return 1;
+ if (!getcwd(cwd, sizeof(cwd)))
+ die_errno("cannot tell cwd");
+ if (!strcmp(git_dir, cwd))
+ return 1;
+ /*
+ * "GIT_DIR=.git or GIT_DIR=something/.git is usually not.
+ */
+ if (!strcmp(git_dir, ".git"))
+ return 0;
+ slash = strrchr(git_dir, '/');
+ if (slash && !strcmp(slash, "/.git"))
+ return 0;
+
+ /*
+ * Otherwise it is often bare. At this point
+ * we are just guessing.
+ */
+ return 1;
+}
+
+static int shared_callback(const struct option *opt, const char *arg, int unset)
+{
+ *((int *) opt->value) = (arg) ? git_config_perm("arg", arg) : PERM_GROUP;
+ return 0;
+}
+
+static const char *const init_db_usage[] = {
+ "git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]",
+ NULL
+};
+
+/*
+ * If you want to, you can share the DB area with any number of branches.
+ * That has advantages: you can save space by sharing all the SHA1 objects.
+ * On the other hand, it might just make lookup slower and messier. You
+ * be the judge. The default case is to have one DB per managed directory.
+ */
+int cmd_init_db(int argc, const char **argv, const char *prefix)
+{
+ const char *git_dir;
+ const char *template_dir = NULL;
+ unsigned int flags = 0;
+ const struct option init_db_options[] = {
+ OPT_STRING(0, "template", &template_dir, "template-directory",
+ "provide the directory from which templates will be used"),
+ OPT_SET_INT(0, "bare", &is_bare_repository_cfg,
+ "create a bare repository", 1),
+ { OPTION_CALLBACK, 0, "shared", &init_shared_repository,
+ "permissions",
+ "specify that the git repository is to be shared amongst several users",
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
+ OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
+
+ if (argc == 1) {
+ int mkdir_tried = 0;
+ retry:
+ if (chdir(argv[0]) < 0) {
+ if (!mkdir_tried) {
+ int saved;
+ /*
+ * At this point we haven't read any configuration,
+ * and we know shared_repository should always be 0;
+ * but just in case we play safe.
+ */
+ saved = shared_repository;
+ shared_repository = 0;
+ switch (safe_create_leading_directories_const(argv[0])) {
+ case -3:
+ errno = EEXIST;
+ /* fallthru */
+ case -1:
+ die_errno("cannot mkdir %s", argv[0]);
+ break;
+ default:
+ break;
+ }
+ shared_repository = saved;
+ if (mkdir(argv[0], 0777) < 0)
+ die_errno("cannot mkdir %s", argv[0]);
+ mkdir_tried = 1;
+ goto retry;
+ }
+ die_errno("cannot chdir to %s", argv[0]);
+ }
+ } else if (0 < argc) {
+ usage(init_db_usage[0]);
+ }
+ if (is_bare_repository_cfg == 1) {
+ static char git_dir[PATH_MAX+1];
+
+ setenv(GIT_DIR_ENVIRONMENT,
+ getcwd(git_dir, sizeof(git_dir)), 0);
+ }
+
+ if (init_shared_repository != -1)
+ shared_repository = init_shared_repository;
+
+ /*
+ * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
+ * without --bare. Catch the error early.
+ */
+ git_dir = getenv(GIT_DIR_ENVIRONMENT);
+ if ((!git_dir || is_bare_repository_cfg == 1)
+ && getenv(GIT_WORK_TREE_ENVIRONMENT))
+ die("%s (or --work-tree=<directory>) not allowed without "
+ "specifying %s (or --git-dir=<directory>)",
+ GIT_WORK_TREE_ENVIRONMENT,
+ GIT_DIR_ENVIRONMENT);
+
+ /*
+ * Set up the default .git directory contents
+ */
+ if (!git_dir)
+ git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+
+ if (is_bare_repository_cfg < 0)
+ is_bare_repository_cfg = guess_repository_type(git_dir);
+
+ if (!is_bare_repository_cfg) {
+ if (git_dir) {
+ const char *git_dir_parent = strrchr(git_dir, '/');
+ if (git_dir_parent) {
+ char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
+ git_work_tree_cfg = xstrdup(make_absolute_path(rel));
+ free(rel);
+ }
+ }
+ if (!git_work_tree_cfg) {
+ git_work_tree_cfg = xcalloc(PATH_MAX, 1);
+ if (!getcwd(git_work_tree_cfg, PATH_MAX))
+ die_errno ("Cannot access current working directory");
+ }
+ if (access(get_git_work_tree(), X_OK))
+ die_errno ("Cannot access work tree '%s'",
+ get_git_work_tree());
+ }
+
+ set_git_dir(make_absolute_path(git_dir));
+
+ return init_db(template_dir, flags);
+}
--- /dev/null
+/*
+ * Builtin "git log" and related commands (show, whatchanged)
+ *
+ * (C) Copyright 2006 Linus Torvalds
+ * 2006 Junio Hamano
+ */
+#include "cache.h"
+#include "color.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "builtin.h"
+#include "tag.h"
+#include "reflog-walk.h"
+#include "patch-ids.h"
+#include "run-command.h"
+#include "shortlog.h"
+#include "remote.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+/* Set a default date-time format for git log ("log.date" config variable) */
+static const char *default_date_mode = NULL;
+
+static int default_show_root = 1;
+static const char *fmt_patch_subject_prefix = "PATCH";
+static const char *fmt_pretty;
+
+static const char * const builtin_log_usage =
+ "git log [<options>] [<since>..<until>] [[--] <path>...]\n"
+ " or: git show [options] <object>...";
+
+static void cmd_log_init(int argc, const char **argv, const char *prefix,
+ struct rev_info *rev)
+{
+ int i;
+ int decoration_style = 0;
+
+ rev->abbrev = DEFAULT_ABBREV;
+ rev->commit_format = CMIT_FMT_DEFAULT;
+ if (fmt_pretty)
+ get_commit_format(fmt_pretty, rev);
+ rev->verbose_header = 1;
+ DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
+ rev->show_root_diff = default_show_root;
+ rev->subject_prefix = fmt_patch_subject_prefix;
+ DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV);
+
+ if (default_date_mode)
+ rev->date_mode = parse_date_format(default_date_mode);
+
+ /*
+ * Check for -h before setup_revisions(), or "git log -h" will
+ * fail when run without a git directory.
+ */
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(builtin_log_usage);
+ argc = setup_revisions(argc, argv, rev, "HEAD");
+
+ if (!rev->show_notes_given && !rev->pretty_given)
+ rev->show_notes = 1;
+
+ if (rev->diffopt.pickaxe || rev->diffopt.filter)
+ rev->always_show_header = 0;
+ if (DIFF_OPT_TST(&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 (!strcmp(arg, "--decorate")) {
+ decoration_style = DECORATE_SHORT_REFS;
+ } else if (!prefixcmp(arg, "--decorate=")) {
+ const char *v = skip_prefix(arg, "--decorate=");
+ if (!strcmp(v, "full"))
+ decoration_style = DECORATE_FULL_REFS;
+ else if (!strcmp(v, "short"))
+ decoration_style = DECORATE_SHORT_REFS;
+ else
+ die("invalid --decorate option: %s", arg);
+ } else if (!strcmp(arg, "--source")) {
+ rev->show_source = 1;
+ } else if (!strcmp(arg, "-h")) {
+ usage(builtin_log_usage);
+ } else
+ die("unrecognized argument: %s", arg);
+ }
+ if (decoration_style) {
+ rev->show_decorations = 1;
+ load_ref_decorations(decoration_style);
+ }
+}
+
+/*
+ * This gives a rough estimate for how many commits we
+ * will print out in the list.
+ */
+static int estimate_commit_count(struct rev_info *rev, struct commit_list *list)
+{
+ int n = 0;
+
+ while (list) {
+ struct commit *commit = list->item;
+ unsigned int flags = commit->object.flags;
+ list = list->next;
+ if (!(flags & (TREESAME | UNINTERESTING)))
+ n++;
+ }
+ return n;
+}
+
+static void show_early_header(struct rev_info *rev, const char *stage, int nr)
+{
+ if (rev->shown_one) {
+ rev->shown_one = 0;
+ if (rev->commit_format != CMIT_FMT_ONELINE)
+ putchar(rev->diffopt.line_termination);
+ }
+ printf("Final output: %d %s\n", nr, stage);
+}
+
+static struct itimerval early_output_timer;
+
+static void log_show_early(struct rev_info *revs, struct commit_list *list)
+{
+ int i = revs->early_output;
+ int show_header = 1;
+
+ sort_in_topological_order(&list, revs->lifo);
+ while (list && i) {
+ struct commit *commit = list->item;
+ switch (simplify_commit(revs, commit)) {
+ case commit_show:
+ if (show_header) {
+ int n = estimate_commit_count(revs, list);
+ show_early_header(revs, "incomplete", n);
+ show_header = 0;
+ }
+ log_tree_commit(revs, commit);
+ i--;
+ break;
+ case commit_ignore:
+ break;
+ case commit_error:
+ return;
+ }
+ list = list->next;
+ }
+
+ /* Did we already get enough commits for the early output? */
+ if (!i)
+ return;
+
+ /*
+ * ..if no, then repeat it twice a second until we
+ * do.
+ *
+ * NOTE! We don't use "it_interval", because if the
+ * reader isn't listening, we want our output to be
+ * throttled by the writing, and not have the timer
+ * trigger every second even if we're blocked on a
+ * reader!
+ */
+ early_output_timer.it_value.tv_sec = 0;
+ early_output_timer.it_value.tv_usec = 500000;
+ setitimer(ITIMER_REAL, &early_output_timer, NULL);
+}
+
+static void early_output(int signal)
+{
+ show_early_output = log_show_early;
+}
+
+static void setup_early_output(struct rev_info *rev)
+{
+ struct sigaction sa;
+
+ /*
+ * Set up the signal handler, minimally intrusively:
+ * we only set a single volatile integer word (not
+ * using sigatomic_t - trying to avoid unnecessary
+ * system dependencies and headers), and using
+ * SA_RESTART.
+ */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = early_output;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGALRM, &sa, NULL);
+
+ /*
+ * If we can get the whole output in less than a
+ * tenth of a second, don't even bother doing the
+ * early-output thing..
+ *
+ * This is a one-time-only trigger.
+ */
+ early_output_timer.it_value.tv_sec = 0;
+ early_output_timer.it_value.tv_usec = 100000;
+ setitimer(ITIMER_REAL, &early_output_timer, NULL);
+}
+
+static void finish_early_output(struct rev_info *rev)
+{
+ int n = estimate_commit_count(rev, rev->commits);
+ signal(SIGALRM, SIG_IGN);
+ show_early_header(rev, "done", n);
+}
+
+static int cmd_log_walk(struct rev_info *rev)
+{
+ struct commit *commit;
+
+ if (rev->early_output)
+ setup_early_output(rev);
+
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
+
+ if (rev->early_output)
+ finish_early_output(rev);
+
+ /*
+ * For --check and --exit-code, the exit code is based on CHECK_FAILED
+ * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to
+ * retain that state information if replacing rev->diffopt in this loop
+ */
+ while ((commit = get_revision(rev)) != NULL) {
+ log_tree_commit(rev, commit);
+ if (!rev->reflog_info) {
+ /* we allow cycles in reflog ancestry */
+ free(commit->buffer);
+ commit->buffer = NULL;
+ }
+ free_commit_list(commit->parents);
+ commit->parents = NULL;
+ }
+ if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
+ DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) {
+ return 02;
+ }
+ return diff_result_code(&rev->diffopt, 0);
+}
+
+static int git_log_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "format.pretty"))
+ return git_config_string(&fmt_pretty, var, value);
+ if (!strcmp(var, "format.subjectprefix"))
+ return git_config_string(&fmt_patch_subject_prefix, var, value);
+ if (!strcmp(var, "log.date"))
+ return git_config_string(&default_date_mode, var, value);
+ if (!strcmp(var, "log.showroot")) {
+ default_show_root = git_config_bool(var, value);
+ return 0;
+ }
+ return git_diff_ui_config(var, value, cb);
+}
+
+int cmd_whatchanged(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info rev;
+
+ git_config(git_log_config, NULL);
+
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ init_revisions(&rev, prefix);
+ rev.diff = 1;
+ rev.simplify_history = 0;
+ cmd_log_init(argc, argv, prefix, &rev);
+ if (!rev.diffopt.output_format)
+ rev.diffopt.output_format = DIFF_FORMAT_RAW;
+ return cmd_log_walk(&rev);
+}
+
+static void show_tagger(char *buf, int len, struct rev_info *rev)
+{
+ struct strbuf out = STRBUF_INIT;
+
+ pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
+ git_log_output_encoding ?
+ git_log_output_encoding: git_commit_encoding);
+ printf("%s", out.buf);
+ strbuf_release(&out);
+}
+
+static int show_object(const unsigned char *sha1, int show_tag_object,
+ struct rev_info *rev)
+{
+ unsigned long size;
+ enum object_type type;
+ char *buf = read_sha1_file(sha1, &type, &size);
+ int offset = 0;
+
+ if (!buf)
+ return error("Could not read object %s", sha1_to_hex(sha1));
+
+ if (show_tag_object)
+ while (offset < size && buf[offset] != '\n') {
+ int new_offset = offset + 1;
+ while (new_offset < size && buf[new_offset++] != '\n')
+ ; /* do nothing */
+ if (!prefixcmp(buf + offset, "tagger "))
+ show_tagger(buf + offset + 7,
+ new_offset - offset - 7, rev);
+ offset = new_offset;
+ }
+
+ if (offset < size)
+ fwrite(buf + offset, size - offset, 1, stdout);
+ free(buf);
+ return 0;
+}
+
+static int show_tree_object(const unsigned char *sha1,
+ const char *base, int baselen,
+ const char *pathname, unsigned mode, int stage, void *context)
+{
+ printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
+ return 0;
+}
+
+int cmd_show(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info rev;
+ struct object_array_entry *objects;
+ int i, count, ret = 0;
+
+ git_config(git_log_config, NULL);
+
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ init_revisions(&rev, prefix);
+ rev.diff = 1;
+ rev.combine_merges = 1;
+ rev.dense_combined_merges = 1;
+ rev.always_show_header = 1;
+ rev.ignore_merges = 0;
+ rev.no_walk = 1;
+ cmd_log_init(argc, argv, prefix, &rev);
+
+ count = rev.pending.nr;
+ objects = rev.pending.objects;
+ for (i = 0; i < count && !ret; i++) {
+ struct object *o = objects[i].item;
+ const char *name = objects[i].name;
+ switch (o->type) {
+ case OBJ_BLOB:
+ ret = show_object(o->sha1, 0, NULL);
+ break;
+ case OBJ_TAG: {
+ struct tag *t = (struct tag *)o;
+
+ if (rev.shown_one)
+ putchar('\n');
+ printf("%stag %s%s\n",
+ diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
+ t->tag,
+ diff_get_color_opt(&rev.diffopt, DIFF_RESET));
+ ret = show_object(o->sha1, 1, &rev);
+ rev.shown_one = 1;
+ if (ret)
+ break;
+ o = parse_object(t->tagged->sha1);
+ if (!o)
+ ret = error("Could not read object %s",
+ sha1_to_hex(t->tagged->sha1));
+ objects[i].item = o;
+ i--;
+ break;
+ }
+ case OBJ_TREE:
+ if (rev.shown_one)
+ putchar('\n');
+ printf("%stree %s%s\n\n",
+ diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
+ name,
+ diff_get_color_opt(&rev.diffopt, DIFF_RESET));
+ read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
+ show_tree_object, NULL);
+ rev.shown_one = 1;
+ break;
+ case OBJ_COMMIT:
+ rev.pending.nr = rev.pending.alloc = 0;
+ rev.pending.objects = NULL;
+ add_object_array(o, name, &rev.pending);
+ ret = cmd_log_walk(&rev);
+ break;
+ default:
+ ret = error("Unknown type: %d", o->type);
+ }
+ }
+ free(objects);
+ return ret;
+}
+
+/*
+ * This is equivalent to "git log -g --abbrev-commit --pretty=oneline"
+ */
+int cmd_log_reflog(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info rev;
+
+ git_config(git_log_config, NULL);
+
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ init_revisions(&rev, prefix);
+ init_reflog_walk(&rev.reflog_info);
+ rev.abbrev_commit = 1;
+ rev.verbose_header = 1;
+ cmd_log_init(argc, argv, prefix, &rev);
+
+ /*
+ * This means that we override whatever commit format the user gave
+ * on the cmd line. Sad, but cmd_log_init() currently doesn't
+ * allow us to set a different default.
+ */
+ rev.commit_format = CMIT_FMT_ONELINE;
+ rev.use_terminator = 1;
+ rev.always_show_header = 1;
+
+ /*
+ * We get called through "git reflog", so unlike the other log
+ * routines, we need to set up our pager manually..
+ */
+ setup_pager();
+
+ return cmd_log_walk(&rev);
+}
+
+int cmd_log(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info rev;
+
+ git_config(git_log_config, NULL);
+
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ init_revisions(&rev, prefix);
+ rev.always_show_header = 1;
+ cmd_log_init(argc, argv, prefix, &rev);
+ return cmd_log_walk(&rev);
+}
+
+/* format-patch */
+
+static const char *fmt_patch_suffix = ".patch";
+static int numbered = 0;
+static int auto_number = 1;
+
+static char *default_attach = NULL;
+
+static char **extra_hdr;
+static int extra_hdr_nr;
+static int extra_hdr_alloc;
+
+static char **extra_to;
+static int extra_to_nr;
+static int extra_to_alloc;
+
+static char **extra_cc;
+static int extra_cc_nr;
+static int extra_cc_alloc;
+
+static void add_header(const char *value)
+{
+ int len = strlen(value);
+ while (len && value[len - 1] == '\n')
+ len--;
+ if (!strncasecmp(value, "to: ", 4)) {
+ ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc);
+ extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4);
+ return;
+ }
+ if (!strncasecmp(value, "cc: ", 4)) {
+ ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
+ extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4);
+ return;
+ }
+ ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc);
+ extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
+}
+
+#define THREAD_SHALLOW 1
+#define THREAD_DEEP 2
+static int thread = 0;
+static int do_signoff = 0;
+
+static int git_format_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "format.headers")) {
+ if (!value)
+ die("format.headers without value");
+ add_header(value);
+ return 0;
+ }
+ if (!strcmp(var, "format.suffix"))
+ return git_config_string(&fmt_patch_suffix, var, value);
+ if (!strcmp(var, "format.cc")) {
+ if (!value)
+ return config_error_nonbool(var);
+ ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
+ extra_cc[extra_cc_nr++] = xstrdup(value);
+ return 0;
+ }
+ if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
+ return 0;
+ }
+ if (!strcmp(var, "format.numbered")) {
+ if (value && !strcasecmp(value, "auto")) {
+ auto_number = 1;
+ return 0;
+ }
+ numbered = git_config_bool(var, value);
+ auto_number = auto_number && numbered;
+ return 0;
+ }
+ if (!strcmp(var, "format.attach")) {
+ if (value && *value)
+ default_attach = xstrdup(value);
+ else
+ default_attach = xstrdup(git_version_string);
+ return 0;
+ }
+ if (!strcmp(var, "format.thread")) {
+ if (value && !strcasecmp(value, "deep")) {
+ thread = THREAD_DEEP;
+ return 0;
+ }
+ if (value && !strcasecmp(value, "shallow")) {
+ thread = THREAD_SHALLOW;
+ return 0;
+ }
+ thread = git_config_bool(var, value) && THREAD_SHALLOW;
+ return 0;
+ }
+ if (!strcmp(var, "format.signoff")) {
+ do_signoff = git_config_bool(var, value);
+ return 0;
+ }
+
+ return git_log_config(var, value, cb);
+}
+
+static FILE *realstdout = NULL;
+static const char *output_directory = NULL;
+static int outdir_offset;
+
+static int reopen_stdout(struct commit *commit, struct rev_info *rev)
+{
+ struct strbuf filename = STRBUF_INIT;
+ int suffix_len = strlen(fmt_patch_suffix) + 1;
+
+ if (output_directory) {
+ strbuf_addstr(&filename, output_directory);
+ if (filename.len >=
+ PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
+ return error("name of output directory is too long");
+ if (filename.buf[filename.len - 1] != '/')
+ strbuf_addch(&filename, '/');
+ }
+
+ get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
+
+ if (!DIFF_OPT_TST(&rev->diffopt, QUICK))
+ fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
+
+ if (freopen(filename.buf, "w", stdout) == NULL)
+ return error("Cannot open patch file %s", filename.buf);
+
+ strbuf_release(&filename);
+ return 0;
+}
+
+static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix)
+{
+ struct rev_info check_rev;
+ struct commit *commit;
+ struct object *o1, *o2;
+ unsigned flags1, flags2;
+
+ if (rev->pending.nr != 2)
+ die("Need exactly one range.");
+
+ o1 = rev->pending.objects[0].item;
+ flags1 = o1->flags;
+ o2 = rev->pending.objects[1].item;
+ flags2 = o2->flags;
+
+ if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
+ die("Not a range.");
+
+ init_patch_ids(ids);
+
+ /* given a range a..b get all patch ids for b..a */
+ init_revisions(&check_rev, prefix);
+ o1->flags ^= UNINTERESTING;
+ o2->flags ^= UNINTERESTING;
+ add_pending_object(&check_rev, o1, "o1");
+ add_pending_object(&check_rev, o2, "o2");
+ if (prepare_revision_walk(&check_rev))
+ die("revision walk setup failed");
+
+ while ((commit = get_revision(&check_rev)) != NULL) {
+ /* ignore merges */
+ if (commit->parents && commit->parents->next)
+ continue;
+
+ add_commit_patch_id(commit, ids);
+ }
+
+ /* reset for next revision walk */
+ clear_commit_marks((struct commit *)o1,
+ SEEN | UNINTERESTING | SHOWN | ADDED);
+ clear_commit_marks((struct commit *)o2,
+ SEEN | UNINTERESTING | SHOWN | ADDED);
+ o1->flags = flags1;
+ o2->flags = flags2;
+}
+
+static void gen_message_id(struct rev_info *info, char *base)
+{
+ const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
+ const char *email_start = strrchr(committer, '<');
+ const char *email_end = strrchr(committer, '>');
+ struct strbuf buf = STRBUF_INIT;
+ if (!email_start || !email_end || email_start > email_end - 1)
+ die("Could not extract email from committer identity.");
+ strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
+ (unsigned long) time(NULL),
+ (int)(email_end - email_start - 1), email_start + 1);
+ info->message_id = strbuf_detach(&buf, NULL);
+}
+
+static void make_cover_letter(struct rev_info *rev, int use_stdout,
+ int numbered, int numbered_files,
+ struct commit *origin,
+ int nr, struct commit **list, struct commit *head)
+{
+ const char *committer;
+ const char *subject_start = NULL;
+ const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
+ const char *msg;
+ const char *extra_headers = rev->extra_headers;
+ struct shortlog log;
+ struct strbuf sb = STRBUF_INIT;
+ int i;
+ const char *encoding = "UTF-8";
+ struct diff_options opts;
+ int need_8bit_cte = 0;
+ struct commit *commit = NULL;
+
+ if (rev->commit_format != CMIT_FMT_EMAIL)
+ die("Cover letter needs email format");
+
+ committer = git_committer_info(0);
+
+ if (!numbered_files) {
+ /*
+ * We fake a commit for the cover letter so we get the filename
+ * desired.
+ */
+ commit = xcalloc(1, sizeof(*commit));
+ commit->buffer = xmalloc(400);
+ snprintf(commit->buffer, 400,
+ "tree 0000000000000000000000000000000000000000\n"
+ "parent %s\n"
+ "author %s\n"
+ "committer %s\n\n"
+ "cover letter\n",
+ sha1_to_hex(head->object.sha1), committer, committer);
+ }
+
+ if (!use_stdout && reopen_stdout(commit, rev))
+ return;
+
+ if (commit) {
+
+ free(commit->buffer);
+ free(commit);
+ }
+
+ log_write_email_headers(rev, head, &subject_start, &extra_headers,
+ &need_8bit_cte);
+
+ for (i = 0; !need_8bit_cte && i < nr; i++)
+ if (has_non_ascii(list[i]->buffer))
+ need_8bit_cte = 1;
+
+ msg = body;
+ pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
+ encoding);
+ pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
+ encoding, need_8bit_cte);
+ pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
+ printf("%s\n", sb.buf);
+
+ strbuf_release(&sb);
+
+ shortlog_init(&log);
+ log.wrap_lines = 1;
+ log.wrap = 72;
+ log.in1 = 2;
+ log.in2 = 4;
+ for (i = 0; i < nr; i++)
+ shortlog_add_commit(&log, list[i]);
+
+ shortlog_output(&log);
+
+ /*
+ * We can only do diffstat with a unique reference point
+ */
+ if (!origin)
+ return;
+
+ memcpy(&opts, &rev->diffopt, sizeof(opts));
+ opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+
+ diff_setup_done(&opts);
+
+ diff_tree_sha1(origin->tree->object.sha1,
+ head->tree->object.sha1,
+ "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+
+ printf("\n");
+}
+
+static const char *clean_message_id(const char *msg_id)
+{
+ char ch;
+ const char *a, *z, *m;
+
+ m = msg_id;
+ while ((ch = *m) && (isspace(ch) || (ch == '<')))
+ m++;
+ a = m;
+ z = NULL;
+ while ((ch = *m)) {
+ if (!isspace(ch) && (ch != '>'))
+ z = m;
+ m++;
+ }
+ if (!z)
+ die("insane in-reply-to: %s", msg_id);
+ if (++z == m)
+ return a;
+ return xmemdupz(a, z - a);
+}
+
+static const char *set_outdir(const char *prefix, const char *output_directory)
+{
+ if (output_directory && is_absolute_path(output_directory))
+ return output_directory;
+
+ if (!prefix || !*prefix) {
+ if (output_directory)
+ return output_directory;
+ /* The user did not explicitly ask for "./" */
+ outdir_offset = 2;
+ return "./";
+ }
+
+ outdir_offset = strlen(prefix);
+ if (!output_directory)
+ return prefix;
+
+ return xstrdup(prefix_filename(prefix, outdir_offset,
+ output_directory));
+}
+
+static const char * const builtin_format_patch_usage[] = {
+ "git format-patch [options] [<since> | <revision range>]",
+ NULL
+};
+
+static int keep_subject = 0;
+
+static int keep_callback(const struct option *opt, const char *arg, int unset)
+{
+ ((struct rev_info *)opt->value)->total = -1;
+ keep_subject = 1;
+ return 0;
+}
+
+static int subject_prefix = 0;
+
+static int subject_prefix_callback(const struct option *opt, const char *arg,
+ int unset)
+{
+ subject_prefix = 1;
+ ((struct rev_info *)opt->value)->subject_prefix = arg;
+ return 0;
+}
+
+static int numbered_cmdline_opt = 0;
+
+static int numbered_callback(const struct option *opt, const char *arg,
+ int unset)
+{
+ *(int *)opt->value = numbered_cmdline_opt = unset ? 0 : 1;
+ if (unset)
+ auto_number = 0;
+ return 0;
+}
+
+static int no_numbered_callback(const struct option *opt, const char *arg,
+ int unset)
+{
+ return numbered_callback(opt, arg, 1);
+}
+
+static int output_directory_callback(const struct option *opt, const char *arg,
+ int unset)
+{
+ const char **dir = (const char **)opt->value;
+ if (*dir)
+ die("Two output directories?");
+ *dir = arg;
+ return 0;
+}
+
+static int thread_callback(const struct option *opt, const char *arg, int unset)
+{
+ int *thread = (int *)opt->value;
+ if (unset)
+ *thread = 0;
+ else if (!arg || !strcmp(arg, "shallow"))
+ *thread = THREAD_SHALLOW;
+ else if (!strcmp(arg, "deep"))
+ *thread = THREAD_DEEP;
+ else
+ return 1;
+ return 0;
+}
+
+static int attach_callback(const struct option *opt, const char *arg, int unset)
+{
+ struct rev_info *rev = (struct rev_info *)opt->value;
+ if (unset)
+ rev->mime_boundary = NULL;
+ else if (arg)
+ rev->mime_boundary = arg;
+ else
+ rev->mime_boundary = git_version_string;
+ rev->no_inline = unset ? 0 : 1;
+ return 0;
+}
+
+static int inline_callback(const struct option *opt, const char *arg, int unset)
+{
+ struct rev_info *rev = (struct rev_info *)opt->value;
+ if (unset)
+ rev->mime_boundary = NULL;
+ else if (arg)
+ rev->mime_boundary = arg;
+ else
+ rev->mime_boundary = git_version_string;
+ rev->no_inline = 0;
+ return 0;
+}
+
+static int header_callback(const struct option *opt, const char *arg, int unset)
+{
+ add_header(arg);
+ return 0;
+}
+
+static int cc_callback(const struct option *opt, const char *arg, int unset)
+{
+ ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
+ extra_cc[extra_cc_nr++] = xstrdup(arg);
+ return 0;
+}
+
+int cmd_format_patch(int argc, const char **argv, const char *prefix)
+{
+ struct commit *commit;
+ struct commit **list = NULL;
+ struct rev_info rev;
+ int nr = 0, total, i;
+ int use_stdout = 0;
+ int start_number = -1;
+ int numbered_files = 0; /* _just_ numbers */
+ int ignore_if_in_upstream = 0;
+ int cover_letter = 0;
+ int boundary_count = 0;
+ int no_binary_diff = 0;
+ struct commit *origin = NULL, *head = NULL;
+ const char *in_reply_to = NULL;
+ struct patch_ids ids;
+ char *add_signoff = NULL;
+ struct strbuf buf = STRBUF_INIT;
+ int use_patch_format = 0;
+ const struct option builtin_format_patch_options[] = {
+ { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
+ "use [PATCH n/m] even with a single patch",
+ PARSE_OPT_NOARG, numbered_callback },
+ { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL,
+ "use [PATCH] even with multiple patches",
+ PARSE_OPT_NOARG, no_numbered_callback },
+ OPT_BOOLEAN('s', "signoff", &do_signoff, "add Signed-off-by:"),
+ OPT_BOOLEAN(0, "stdout", &use_stdout,
+ "print patches to standard out"),
+ OPT_BOOLEAN(0, "cover-letter", &cover_letter,
+ "generate a cover letter"),
+ OPT_BOOLEAN(0, "numbered-files", &numbered_files,
+ "use simple number sequence for output file names"),
+ OPT_STRING(0, "suffix", &fmt_patch_suffix, "sfx",
+ "use <sfx> instead of '.patch'"),
+ OPT_INTEGER(0, "start-number", &start_number,
+ "start numbering patches at <n> instead of 1"),
+ { OPTION_CALLBACK, 0, "subject-prefix", &rev, "prefix",
+ "Use [<prefix>] instead of [PATCH]",
+ PARSE_OPT_NONEG, subject_prefix_callback },
+ { OPTION_CALLBACK, 'o', "output-directory", &output_directory,
+ "dir", "store resulting files in <dir>",
+ PARSE_OPT_NONEG, output_directory_callback },
+ { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL,
+ "don't strip/add [PATCH]",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
+ OPT_BOOLEAN(0, "no-binary", &no_binary_diff,
+ "don't output binary diffs"),
+ OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
+ "don't include a patch matching a commit upstream"),
+ { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL,
+ "show patch format instead of default (patch + stat)",
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG },
+ OPT_GROUP("Messaging"),
+ { OPTION_CALLBACK, 0, "add-header", NULL, "header",
+ "add email header", PARSE_OPT_NONEG,
+ header_callback },
+ { OPTION_CALLBACK, 0, "cc", NULL, "email", "add Cc: header",
+ PARSE_OPT_NONEG, cc_callback },
+ OPT_STRING(0, "in-reply-to", &in_reply_to, "message-id",
+ "make first mail a reply to <message-id>"),
+ { OPTION_CALLBACK, 0, "attach", &rev, "boundary",
+ "attach the patch", PARSE_OPT_OPTARG,
+ attach_callback },
+ { OPTION_CALLBACK, 0, "inline", &rev, "boundary",
+ "inline the patch",
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
+ inline_callback },
+ { OPTION_CALLBACK, 0, "thread", &thread, "style",
+ "enable message threading, styles: shallow, deep",
+ PARSE_OPT_OPTARG, thread_callback },
+ OPT_END()
+ };
+
+ git_config(git_format_config, NULL);
+ init_revisions(&rev, prefix);
+ rev.commit_format = CMIT_FMT_EMAIL;
+ rev.verbose_header = 1;
+ rev.diff = 1;
+ rev.combine_merges = 0;
+ rev.ignore_merges = 1;
+ DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
+
+ rev.subject_prefix = fmt_patch_subject_prefix;
+
+ if (default_attach) {
+ rev.mime_boundary = default_attach;
+ rev.no_inline = 1;
+ }
+
+ /*
+ * Parse the arguments before setup_revisions(), or something
+ * like "git format-patch -o a123 HEAD^.." may fail; a123 is
+ * possibly a valid SHA1.
+ */
+ argc = parse_options(argc, argv, prefix, builtin_format_patch_options,
+ builtin_format_patch_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_DASHDASH);
+
+ if (do_signoff) {
+ const char *committer;
+ const char *endpos;
+ committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ endpos = strchr(committer, '>');
+ if (!endpos)
+ die("bogus committer info %s", committer);
+ add_signoff = xmemdupz(committer, endpos - committer + 1);
+ }
+
+ for (i = 0; i < extra_hdr_nr; i++) {
+ strbuf_addstr(&buf, extra_hdr[i]);
+ strbuf_addch(&buf, '\n');
+ }
+
+ if (extra_to_nr)
+ strbuf_addstr(&buf, "To: ");
+ for (i = 0; i < extra_to_nr; i++) {
+ if (i)
+ strbuf_addstr(&buf, " ");
+ strbuf_addstr(&buf, extra_to[i]);
+ if (i + 1 < extra_to_nr)
+ strbuf_addch(&buf, ',');
+ strbuf_addch(&buf, '\n');
+ }
+
+ if (extra_cc_nr)
+ strbuf_addstr(&buf, "Cc: ");
+ for (i = 0; i < extra_cc_nr; i++) {
+ if (i)
+ strbuf_addstr(&buf, " ");
+ strbuf_addstr(&buf, extra_cc[i]);
+ if (i + 1 < extra_cc_nr)
+ strbuf_addch(&buf, ',');
+ strbuf_addch(&buf, '\n');
+ }
+
+ rev.extra_headers = strbuf_detach(&buf, NULL);
+
+ if (start_number < 0)
+ start_number = 1;
+
+ /*
+ * If numbered is set solely due to format.numbered in config,
+ * and it would conflict with --keep-subject (-k) from the
+ * command line, reset "numbered".
+ */
+ if (numbered && keep_subject && !numbered_cmdline_opt)
+ numbered = 0;
+
+ if (numbered && keep_subject)
+ die ("-n and -k are mutually exclusive.");
+ if (keep_subject && subject_prefix)
+ die ("--subject-prefix and -k are mutually exclusive.");
+
+ argc = setup_revisions(argc, argv, &rev, "HEAD");
+ if (argc > 1)
+ die ("unrecognized argument: %s", argv[1]);
+
+ if (rev.diffopt.output_format & DIFF_FORMAT_NAME)
+ die("--name-only does not make sense");
+ if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS)
+ die("--name-status does not make sense");
+ if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
+ die("--check does not make sense");
+
+ if (!use_patch_format &&
+ (!rev.diffopt.output_format ||
+ rev.diffopt.output_format == DIFF_FORMAT_PATCH))
+ rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY;
+
+ /* Always generate a patch */
+ rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
+
+ if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
+ DIFF_OPT_SET(&rev.diffopt, BINARY);
+
+ if (!use_stdout)
+ output_directory = set_outdir(prefix, output_directory);
+
+ if (output_directory) {
+ if (use_stdout)
+ die("standard output, or directory, which one?");
+ if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
+ die_errno("Could not create directory '%s'",
+ output_directory);
+ }
+
+ if (rev.pending.nr == 1) {
+ if (rev.max_count < 0 && !rev.show_root_diff) {
+ /*
+ * This is traditional behaviour of "git format-patch
+ * origin" that prepares what the origin side still
+ * does not have.
+ */
+ rev.pending.objects[0].item->flags |= UNINTERESTING;
+ add_head_to_pending(&rev);
+ }
+ /*
+ * Otherwise, it is "format-patch -22 HEAD", and/or
+ * "format-patch --root HEAD". The user wants
+ * get_revision() to do the usual traversal.
+ */
+ }
+
+ /*
+ * We cannot move this anywhere earlier because we do want to
+ * know if --root was given explicitly from the command line.
+ */
+ rev.show_root_diff = 1;
+
+ if (cover_letter) {
+ /* remember the range */
+ int i;
+ for (i = 0; i < rev.pending.nr; i++) {
+ struct object *o = rev.pending.objects[i].item;
+ if (!(o->flags & UNINTERESTING))
+ head = (struct commit *)o;
+ }
+ /* We can't generate a cover letter without any patches */
+ if (!head)
+ return 0;
+ }
+
+ if (ignore_if_in_upstream)
+ get_patch_ids(&rev, &ids, prefix);
+
+ if (!use_stdout)
+ realstdout = xfdopen(xdup(1), "w");
+
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+ rev.boundary = 1;
+ while ((commit = get_revision(&rev)) != NULL) {
+ if (commit->object.flags & BOUNDARY) {
+ boundary_count++;
+ origin = (boundary_count == 1) ? commit : NULL;
+ continue;
+ }
+
+ /* ignore merges */
+ if (commit->parents && commit->parents->next)
+ continue;
+
+ if (ignore_if_in_upstream &&
+ has_commit_patch_id(commit, &ids))
+ continue;
+
+ nr++;
+ list = xrealloc(list, nr * sizeof(list[0]));
+ list[nr - 1] = commit;
+ }
+ total = nr;
+ if (!keep_subject && auto_number && total > 1)
+ numbered = 1;
+ if (numbered)
+ rev.total = total + start_number - 1;
+ if (in_reply_to || thread || cover_letter)
+ rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
+ if (in_reply_to) {
+ const char *msgid = clean_message_id(in_reply_to);
+ string_list_append(msgid, rev.ref_message_ids);
+ }
+ rev.numbered_files = numbered_files;
+ rev.patch_suffix = fmt_patch_suffix;
+ if (cover_letter) {
+ if (thread)
+ gen_message_id(&rev, "cover");
+ make_cover_letter(&rev, use_stdout, numbered, numbered_files,
+ origin, nr, list, head);
+ total++;
+ start_number--;
+ }
+ rev.add_signoff = add_signoff;
+ while (0 <= --nr) {
+ int shown;
+ commit = list[nr];
+ rev.nr = total - nr + (start_number - 1);
+ /* Make the second and subsequent mails replies to the first */
+ if (thread) {
+ /* Have we already had a message ID? */
+ if (rev.message_id) {
+ /*
+ * For deep threading: make every mail
+ * a reply to the previous one, no
+ * matter what other options are set.
+ *
+ * For shallow threading:
+ *
+ * Without --cover-letter and
+ * --in-reply-to, make every mail a
+ * reply to the one before.
+ *
+ * With --in-reply-to but no
+ * --cover-letter, make every mail a
+ * reply to the <reply-to>.
+ *
+ * With --cover-letter, make every
+ * mail but the cover letter a reply
+ * to the cover letter. The cover
+ * letter is a reply to the
+ * --in-reply-to, if specified.
+ */
+ if (thread == THREAD_SHALLOW
+ && rev.ref_message_ids->nr > 0
+ && (!cover_letter || rev.nr > 1))
+ free(rev.message_id);
+ else
+ string_list_append(rev.message_id,
+ rev.ref_message_ids);
+ }
+ gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
+ }
+
+ if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit,
+ &rev))
+ die("Failed to create output files");
+ shown = log_tree_commit(&rev, commit);
+ free(commit->buffer);
+ commit->buffer = NULL;
+
+ /* We put one extra blank line between formatted
+ * patches and this flag is used by log-tree code
+ * to see if it needs to emit a LF before showing
+ * the log; when using one file per patch, we do
+ * not want the extra blank line.
+ */
+ if (!use_stdout)
+ rev.shown_one = 0;
+ if (shown) {
+ if (rev.mime_boundary)
+ printf("\n--%s%s--\n\n\n",
+ mime_boundary_leader,
+ rev.mime_boundary);
+ else
+ printf("-- \n%s\n\n", git_version_string);
+ }
+ if (!use_stdout)
+ fclose(stdout);
+ }
+ free(list);
+ if (ignore_if_in_upstream)
+ free_patch_ids(&ids);
+ return 0;
+}
+
+static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
+{
+ unsigned char sha1[20];
+ if (get_sha1(arg, sha1) == 0) {
+ struct commit *commit = lookup_commit_reference(sha1);
+ if (commit) {
+ commit->object.flags |= flags;
+ add_pending_object(revs, &commit->object, arg);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static const char cherry_usage[] =
+"git cherry [-v] [<upstream> [<head> [<limit>]]]";
+int cmd_cherry(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info revs;
+ struct patch_ids ids;
+ struct commit *commit;
+ struct commit_list *list = NULL;
+ struct branch *current_branch;
+ const char *upstream;
+ const char *head = "HEAD";
+ const char *limit = NULL;
+ int verbose = 0;
+
+ if (argc > 1 && !strcmp(argv[1], "-v")) {
+ verbose = 1;
+ argc--;
+ argv++;
+ }
+
+ if (argc > 1 && !strcmp(argv[1], "-h"))
+ usage(cherry_usage);
+
+ switch (argc) {
+ case 4:
+ limit = argv[3];
+ /* FALLTHROUGH */
+ case 3:
+ head = argv[2];
+ /* FALLTHROUGH */
+ case 2:
+ upstream = argv[1];
+ break;
+ default:
+ current_branch = branch_get(NULL);
+ if (!current_branch || !current_branch->merge
+ || !current_branch->merge[0]
+ || !current_branch->merge[0]->dst) {
+ fprintf(stderr, "Could not find a tracked"
+ " remote branch, please"
+ " specify <upstream> manually.\n");
+ usage(cherry_usage);
+ }
+
+ upstream = current_branch->merge[0]->dst;
+ }
+
+ init_revisions(&revs, prefix);
+ revs.diff = 1;
+ revs.combine_merges = 0;
+ revs.ignore_merges = 1;
+ DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
+
+ if (add_pending_commit(head, &revs, 0))
+ die("Unknown commit %s", head);
+ if (add_pending_commit(upstream, &revs, UNINTERESTING))
+ die("Unknown commit %s", upstream);
+
+ /* Don't say anything if head and upstream are the same. */
+ if (revs.pending.nr == 2) {
+ struct object_array_entry *o = revs.pending.objects;
+ if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
+ return 0;
+ }
+
+ get_patch_ids(&revs, &ids, prefix);
+
+ if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
+ die("Unknown commit %s", limit);
+
+ /* reverse the list of commits */
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ while ((commit = get_revision(&revs)) != NULL) {
+ /* ignore merges */
+ if (commit->parents && commit->parents->next)
+ continue;
+
+ commit_list_insert(commit, &list);
+ }
+
+ while (list) {
+ char sign = '+';
+
+ commit = list->item;
+ if (has_commit_patch_id(commit, &ids))
+ sign = '-';
+
+ if (verbose) {
+ struct strbuf buf = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ pretty_print_commit(CMIT_FMT_ONELINE, commit,
+ &buf, &ctx);
+ printf("%c %s %s\n", sign,
+ sha1_to_hex(commit->object.sha1), buf.buf);
+ strbuf_release(&buf);
+ }
+ else {
+ printf("%c %s\n", sign,
+ sha1_to_hex(commit->object.sha1));
+ }
+
+ list = list->next;
+ }
+
+ free_patch_ids(&ids);
+ return 0;
+}
--- /dev/null
+/*
+ * This merges the file listing in the directory cache index
+ * with the actual working directory list, and shows different
+ * combinations of the two.
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "quote.h"
+#include "dir.h"
+#include "builtin.h"
+#include "tree.h"
+#include "parse-options.h"
+#include "resolve-undo.h"
+#include "string-list.h"
+
+static int abbrev;
+static int show_deleted;
+static int show_cached;
+static int show_others;
+static int show_stage;
+static int show_unmerged;
+static int show_resolve_undo;
+static int show_modified;
+static int show_killed;
+static int show_valid_bit;
+static int line_terminator = '\n';
+
+static int prefix_len;
+static int prefix_offset;
+static const char **pathspec;
+static int error_unmatch;
+static char *ps_matched;
+static const char *with_tree;
+static int exc_given;
+
+static const char *tag_cached = "";
+static const char *tag_unmerged = "";
+static const char *tag_removed = "";
+static const char *tag_other = "";
+static const char *tag_killed = "";
+static const char *tag_modified = "";
+static const char *tag_skip_worktree = "";
+static const char *tag_resolve_undo = "";
+
+static void show_dir_entry(const char *tag, struct dir_entry *ent)
+{
+ int len = prefix_len;
+ int offset = prefix_offset;
+
+ if (len >= ent->len)
+ die("git ls-files: internal error - directory entry not superset of prefix");
+
+ if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched))
+ return;
+
+ fputs(tag, stdout);
+ write_name_quoted(ent->name + offset, stdout, line_terminator);
+}
+
+static void show_other_files(struct dir_struct *dir)
+{
+ int i;
+
+ for (i = 0; i < dir->nr; i++) {
+ struct dir_entry *ent = dir->entries[i];
+ if (!cache_name_is_other(ent->name, ent->len))
+ continue;
+ show_dir_entry(tag_other, ent);
+ }
+}
+
+static void show_killed_files(struct dir_struct *dir)
+{
+ int i;
+ for (i = 0; i < dir->nr; i++) {
+ struct dir_entry *ent = dir->entries[i];
+ char *cp, *sp;
+ int pos, len, killed = 0;
+
+ for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
+ sp = strchr(cp, '/');
+ if (!sp) {
+ /* If ent->name is prefix of an entry in the
+ * cache, it will be killed.
+ */
+ pos = cache_name_pos(ent->name, ent->len);
+ if (0 <= pos)
+ die("bug in show-killed-files");
+ pos = -pos - 1;
+ while (pos < active_nr &&
+ ce_stage(active_cache[pos]))
+ pos++; /* skip unmerged */
+ if (active_nr <= pos)
+ break;
+ /* pos points at a name immediately after
+ * ent->name in the cache. Does it expect
+ * ent->name to be a directory?
+ */
+ len = ce_namelen(active_cache[pos]);
+ if ((ent->len < len) &&
+ !strncmp(active_cache[pos]->name,
+ ent->name, ent->len) &&
+ active_cache[pos]->name[ent->len] == '/')
+ killed = 1;
+ break;
+ }
+ if (0 <= cache_name_pos(ent->name, sp - ent->name)) {
+ /* If any of the leading directories in
+ * ent->name is registered in the cache,
+ * ent->name will be killed.
+ */
+ killed = 1;
+ break;
+ }
+ }
+ if (killed)
+ show_dir_entry(tag_killed, dir->entries[i]);
+ }
+}
+
+static void show_ce_entry(const char *tag, struct cache_entry *ce)
+{
+ int len = prefix_len;
+ int offset = prefix_offset;
+
+ if (len >= ce_namelen(ce))
+ die("git ls-files: internal error - cache entry not superset of prefix");
+
+ if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched))
+ return;
+
+ if (tag && *tag && show_valid_bit &&
+ (ce->ce_flags & CE_VALID)) {
+ static char alttag[4];
+ memcpy(alttag, tag, 3);
+ if (isalpha(tag[0]))
+ alttag[0] = tolower(tag[0]);
+ else if (tag[0] == '?')
+ alttag[0] = '!';
+ else {
+ alttag[0] = 'v';
+ alttag[1] = tag[0];
+ alttag[2] = ' ';
+ alttag[3] = 0;
+ }
+ tag = alttag;
+ }
+
+ if (!show_stage) {
+ fputs(tag, stdout);
+ } else {
+ printf("%s%06o %s %d\t",
+ tag,
+ ce->ce_mode,
+ abbrev ? find_unique_abbrev(ce->sha1,abbrev)
+ : sha1_to_hex(ce->sha1),
+ ce_stage(ce));
+ }
+ write_name_quoted(ce->name + offset, stdout, line_terminator);
+}
+
+static int show_one_ru(struct string_list_item *item, void *cbdata)
+{
+ int offset = prefix_offset;
+ const char *path = item->string;
+ struct resolve_undo_info *ui = item->util;
+ int i, len;
+
+ len = strlen(path);
+ if (len < prefix_len)
+ return 0; /* outside of the prefix */
+ if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched))
+ return 0; /* uninterested */
+ for (i = 0; i < 3; i++) {
+ if (!ui->mode[i])
+ continue;
+ printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
+ abbrev
+ ? find_unique_abbrev(ui->sha1[i], abbrev)
+ : sha1_to_hex(ui->sha1[i]),
+ i + 1);
+ write_name_quoted(path + offset, stdout, line_terminator);
+ }
+ return 0;
+}
+
+static void show_ru_info(const char *prefix)
+{
+ if (!the_index.resolve_undo)
+ return;
+ for_each_string_list(show_one_ru, the_index.resolve_undo, NULL);
+}
+
+static void show_files(struct dir_struct *dir, const char *prefix)
+{
+ int i;
+
+ /* For cached/deleted files we don't need to even do the readdir */
+ if (show_others || show_killed) {
+ fill_directory(dir, pathspec);
+ if (show_others)
+ show_other_files(dir);
+ if (show_killed)
+ show_killed_files(dir);
+ }
+ if (show_cached | show_stage) {
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ int dtype = ce_to_dtype(ce);
+ if (dir->flags & DIR_SHOW_IGNORED &&
+ !excluded(dir, ce->name, &dtype))
+ continue;
+ if (show_unmerged && !ce_stage(ce))
+ continue;
+ if (ce->ce_flags & CE_UPDATE)
+ continue;
+ show_ce_entry(ce_stage(ce) ? tag_unmerged :
+ (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce);
+ }
+ }
+ if (show_deleted | show_modified) {
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ struct stat st;
+ int err;
+ int dtype = ce_to_dtype(ce);
+ if (dir->flags & DIR_SHOW_IGNORED &&
+ !excluded(dir, ce->name, &dtype))
+ continue;
+ if (ce->ce_flags & CE_UPDATE)
+ continue;
+ if (ce_skip_worktree(ce))
+ continue;
+ err = lstat(ce->name, &st);
+ if (show_deleted && err)
+ show_ce_entry(tag_removed, ce);
+ if (show_modified && ce_modified(ce, &st, 0))
+ show_ce_entry(tag_modified, ce);
+ }
+ }
+}
+
+/*
+ * Prune the index to only contain stuff starting with "prefix"
+ */
+static void prune_cache(const char *prefix)
+{
+ int pos = cache_name_pos(prefix, prefix_len);
+ unsigned int first, last;
+
+ if (pos < 0)
+ pos = -pos-1;
+ memmove(active_cache, active_cache + pos,
+ (active_nr - pos) * sizeof(struct cache_entry *));
+ active_nr -= pos;
+ first = 0;
+ last = active_nr;
+ while (last > first) {
+ int next = (last + first) >> 1;
+ struct cache_entry *ce = active_cache[next];
+ if (!strncmp(ce->name, prefix, prefix_len)) {
+ first = next+1;
+ continue;
+ }
+ last = next;
+ }
+ active_nr = last;
+}
+
+static const char *verify_pathspec(const char *prefix)
+{
+ const char **p, *n, *prev;
+ unsigned long max;
+
+ prev = NULL;
+ max = PATH_MAX;
+ for (p = pathspec; (n = *p) != NULL; p++) {
+ int i, len = 0;
+ for (i = 0; i < max; i++) {
+ char c = n[i];
+ if (prev && prev[i] != c)
+ break;
+ if (!c || c == '*' || c == '?')
+ break;
+ if (c == '/')
+ len = i+1;
+ }
+ prev = n;
+ if (len < max) {
+ max = len;
+ if (!max)
+ break;
+ }
+ }
+
+ if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
+ die("git ls-files: cannot generate relative filenames containing '..'");
+
+ prefix_len = max;
+ return max ? xmemdupz(prev, max) : NULL;
+}
+
+static void strip_trailing_slash_from_submodules(void)
+{
+ const char **p;
+
+ for (p = pathspec; *p != NULL; p++) {
+ int len = strlen(*p), pos;
+
+ if (len < 1 || (*p)[len - 1] != '/')
+ continue;
+ pos = cache_name_pos(*p, len - 1);
+ if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
+ *p = xstrndup(*p, len - 1);
+ }
+}
+
+/*
+ * Read the tree specified with --with-tree option
+ * (typically, HEAD) into stage #1 and then
+ * squash them down to stage #0. This is used for
+ * --error-unmatch to list and check the path patterns
+ * that were given from the command line. We are not
+ * going to write this index out.
+ */
+void overlay_tree_on_cache(const char *tree_name, const char *prefix)
+{
+ struct tree *tree;
+ unsigned char sha1[20];
+ const char **match;
+ struct cache_entry *last_stage0 = NULL;
+ int i;
+
+ if (get_sha1(tree_name, sha1))
+ die("tree-ish %s not found.", tree_name);
+ tree = parse_tree_indirect(sha1);
+ if (!tree)
+ die("bad tree-ish %s", tree_name);
+
+ /* Hoist the unmerged entries up to stage #3 to make room */
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (!ce_stage(ce))
+ continue;
+ ce->ce_flags |= CE_STAGEMASK;
+ }
+
+ if (prefix) {
+ static const char *(matchbuf[2]);
+ matchbuf[0] = prefix;
+ matchbuf[1] = NULL;
+ match = matchbuf;
+ } else
+ match = NULL;
+ if (read_tree(tree, 1, match))
+ die("unable to read tree entries %s", tree_name);
+
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ switch (ce_stage(ce)) {
+ case 0:
+ last_stage0 = ce;
+ /* fallthru */
+ default:
+ continue;
+ case 1:
+ /*
+ * If there is stage #0 entry for this, we do not
+ * need to show it. We use CE_UPDATE bit to mark
+ * such an entry.
+ */
+ if (last_stage0 &&
+ !strcmp(last_stage0->name, ce->name))
+ ce->ce_flags |= CE_UPDATE;
+ }
+ }
+}
+
+int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset)
+{
+ /*
+ * Make sure all pathspec matched; otherwise it is an error.
+ */
+ int num, errors = 0;
+ for (num = 0; pathspec[num]; num++) {
+ int other, found_dup;
+
+ if (ps_matched[num])
+ continue;
+ /*
+ * The caller might have fed identical pathspec
+ * twice. Do not barf on such a mistake.
+ */
+ for (found_dup = other = 0;
+ !found_dup && pathspec[other];
+ other++) {
+ if (other == num || !ps_matched[other])
+ continue;
+ if (!strcmp(pathspec[other], pathspec[num]))
+ /*
+ * Ok, we have a match already.
+ */
+ found_dup = 1;
+ }
+ if (found_dup)
+ continue;
+
+ error("pathspec '%s' did not match any file(s) known to git.",
+ pathspec[num] + prefix_offset);
+ errors++;
+ }
+ return errors;
+}
+
+static const char * const ls_files_usage[] = {
+ "git ls-files [options] [<file>]*",
+ NULL
+};
+
+static int option_parse_z(const struct option *opt,
+ const char *arg, int unset)
+{
+ line_terminator = unset ? '\n' : '\0';
+
+ return 0;
+}
+
+static int option_parse_exclude(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct exclude_list *list = opt->value;
+
+ exc_given = 1;
+ add_exclude(arg, "", 0, list);
+
+ return 0;
+}
+
+static int option_parse_exclude_from(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct dir_struct *dir = opt->value;
+
+ exc_given = 1;
+ add_excludes_from_file(dir, arg);
+
+ return 0;
+}
+
+static int option_parse_exclude_standard(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct dir_struct *dir = opt->value;
+
+ exc_given = 1;
+ setup_standard_excludes(dir);
+
+ return 0;
+}
+
+int cmd_ls_files(int argc, const char **argv, const char *prefix)
+{
+ int require_work_tree = 0, show_tag = 0;
+ struct dir_struct dir;
+ struct option builtin_ls_files_options[] = {
+ { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+ "paths are separated with NUL character",
+ PARSE_OPT_NOARG, option_parse_z },
+ OPT_BOOLEAN('t', NULL, &show_tag,
+ "identify the file status with tags"),
+ OPT_BOOLEAN('v', NULL, &show_valid_bit,
+ "use lowercase letters for 'assume unchanged' files"),
+ OPT_BOOLEAN('c', "cached", &show_cached,
+ "show cached files in the output (default)"),
+ OPT_BOOLEAN('d', "deleted", &show_deleted,
+ "show deleted files in the output"),
+ OPT_BOOLEAN('m', "modified", &show_modified,
+ "show modified files in the output"),
+ OPT_BOOLEAN('o', "others", &show_others,
+ "show other files in the output"),
+ OPT_BIT('i', "ignored", &dir.flags,
+ "show ignored files in the output",
+ DIR_SHOW_IGNORED),
+ OPT_BOOLEAN('s', "stage", &show_stage,
+ "show staged contents' object name in the output"),
+ OPT_BOOLEAN('k', "killed", &show_killed,
+ "show files on the filesystem that need to be removed"),
+ OPT_BIT(0, "directory", &dir.flags,
+ "show 'other' directories' name only",
+ DIR_SHOW_OTHER_DIRECTORIES),
+ OPT_NEGBIT(0, "empty-directory", &dir.flags,
+ "don't show empty directories",
+ DIR_HIDE_EMPTY_DIRECTORIES),
+ OPT_BOOLEAN('u', "unmerged", &show_unmerged,
+ "show unmerged files in the output"),
+ OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
+ "show resolve-undo information"),
+ { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
+ "skip files matching pattern",
+ 0, option_parse_exclude },
+ { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
+ "exclude patterns are read from <file>",
+ 0, option_parse_exclude_from },
+ OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file",
+ "read additional per-directory exclude patterns in <file>"),
+ { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
+ "add the standard git exclusions",
+ PARSE_OPT_NOARG, option_parse_exclude_standard },
+ { OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL,
+ "make the output relative to the project top directory",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
+ OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
+ "if any <file> is not in the index, treat this as an error"),
+ OPT_STRING(0, "with-tree", &with_tree, "tree-ish",
+ "pretend that paths removed since <tree-ish> are still present"),
+ OPT__ABBREV(&abbrev),
+ OPT_END()
+ };
+
+ memset(&dir, 0, sizeof(dir));
+ if (prefix)
+ prefix_offset = strlen(prefix);
+ git_config(git_default_config, NULL);
+
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
+ ls_files_usage, 0);
+ if (show_tag || show_valid_bit) {
+ tag_cached = "H ";
+ tag_unmerged = "M ";
+ tag_removed = "R ";
+ tag_modified = "C ";
+ tag_other = "? ";
+ tag_killed = "K ";
+ tag_skip_worktree = "S ";
+ tag_resolve_undo = "U ";
+ }
+ if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
+ require_work_tree = 1;
+ if (show_unmerged)
+ /*
+ * There's no point in showing unmerged unless
+ * you also show the stage information.
+ */
+ show_stage = 1;
+ if (dir.exclude_per_dir)
+ exc_given = 1;
+
+ if (require_work_tree && !is_inside_work_tree())
+ setup_work_tree();
+
+ pathspec = get_pathspec(prefix, argv);
+
+ /* be nice with submodule paths ending in a slash */
+ if (pathspec)
+ strip_trailing_slash_from_submodules();
+
+ /* Verify that the pathspec matches the prefix */
+ if (pathspec)
+ prefix = verify_pathspec(prefix);
+
+ /* Treat unmatching pathspec elements as errors */
+ if (pathspec && error_unmatch) {
+ int num;
+ for (num = 0; pathspec[num]; num++)
+ ;
+ ps_matched = xcalloc(1, num);
+ }
+
+ if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
+ die("ls-files --ignored needs some exclude pattern");
+
+ /* With no flags, we default to showing the cached files */
+ if (!(show_stage | show_deleted | show_others | show_unmerged |
+ show_killed | show_modified | show_resolve_undo))
+ show_cached = 1;
+
+ if (prefix)
+ prune_cache(prefix);
+ if (with_tree) {
+ /*
+ * Basic sanity check; show-stages and show-unmerged
+ * would not make any sense with this option.
+ */
+ if (show_stage || show_unmerged)
+ die("ls-files --with-tree is incompatible with -s or -u");
+ overlay_tree_on_cache(with_tree, prefix);
+ }
+ show_files(&dir, prefix);
+ if (show_resolve_undo)
+ show_ru_info(prefix);
+
+ if (ps_matched) {
+ int bad;
+ bad = report_path_error(ps_matched, pathspec, prefix_offset);
+ if (bad)
+ fprintf(stderr, "Did you forget to 'git add'?\n");
+
+ return bad ? 1 : 0;
+ }
+
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "transport.h"
+#include "remote.h"
+
+static const char ls_remote_usage[] =
+"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>] <repository> <refs>...";
+
+/*
+ * Is there one among the list of patterns that match the tail part
+ * of the path?
+ */
+static int tail_match(const char **pattern, const char *path)
+{
+ const char *p;
+ char pathbuf[PATH_MAX];
+
+ if (!pattern)
+ return 1; /* no restriction */
+
+ if (snprintf(pathbuf, sizeof(pathbuf), "/%s", path) > sizeof(pathbuf))
+ return error("insanely long ref %.*s...", 20, path);
+ while ((p = *(pattern++)) != NULL) {
+ if (!fnmatch(p, pathbuf, 0))
+ return 1;
+ }
+ return 0;
+}
+
+int cmd_ls_remote(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ const char *dest = NULL;
+ int nongit;
+ unsigned flags = 0;
+ const char *uploadpack = NULL;
+ const char **pattern = NULL;
+
+ struct remote *remote;
+ struct transport *transport;
+ const struct ref *ref;
+
+ setup_git_directory_gently(&nongit);
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!prefixcmp(arg, "--upload-pack=")) {
+ uploadpack = arg + 14;
+ continue;
+ }
+ if (!prefixcmp(arg, "--exec=")) {
+ uploadpack = arg + 7;
+ continue;
+ }
+ if (!strcmp("--tags", arg) || !strcmp("-t", arg)) {
+ flags |= REF_TAGS;
+ continue;
+ }
+ if (!strcmp("--heads", arg) || !strcmp("-h", arg)) {
+ flags |= REF_HEADS;
+ continue;
+ }
+ if (!strcmp("--refs", arg)) {
+ flags |= REF_NORMAL;
+ continue;
+ }
+ usage(ls_remote_usage);
+ }
+ dest = arg;
+ i++;
+ break;
+ }
+
+ if (!dest)
+ usage(ls_remote_usage);
+
+ if (argv[i]) {
+ int j;
+ pattern = xcalloc(sizeof(const char *), argc - i + 1);
+ for (j = i; j < argc; j++) {
+ int len = strlen(argv[j]);
+ char *p = xmalloc(len + 3);
+ sprintf(p, "*/%s", argv[j]);
+ pattern[j - i] = p;
+ }
+ }
+ remote = remote_get(dest);
+ if (!remote->url_nr)
+ die("remote %s has no configured URL", dest);
+ transport = transport_get(remote, NULL);
+ if (uploadpack != NULL)
+ transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
+
+ ref = transport_get_remote_refs(transport);
+ if (transport_disconnect(transport))
+ return 1;
+ for ( ; ref; ref = ref->next) {
+ if (!check_ref_type(ref, flags))
+ continue;
+ if (!tail_match(pattern, ref->name))
+ continue;
+ printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name);
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "quote.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static int line_termination = '\n';
+#define LS_RECURSIVE 1
+#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 int chomp_prefix;
+static const char *ls_tree_prefix;
+
+static const char * const ls_tree_usage[] = {
+ "git ls-tree [<options>] <tree-ish> [path...]",
+ NULL
+};
+
+static int show_recursive(const char *base, int baselen, const char *pathname)
+{
+ const char **s;
+
+ if (ls_options & LS_RECURSIVE)
+ return 1;
+
+ s = pathspec;
+ if (!s)
+ return 0;
+
+ for (;;) {
+ const char *spec = *s++;
+ int len, speclen;
+
+ if (!spec)
+ return 0;
+ if (strncmp(base, spec, baselen))
+ continue;
+ len = strlen(pathname);
+ spec += baselen;
+ speclen = strlen(spec);
+ if (speclen <= len)
+ continue;
+ if (memcmp(pathname, spec, len))
+ continue;
+ return 1;
+ }
+}
+
+static int show_tree(const unsigned char *sha1, const char *base, int baselen,
+ const char *pathname, unsigned mode, int stage, void *context)
+{
+ int retval = 0;
+ const char *type = blob_type;
+
+ if (S_ISGITLINK(mode)) {
+ /*
+ * Maybe we want to have some recursive version here?
+ *
+ * Something similar to this incomplete example:
+ *
+ if (show_subprojects(base, baselen, pathname))
+ retval = READ_TREE_RECURSIVE;
+ *
+ */
+ type = commit_type;
+ } else if (S_ISDIR(mode)) {
+ if (show_recursive(base, baselen, pathname)) {
+ retval = READ_TREE_RECURSIVE;
+ if (!(ls_options & LS_SHOW_TREES))
+ return retval;
+ }
+ type = tree_type;
+ }
+ else if (ls_options & LS_TREE_ONLY)
+ return 0;
+
+ if (chomp_prefix &&
+ (baselen < chomp_prefix || memcmp(ls_tree_prefix, base, chomp_prefix)))
+ return 0;
+
+ if (!(ls_options & LS_NAME_ONLY)) {
+ if (ls_options & LS_SHOW_SIZE) {
+ char size_text[24];
+ if (!strcmp(type, blob_type)) {
+ unsigned long size;
+ if (sha1_object_info(sha1, &size) == OBJ_BAD)
+ strcpy(size_text, "BAD");
+ else
+ snprintf(size_text, sizeof(size_text),
+ "%lu", size);
+ } else
+ strcpy(size_text, "-");
+ printf("%06o %s %s %7s\t", mode, type,
+ abbrev ? find_unique_abbrev(sha1, abbrev)
+ : sha1_to_hex(sha1),
+ size_text);
+ } else
+ printf("%06o %s %s\t", mode, type,
+ abbrev ? find_unique_abbrev(sha1, abbrev)
+ : sha1_to_hex(sha1));
+ }
+ write_name_quotedpfx(base + chomp_prefix, baselen - chomp_prefix,
+ pathname, stdout, line_termination);
+ return retval;
+}
+
+int cmd_ls_tree(int argc, const char **argv, const char *prefix)
+{
+ unsigned char sha1[20];
+ struct tree *tree;
+ int full_tree = 0;
+ const struct option ls_tree_options[] = {
+ OPT_BIT('d', NULL, &ls_options, "only show trees",
+ LS_TREE_ONLY),
+ OPT_BIT('r', NULL, &ls_options, "recurse into subtrees",
+ LS_RECURSIVE),
+ OPT_BIT('t', NULL, &ls_options, "show trees when recursing",
+ LS_SHOW_TREES),
+ OPT_SET_INT('z', NULL, &line_termination,
+ "terminate entries with NUL byte", 0),
+ OPT_BIT('l', "long", &ls_options, "include object size",
+ LS_SHOW_SIZE),
+ OPT_BIT(0, "name-only", &ls_options, "list only filenames",
+ LS_NAME_ONLY),
+ OPT_BIT(0, "name-status", &ls_options, "list only filenames",
+ LS_NAME_ONLY),
+ OPT_SET_INT(0, "full-name", &chomp_prefix,
+ "use full path names", 0),
+ OPT_BOOLEAN(0, "full-tree", &full_tree,
+ "list entire tree; not just current directory "
+ "(implies --full-name)"),
+ OPT__ABBREV(&abbrev),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+ ls_tree_prefix = prefix;
+ if (prefix && *prefix)
+ chomp_prefix = strlen(prefix);
+
+ argc = parse_options(argc, argv, prefix, ls_tree_options,
+ ls_tree_usage, 0);
+ if (full_tree) {
+ ls_tree_prefix = prefix = NULL;
+ chomp_prefix = 0;
+ }
+ /* -d -r should imply -t, but -d by itself should not have to. */
+ if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
+ ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
+ ls_options |= LS_SHOW_TREES;
+
+ if (argc < 1)
+ usage_with_options(ls_tree_usage, ls_tree_options);
+ if (get_sha1(argv[0], sha1))
+ die("Not a valid object name %s", argv[0]);
+
+ pathspec = get_pathspec(prefix, argv + 1);
+ tree = parse_tree_indirect(sha1);
+ if (!tree)
+ die("not a tree object");
+ read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL);
+
+ return 0;
+}
--- /dev/null
+/*
+ * Another stupid program, this one parsing the headers of an
+ * email to figure out authorship and subject
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "utf8.h"
+#include "strbuf.h"
+
+static FILE *cmitmsg, *patchfile, *fin, *fout;
+
+static int keep_subject;
+static int keep_non_patch_brackets_in_subject;
+static const char *metainfo_charset;
+static struct strbuf line = STRBUF_INIT;
+static struct strbuf name = STRBUF_INIT;
+static struct strbuf email = STRBUF_INIT;
+
+static enum {
+ TE_DONTCARE, TE_QP, TE_BASE64,
+} transfer_encoding;
+static enum {
+ TYPE_TEXT, TYPE_OTHER,
+} message_type;
+
+static struct strbuf charset = STRBUF_INIT;
+static int patch_lines;
+static struct strbuf **p_hdr_data, **s_hdr_data;
+static int use_scissors;
+static int use_inbody_headers = 1;
+
+#define MAX_HDR_PARSED 10
+#define MAX_BOUNDARIES 5
+
+static void cleanup_space(struct strbuf *sb);
+
+
+static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
+{
+ struct strbuf *src = name;
+ if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
+ strchr(name->buf, '<') || strchr(name->buf, '>'))
+ src = email;
+ else if (name == out)
+ return;
+ strbuf_reset(out);
+ strbuf_addbuf(out, src);
+}
+
+static void parse_bogus_from(const struct strbuf *line)
+{
+ /* John Doe <johndoe> */
+
+ char *bra, *ket;
+ /* This is fallback, so do not bother if we already have an
+ * e-mail address.
+ */
+ if (email.len)
+ return;
+
+ bra = strchr(line->buf, '<');
+ if (!bra)
+ return;
+ ket = strchr(bra, '>');
+ if (!ket)
+ return;
+
+ strbuf_reset(&email);
+ strbuf_add(&email, bra + 1, ket - bra - 1);
+
+ strbuf_reset(&name);
+ strbuf_add(&name, line->buf, bra - line->buf);
+ strbuf_trim(&name);
+ get_sane_name(&name, &name, &email);
+}
+
+static void handle_from(const struct strbuf *from)
+{
+ char *at;
+ size_t el;
+ struct strbuf f;
+
+ strbuf_init(&f, from->len);
+ strbuf_addbuf(&f, from);
+
+ at = strchr(f.buf, '@');
+ if (!at) {
+ parse_bogus_from(from);
+ return;
+ }
+
+ /*
+ * If we already have one email, don't take any confusing lines
+ */
+ if (email.len && strchr(at + 1, '@')) {
+ strbuf_release(&f);
+ return;
+ }
+
+ /* Pick up the string around '@', possibly delimited with <>
+ * pair; that is the email part.
+ */
+ while (at > f.buf) {
+ char c = at[-1];
+ if (isspace(c))
+ break;
+ if (c == '<') {
+ at[-1] = ' ';
+ break;
+ }
+ at--;
+ }
+ el = strcspn(at, " \n\t\r\v\f>");
+ strbuf_reset(&email);
+ strbuf_add(&email, at, el);
+ strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
+
+ /* The remainder is name. It could be
+ *
+ * - "John Doe <john.doe@xz>" (a), or
+ * - "john.doe@xz (John Doe)" (b), or
+ * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
+ *
+ * but we have removed the email part, so
+ *
+ * - remove extra spaces which could stay after email (case 'c'), and
+ * - trim from both ends, possibly removing the () pair at the end
+ * (cases 'a' and 'b').
+ */
+ cleanup_space(&f);
+ strbuf_trim(&f);
+ if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
+ strbuf_remove(&f, 0, 1);
+ strbuf_setlen(&f, f.len - 1);
+ }
+
+ get_sane_name(&name, &f, &email);
+ strbuf_release(&f);
+}
+
+static void handle_header(struct strbuf **out, const struct strbuf *line)
+{
+ if (!*out) {
+ *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(*out, line->len);
+ } else
+ strbuf_reset(*out);
+
+ strbuf_addbuf(*out, line);
+}
+
+/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists. For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
+{
+ const char *ends, *ap = strcasestr(line, name);
+ size_t sz;
+
+ if (!ap) {
+ strbuf_setlen(attr, 0);
+ return 0;
+ }
+ ap += strlen(name);
+ if (*ap == '"') {
+ ap++;
+ ends = "\"";
+ }
+ else
+ ends = "; \t";
+ sz = strcspn(ap, ends);
+ strbuf_add(attr, ap, sz);
+ return 1;
+}
+
+static struct strbuf *content[MAX_BOUNDARIES];
+
+static struct strbuf **content_top = content;
+
+static void handle_content_type(struct strbuf *line)
+{
+ struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+ strbuf_init(boundary, line->len);
+
+ if (!strcasestr(line->buf, "text/"))
+ message_type = TYPE_OTHER;
+ if (slurp_attr(line->buf, "boundary=", boundary)) {
+ strbuf_insert(boundary, 0, "--", 2);
+ if (++content_top > &content[MAX_BOUNDARIES]) {
+ fprintf(stderr, "Too many boundaries to handle\n");
+ exit(1);
+ }
+ *content_top = boundary;
+ boundary = NULL;
+ }
+ slurp_attr(line->buf, "charset=", &charset);
+
+ if (boundary) {
+ strbuf_release(boundary);
+ free(boundary);
+ }
+}
+
+static void handle_content_transfer_encoding(const struct strbuf *line)
+{
+ if (strcasestr(line->buf, "base64"))
+ transfer_encoding = TE_BASE64;
+ else if (strcasestr(line->buf, "quoted-printable"))
+ transfer_encoding = TE_QP;
+ else
+ transfer_encoding = TE_DONTCARE;
+}
+
+static int is_multipart_boundary(const struct strbuf *line)
+{
+ return (((*content_top)->len <= line->len) &&
+ !memcmp(line->buf, (*content_top)->buf, (*content_top)->len));
+}
+
+static void cleanup_subject(struct strbuf *subject)
+{
+ size_t at = 0;
+
+ while (at < subject->len) {
+ char *pos;
+ size_t remove;
+
+ switch (subject->buf[at]) {
+ case 'r': case 'R':
+ if (subject->len <= at + 3)
+ break;
+ if (!memcmp(subject->buf + at + 1, "e:", 2)) {
+ strbuf_remove(subject, at, 3);
+ continue;
+ }
+ at++;
+ break;
+ case ' ': case '\t': case ':':
+ strbuf_remove(subject, at, 1);
+ continue;
+ case '[':
+ pos = strchr(subject->buf + at, ']');
+ if (!pos)
+ break;
+ remove = pos - subject->buf + at + 1;
+ if (!keep_non_patch_brackets_in_subject ||
+ (7 <= remove &&
+ memmem(subject->buf + at, remove, "PATCH", 5)))
+ strbuf_remove(subject, at, remove);
+ else
+ at += remove;
+ continue;
+ }
+ break;
+ }
+ strbuf_trim(subject);
+}
+
+static void cleanup_space(struct strbuf *sb)
+{
+ size_t pos, cnt;
+ for (pos = 0; pos < sb->len; pos++) {
+ if (isspace(sb->buf[pos])) {
+ sb->buf[pos] = ' ';
+ for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
+ strbuf_remove(sb, pos + 1, cnt);
+ }
+ }
+}
+
+static void decode_header(struct strbuf *line);
+static const char *header[MAX_HDR_PARSED] = {
+ "From","Subject","Date",
+};
+
+static inline int cmp_header(const struct strbuf *line, const char *hdr)
+{
+ int len = strlen(hdr);
+ return !strncasecmp(line->buf, hdr, len) && line->len > len &&
+ line->buf[len] == ':' && isspace(line->buf[len + 1]);
+}
+
+static int check_header(const struct strbuf *line,
+ struct strbuf *hdr_data[], int overwrite)
+{
+ int i, ret = 0, len;
+ struct strbuf sb = STRBUF_INIT;
+ /* search for the interesting parts */
+ for (i = 0; header[i]; i++) {
+ int len = strlen(header[i]);
+ if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
+ /* Unwrap inline B and Q encoding, and optionally
+ * normalize the meta information to utf8.
+ */
+ strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
+ decode_header(&sb);
+ handle_header(&hdr_data[i], &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ }
+
+ /* Content stuff */
+ if (cmp_header(line, "Content-Type")) {
+ len = strlen("Content-Type: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(&sb);
+ strbuf_insert(&sb, 0, "Content-Type: ", len);
+ handle_content_type(&sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Content-Transfer-Encoding")) {
+ len = strlen("Content-Transfer-Encoding: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(&sb);
+ handle_content_transfer_encoding(&sb);
+ ret = 1;
+ goto check_header_out;
+ }
+
+ /* for inbody stuff */
+ if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) {
+ ret = 1; /* Should this return 0? */
+ goto check_header_out;
+ }
+ if (!prefixcmp(line->buf, "[PATCH]") && isspace(line->buf[7])) {
+ for (i = 0; header[i]; i++) {
+ if (!memcmp("Subject", header[i], 7)) {
+ handle_header(&hdr_data[i], line);
+ ret = 1;
+ goto check_header_out;
+ }
+ }
+ }
+
+check_header_out:
+ strbuf_release(&sb);
+ return ret;
+}
+
+static int is_rfc2822_header(const struct strbuf *line)
+{
+ /*
+ * The section that defines the loosest possible
+ * field name is "3.6.8 Optional fields".
+ *
+ * optional-field = field-name ":" unstructured CRLF
+ * field-name = 1*ftext
+ * ftext = %d33-57 / %59-126
+ */
+ int ch;
+ char *cp = line->buf;
+
+ /* Count mbox From headers as headers */
+ if (!prefixcmp(cp, "From ") || !prefixcmp(cp, ">From "))
+ return 1;
+
+ while ((ch = *cp++)) {
+ if (ch == ':')
+ return 1;
+ if ((33 <= ch && ch <= 57) ||
+ (59 <= ch && ch <= 126))
+ continue;
+ break;
+ }
+ return 0;
+}
+
+static int read_one_header_line(struct strbuf *line, FILE *in)
+{
+ /* Get the first part of the line. */
+ if (strbuf_getline(line, in, '\n'))
+ return 0;
+
+ /*
+ * Is it an empty line or not a valid rfc2822 header?
+ * If so, stop here, and return false ("not a header")
+ */
+ strbuf_rtrim(line);
+ if (!line->len || !is_rfc2822_header(line)) {
+ /* Re-add the newline */
+ strbuf_addch(line, '\n');
+ return 0;
+ }
+
+ /*
+ * Now we need to eat all the continuation lines..
+ * Yuck, 2822 header "folding"
+ */
+ for (;;) {
+ int peek;
+ struct strbuf continuation = STRBUF_INIT;
+
+ peek = fgetc(in); ungetc(peek, in);
+ if (peek != ' ' && peek != '\t')
+ break;
+ if (strbuf_getline(&continuation, in, '\n'))
+ break;
+ continuation.buf[0] = '\n';
+ strbuf_rtrim(&continuation);
+ strbuf_addbuf(line, &continuation);
+ }
+
+ return 1;
+}
+
+static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
+{
+ const char *in = q_seg->buf;
+ int c;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, q_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '=') {
+ int d = *in++;
+ if (d == '\n' || !d)
+ break; /* drop trailing newline */
+ strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
+ continue;
+ }
+ if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+ c = 0x20;
+ strbuf_addch(out, c);
+ }
+ return out;
+}
+
+static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
+{
+ /* Decode in..ep, possibly in-place to ot */
+ int c, pos = 0, acc = 0;
+ const char *in = b_seg->buf;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, b_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '+')
+ c = 62;
+ else if (c == '/')
+ c = 63;
+ else if ('A' <= c && c <= 'Z')
+ c -= 'A';
+ else if ('a' <= c && c <= 'z')
+ c -= 'a' - 26;
+ else if ('0' <= c && c <= '9')
+ c -= '0' - 52;
+ else
+ continue; /* garbage */
+ switch (pos++) {
+ case 0:
+ acc = (c << 2);
+ break;
+ case 1:
+ strbuf_addch(out, (acc | (c >> 4)));
+ acc = (c & 15) << 4;
+ break;
+ case 2:
+ strbuf_addch(out, (acc | (c >> 2)));
+ acc = (c & 3) << 6;
+ break;
+ case 3:
+ strbuf_addch(out, (acc | c));
+ acc = pos = 0;
+ break;
+ }
+ }
+ return out;
+}
+
+/*
+ * 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 struct strbuf *line, const char *target_charset)
+{
+ if (is_encoding_utf8(target_charset)) {
+ if (is_utf8(line->buf))
+ return NULL;
+ }
+ return "ISO8859-1";
+}
+
+static void convert_to_utf8(struct strbuf *line, const char *charset)
+{
+ char *out;
+
+ if (!charset || !*charset) {
+ charset = guess_charset(line, metainfo_charset);
+ if (!charset)
+ return;
+ }
+
+ if (!strcasecmp(metainfo_charset, charset))
+ return;
+ out = reencode_string(line->buf, metainfo_charset, charset);
+ if (!out)
+ die("cannot convert from %s to %s",
+ charset, metainfo_charset);
+ strbuf_attach(line, out, strlen(out), strlen(out));
+}
+
+static int decode_header_bq(struct strbuf *it)
+{
+ char *in, *ep, *cp;
+ struct strbuf outbuf = STRBUF_INIT, *dec;
+ struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
+ int rfc2047 = 0;
+
+ in = it->buf;
+ while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
+ int encoding;
+ strbuf_reset(&charset_q);
+ strbuf_reset(&piecebuf);
+ rfc2047 = 1;
+
+ if (in != ep) {
+ /*
+ * We are about to process an encoded-word
+ * that begins at ep, but there is something
+ * before the encoded word.
+ */
+ char *scan;
+ for (scan = in; scan < ep; scan++)
+ if (!isspace(*scan))
+ break;
+
+ if (scan != ep || in == it->buf) {
+ /*
+ * We should not lose that "something",
+ * unless we have just processed an
+ * encoded-word, and there is only LWS
+ * before the one we are about to process.
+ */
+ strbuf_add(&outbuf, in, ep - in);
+ }
+ }
+ /* E.g.
+ * ep : "=?iso-2022-jp?B?GyR...?= foo"
+ * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+ */
+ ep += 2;
+
+ if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
+ goto decode_header_bq_out;
+
+ if (cp + 3 - it->buf > it->len)
+ goto decode_header_bq_out;
+ strbuf_add(&charset_q, ep, cp - ep);
+
+ encoding = cp[1];
+ if (!encoding || cp[2] != '?')
+ goto decode_header_bq_out;
+ ep = strstr(cp + 3, "?=");
+ if (!ep)
+ goto decode_header_bq_out;
+ strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
+ switch (tolower(encoding)) {
+ default:
+ goto decode_header_bq_out;
+ case 'b':
+ dec = decode_b_segment(&piecebuf);
+ break;
+ case 'q':
+ dec = decode_q_segment(&piecebuf, 1);
+ break;
+ }
+ if (metainfo_charset)
+ convert_to_utf8(dec, charset_q.buf);
+
+ strbuf_addbuf(&outbuf, dec);
+ strbuf_release(dec);
+ free(dec);
+ in = ep + 2;
+ }
+ strbuf_addstr(&outbuf, in);
+ strbuf_reset(it);
+ strbuf_addbuf(it, &outbuf);
+decode_header_bq_out:
+ strbuf_release(&outbuf);
+ strbuf_release(&charset_q);
+ strbuf_release(&piecebuf);
+ return rfc2047;
+}
+
+static void decode_header(struct strbuf *it)
+{
+ if (decode_header_bq(it))
+ return;
+ /* otherwise "it" is a straight copy of the input.
+ * This can be binary guck but there is no charset specified.
+ */
+ if (metainfo_charset)
+ convert_to_utf8(it, "");
+}
+
+static void decode_transfer_encoding(struct strbuf *line)
+{
+ struct strbuf *ret;
+
+ switch (transfer_encoding) {
+ case TE_QP:
+ ret = decode_q_segment(line, 0);
+ break;
+ case TE_BASE64:
+ ret = decode_b_segment(line);
+ break;
+ case TE_DONTCARE:
+ default:
+ return;
+ }
+ strbuf_reset(line);
+ strbuf_addbuf(line, ret);
+ strbuf_release(ret);
+ free(ret);
+}
+
+static void handle_filter(struct strbuf *line);
+
+static int find_boundary(void)
+{
+ while (!strbuf_getline(&line, fin, '\n')) {
+ if (*content_top && is_multipart_boundary(&line))
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_boundary(void)
+{
+ struct strbuf newline = STRBUF_INIT;
+
+ strbuf_addch(&newline, '\n');
+again:
+ if (line.len >= (*content_top)->len + 2 &&
+ !memcmp(line.buf + (*content_top)->len, "--", 2)) {
+ /* we hit an end boundary */
+ /* pop the current boundary off the stack */
+ strbuf_release(*content_top);
+ free(*content_top);
+ *content_top = NULL;
+
+ /* technically won't happen as is_multipart_boundary()
+ will fail first. But just in case..
+ */
+ if (--content_top < content) {
+ fprintf(stderr, "Detected mismatched boundaries, "
+ "can't recover\n");
+ exit(1);
+ }
+ handle_filter(&newline);
+ strbuf_release(&newline);
+
+ /* skip to the next boundary */
+ if (!find_boundary())
+ return 0;
+ goto again;
+ }
+
+ /* set some defaults */
+ transfer_encoding = TE_DONTCARE;
+ strbuf_reset(&charset);
+ message_type = TYPE_TEXT;
+
+ /* slurp in this section's info */
+ while (read_one_header_line(&line, fin))
+ check_header(&line, p_hdr_data, 0);
+
+ strbuf_release(&newline);
+ /* replenish line */
+ if (strbuf_getline(&line, fin, '\n'))
+ return 0;
+ strbuf_addch(&line, '\n');
+ return 1;
+}
+
+static inline int patchbreak(const struct strbuf *line)
+{
+ size_t i;
+
+ /* Beginning of a "diff -" header? */
+ if (!prefixcmp(line->buf, "diff -"))
+ return 1;
+
+ /* CVS "Index: " line? */
+ if (!prefixcmp(line->buf, "Index: "))
+ return 1;
+
+ /*
+ * "--- <filename>" starts patches without headers
+ * "---<sp>*" is a manual separator
+ */
+ if (line->len < 4)
+ return 0;
+
+ if (!prefixcmp(line->buf, "---")) {
+ /* space followed by a filename? */
+ if (line->buf[3] == ' ' && !isspace(line->buf[4]))
+ return 1;
+ /* Just whitespace? */
+ for (i = 3; i < line->len; i++) {
+ unsigned char c = line->buf[i];
+ if (c == '\n')
+ return 1;
+ if (!isspace(c))
+ break;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+static int is_scissors_line(const struct strbuf *line)
+{
+ size_t i, len = line->len;
+ int scissors = 0, gap = 0;
+ int first_nonblank = -1;
+ int last_nonblank = 0, visible, perforation = 0, in_perforation = 0;
+ const char *buf = line->buf;
+
+ for (i = 0; i < len; i++) {
+ if (isspace(buf[i])) {
+ if (in_perforation) {
+ perforation++;
+ gap++;
+ }
+ continue;
+ }
+ last_nonblank = i;
+ if (first_nonblank < 0)
+ first_nonblank = i;
+ if (buf[i] == '-') {
+ in_perforation = 1;
+ perforation++;
+ continue;
+ }
+ if (i + 1 < len &&
+ (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2))) {
+ in_perforation = 1;
+ perforation += 2;
+ scissors += 2;
+ i++;
+ continue;
+ }
+ in_perforation = 0;
+ }
+
+ /*
+ * The mark must be at least 8 bytes long (e.g. "-- >8 --").
+ * Even though there can be arbitrary cruft on the same line
+ * (e.g. "cut here"), in order to avoid misidentification, the
+ * perforation must occupy more than a third of the visible
+ * width of the line, and dashes and scissors must occupy more
+ * than half of the perforation.
+ */
+
+ visible = last_nonblank - first_nonblank + 1;
+ return (scissors && 8 <= visible &&
+ visible < perforation * 3 &&
+ gap * 2 < perforation);
+}
+
+static int handle_commit_msg(struct strbuf *line)
+{
+ static int still_looking = 1;
+
+ if (!cmitmsg)
+ return 0;
+
+ if (still_looking) {
+ if (!line->len || (line->len == 1 && line->buf[0] == '\n'))
+ return 0;
+ }
+
+ if (use_inbody_headers && still_looking) {
+ still_looking = check_header(line, s_hdr_data, 0);
+ if (still_looking)
+ return 0;
+ } else
+ /* Only trim the first (blank) line of the commit message
+ * when ignoring in-body headers.
+ */
+ still_looking = 0;
+
+ /* normalize the log message to UTF-8. */
+ if (metainfo_charset)
+ convert_to_utf8(line, charset.buf);
+
+ if (use_scissors && is_scissors_line(line)) {
+ int i;
+ if (fseek(cmitmsg, 0L, SEEK_SET))
+ die_errno("Could not rewind output message file");
+ if (ftruncate(fileno(cmitmsg), 0))
+ die_errno("Could not truncate output message file at scissors");
+ still_looking = 1;
+
+ /*
+ * We may have already read "secondary headers"; purge
+ * them to give ourselves a clean restart.
+ */
+ for (i = 0; header[i]; i++) {
+ if (s_hdr_data[i])
+ strbuf_release(s_hdr_data[i]);
+ s_hdr_data[i] = NULL;
+ }
+ return 0;
+ }
+
+ if (patchbreak(line)) {
+ fclose(cmitmsg);
+ cmitmsg = NULL;
+ return 1;
+ }
+
+ fputs(line->buf, cmitmsg);
+ return 0;
+}
+
+static void handle_patch(const struct strbuf *line)
+{
+ fwrite(line->buf, 1, line->len, patchfile);
+ patch_lines++;
+}
+
+static void handle_filter(struct strbuf *line)
+{
+ static int filter = 0;
+
+ /* filter tells us which part we left off on */
+ switch (filter) {
+ case 0:
+ if (!handle_commit_msg(line))
+ break;
+ filter++;
+ case 1:
+ handle_patch(line);
+ break;
+ }
+}
+
+static void handle_body(void)
+{
+ struct strbuf prev = STRBUF_INIT;
+
+ /* Skip up to the first boundary */
+ if (*content_top) {
+ if (!find_boundary())
+ goto handle_body_out;
+ }
+
+ do {
+ /* process any boundary lines */
+ if (*content_top && is_multipart_boundary(&line)) {
+ /* flush any leftover */
+ if (prev.len) {
+ handle_filter(&prev);
+ strbuf_reset(&prev);
+ }
+ if (!handle_boundary())
+ goto handle_body_out;
+ }
+
+ /* Unwrap transfer encoding */
+ decode_transfer_encoding(&line);
+
+ switch (transfer_encoding) {
+ case TE_BASE64:
+ case TE_QP:
+ {
+ struct strbuf **lines, **it, *sb;
+
+ /* Prepend any previous partial lines */
+ strbuf_insert(&line, 0, prev.buf, prev.len);
+ strbuf_reset(&prev);
+
+ /* binary data most likely doesn't have newlines */
+ if (message_type != TYPE_TEXT) {
+ handle_filter(&line);
+ break;
+ }
+ /*
+ * This is a decoded line that may contain
+ * multiple new lines. Pass only one chunk
+ * at a time to handle_filter()
+ */
+ lines = strbuf_split(&line, '\n');
+ for (it = lines; (sb = *it); it++) {
+ if (*(it + 1) == NULL) /* The last line */
+ if (sb->buf[sb->len - 1] != '\n') {
+ /* Partial line, save it for later. */
+ strbuf_addbuf(&prev, sb);
+ break;
+ }
+ handle_filter(sb);
+ }
+ /*
+ * The partial chunk is saved in "prev" and will be
+ * appended by the next iteration of read_line_with_nul().
+ */
+ strbuf_list_free(lines);
+ break;
+ }
+ default:
+ handle_filter(&line);
+ }
+
+ } while (!strbuf_getwholeline(&line, fin, '\n'));
+
+handle_body_out:
+ strbuf_release(&prev);
+}
+
+static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
+{
+ const char *sp = data->buf;
+ while (1) {
+ char *ep = strchr(sp, '\n');
+ int len;
+ if (!ep)
+ len = strlen(sp);
+ else
+ len = ep - sp;
+ fprintf(fout, "%s: %.*s\n", hdr, len, sp);
+ if (!ep)
+ break;
+ sp = ep + 1;
+ }
+}
+
+static void handle_info(void)
+{
+ struct strbuf *hdr;
+ int i;
+
+ for (i = 0; header[i]; i++) {
+ /* only print inbody headers if we output a patch file */
+ if (patch_lines && s_hdr_data[i])
+ hdr = s_hdr_data[i];
+ else if (p_hdr_data[i])
+ hdr = p_hdr_data[i];
+ else
+ continue;
+
+ if (!memcmp(header[i], "Subject", 7)) {
+ if (!keep_subject) {
+ cleanup_subject(hdr);
+ cleanup_space(hdr);
+ }
+ output_header_lines(fout, "Subject", hdr);
+ } else if (!memcmp(header[i], "From", 4)) {
+ cleanup_space(hdr);
+ handle_from(hdr);
+ fprintf(fout, "Author: %s\n", name.buf);
+ fprintf(fout, "Email: %s\n", email.buf);
+ } else {
+ cleanup_space(hdr);
+ fprintf(fout, "%s: %s\n", header[i], hdr->buf);
+ }
+ }
+ fprintf(fout, "\n");
+}
+
+static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch)
+{
+ int peek;
+ fin = in;
+ fout = out;
+
+ cmitmsg = fopen(msg, "w");
+ if (!cmitmsg) {
+ perror(msg);
+ return -1;
+ }
+ patchfile = fopen(patch, "w");
+ if (!patchfile) {
+ perror(patch);
+ fclose(cmitmsg);
+ return -1;
+ }
+
+ p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data));
+ s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data));
+
+ do {
+ peek = fgetc(in);
+ } while (isspace(peek));
+ ungetc(peek, in);
+
+ /* process the email header */
+ while (read_one_header_line(&line, fin))
+ check_header(&line, p_hdr_data, 1);
+
+ handle_body();
+ handle_info();
+
+ return 0;
+}
+
+static int git_mailinfo_config(const char *var, const char *value, void *unused)
+{
+ if (prefixcmp(var, "mailinfo."))
+ return git_default_config(var, value, unused);
+ if (!strcmp(var, "mailinfo.scissors")) {
+ use_scissors = git_config_bool(var, value);
+ return 0;
+ }
+ /* perhaps others here */
+ return 0;
+}
+
+static const char mailinfo_usage[] =
+ "git mailinfo [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
+
+int cmd_mailinfo(int argc, const char **argv, const char *prefix)
+{
+ const char *def_charset;
+
+ /* NEEDSWORK: might want to do the optional .git/ directory
+ * discovery
+ */
+ git_config(git_mailinfo_config, NULL);
+
+ def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8");
+ metainfo_charset = def_charset;
+
+ while (1 < argc && argv[1][0] == '-') {
+ if (!strcmp(argv[1], "-k"))
+ keep_subject = 1;
+ else if (!strcmp(argv[1], "-b"))
+ keep_non_patch_brackets_in_subject = 1;
+ else if (!strcmp(argv[1], "-u"))
+ metainfo_charset = def_charset;
+ else if (!strcmp(argv[1], "-n"))
+ metainfo_charset = NULL;
+ else if (!prefixcmp(argv[1], "--encoding="))
+ metainfo_charset = argv[1] + 11;
+ else if (!strcmp(argv[1], "--scissors"))
+ use_scissors = 1;
+ else if (!strcmp(argv[1], "--no-scissors"))
+ use_scissors = 0;
+ else if (!strcmp(argv[1], "--no-inbody-headers"))
+ use_inbody_headers = 0;
+ else
+ usage(mailinfo_usage);
+ argc--; argv++;
+ }
+
+ if (argc != 3)
+ usage(mailinfo_usage);
+
+ return !!mailinfo(stdin, stdout, argv[1], argv[2]);
+}
--- /dev/null
+/*
+ * Totally braindamaged mbox splitter program.
+ *
+ * It just splits a mbox into a list of files: "0001" "0002" ..
+ * so you can process them further from there.
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "string-list.h"
+#include "strbuf.h"
+
+static const char git_mailsplit_usage[] =
+"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [<mbox>|<Maildir>...]";
+
+static int is_from_line(const char *line, int len)
+{
+ const char *colon;
+
+ if (len < 20 || memcmp("From ", line, 5))
+ return 0;
+
+ colon = line + len - 2;
+ line += 5;
+ for (;;) {
+ if (colon < line)
+ return 0;
+ if (*--colon == ':')
+ break;
+ }
+
+ if (!isdigit(colon[-4]) ||
+ !isdigit(colon[-2]) ||
+ !isdigit(colon[-1]) ||
+ !isdigit(colon[ 1]) ||
+ !isdigit(colon[ 2]))
+ return 0;
+
+ /* year */
+ if (strtol(colon+3, NULL, 10) <= 90)
+ return 0;
+
+ /* Ok, close enough */
+ return 1;
+}
+
+static struct strbuf buf = STRBUF_INIT;
+static int keep_cr;
+
+/* Called with the first line (potentially partial)
+ * already in buf[] -- normally that should begin with
+ * the Unix "From " line. Write it into the specified
+ * file.
+ */
+static int split_one(FILE *mbox, const char *name, int allow_bare)
+{
+ FILE *output = NULL;
+ int fd;
+ int status = 0;
+ int is_bare = !is_from_line(buf.buf, buf.len);
+
+ if (is_bare && !allow_bare)
+ goto corrupt;
+
+ fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (fd < 0)
+ die_errno("cannot open output file '%s'", name);
+ output = xfdopen(fd, "w");
+
+ /* Copy it out, while searching for a line that begins with
+ * "From " and having something that looks like a date format.
+ */
+ for (;;) {
+ if (!keep_cr && buf.len > 1 && buf.buf[buf.len-1] == '\n' &&
+ buf.buf[buf.len-2] == '\r') {
+ strbuf_setlen(&buf, buf.len-2);
+ strbuf_addch(&buf, '\n');
+ }
+
+ if (fwrite(buf.buf, 1, buf.len, output) != buf.len)
+ die_errno("cannot write output");
+
+ if (strbuf_getwholeline(&buf, mbox, '\n')) {
+ if (feof(mbox)) {
+ status = 1;
+ break;
+ }
+ die_errno("cannot read mbox");
+ }
+ if (!is_bare && is_from_line(buf.buf, buf.len))
+ break; /* done with one message */
+ }
+ fclose(output);
+ return status;
+
+ corrupt:
+ if (output)
+ fclose(output);
+ unlink(name);
+ fprintf(stderr, "corrupt mailbox\n");
+ exit(1);
+}
+
+static int populate_maildir_list(struct string_list *list, const char *path)
+{
+ DIR *dir;
+ struct dirent *dent;
+ char name[PATH_MAX];
+ char *subs[] = { "cur", "new", NULL };
+ char **sub;
+
+ for (sub = subs; *sub; ++sub) {
+ snprintf(name, sizeof(name), "%s/%s", path, *sub);
+ if ((dir = opendir(name)) == NULL) {
+ if (errno == ENOENT)
+ continue;
+ error("cannot opendir %s (%s)", name, strerror(errno));
+ return -1;
+ }
+
+ while ((dent = readdir(dir)) != NULL) {
+ if (dent->d_name[0] == '.')
+ continue;
+ snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name);
+ string_list_insert(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 name[PATH_MAX];
+ int ret = -1;
+ int i;
+ struct string_list list = {NULL, 0, 0, 1};
+
+ if (populate_maildir_list(&list, maildir) < 0)
+ goto out;
+
+ for (i = 0; i < list.nr; i++) {
+ FILE *f;
+ snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string);
+ f = fopen(file, "r");
+ if (!f) {
+ error("cannot open mail %s (%s)", file, strerror(errno));
+ goto out;
+ }
+
+ if (strbuf_getwholeline(&buf, f, '\n')) {
+ error("cannot read mail %s (%s)", file, strerror(errno));
+ goto out;
+ }
+
+ sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+ split_one(f, name, 1);
+
+ fclose(f);
+ }
+
+ ret = skip;
+out:
+ string_list_clear(&list, 1);
+ 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;
+ int peek;
+
+ FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
+ int file_done = 0;
+
+ if (!f) {
+ error("cannot open mbox %s", file);
+ goto out;
+ }
+
+ do {
+ peek = fgetc(f);
+ } while (isspace(peek));
+ ungetc(peek, f);
+
+ if (strbuf_getwholeline(&buf, f, '\n')) {
+ /* empty stdin is OK */
+ if (f != stdin) {
+ error("cannot read mbox %s", file);
+ goto out;
+ }
+ file_done = 1;
+ }
+
+ 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:
+ return ret;
+}
+
+int cmd_mailsplit(int argc, const char **argv, const char *prefix)
+{
+ int nr = 0, nr_prec = 4, num = 0;
+ int allow_bare = 0;
+ const char *dir = NULL;
+ const char **argp;
+ static const char *stdin_only[] = { "-", NULL };
+
+ for (argp = argv+1; *argp; argp++) {
+ const char *arg = *argp;
+
+ if (arg[0] != '-')
+ break;
+ /* do flags here */
+ if ( arg[1] == 'd' ) {
+ nr_prec = strtol(arg+2, NULL, 10);
+ if (nr_prec < 3 || 10 <= nr_prec)
+ usage(git_mailsplit_usage);
+ continue;
+ } else if ( arg[1] == 'f' ) {
+ nr = strtol(arg+2, NULL, 10);
+ } else if ( arg[1] == 'h' ) {
+ usage(git_mailsplit_usage);
+ } else if ( arg[1] == 'b' && !arg[2] ) {
+ allow_bare = 1;
+ } else if (!strcmp(arg, "--keep-cr")) {
+ keep_cr = 1;
+ } else if ( arg[1] == 'o' && arg[2] ) {
+ dir = arg+2;
+ } else if ( arg[1] == '-' && !arg[2] ) {
+ argp++; /* -- marks end of options */
+ break;
+ } else {
+ die("unknown option: %s", arg);
+ }
+ }
+
+ if ( !dir ) {
+ /* Backwards compatibility: if no -o specified, accept
+ <mbox> <dir> or just <dir> */
+ switch (argc - (argp-argv)) {
+ case 1:
+ dir = argp[0];
+ argp = stdin_only;
+ break;
+ case 2:
+ stdin_only[0] = argp[0];
+ dir = argp[1];
+ argp = stdin_only;
+ break;
+ default:
+ usage(git_mailsplit_usage);
+ }
+ } else {
+ /* New usage: if no more argument, parse stdin */
+ if ( !*argp )
+ argp = stdin_only;
+ }
+
+ 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 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "parse-options.h"
+
+static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
+{
+ struct commit_list *result;
+
+ result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0);
+
+ if (!result)
+ return 1;
+
+ while (result) {
+ printf("%s\n", sha1_to_hex(result->item->object.sha1));
+ if (!show_all)
+ return 0;
+ result = result->next;
+ }
+
+ return 0;
+}
+
+static const char * const merge_base_usage[] = {
+ "git merge-base [-a|--all] <commit> <commit>...",
+ NULL
+};
+
+static struct commit *get_commit_reference(const char *arg)
+{
+ unsigned char revkey[20];
+ struct commit *r;
+
+ if (get_sha1(arg, revkey))
+ die("Not a valid object name %s", arg);
+ r = lookup_commit_reference(revkey);
+ if (!r)
+ die("Not a valid commit name %s", arg);
+
+ return r;
+}
+
+int cmd_merge_base(int argc, const char **argv, const char *prefix)
+{
+ struct commit **rev;
+ int rev_nr = 0;
+ int show_all = 0;
+
+ struct option options[] = {
+ OPT_BOOLEAN('a', "all", &show_all, "outputs all common ancestors"),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
+ if (argc < 2)
+ usage_with_options(merge_base_usage, options);
+ rev = xmalloc(argc * sizeof(*rev));
+ while (argc-- > 0)
+ rev[rev_nr++] = get_commit_reference(*argv++);
+ return show_merge_base(rev, rev_nr, show_all);
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
+#include "parse-options.h"
+
+static const char *const merge_file_usage[] = {
+ "git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2",
+ NULL
+};
+
+static int label_cb(const struct option *opt, const char *arg, int unset)
+{
+ static int label_count = 0;
+ const char **names = (const char **)opt->value;
+
+ if (label_count >= 3)
+ return error("too many labels on the command line");
+ names[label_count++] = arg;
+ return 0;
+}
+
+int cmd_merge_file(int argc, const char **argv, const char *prefix)
+{
+ const char *names[3] = { NULL, NULL, NULL };
+ mmfile_t mmfs[3];
+ mmbuffer_t result = {NULL, 0};
+ xmparam_t xmp = {{XDF_NEED_MINIMAL}};
+ int ret = 0, i = 0, to_stdout = 0;
+ int level = XDL_MERGE_ZEALOUS_ALNUM;
+ int style = 0, quiet = 0;
+ int favor = 0;
+ int nongit;
+
+ struct option options[] = {
+ OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"),
+ OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3),
+ OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version",
+ XDL_MERGE_FAVOR_OURS),
+ OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version",
+ XDL_MERGE_FAVOR_THEIRS),
+ OPT__QUIET(&quiet),
+ OPT_CALLBACK('L', NULL, names, "name",
+ "set labels for file1/orig_file/file2", &label_cb),
+ OPT_END(),
+ };
+
+ prefix = setup_git_directory_gently(&nongit);
+ if (!nongit) {
+ /* Read the configuration file */
+ git_config(git_xmerge_config, NULL);
+ if (0 <= git_xmerge_style)
+ style = git_xmerge_style;
+ }
+
+ argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0);
+ if (argc != 3)
+ usage_with_options(merge_file_usage, options);
+ if (quiet) {
+ if (!freopen("/dev/null", "w", stderr))
+ return error("failed to redirect stderr to /dev/null: "
+ "%s\n", strerror(errno));
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (!names[i])
+ names[i] = argv[i];
+ if (read_mmfile(mmfs + i, argv[i]))
+ return -1;
+ if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
+ return error("Cannot merge binary files: %s\n",
+ argv[i]);
+ }
+
+ ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
+ &xmp, XDL_MERGE_FLAGS(level, style, favor), &result);
+
+ for (i = 0; i < 3; i++)
+ free(mmfs[i].ptr);
+
+ if (ret >= 0) {
+ const char *filename = argv[0];
+ FILE *f = to_stdout ? stdout : fopen(filename, "wb");
+
+ if (!f)
+ ret = error("Could not open %s for writing", filename);
+ else if (result.size &&
+ fwrite(result.ptr, result.size, 1, f) != 1)
+ ret = error("Could not write to %s", filename);
+ else if (fclose(f))
+ ret = error("Could not close %s", filename);
+ free(result.ptr);
+ }
+
+ return ret;
+}
--- /dev/null
+#include "cache.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+
+static const char *pgm;
+static int one_shot, quiet;
+static int err;
+
+static int merge_entry(int pos, const char *path)
+{
+ int found;
+ const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
+ char hexbuf[4][60];
+ char ownbuf[4][60];
+
+ if (pos >= active_nr)
+ die("git merge-index: %s not in the cache", path);
+ found = 0;
+ do {
+ struct cache_entry *ce = active_cache[pos];
+ int stage = ce_stage(ce);
+
+ if (strcmp(ce->name, path))
+ break;
+ found++;
+ strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
+ sprintf(ownbuf[stage], "%o", ce->ce_mode);
+ arguments[stage] = hexbuf[stage];
+ arguments[stage + 4] = ownbuf[stage];
+ } while (++pos < active_nr);
+ if (!found)
+ die("git merge-index: %s not in the cache", path);
+
+ if (run_command_v_opt(arguments, 0)) {
+ if (one_shot)
+ err++;
+ else {
+ if (!quiet)
+ die("merge program failed");
+ exit(1);
+ }
+ }
+ return found;
+}
+
+static void merge_file(const char *path)
+{
+ int pos = cache_name_pos(path, strlen(path));
+
+ /*
+ * If it already exists in the cache as stage0, it's
+ * already merged and there is nothing to do.
+ */
+ if (pos < 0)
+ merge_entry(-pos-1, path);
+}
+
+static void merge_all(void)
+{
+ int i;
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (!ce_stage(ce))
+ continue;
+ i += merge_entry(i, ce->name)-1;
+ }
+}
+
+int cmd_merge_index(int argc, const char **argv, const char *prefix)
+{
+ int i, force_file = 0;
+
+ /* Without this we cannot rely on waitpid() to tell
+ * what happened to our children.
+ */
+ signal(SIGCHLD, SIG_DFL);
+
+ if (argc < 3)
+ usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
+
+ read_cache();
+
+ i = 1;
+ if (!strcmp(argv[i], "-o")) {
+ one_shot = 1;
+ i++;
+ }
+ if (!strcmp(argv[i], "-q")) {
+ quiet = 1;
+ i++;
+ }
+ pgm = argv[i++];
+ for (; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!force_file && *arg == '-') {
+ if (!strcmp(arg, "--")) {
+ force_file = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-a")) {
+ merge_all();
+ continue;
+ }
+ die("git merge-index: unknown option %s", arg);
+ }
+ merge_file(arg);
+ }
+ if (err && !quiet)
+ die("merge program failed");
+ return err;
+}
--- /dev/null
+/*
+ * Implementation of git-merge-ours.sh as builtin
+ *
+ * Copyright (c) 2007 Thomas Harning Jr
+ * Original:
+ * Original Copyright (c) 2005 Junio C Hamano
+ *
+ * Pretend we resolved the heads, but declare our tree trumps everybody else.
+ */
+#include "git-compat-util.h"
+#include "builtin.h"
+
+static const char builtin_merge_ours_usage[] =
+ "git merge-ours <base>... -- HEAD <remote>...";
+
+static const char *diff_index_args[] = {
+ "diff-index", "--quiet", "--cached", "HEAD", "--", NULL
+};
+#define NARGS (ARRAY_SIZE(diff_index_args) - 1)
+
+int cmd_merge_ours(int argc, const char **argv, const char *prefix)
+{
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(builtin_merge_ours_usage);
+
+ /*
+ * We need to exit with 2 if the index does not match our HEAD tree,
+ * because the current index is what we will be committing as the
+ * merge result.
+ */
+ if (cmd_diff_index(NARGS, diff_index_args, prefix))
+ exit(2);
+ exit(0);
+}
--- /dev/null
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "merge-recursive.h"
+
+static const char *better_branch_name(const char *branch)
+{
+ static char githead_env[8 + 40 + 1];
+ char *name;
+
+ if (strlen(branch) != 40)
+ return branch;
+ sprintf(githead_env, "GITHEAD_%s", branch);
+ name = getenv(githead_env);
+ return name ? name : branch;
+}
+
+int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
+{
+ const unsigned char *bases[21];
+ unsigned bases_count = 0;
+ int i, failed;
+ unsigned char h1[20], h2[20];
+ struct merge_options o;
+ struct commit *result;
+
+ init_merge_options(&o);
+ if (argv[0] && !suffixcmp(argv[0], "-subtree"))
+ o.subtree_shift = "";
+
+ if (argc < 4)
+ usagef("%s <base>... -- <head> <remote> ...", argv[0]);
+
+ for (i = 1; i < argc; ++i) {
+ const char *arg = argv[i];
+
+ if (!prefixcmp(arg, "--")) {
+ if (!arg[2])
+ break;
+ if (!strcmp(arg+2, "ours"))
+ o.recursive_variant = MERGE_RECURSIVE_OURS;
+ else if (!strcmp(arg+2, "theirs"))
+ o.recursive_variant = MERGE_RECURSIVE_THEIRS;
+ else if (!strcmp(arg+2, "subtree"))
+ o.subtree_shift = "";
+ else if (!prefixcmp(arg+2, "subtree="))
+ o.subtree_shift = arg + 10;
+ else
+ die("Unknown option %s", arg);
+ continue;
+ }
+ if (bases_count < ARRAY_SIZE(bases)-1) {
+ unsigned char *sha = xmalloc(20);
+ if (get_sha1(argv[i], sha))
+ die("Could not parse object '%s'", argv[i]);
+ bases[bases_count++] = sha;
+ }
+ else
+ warning("Cannot handle more than %d bases. "
+ "Ignoring %s.",
+ (int)ARRAY_SIZE(bases)-1, argv[i]);
+ }
+ if (argc - i != 3) /* "--" "<head>" "<remote>" */
+ die("Not handling anything other than two heads merge.");
+
+ o.branch1 = argv[++i];
+ o.branch2 = argv[++i];
+
+ if (get_sha1(o.branch1, h1))
+ die("Could not resolve ref '%s'", o.branch1);
+ if (get_sha1(o.branch2, h2))
+ die("Could not resolve ref '%s'", o.branch2);
+
+ o.branch1 = better_branch_name(o.branch1);
+ o.branch2 = better_branch_name(o.branch2);
+
+ if (o.verbosity >= 3)
+ printf("Merging %s with %s\n", o.branch1, o.branch2);
+
+ failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result);
+ if (failed < 0)
+ return 128; /* die() error code */
+ return failed;
+}
--- /dev/null
+#include "cache.h"
+#include "tree-walk.h"
+#include "xdiff-interface.h"
+#include "blob.h"
+#include "exec_cmd.h"
+
+static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
+static int resolve_directories = 1;
+
+struct merge_list {
+ struct merge_list *next;
+ struct merge_list *link; /* other stages for this object */
+
+ unsigned int stage : 2,
+ flags : 30;
+ unsigned int mode;
+ const char *path;
+ struct blob *blob;
+};
+
+static struct merge_list *merge_result, **merge_result_end = &merge_result;
+
+static void add_merge_entry(struct merge_list *entry)
+{
+ *merge_result_end = entry;
+ merge_result_end = &entry->next;
+}
+
+static void merge_trees(struct tree_desc t[3], const char *base);
+
+static const char *explanation(struct merge_list *entry)
+{
+ switch (entry->stage) {
+ case 0:
+ return "merged";
+ case 3:
+ return "added in remote";
+ case 2:
+ if (entry->link)
+ return "added in both";
+ return "added in local";
+ }
+
+ /* Existed in base */
+ entry = entry->link;
+ if (!entry)
+ return "removed in both";
+
+ if (entry->link)
+ return "changed in both";
+
+ if (entry->stage == 3)
+ return "removed in local";
+ return "removed in remote";
+}
+
+extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *);
+
+static void *result(struct merge_list *entry, unsigned long *size)
+{
+ enum object_type type;
+ struct blob *base, *our, *their;
+
+ if (!entry->stage)
+ return read_sha1_file(entry->blob->object.sha1, &type, size);
+ base = NULL;
+ if (entry->stage == 1) {
+ base = entry->blob;
+ entry = entry->link;
+ }
+ our = NULL;
+ if (entry && entry->stage == 2) {
+ our = entry->blob;
+ entry = entry->link;
+ }
+ their = NULL;
+ if (entry)
+ their = entry->blob;
+ return merge_file(entry->path, base, our, their, size);
+}
+
+static void *origin(struct merge_list *entry, unsigned long *size)
+{
+ enum object_type type;
+ while (entry) {
+ if (entry->stage == 2)
+ return read_sha1_file(entry->blob->object.sha1, &type, size);
+ entry = entry->link;
+ }
+ return NULL;
+}
+
+static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+ int i;
+ for (i = 0; i < nbuf; i++)
+ printf("%.*s", (int) mb[i].size, mb[i].ptr);
+ return 0;
+}
+
+static void show_diff(struct merge_list *entry)
+{
+ unsigned long size;
+ mmfile_t src, dst;
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+
+ xpp.flags = XDF_NEED_MINIMAL;
+ memset(&xecfg, 0, sizeof(xecfg));
+ xecfg.ctxlen = 3;
+ ecb.outf = show_outf;
+ ecb.priv = NULL;
+
+ src.ptr = origin(entry, &size);
+ if (!src.ptr)
+ size = 0;
+ src.size = size;
+ dst.ptr = result(entry, &size);
+ if (!dst.ptr)
+ size = 0;
+ dst.size = size;
+ xdi_diff(&src, &dst, &xpp, &xecfg, &ecb);
+ free(src.ptr);
+ free(dst.ptr);
+}
+
+static void show_result_list(struct merge_list *entry)
+{
+ printf("%s\n", explanation(entry));
+ do {
+ struct merge_list *link = entry->link;
+ static const char *desc[4] = { "result", "base", "our", "their" };
+ printf(" %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path);
+ entry = link;
+ } while (entry);
+}
+
+static void show_result(void)
+{
+ struct merge_list *walk;
+
+ walk = merge_result;
+ while (walk) {
+ show_result_list(walk);
+ show_diff(walk);
+ walk = walk->next;
+ }
+}
+
+/* An empty entry never compares same, not even to another empty entry */
+static int same_entry(struct name_entry *a, struct name_entry *b)
+{
+ return a->sha1 &&
+ b->sha1 &&
+ !hashcmp(a->sha1, b->sha1) &&
+ a->mode == b->mode;
+}
+
+static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path)
+{
+ struct merge_list *res = xcalloc(1, sizeof(*res));
+
+ res->stage = stage;
+ res->path = path;
+ res->mode = mode;
+ res->blob = lookup_blob(sha1);
+ return res;
+}
+
+static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
+{
+ char *path = xmalloc(traverse_path_len(info, n) + 1);
+ return make_traverse_path(path, info, n);
+}
+
+static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result)
+{
+ struct merge_list *orig, *final;
+ const char *path;
+
+ /* If it's already branch1, don't bother showing it */
+ if (!branch1)
+ return;
+
+ path = traverse_path(info, result);
+ orig = create_entry(2, branch1->mode, branch1->sha1, path);
+ final = create_entry(0, result->mode, result->sha1, path);
+
+ final->link = orig;
+
+ add_merge_entry(final);
+}
+
+static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3])
+{
+ char *newbase;
+ struct name_entry *p;
+ struct tree_desc t[3];
+ void *buf0, *buf1, *buf2;
+
+ if (!resolve_directories)
+ return 0;
+ p = n;
+ if (!p->mode) {
+ p++;
+ if (!p->mode)
+ p++;
+ }
+ if (!S_ISDIR(p->mode))
+ return 0;
+ newbase = traverse_path(info, p);
+ buf0 = fill_tree_descriptor(t+0, n[0].sha1);
+ buf1 = fill_tree_descriptor(t+1, n[1].sha1);
+ buf2 = fill_tree_descriptor(t+2, n[2].sha1);
+ merge_trees(t, newbase);
+
+ free(buf0);
+ free(buf1);
+ free(buf2);
+ free(newbase);
+ return 1;
+}
+
+
+static struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry)
+{
+ const char *path;
+ struct merge_list *link;
+
+ if (!n->mode)
+ return entry;
+ if (entry)
+ path = entry->path;
+ else
+ path = traverse_path(info, n);
+ link = create_entry(stage, n->mode, n->sha1, path);
+ link->link = entry;
+ return link;
+}
+
+static void unresolved(const struct traverse_info *info, struct name_entry n[3])
+{
+ struct merge_list *entry = NULL;
+
+ if (unresolved_directory(info, n))
+ return;
+
+ /*
+ * Do them in reverse order so that the resulting link
+ * list has the stages in order - link_entry adds new
+ * links at the front.
+ */
+ entry = link_entry(3, info, n + 2, entry);
+ entry = link_entry(2, info, n + 1, entry);
+ entry = link_entry(1, info, n + 0, entry);
+
+ add_merge_entry(entry);
+}
+
+/*
+ * Merge two trees together (t[1] and t[2]), using a common base (t[0])
+ * as the origin.
+ *
+ * This walks the (sorted) trees in lock-step, checking every possible
+ * name. Note that directories automatically sort differently from other
+ * files (see "base_name_compare"), so you'll never see file/directory
+ * conflicts, because they won't ever compare the same.
+ *
+ * IOW, if a directory changes to a filename, it will automatically be
+ * seen as the directory going away, and the filename being created.
+ *
+ * Think of this as a three-way diff.
+ *
+ * The output will be either:
+ * - successful merge
+ * "0 mode sha1 filename"
+ * NOTE NOTE NOTE! FIXME! We really really need to walk the index
+ * in parallel with this too!
+ *
+ * - conflict:
+ * "1 mode sha1 filename"
+ * "2 mode sha1 filename"
+ * "3 mode sha1 filename"
+ * where not all of the 1/2/3 lines may exist, of course.
+ *
+ * The successful merge rules are the same as for the three-way merge
+ * in git-read-tree.
+ */
+static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info)
+{
+ /* Same in both? */
+ if (same_entry(entry+1, entry+2)) {
+ if (entry[0].sha1) {
+ resolve(info, NULL, entry+1);
+ return mask;
+ }
+ }
+
+ if (same_entry(entry+0, entry+1)) {
+ if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
+ resolve(info, entry+1, entry+2);
+ return mask;
+ }
+ }
+
+ if (same_entry(entry+0, entry+2)) {
+ if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
+ resolve(info, NULL, entry+1);
+ return mask;
+ }
+ }
+
+ unresolved(info, entry);
+ return mask;
+}
+
+static void merge_trees(struct tree_desc t[3], const char *base)
+{
+ struct traverse_info info;
+
+ setup_traverse_info(&info, base);
+ info.fn = threeway_callback;
+ traverse_trees(3, t, &info);
+}
+
+static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
+{
+ unsigned char sha1[20];
+ void *buf;
+
+ if (get_sha1(rev, sha1))
+ die("unknown rev %s", rev);
+ buf = fill_tree_descriptor(desc, sha1);
+ if (!buf)
+ die("%s is not a tree", rev);
+ return buf;
+}
+
+int cmd_merge_tree(int argc, const char **argv, const char *prefix)
+{
+ struct tree_desc t[3];
+ void *buf1, *buf2, *buf3;
+
+ if (argc != 4)
+ usage(merge_tree_usage);
+
+ buf1 = get_tree_descriptor(t+0, argv[1]);
+ buf2 = get_tree_descriptor(t+1, argv[2]);
+ buf3 = get_tree_descriptor(t+2, argv[3]);
+ merge_trees(t, "");
+ free(buf1);
+ free(buf2);
+ free(buf3);
+
+ show_result();
+ return 0;
+}
--- /dev/null
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+#include "rerere.h"
+#include "help.h"
+#include "merge-recursive.h"
+#include "resolve-undo.h"
+
+#define DEFAULT_TWOHEAD (1<<0)
+#define DEFAULT_OCTOPUS (1<<1)
+#define NO_FAST_FORWARD (1<<2)
+#define NO_TRIVIAL (1<<3)
+
+struct strategy {
+ const char *name;
+ unsigned attr;
+};
+
+static const char * const builtin_merge_usage[] = {
+ "git merge [options] <remote>...",
+ "git merge [options] <msg> HEAD <remote>",
+ NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int fast_forward_only;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct strategy **use_strategies;
+static size_t use_strategies_nr, use_strategies_alloc;
+static const char **xopts;
+static size_t xopts_nr, xopts_alloc;
+static const char *branch;
+static int verbosity;
+static int allow_rerere_auto;
+
+static struct strategy all_strategy[] = {
+ { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
+ { "octopus", DEFAULT_OCTOPUS },
+ { "resolve", 0 },
+ { "ours", NO_FAST_FORWARD | NO_TRIVIAL },
+ { "subtree", NO_FAST_FORWARD | NO_TRIVIAL },
+};
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct strbuf *buf = opt->value;
+
+ if (unset)
+ strbuf_setlen(buf, 0);
+ else if (arg) {
+ strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg);
+ have_message = 1;
+ } else
+ return error("switch `m' requires a value");
+ return 0;
+}
+
+static struct strategy *get_strategy(const char *name)
+{
+ int i;
+ struct strategy *ret;
+ static struct cmdnames main_cmds, other_cmds;
+ static int loaded;
+
+ if (!name)
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+ if (!strcmp(name, all_strategy[i].name))
+ return &all_strategy[i];
+
+ if (!loaded) {
+ struct cmdnames not_strategies;
+ loaded = 1;
+
+ memset(¬_strategies, 0, sizeof(struct cmdnames));
+ load_command_list("git-merge-", &main_cmds, &other_cmds);
+ for (i = 0; i < main_cmds.cnt; i++) {
+ int j, found = 0;
+ struct cmdname *ent = main_cmds.names[i];
+ for (j = 0; j < ARRAY_SIZE(all_strategy); j++)
+ if (!strncmp(ent->name, all_strategy[j].name, ent->len)
+ && !all_strategy[j].name[ent->len])
+ found = 1;
+ if (!found)
+ add_cmdname(¬_strategies, ent->name, ent->len);
+ }
+ exclude_cmds(&main_cmds, ¬_strategies);
+ }
+ if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
+ fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
+ fprintf(stderr, "Available strategies are:");
+ for (i = 0; i < main_cmds.cnt; i++)
+ fprintf(stderr, " %s", main_cmds.names[i]->name);
+ fprintf(stderr, ".\n");
+ if (other_cmds.cnt) {
+ fprintf(stderr, "Available custom strategies are:");
+ for (i = 0; i < other_cmds.cnt; i++)
+ fprintf(stderr, " %s", other_cmds.names[i]->name);
+ fprintf(stderr, ".\n");
+ }
+ exit(1);
+ }
+
+ ret = xcalloc(1, sizeof(struct strategy));
+ ret->name = xstrdup(name);
+ return ret;
+}
+
+static void append_strategy(struct strategy *s)
+{
+ ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
+ use_strategies[use_strategies_nr++] = s;
+}
+
+static int option_parse_strategy(const struct option *opt,
+ const char *name, int unset)
+{
+ if (unset)
+ return 0;
+
+ append_strategy(get_strategy(name));
+ return 0;
+}
+
+static int option_parse_x(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ return 0;
+
+ ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
+ xopts[xopts_nr++] = xstrdup(arg);
+ return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+ const char *arg, int unset)
+{
+ show_diffstat = unset;
+ return 0;
+}
+
+static struct option builtin_merge_options[] = {
+ { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+ "do not show a diffstat at the end of the merge",
+ PARSE_OPT_NOARG, option_parse_n },
+ OPT_BOOLEAN(0, "stat", &show_diffstat,
+ "show a diffstat at the end of the merge"),
+ OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+ OPT_BOOLEAN(0, "log", &option_log,
+ "add list of one-line log to merge commit message"),
+ OPT_BOOLEAN(0, "squash", &squash,
+ "create a single commit instead of doing a merge"),
+ OPT_BOOLEAN(0, "commit", &option_commit,
+ "perform a commit if the merge succeeds (default)"),
+ OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+ "allow fast-forward (default)"),
+ OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
+ "abort if fast-forward is not possible"),
+ OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
+ OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+ "merge strategy to use", option_parse_strategy),
+ OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
+ "option for selected merge strategy", option_parse_x),
+ OPT_CALLBACK('m', "message", &merge_msg, "message",
+ "message to be used for the merge commit (if any)",
+ option_parse_message),
+ OPT__VERBOSITY(&verbosity),
+ OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+ unlink(git_path("MERGE_MODE"));
+}
+
+static void save_state(void)
+{
+ int len;
+ struct child_process cp;
+ struct strbuf buffer = STRBUF_INIT;
+ const char *argv[] = {"stash", "create", NULL};
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.out = -1;
+ cp.git_cmd = 1;
+
+ if (start_command(&cp))
+ die("could not run stash.");
+ len = strbuf_read(&buffer, cp.out, 1024);
+ close(cp.out);
+
+ if (finish_command(&cp) || len < 0)
+ die("stash failed");
+ else if (!len)
+ return;
+ strbuf_setlen(&buffer, buffer.len-1);
+ if (get_sha1(buffer.buf, stash))
+ die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+ int i = 0;
+ const char *args[6];
+
+ args[i++] = "read-tree";
+ if (verbose)
+ args[i++] = "-v";
+ args[i++] = "--reset";
+ args[i++] = "-u";
+ args[i++] = sha1_to_hex(sha1);
+ args[i] = NULL;
+
+ if (run_command_v_opt(args, RUN_GIT_CMD))
+ die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ const char *args[] = { "stash", "apply", NULL, NULL };
+
+ if (is_null_sha1(stash))
+ return;
+
+ reset_hard(head, 1);
+
+ args[2] = sha1_to_hex(stash);
+
+ /*
+ * It is OK to ignore error here, for example when there was
+ * nothing to restore.
+ */
+ run_command_v_opt(args, RUN_GIT_CMD);
+
+ strbuf_release(&sb);
+ refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+ if (verbosity >= 0)
+ printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+ drop_save();
+}
+
+static void squash_message(void)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ struct strbuf out = STRBUF_INIT;
+ struct commit_list *j;
+ int fd;
+ struct pretty_print_context ctx = {0};
+
+ printf("Squash commit -- not updating HEAD\n");
+ fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno("Could not write to '%s'", git_path("SQUASH_MSG"));
+
+ init_revisions(&rev, NULL);
+ rev.ignore_merges = 1;
+ rev.commit_format = CMIT_FMT_MEDIUM;
+
+ commit = lookup_commit(head);
+ commit->object.flags |= UNINTERESTING;
+ add_pending_object(&rev, &commit->object, NULL);
+
+ for (j = remoteheads; j; j = j->next)
+ add_pending_object(&rev, &j->item->object, NULL);
+
+ setup_revisions(0, NULL, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ ctx.abbrev = rev.abbrev;
+ ctx.date_mode = rev.date_mode;
+
+ strbuf_addstr(&out, "Squashed commit of the following:\n");
+ while ((commit = get_revision(&rev)) != NULL) {
+ strbuf_addch(&out, '\n');
+ strbuf_addf(&out, "commit %s\n",
+ sha1_to_hex(commit->object.sha1));
+ pretty_print_commit(rev.commit_format, commit, &out, &ctx);
+ }
+ if (write(fd, out.buf, out.len) < 0)
+ die_errno("Writing SQUASH_MSG");
+ if (close(fd))
+ die_errno("Finishing SQUASH_MSG");
+ strbuf_release(&out);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+ struct strbuf reflog_message = STRBUF_INIT;
+
+ if (!msg)
+ strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+ else {
+ if (verbosity >= 0)
+ printf("%s\n", msg);
+ strbuf_addf(&reflog_message, "%s: %s",
+ getenv("GIT_REFLOG_ACTION"), msg);
+ }
+ if (squash) {
+ squash_message();
+ } else {
+ if (verbosity >= 0 && !merge_msg.len)
+ printf("No merge message -- not updating HEAD\n");
+ else {
+ const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+ update_ref(reflog_message.buf, "HEAD",
+ new_head, head, 0,
+ DIE_ON_ERR);
+ /*
+ * We ignore errors in 'gc --auto', since the
+ * user should see them.
+ */
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+ }
+ if (new_head && show_diffstat) {
+ struct diff_options opts;
+ diff_setup(&opts);
+ opts.output_format |=
+ DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+ opts.detect_rename = DIFF_DETECT_RENAME;
+ if (diff_use_color_default > 0)
+ DIFF_OPT_SET(&opts, COLOR_DIFF);
+ if (diff_setup_done(&opts) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(head, new_head, "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+ }
+
+ /* Run a post-merge hook */
+ run_hook(NULL, "post-merge", squash ? "1" : "0", NULL);
+
+ strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+ struct object *remote_head;
+ unsigned char branch_head[20], buf_sha[20];
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf bname = STRBUF_INIT;
+ const char *ptr;
+ char *found_ref;
+ int len, early;
+
+ strbuf_branchname(&bname, remote);
+ remote = bname.buf;
+
+ memset(branch_head, 0, sizeof(branch_head));
+ remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("'%s' does not point to a commit", remote);
+
+ if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) {
+ if (!prefixcmp(found_ref, "refs/heads/")) {
+ strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+ sha1_to_hex(branch_head), remote);
+ goto cleanup;
+ }
+ if (!prefixcmp(found_ref, "refs/remotes/")) {
+ strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n",
+ sha1_to_hex(branch_head), remote);
+ goto cleanup;
+ }
+ }
+
+ /* See if remote matches <name>^^^.. or <name>~<number> */
+ for (len = 0, ptr = remote + strlen(remote);
+ remote < ptr && ptr[-1] == '^';
+ ptr--)
+ len++;
+ if (len)
+ early = 1;
+ else {
+ early = 0;
+ ptr = strrchr(remote, '~');
+ if (ptr) {
+ int seen_nonzero = 0;
+
+ len++; /* count ~ */
+ while (*++ptr && isdigit(*ptr)) {
+ seen_nonzero |= (*ptr != '0');
+ len++;
+ }
+ if (*ptr)
+ len = 0; /* not ...~<number> */
+ else if (seen_nonzero)
+ early = 1;
+ else if (len == 1)
+ early = 1; /* "name~" is "name~1"! */
+ }
+ }
+ if (len) {
+ struct strbuf truname = STRBUF_INIT;
+ strbuf_addstr(&truname, "refs/heads/");
+ strbuf_addstr(&truname, remote);
+ strbuf_setlen(&truname, truname.len - len);
+ if (resolve_ref(truname.buf, buf_sha, 0, NULL)) {
+ strbuf_addf(msg,
+ "%s\t\tbranch '%s'%s of .\n",
+ sha1_to_hex(remote_head->sha1),
+ truname.buf + 11,
+ (early ? " (early part)" : ""));
+ strbuf_release(&truname);
+ goto cleanup;
+ }
+ }
+
+ if (!strcmp(remote, "FETCH_HEAD") &&
+ !access(git_path("FETCH_HEAD"), R_OK)) {
+ FILE *fp;
+ struct strbuf line = STRBUF_INIT;
+ char *ptr;
+
+ fp = fopen(git_path("FETCH_HEAD"), "r");
+ if (!fp)
+ die_errno("could not open '%s' for reading",
+ git_path("FETCH_HEAD"));
+ strbuf_getline(&line, fp, '\n');
+ fclose(fp);
+ ptr = strstr(line.buf, "\tnot-for-merge\t");
+ if (ptr)
+ strbuf_remove(&line, ptr-line.buf+1, 13);
+ strbuf_addbuf(msg, &line);
+ strbuf_release(&line);
+ goto cleanup;
+ }
+ strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+ sha1_to_hex(remote_head->sha1), remote);
+cleanup:
+ strbuf_release(&buf);
+ strbuf_release(&bname);
+}
+
+static int git_merge_config(const char *k, const char *v, void *cb)
+{
+ if (branch && !prefixcmp(k, "branch.") &&
+ !prefixcmp(k + 7, branch) &&
+ !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+ const char **argv;
+ int argc;
+ char *buf;
+
+ buf = xstrdup(v);
+ argc = split_cmdline(buf, &argv);
+ if (argc < 0)
+ die("Bad branch.%s.mergeoptions string", branch);
+ argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+ memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+ argc++;
+ parse_options(argc, argv, NULL, builtin_merge_options,
+ builtin_merge_usage, 0);
+ free(buf);
+ }
+
+ if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+ show_diffstat = git_config_bool(k, v);
+ else if (!strcmp(k, "pull.twohead"))
+ return git_config_string(&pull_twohead, k, v);
+ else if (!strcmp(k, "pull.octopus"))
+ return git_config_string(&pull_octopus, k, v);
+ else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
+ option_log = git_config_bool(k, v);
+ return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+ unsigned char *one)
+{
+ int i, nr_trees = 0;
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct tree_desc t[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 2;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.trivial_merges_only = 1;
+ opts.merge = 1;
+ trees[nr_trees] = parse_tree_indirect(common);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(one);
+ if (!trees[nr_trees++])
+ return -1;
+ opts.fn = threeway_merge;
+ cache_tree_free(&active_cache_tree);
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+ if (write_cache_as_tree(sha1, 0, NULL))
+ die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(const char *strategy, struct commit_list *common,
+ const char *head_arg)
+{
+ const char **args;
+ int i = 0, x = 0, ret;
+ struct commit_list *j;
+ struct strbuf buf = STRBUF_INIT;
+ int index_fd;
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+
+ index_fd = hold_locked_index(lock, 1);
+ refresh_cache(REFRESH_QUIET);
+ if (active_cache_changed &&
+ (write_cache(index_fd, active_cache, active_nr) ||
+ commit_locked_index(lock)))
+ return error("Unable to write index.");
+ rollback_lock_file(lock);
+
+ if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
+ int clean;
+ struct commit *result;
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ int index_fd;
+ struct commit_list *reversed = NULL;
+ struct merge_options o;
+
+ if (remoteheads->next) {
+ error("Not handling anything other than two heads merge.");
+ return 2;
+ }
+
+ init_merge_options(&o);
+ if (!strcmp(strategy, "subtree"))
+ o.subtree_shift = "";
+
+ for (x = 0; x < xopts_nr; x++) {
+ if (!strcmp(xopts[x], "ours"))
+ o.recursive_variant = MERGE_RECURSIVE_OURS;
+ else if (!strcmp(xopts[x], "theirs"))
+ o.recursive_variant = MERGE_RECURSIVE_THEIRS;
+ else if (!strcmp(xopts[x], "subtree"))
+ o.subtree_shift = "";
+ else if (!prefixcmp(xopts[x], "subtree="))
+ o.subtree_shift = xopts[x]+8;
+ else
+ die("Unknown option for merge-recursive: -X%s", xopts[x]);
+ }
+
+ o.branch1 = head_arg;
+ o.branch2 = remoteheads->item->util;
+
+ for (j = common; j; j = j->next)
+ commit_list_insert(j->item, &reversed);
+
+ index_fd = hold_locked_index(lock, 1);
+ clean = merge_recursive(&o, lookup_commit(head),
+ remoteheads->item, reversed, &result);
+ if (active_cache_changed &&
+ (write_cache(index_fd, active_cache, active_nr) ||
+ commit_locked_index(lock)))
+ die ("unable to write %s", get_index_file());
+ rollback_lock_file(lock);
+ return clean ? 0 : 1;
+ } else {
+ args = xmalloc((4 + xopts_nr + commit_list_count(common) +
+ commit_list_count(remoteheads)) * sizeof(char *));
+ strbuf_addf(&buf, "merge-%s", strategy);
+ args[i++] = buf.buf;
+ for (x = 0; x < xopts_nr; x++) {
+ char *s = xmalloc(strlen(xopts[x])+2+1);
+ strcpy(s, "--");
+ strcpy(s+2, xopts[x]);
+ args[i++] = s;
+ }
+ for (j = common; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = "--";
+ args[i++] = head_arg;
+ for (j = remoteheads; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i] = NULL;
+ ret = run_command_v_opt(args, RUN_GIT_CMD);
+ strbuf_release(&buf);
+ i = 1;
+ for (x = 0; x < xopts_nr; x++)
+ free((void *)args[i++]);
+ for (j = common; j; j = j->next)
+ free((void *)args[i++]);
+ i += 2;
+ for (j = remoteheads; j; j = j->next)
+ free((void *)args[i++]);
+ free(args);
+ discard_cache();
+ if (read_cache() < 0)
+ die("failed to read the cache");
+ resolve_undo_clear();
+ return ret;
+ }
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+ struct diff_options *opt, void *data)
+{
+ int *count = data;
+
+ (*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+ int i, ret = 0;
+
+ for (i = 0; i < active_nr; i++)
+ if (ce_stage(active_cache[i]))
+ ret++;
+
+ return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+ struct tree_desc t[MAX_UNPACK_TREES];
+ int i, fd, nr_trees = 0;
+ struct dir_struct dir;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ refresh_cache(REFRESH_QUIET);
+
+ fd = hold_locked_index(lock_file, 1);
+
+ memset(&trees, 0, sizeof(trees));
+ memset(&opts, 0, sizeof(opts));
+ memset(&t, 0, sizeof(t));
+ memset(&dir, 0, sizeof(dir));
+ dir.flags |= DIR_SHOW_IGNORED;
+ dir.exclude_per_dir = ".gitignore";
+ opts.dir = &dir;
+
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.merge = 1;
+ opts.fn = twoway_merge;
+ opts.msgs = get_porcelain_error_msgs();
+
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(remote);
+ if (!trees[nr_trees++])
+ return -1;
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+ return 0;
+}
+
+static void split_merge_strategies(const char *string, struct strategy **list,
+ int *nr, int *alloc)
+{
+ char *p, *q, *buf;
+
+ if (!string)
+ return;
+
+ buf = xstrdup(string);
+ q = buf;
+ for (;;) {
+ p = strchr(q, ' ');
+ if (!p) {
+ ALLOC_GROW(*list, *nr + 1, *alloc);
+ (*list)[(*nr)++].name = xstrdup(q);
+ free(buf);
+ return;
+ } else {
+ *p = '\0';
+ ALLOC_GROW(*list, *nr + 1, *alloc);
+ (*list)[(*nr)++].name = xstrdup(q);
+ q = ++p;
+ }
+ }
+}
+
+static void add_strategies(const char *string, unsigned attr)
+{
+ struct strategy *list = NULL;
+ int list_alloc = 0, list_nr = 0, i;
+
+ memset(&list, 0, sizeof(list));
+ split_merge_strategies(string, &list, &list_nr, &list_alloc);
+ if (list) {
+ for (i = 0; i < list_nr; i++)
+ append_strategy(get_strategy(list[i].name));
+ return;
+ }
+ for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+ if (all_strategy[i].attr & attr)
+ append_strategy(&all_strategy[i]);
+
+}
+
+static int merge_trivial(void)
+{
+ unsigned char result_tree[20], result_commit[20];
+ struct commit_list *parent = xmalloc(sizeof(*parent));
+
+ write_tree_trivial(result_tree);
+ printf("Wonderful.\n");
+ parent->item = lookup_commit(head);
+ parent->next = xmalloc(sizeof(*parent->next));
+ parent->next->item = remoteheads->item;
+ parent->next->next = NULL;
+ commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
+ finish(result_commit, "In-index merge");
+ drop_save();
+ return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+ unsigned char *result_tree,
+ const char *wt_strategy)
+{
+ struct commit_list *parents = NULL, *j;
+ struct strbuf buf = STRBUF_INIT;
+ unsigned char result_commit[20];
+
+ free_commit_list(common);
+ if (allow_fast_forward) {
+ parents = remoteheads;
+ commit_list_insert(lookup_commit(head), &parents);
+ parents = reduce_heads(parents);
+ } else {
+ struct commit_list **pptr = &parents;
+
+ pptr = &commit_list_insert(lookup_commit(head),
+ pptr)->next;
+ for (j = remoteheads; j; j = j->next)
+ pptr = &commit_list_insert(j->item, pptr)->next;
+ }
+ free_commit_list(remoteheads);
+ strbuf_addch(&merge_msg, '\n');
+ commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
+ strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
+ finish(result_commit, buf.buf);
+ strbuf_release(&buf);
+ drop_save();
+ return 0;
+}
+
+static int suggest_conflicts(void)
+{
+ FILE *fp;
+ int pos;
+
+ fp = fopen(git_path("MERGE_MSG"), "a");
+ if (!fp)
+ die_errno("Could not open '%s' for writing",
+ git_path("MERGE_MSG"));
+ fprintf(fp, "\nConflicts:\n");
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+
+ if (ce_stage(ce)) {
+ fprintf(fp, "\t%s\n", ce->name);
+ while (pos + 1 < active_nr &&
+ !strcmp(ce->name,
+ active_cache[pos + 1]->name))
+ pos++;
+ }
+ }
+ fclose(fp);
+ rerere(allow_rerere_auto);
+ printf("Automatic merge failed; "
+ "fix conflicts and then commit the result.\n");
+ return 1;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+ struct commit *second_token = NULL;
+ if (argc > 2) {
+ unsigned char second_sha1[20];
+
+ if (get_sha1(argv[1], second_sha1))
+ return NULL;
+ second_token = lookup_commit_reference_gently(second_sha1, 0);
+ if (!second_token)
+ die("'%s' is not a commit", argv[1]);
+ if (hashcmp(second_token->object.sha1, head))
+ return NULL;
+ }
+ return second_token;
+}
+
+static int evaluate_result(void)
+{
+ int cnt = 0;
+ struct rev_info rev;
+
+ /* Check how many files differ. */
+ init_revisions(&rev, "");
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.diffopt.output_format |=
+ DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = count_diff_files;
+ rev.diffopt.format_callback_data = &cnt;
+ run_diff_files(&rev, 0);
+
+ /*
+ * Check how many unmerged entries are
+ * there.
+ */
+ cnt += count_unmerged_entries();
+
+ return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+ unsigned char result_tree[20];
+ struct strbuf buf = STRBUF_INIT;
+ const char *head_arg;
+ int flag, head_invalid = 0, i;
+ int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+ struct commit_list *common = NULL;
+ const char *best_strategy = NULL, *wt_strategy = NULL;
+ struct commit_list **remotes = &remoteheads;
+
+ if (read_cache_unmerged()) {
+ die_resolve_conflict("merge");
+ }
+ if (file_exists(git_path("MERGE_HEAD"))) {
+ /*
+ * There is no unmerged entry, don't advise 'git
+ * add/rm <file>', just 'git commit'.
+ */
+ if (advice_resolve_conflict)
+ die("You have not concluded your merge (MERGE_HEAD exists).\n"
+ "Please, commit your changes before you can merge.");
+ else
+ die("You have not concluded your merge (MERGE_HEAD exists).");
+ }
+
+ resolve_undo_clear();
+ /*
+ * Check if we are _not_ on a detached HEAD, i.e. if there is a
+ * current branch.
+ */
+ branch = resolve_ref("HEAD", head, 0, &flag);
+ if (branch && !prefixcmp(branch, "refs/heads/"))
+ branch += 11;
+ if (is_null_sha1(head))
+ head_invalid = 1;
+
+ git_config(git_merge_config, NULL);
+
+ /* for color.ui */
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ argc = parse_options(argc, argv, prefix, builtin_merge_options,
+ builtin_merge_usage, 0);
+ if (verbosity < 0)
+ show_diffstat = 0;
+
+ if (squash) {
+ if (!allow_fast_forward)
+ die("You cannot combine --squash with --no-ff.");
+ option_commit = 0;
+ }
+
+ if (!allow_fast_forward && fast_forward_only)
+ die("You cannot combine --no-ff with --ff-only.");
+
+ if (!argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ /*
+ * 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 is the same as HEAD there instead.
+ * Traditional format never would have "-m" so it is an
+ * additional safety measure to check for it.
+ */
+
+ if (!have_message && is_old_style_invocation(argc, argv)) {
+ strbuf_addstr(&merge_msg, argv[0]);
+ head_arg = argv[1];
+ argv += 2;
+ argc -= 2;
+ } else if (head_invalid) {
+ struct object *remote_head;
+ /*
+ * 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 the same for "git pull".
+ */
+ if (argc != 1)
+ die("Can merge only exactly one commit into "
+ "empty head");
+ if (squash)
+ die("Squash commit into empty head not supported yet");
+ if (!allow_fast_forward)
+ die("Non-fast-forward commit does not make sense into "
+ "an empty head");
+ remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("%s - not something we can merge", argv[0]);
+ update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+ DIE_ON_ERR);
+ reset_hard(remote_head->sha1, 0);
+ return 0;
+ } else {
+ struct strbuf msg = STRBUF_INIT;
+
+ /* We are invoked directly as the first-class UI. */
+ head_arg = "HEAD";
+
+ /*
+ * All the rest are the commits being merged;
+ * prepare the standard merge summary message to
+ * be appended to the given message. If remote
+ * is invalid we will die later in the common
+ * codepath so we discard the error in this
+ * loop.
+ */
+ if (!have_message) {
+ for (i = 0; i < argc; i++)
+ merge_name(argv[i], &msg);
+ fmt_merge_msg(option_log, &msg, &merge_msg);
+ if (merge_msg.len)
+ strbuf_setlen(&merge_msg, merge_msg.len-1);
+ }
+ }
+
+ if (head_invalid || !argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ strbuf_addstr(&buf, "merge");
+ for (i = 0; i < argc; i++)
+ strbuf_addf(&buf, " %s", argv[i]);
+ setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+ strbuf_reset(&buf);
+
+ for (i = 0; i < argc; i++) {
+ struct object *o;
+ struct commit *commit;
+
+ o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+ if (!o)
+ die("%s - not something we can merge", argv[i]);
+ commit = lookup_commit(o->sha1);
+ commit->util = (void *)argv[i];
+ remotes = &commit_list_insert(commit, remotes)->next;
+
+ strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+ setenv(buf.buf, argv[i], 1);
+ strbuf_reset(&buf);
+ }
+
+ if (!use_strategies) {
+ if (!remoteheads->next)
+ add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+ else
+ add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+ }
+
+ for (i = 0; i < use_strategies_nr; i++) {
+ if (use_strategies[i]->attr & NO_FAST_FORWARD)
+ allow_fast_forward = 0;
+ if (use_strategies[i]->attr & NO_TRIVIAL)
+ allow_trivial = 0;
+ }
+
+ if (!remoteheads->next)
+ common = get_merge_bases(lookup_commit(head),
+ remoteheads->item, 1);
+ else {
+ struct commit_list *list = remoteheads;
+ commit_list_insert(lookup_commit(head), &list);
+ common = get_octopus_merge_bases(list);
+ free(list);
+ }
+
+ update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+ DIE_ON_ERR);
+
+ if (!common)
+ ; /* No common ancestors found. We need a real merge. */
+ else if (!remoteheads->next && !common->next &&
+ common->item == remoteheads->item) {
+ /*
+ * If head can reach all the merge then we are up to date.
+ * but first the most common case of merging one remote.
+ */
+ finish_up_to_date("Already up-to-date.");
+ return 0;
+ } else if (allow_fast_forward && !remoteheads->next &&
+ !common->next &&
+ !hashcmp(common->item->object.sha1, head)) {
+ /* Again the most common case of merging one remote. */
+ struct strbuf msg = STRBUF_INIT;
+ struct object *o;
+ char hex[41];
+
+ strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+ if (verbosity >= 0)
+ printf("Updating %s..%s\n",
+ hex,
+ find_unique_abbrev(remoteheads->item->object.sha1,
+ DEFAULT_ABBREV));
+ strbuf_addstr(&msg, "Fast-forward");
+ if (have_message)
+ strbuf_addstr(&msg,
+ " (no commit created; -m option ignored)");
+ o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+ 0, NULL, OBJ_COMMIT);
+ if (!o)
+ return 1;
+
+ if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+ return 1;
+
+ finish(o->sha1, msg.buf);
+ drop_save();
+ return 0;
+ } else if (!remoteheads->next && common->next)
+ ;
+ /*
+ * We are not doing octopus and not fast-forward. Need
+ * a real merge.
+ */
+ else if (!remoteheads->next && !common->next && option_commit) {
+ /*
+ * We are not doing octopus, not fast-forward, and have
+ * only one common.
+ */
+ refresh_cache(REFRESH_QUIET);
+ if (allow_trivial && !fast_forward_only) {
+ /* See if it is really trivial. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ printf("Trying really trivial in-index merge...\n");
+ if (!read_tree_trivial(common->item->object.sha1,
+ head, remoteheads->item->object.sha1))
+ return merge_trivial();
+ printf("Nope.\n");
+ }
+ } else {
+ /*
+ * An octopus. If we can reach all the remote we are up
+ * to date.
+ */
+ int up_to_date = 1;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next) {
+ struct commit_list *common_one;
+
+ /*
+ * Here we *have* to calculate the individual
+ * merge_bases again, otherwise "git merge HEAD^
+ * HEAD^^" would be missed.
+ */
+ common_one = get_merge_bases(lookup_commit(head),
+ j->item, 1);
+ if (hashcmp(common_one->item->object.sha1,
+ j->item->object.sha1)) {
+ up_to_date = 0;
+ break;
+ }
+ }
+ if (up_to_date) {
+ finish_up_to_date("Already up-to-date. Yeeah!");
+ return 0;
+ }
+ }
+
+ if (fast_forward_only)
+ die("Not possible to fast-forward, aborting.");
+
+ /* We are going to make a new commit. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+ /*
+ * At this point, we need a real merge. No matter what strategy
+ * we use, it would operate on the index, possibly affecting the
+ * working tree, and when resolved cleanly, have the desired
+ * tree in the index -- this means that the index must be in
+ * sync with the head commit. The strategies are responsible
+ * to ensure this.
+ */
+ if (use_strategies_nr != 1) {
+ /*
+ * Stash away the local changes so that we can try more
+ * than one.
+ */
+ save_state();
+ } else {
+ memcpy(stash, null_sha1, 20);
+ }
+
+ for (i = 0; i < use_strategies_nr; i++) {
+ int ret;
+ if (i) {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ }
+ if (use_strategies_nr != 1)
+ printf("Trying merge strategy %s...\n",
+ use_strategies[i]->name);
+ /*
+ * Remember which strategy left the state in the working
+ * tree.
+ */
+ wt_strategy = use_strategies[i]->name;
+
+ ret = try_merge_strategy(use_strategies[i]->name,
+ common, head_arg);
+ if (!option_commit && !ret) {
+ merge_was_ok = 1;
+ /*
+ * This is necessary here just to avoid writing
+ * the tree, but later we will *not* exit with
+ * status code 1 because merge_was_ok is set.
+ */
+ ret = 1;
+ }
+
+ if (ret) {
+ /*
+ * The backend exits with 1 when conflicts are
+ * left to be resolved, with 2 when it does not
+ * handle the given merge at all.
+ */
+ if (ret == 1) {
+ int cnt = evaluate_result();
+
+ if (best_cnt <= 0 || cnt <= best_cnt) {
+ best_strategy = use_strategies[i]->name;
+ best_cnt = cnt;
+ }
+ }
+ if (merge_was_ok)
+ break;
+ else
+ continue;
+ }
+
+ /* Automerge succeeded. */
+ write_tree_trivial(result_tree);
+ automerge_was_ok = 1;
+ break;
+ }
+
+ /*
+ * If we have a resulting tree, that means the strategy module
+ * auto resolved the merge cleanly.
+ */
+ if (automerge_was_ok)
+ return finish_automerge(common, result_tree, wt_strategy);
+
+ /*
+ * Pick the result from the best strategy and have the user fix
+ * it up.
+ */
+ if (!best_strategy) {
+ restore_state();
+ if (use_strategies_nr > 1)
+ fprintf(stderr,
+ "No merge strategy handled the merge.\n");
+ else
+ fprintf(stderr, "Merge with strategy %s failed.\n",
+ use_strategies[0]->name);
+ return 2;
+ } else if (best_strategy == wt_strategy)
+ ; /* We already have its result in the working tree. */
+ else {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ printf("Using the %s to prepare resolving by hand.\n",
+ best_strategy);
+ try_merge_strategy(best_strategy, common, head_arg);
+ }
+
+ if (squash)
+ finish(NULL, NULL);
+ else {
+ int fd;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next)
+ strbuf_addf(&buf, "%s\n",
+ sha1_to_hex(j->item->object.sha1));
+ fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno("Could not open '%s' for writing",
+ git_path("MERGE_HEAD"));
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die_errno("Could not write to '%s'", git_path("MERGE_HEAD"));
+ close(fd);
+ strbuf_addch(&merge_msg, '\n');
+ fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno("Could not open '%s' for writing",
+ git_path("MERGE_MSG"));
+ if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+ merge_msg.len)
+ die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
+ close(fd);
+ fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0)
+ die_errno("Could not open '%s' for writing",
+ git_path("MERGE_MODE"));
+ strbuf_reset(&buf);
+ if (!allow_fast_forward)
+ strbuf_addf(&buf, "no-ff");
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die_errno("Could not write to '%s'", git_path("MERGE_MODE"));
+ close(fd);
+ }
+
+ if (merge_was_ok) {
+ fprintf(stderr, "Automatic merge went well; "
+ "stopped before committing as requested\n");
+ return 0;
+ } else
+ return suggest_conflicts();
+}
--- /dev/null
+#include "cache.h"
+#include "tag.h"
+#include "exec_cmd.h"
+
+/*
+ * A signature file has a very simple fixed format: four lines
+ * of "object <sha1>" + "type <typename>" + "tag <tagname>" +
+ * "tagger <committer>", followed by a blank line, a free-form tag
+ * message and a signature block that git itself doesn't care about,
+ * but that can be verified with gpg or similar.
+ *
+ * The first four lines are guaranteed to be at least 83 bytes:
+ * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
+ * shortest possible type-line, "tag .\n" at 6 bytes is the shortest
+ * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is
+ * the shortest possible tagger-line.
+ */
+
+/*
+ * We refuse to tag something we can't verify. Just because.
+ */
+static int verify_object(const unsigned char *sha1, const char *expected_type)
+{
+ int ret = -1;
+ enum object_type type;
+ unsigned long size;
+ const unsigned char *repl;
+ void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl);
+
+ if (buffer) {
+ if (type == type_from_string(expected_type))
+ ret = check_sha1_signature(repl, buffer, size, expected_type);
+ free(buffer);
+ }
+ return ret;
+}
+
+#ifdef NO_C99_FORMAT
+#define PD_FMT "%d"
+#else
+#define PD_FMT "%td"
+#endif
+
+static int verify_tag(char *buffer, unsigned long size)
+{
+ int typelen;
+ char type[20];
+ unsigned char sha1[20];
+ const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb;
+ size_t len;
+
+ if (size < 84)
+ return error("wanna fool me ? you obviously got the size wrong !");
+
+ buffer[size] = 0;
+
+ /* Verify object line */
+ object = buffer;
+ if (memcmp(object, "object ", 7))
+ return error("char%d: does not start with \"object \"", 0);
+
+ if (get_sha1_hex(object + 7, sha1))
+ return error("char%d: could not get SHA1 hash", 7);
+
+ /* Verify type line */
+ type_line = object + 48;
+ if (memcmp(type_line - 1, "\ntype ", 6))
+ return error("char%d: could not find \"\\ntype \"", 47);
+
+ /* Verify tag-line */
+ tag_line = strchr(type_line, '\n');
+ if (!tag_line)
+ return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer);
+ tag_line++;
+ if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
+ return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer);
+
+ /* Get the actual type */
+ typelen = tag_line - type_line - strlen("type \n");
+ if (typelen >= sizeof(type))
+ return error("char" PD_FMT ": type too long", type_line+5 - buffer);
+
+ memcpy(type, type_line+5, typelen);
+ type[typelen] = 0;
+
+ /* Verify that the object matches */
+ if (verify_object(sha1, type))
+ return error("char%d: could not verify object %s", 7, sha1_to_hex(sha1));
+
+ /* Verify the tag-name: we don't allow control characters or spaces in it */
+ tag_line += 4;
+ for (;;) {
+ unsigned char c = *tag_line++;
+ if (c == '\n')
+ break;
+ if (c > ' ')
+ continue;
+ return error("char" PD_FMT ": could not verify tag name", tag_line - buffer);
+ }
+
+ /* Verify the tagger line */
+ tagger_line = tag_line;
+
+ if (memcmp(tagger_line, "tagger ", 7))
+ return error("char" PD_FMT ": could not find \"tagger \"",
+ tagger_line - buffer);
+
+ /*
+ * Check for correct form for name and email
+ * i.e. " <" followed by "> " on _this_ line
+ * No angle brackets within the name or email address fields.
+ * No spaces within the email address field.
+ */
+ tagger_line += 7;
+ if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) ||
+ strpbrk(tagger_line, "<>\n") != lb+1 ||
+ strpbrk(lb+2, "><\n ") != rb)
+ return error("char" PD_FMT ": malformed tagger field",
+ tagger_line - buffer);
+
+ /* Check for author name, at least one character, space is acceptable */
+ if (lb == tagger_line)
+ return error("char" PD_FMT ": missing tagger name",
+ tagger_line - buffer);
+
+ /* timestamp, 1 or more digits followed by space */
+ tagger_line = rb + 2;
+ if (!(len = strspn(tagger_line, "0123456789")))
+ return error("char" PD_FMT ": missing tag timestamp",
+ tagger_line - buffer);
+ tagger_line += len;
+ if (*tagger_line != ' ')
+ return error("char" PD_FMT ": malformed tag timestamp",
+ tagger_line - buffer);
+ tagger_line++;
+
+ /* timezone, 5 digits [+-]hhmm, max. 1400 */
+ if (!((tagger_line[0] == '+' || tagger_line[0] == '-') &&
+ strspn(tagger_line+1, "0123456789") == 4 &&
+ tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400))
+ return error("char" PD_FMT ": malformed tag timezone",
+ tagger_line - buffer);
+ tagger_line += 6;
+
+ /* Verify the blank line separating the header from the body */
+ if (*tagger_line != '\n')
+ return error("char" PD_FMT ": trailing garbage in tag header",
+ tagger_line - buffer);
+
+ /* The actual stuff afterwards we don't care about.. */
+ return 0;
+}
+
+#undef PD_FMT
+
+int cmd_mktag(int argc, const char **argv, const char *prefix)
+{
+ struct strbuf buf = STRBUF_INIT;
+ unsigned char result_sha1[20];
+
+ if (argc != 1)
+ usage("git mktag < signaturefile");
+
+ if (strbuf_read(&buf, 0, 4096) < 0) {
+ die_errno("could not read from stdin");
+ }
+
+ /* Verify it for some basic sanity: it needs to start with
+ "object <sha1>\ntype\ntagger " */
+ if (verify_tag(buf.buf, buf.len) < 0)
+ die("invalid tag signature file");
+
+ if (write_sha1_file(buf.buf, buf.len, tag_type, result_sha1) < 0)
+ die("unable to write tag file");
+
+ strbuf_release(&buf);
+ printf("%s\n", sha1_to_hex(result_sha1));
+ return 0;
+}
--- /dev/null
+/*
+ * GIT - the stupid content tracker
+ *
+ * Copyright (c) Junio C Hamano, 2006, 2009
+ */
+#include "builtin.h"
+#include "quote.h"
+#include "tree.h"
+#include "parse-options.h"
+
+static struct treeent {
+ unsigned mode;
+ unsigned char sha1[20];
+ int len;
+ char name[FLEX_ARRAY];
+} **entries;
+static int alloc, used;
+
+static void append_to_tree(unsigned mode, unsigned char *sha1, char *path)
+{
+ struct treeent *ent;
+ int len = strlen(path);
+ if (strchr(path, '/'))
+ die("path %s contains slash", path);
+
+ if (alloc <= used) {
+ alloc = alloc_nr(used);
+ entries = xrealloc(entries, sizeof(*entries) * alloc);
+ }
+ ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1);
+ ent->mode = mode;
+ ent->len = len;
+ hashcpy(ent->sha1, sha1);
+ memcpy(ent->name, path, len+1);
+}
+
+static int ent_compare(const void *a_, const void *b_)
+{
+ struct treeent *a = *(struct treeent **)a_;
+ struct treeent *b = *(struct treeent **)b_;
+ return base_name_compare(a->name, a->len, a->mode,
+ b->name, b->len, b->mode);
+}
+
+static void write_tree(unsigned char *sha1)
+{
+ struct strbuf buf;
+ size_t size;
+ int i;
+
+ qsort(entries, used, sizeof(*entries), ent_compare);
+ for (size = i = 0; i < used; i++)
+ size += 32 + entries[i]->len;
+
+ strbuf_init(&buf, size);
+ for (i = 0; i < used; i++) {
+ struct treeent *ent = entries[i];
+ strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0');
+ strbuf_add(&buf, ent->sha1, 20);
+ }
+
+ write_sha1_file(buf.buf, buf.len, tree_type, sha1);
+}
+
+static const char *mktree_usage[] = {
+ "git mktree [-z] [--missing] [--batch]",
+ NULL
+};
+
+static void mktree_line(char *buf, size_t len, int line_termination, int allow_missing)
+{
+ char *ptr, *ntr;
+ unsigned mode;
+ enum object_type mode_type; /* object type derived from mode */
+ enum object_type obj_type; /* object type derived from sha */
+ char *path;
+ unsigned char sha1[20];
+
+ ptr = buf;
+ /*
+ * Read non-recursive ls-tree output format:
+ * mode SP type SP sha1 TAB name
+ */
+ mode = strtoul(ptr, &ntr, 8);
+ if (ptr == ntr || !ntr || *ntr != ' ')
+ die("input format error: %s", buf);
+ ptr = ntr + 1; /* type */
+ ntr = strchr(ptr, ' ');
+ if (!ntr || buf + len <= ntr + 40 ||
+ ntr[41] != '\t' ||
+ get_sha1_hex(ntr + 1, sha1))
+ die("input format error: %s", buf);
+
+ /* It is perfectly normal if we do not have a commit from a submodule */
+ if (S_ISGITLINK(mode))
+ allow_missing = 1;
+
+
+ *ntr++ = 0; /* now at the beginning of SHA1 */
+
+ path = ntr + 41; /* at the beginning of name */
+ if (line_termination && path[0] == '"') {
+ struct strbuf p_uq = STRBUF_INIT;
+ if (unquote_c_style(&p_uq, path, NULL))
+ die("invalid quoting");
+ path = strbuf_detach(&p_uq, NULL);
+ }
+
+ /*
+ * Object type is redundantly derivable three ways.
+ * These should all agree.
+ */
+ mode_type = object_type(mode);
+ if (mode_type != type_from_string(ptr)) {
+ die("entry '%s' object type (%s) doesn't match mode type (%s)",
+ path, ptr, typename(mode_type));
+ }
+
+ /* Check the type of object identified by sha1 */
+ obj_type = sha1_object_info(sha1, NULL);
+ if (obj_type < 0) {
+ if (allow_missing) {
+ ; /* no problem - missing objects are presumed to be of the right type */
+ } else {
+ die("entry '%s' object %s is unavailable", path, sha1_to_hex(sha1));
+ }
+ } else {
+ if (obj_type != mode_type) {
+ /*
+ * The object exists but is of the wrong type.
+ * This is a problem regardless of allow_missing
+ * because the new tree entry will never be correct.
+ */
+ die("entry '%s' object %s is a %s but specified type was (%s)",
+ path, sha1_to_hex(sha1), typename(obj_type), typename(mode_type));
+ }
+ }
+
+ append_to_tree(mode, sha1, path);
+}
+
+int cmd_mktree(int ac, const char **av, const char *prefix)
+{
+ struct strbuf sb = STRBUF_INIT;
+ unsigned char sha1[20];
+ int line_termination = '\n';
+ int allow_missing = 0;
+ int is_batch_mode = 0;
+ int got_eof = 0;
+
+ const struct option option[] = {
+ OPT_SET_INT('z', NULL, &line_termination, "input is NUL terminated", '\0'),
+ OPT_SET_INT( 0 , "missing", &allow_missing, "allow missing objects", 1),
+ OPT_SET_INT( 0 , "batch", &is_batch_mode, "allow creation of more than one tree", 1),
+ OPT_END()
+ };
+
+ ac = parse_options(ac, av, prefix, option, mktree_usage, 0);
+
+ while (!got_eof) {
+ while (1) {
+ if (strbuf_getline(&sb, stdin, line_termination) == EOF) {
+ got_eof = 1;
+ break;
+ }
+ if (sb.buf[0] == '\0') {
+ /* empty lines denote tree boundaries in batch mode */
+ if (is_batch_mode)
+ break;
+ die("input format error: (blank line only valid in batch mode)");
+ }
+ mktree_line(sb.buf, sb.len, line_termination, allow_missing);
+ }
+ if (is_batch_mode && got_eof && used < 1) {
+ /*
+ * Execution gets here if the last tree entry is terminated with a
+ * new-line. The final new-line has been made optional to be
+ * consistent with the original non-batch behaviour of mktree.
+ */
+ ; /* skip creating an empty tree */
+ } else {
+ write_tree(sha1);
+ puts(sha1_to_hex(sha1));
+ fflush(stdout);
+ }
+ used=0; /* reset tree entry buffer for re-use in batch mode */
+ }
+ strbuf_release(&sb);
+ exit(0);
+}
--- /dev/null
+/*
+ * "git mv" builtin command
+ *
+ * Copyright (C) 2006 Johannes Schindelin
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "cache-tree.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+static const char * const builtin_mv_usage[] = {
+ "git mv [options] <source>... <destination>",
+ NULL
+};
+
+static const char **copy_pathspec(const char *prefix, const char **pathspec,
+ int count, int base_name)
+{
+ int i;
+ const char **result = xmalloc((count + 1) * sizeof(const char *));
+ memcpy(result, pathspec, count * sizeof(const char *));
+ result[count] = NULL;
+ for (i = 0; i < count; i++) {
+ int length = strlen(result[i]);
+ int to_copy = length;
+ while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
+ to_copy--;
+ if (to_copy != length || base_name) {
+ char *it = xmemdupz(result[i], to_copy);
+ result[i] = base_name ? strdup(basename(it)) : it;
+ }
+ }
+ return get_pathspec(prefix, result);
+}
+
+static const char *add_slash(const char *path)
+{
+ int len = strlen(path);
+ if (path[len - 1] != '/') {
+ char *with_slash = xmalloc(len + 2);
+ memcpy(with_slash, path, len);
+ with_slash[len++] = '/';
+ with_slash[len] = 0;
+ return with_slash;
+ }
+ return path;
+}
+
+static struct lock_file lock_file;
+
+int cmd_mv(int argc, const char **argv, const char *prefix)
+{
+ int i, newfd;
+ int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
+ struct option builtin_mv_options[] = {
+ OPT__DRY_RUN(&show_only),
+ OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"),
+ OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
+ OPT_END(),
+ };
+ const char **source, **destination, **dest_path;
+ enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
+ struct stat st;
+ struct string_list src_for_dst = {NULL, 0, 0, 0};
+
+ git_config(git_default_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, builtin_mv_options,
+ builtin_mv_usage, 0);
+ if (--argc < 1)
+ usage_with_options(builtin_mv_usage, builtin_mv_options);
+
+ newfd = hold_locked_index(&lock_file, 1);
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ source = copy_pathspec(prefix, argv, argc, 0);
+ modes = xcalloc(argc, sizeof(enum update_mode));
+ dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
+
+ if (dest_path[0][0] == '\0')
+ /* special case: "." was normalized to "" */
+ destination = copy_pathspec(dest_path[0], argv, argc, 1);
+ else if (!lstat(dest_path[0], &st) &&
+ S_ISDIR(st.st_mode)) {
+ dest_path[0] = add_slash(dest_path[0]);
+ destination = copy_pathspec(dest_path[0], argv, argc, 1);
+ } else {
+ if (argc != 1)
+ usage_with_options(builtin_mv_usage, builtin_mv_options);
+ destination = dest_path;
+ }
+
+ /* Checking */
+ for (i = 0; i < argc; i++) {
+ const char *src = source[i], *dst = destination[i];
+ int length, src_is_dir;
+ const char *bad = NULL;
+
+ if (show_only)
+ printf("Checking rename of '%s' to '%s'\n", src, dst);
+
+ length = strlen(src);
+ if (lstat(src, &st) < 0)
+ bad = "bad source";
+ else if (!strncmp(src, dst, length) &&
+ (dst[length] == 0 || dst[length] == '/')) {
+ bad = "can not move directory into itself";
+ } else if ((src_is_dir = S_ISDIR(st.st_mode))
+ && lstat(dst, &st) == 0)
+ bad = "cannot move directory over file";
+ else if (src_is_dir) {
+ const char *src_w_slash = add_slash(src);
+ int len_w_slash = length + 1;
+ int first, last;
+
+ modes[i] = WORKING_DIRECTORY;
+
+ first = cache_name_pos(src_w_slash, len_w_slash);
+ if (first >= 0)
+ die ("Huh? %.*s is in index?",
+ len_w_slash, src_w_slash);
+
+ first = -1 - first;
+ for (last = first; last < active_nr; last++) {
+ const char *path = active_cache[last]->name;
+ if (strncmp(path, src_w_slash, len_w_slash))
+ break;
+ }
+ free((char *)src_w_slash);
+
+ if (last - first < 1)
+ bad = "source directory is empty";
+ else {
+ int j, dst_len;
+
+ if (last - first > 0) {
+ source = xrealloc(source,
+ (argc + last - first)
+ * sizeof(char *));
+ destination = xrealloc(destination,
+ (argc + last - first)
+ * sizeof(char *));
+ modes = xrealloc(modes,
+ (argc + last - first)
+ * sizeof(enum update_mode));
+ }
+
+ dst = add_slash(dst);
+ dst_len = strlen(dst);
+
+ for (j = 0; j < last - first; j++) {
+ const char *path =
+ active_cache[first + j]->name;
+ source[argc + j] = path;
+ destination[argc + j] =
+ prefix_path(dst, dst_len,
+ path + length + 1);
+ modes[argc + j] = INDEX;
+ }
+ argc += last - first;
+ }
+ } else if (cache_name_pos(src, length) < 0)
+ bad = "not under version control";
+ else if (lstat(dst, &st) == 0) {
+ bad = "destination exists";
+ if (force) {
+ /*
+ * only files can overwrite each other:
+ * check both source and destination
+ */
+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
+ warning("%s; will overwrite!", bad);
+ bad = NULL;
+ } else
+ bad = "Cannot overwrite";
+ }
+ } else if (string_list_has_string(&src_for_dst, dst))
+ bad = "multiple sources for the same target";
+ else
+ string_list_insert(dst, &src_for_dst);
+
+ if (bad) {
+ if (ignore_errors) {
+ if (--argc > 0) {
+ memmove(source + i, source + i + 1,
+ (argc - i) * sizeof(char *));
+ memmove(destination + i,
+ destination + i + 1,
+ (argc - i) * sizeof(char *));
+ i--;
+ }
+ } else
+ die ("%s, source=%s, destination=%s",
+ bad, src, dst);
+ }
+ }
+
+ for (i = 0; i < argc; i++) {
+ const char *src = source[i], *dst = destination[i];
+ enum update_mode mode = modes[i];
+ int pos;
+ if (show_only || verbose)
+ printf("Renaming %s to %s\n", src, dst);
+ if (!show_only && mode != INDEX &&
+ rename(src, dst) < 0 && !ignore_errors)
+ die_errno ("renaming '%s' failed", src);
+
+ if (mode == WORKING_DIRECTORY)
+ continue;
+
+ pos = cache_name_pos(src, strlen(src));
+ assert(pos >= 0);
+ if (!show_only)
+ rename_cache_entry_at(pos, dst);
+ }
+
+ if (active_cache_changed) {
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(&lock_file))
+ die("Unable to write new index file");
+ }
+
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+#include "parse-options.h"
+
+#define CUTOFF_DATE_SLOP 86400 /* one day */
+
+typedef struct rev_name {
+ const char *tip_name;
+ int generation;
+ int distance;
+} rev_name;
+
+static long cutoff = LONG_MAX;
+
+/* How many generations are maximally preferred over _one_ merge traversal? */
+#define MERGE_TRAVERSAL_WEIGHT 65535
+
+static void name_rev(struct commit *commit,
+ const char *tip_name, int generation, int distance,
+ int deref)
+{
+ struct rev_name *name = (struct rev_name *)commit->util;
+ struct commit_list *parents;
+ int parent_number = 1;
+
+ if (!commit->object.parsed)
+ parse_commit(commit);
+
+ if (commit->date < cutoff)
+ return;
+
+ if (deref) {
+ char *new_name = xmalloc(strlen(tip_name)+3);
+ strcpy(new_name, tip_name);
+ strcat(new_name, "^0");
+ tip_name = new_name;
+
+ if (generation)
+ die("generation: %d, but deref?", generation);
+ }
+
+ if (name == NULL) {
+ name = xmalloc(sizeof(rev_name));
+ commit->util = name;
+ goto copy_data;
+ } else if (name->distance > distance) {
+copy_data:
+ name->tip_name = tip_name;
+ name->generation = generation;
+ name->distance = distance;
+ } else
+ return;
+
+ for (parents = commit->parents;
+ parents;
+ parents = parents->next, parent_number++) {
+ if (parent_number > 1) {
+ int len = strlen(tip_name);
+ char *new_name = xmalloc(len +
+ 1 + decimal_length(generation) + /* ~<n> */
+ 1 + 2 + /* ^NN */
+ 1);
+
+ if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
+ len -= 2;
+ if (generation > 0)
+ sprintf(new_name, "%.*s~%d^%d", len, tip_name,
+ generation, parent_number);
+ else
+ sprintf(new_name, "%.*s^%d", len, tip_name,
+ parent_number);
+
+ name_rev(parents->item, new_name, 0,
+ distance + MERGE_TRAVERSAL_WEIGHT, 0);
+ } else {
+ name_rev(parents->item, tip_name, generation + 1,
+ distance + 1, 0);
+ }
+ }
+}
+
+struct name_ref_data {
+ int tags_only;
+ int name_only;
+ const char *ref_filter;
+};
+
+static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
+{
+ struct object *o = parse_object(sha1);
+ struct name_ref_data *data = cb_data;
+ int deref = 0;
+
+ if (data->tags_only && prefixcmp(path, "refs/tags/"))
+ return 0;
+
+ if (data->ref_filter && fnmatch(data->ref_filter, path, 0))
+ return 0;
+
+ while (o && o->type == OBJ_TAG) {
+ struct tag *t = (struct tag *) o;
+ if (!t->tagged)
+ break; /* broken repository */
+ o = parse_object(t->tagged->sha1);
+ deref = 1;
+ }
+ if (o && o->type == OBJ_COMMIT) {
+ struct commit *commit = (struct commit *)o;
+
+ if (!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;
+
+ name_rev(commit, xstrdup(path), 0, 0, deref);
+ }
+ return 0;
+}
+
+/* returns a static buffer */
+static const char *get_rev_name(const struct object *o)
+{
+ static char buffer[1024];
+ struct rev_name *n;
+ struct commit *c;
+
+ if (o->type != OBJ_COMMIT)
+ return NULL;
+ c = (struct commit *) o;
+ n = c->util;
+ if (!n)
+ return NULL;
+
+ if (!n->generation)
+ return n->tip_name;
+ else {
+ int len = strlen(n->tip_name);
+ if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
+ len -= 2;
+ snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name,
+ n->generation);
+
+ return buffer;
+ }
+}
+
+static void show_name(const struct object *obj,
+ const char *caller_name,
+ int always, int allow_undefined, int name_only)
+{
+ const char *name;
+ const unsigned char *sha1 = obj->sha1;
+
+ if (!name_only)
+ printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1));
+ name = get_rev_name(obj);
+ if (name)
+ printf("%s\n", name);
+ else if (allow_undefined)
+ printf("undefined\n");
+ else if (always)
+ printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ else
+ die("cannot describe '%s'", sha1_to_hex(sha1));
+}
+
+static char const * const name_rev_usage[] = {
+ "git name-rev [options] ( --all | --stdin | <commit>... )",
+ NULL
+};
+
+static void name_rev_line(char *p, struct name_ref_data *data)
+{
+ int forty = 0;
+ char *p_start;
+ for (p_start = p; *p; p++) {
+#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
+ if (!ishex(*p))
+ forty = 0;
+ else if (++forty == 40 &&
+ !ishex(*(p+1))) {
+ unsigned char sha1[40];
+ const char *name = NULL;
+ char c = *(p+1);
+ int p_len = p - p_start + 1;
+
+ forty = 0;
+
+ *(p+1) = 0;
+ if (!get_sha1(p - 39, sha1)) {
+ struct object *o =
+ lookup_object(sha1);
+ if (o)
+ name = get_rev_name(o);
+ }
+ *(p+1) = c;
+
+ if (!name)
+ continue;
+
+ if (data->name_only)
+ printf("%.*s%s", p_len - 40, p_start, name);
+ else
+ printf("%.*s (%s)", p_len, p_start, name);
+ p_start = p + 1;
+ }
+ }
+
+ /* flush */
+ if (p_start != p)
+ fwrite(p_start, p - p_start, 1, stdout);
+}
+
+int cmd_name_rev(int argc, const char **argv, const char *prefix)
+{
+ struct object_array revs = { 0, 0, NULL };
+ int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0;
+ struct name_ref_data data = { 0, 0, NULL };
+ struct option opts[] = {
+ OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"),
+ OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"),
+ OPT_STRING(0, "refs", &data.ref_filter, "pattern",
+ "only use refs matching <pattern>"),
+ OPT_GROUP(""),
+ OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"),
+ OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"),
+ OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"),
+ OPT_BOOLEAN(0, "always", &always,
+ "show abbreviated commit object as fallback"),
+ OPT_END(),
+ };
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0);
+ if (!!all + !!transform_stdin + !!argc > 1) {
+ error("Specify either a list, or --all, not both!");
+ usage_with_options(name_rev_usage, opts);
+ }
+ if (all || transform_stdin)
+ cutoff = 0;
+
+ for (; argc; argc--, argv++) {
+ unsigned char sha1[20];
+ struct object *o;
+ struct commit *commit;
+
+ if (get_sha1(*argv, sha1)) {
+ fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
+ *argv);
+ continue;
+ }
+
+ o = deref_tag(parse_object(sha1), *argv, 0);
+ if (!o || o->type != OBJ_COMMIT) {
+ fprintf(stderr, "Could not get commit for %s. Skipping.\n",
+ *argv);
+ continue;
+ }
+
+ commit = (struct commit *)o;
+ if (cutoff > commit->date)
+ cutoff = commit->date;
+ add_object_array((struct object *)commit, *argv, &revs);
+ }
+
+ if (cutoff)
+ cutoff = cutoff - CUTOFF_DATE_SLOP;
+ for_each_ref(name_ref, &data);
+
+ if (transform_stdin) {
+ char buffer[2048];
+
+ while (!feof(stdin)) {
+ char *p = fgets(buffer, sizeof(buffer), stdin);
+ if (!p)
+ break;
+ name_rev_line(p, &data);
+ }
+ } else if (all) {
+ int i, max;
+
+ max = get_max_object_index();
+ for (i = 0; i < max; i++) {
+ struct object *obj = get_indexed_object(i);
+ if (!obj)
+ continue;
+ show_name(obj, NULL,
+ always, allow_undefined, data.name_only);
+ }
+ } else {
+ int i;
+ for (i = 0; i < revs.nr; i++)
+ show_name(revs.objects[i].item, revs.objects[i].name,
+ always, allow_undefined, data.name_only);
+ }
+
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "attr.h"
+#include "object.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
+#include "delta.h"
+#include "pack.h"
+#include "pack-revindex.h"
+#include "csum-file.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "progress.h"
+#include "refs.h"
+
+#ifndef NO_PTHREADS
+#include "thread-utils.h"
+#include <pthread.h>
+#endif
+
+static const char pack_usage[] =
+ "git pack-objects [{ -q | --progress | --all-progress }]\n"
+ " [--all-progress-implied]\n"
+ " [--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"
+ " [--threads=N] [--non-empty] [--revs [--unpacked | --all]*]\n"
+ " [--reflog] [--stdout | base-name] [--include-tag]\n"
+ " [--keep-unreachable | --unpack-unreachable \n"
+ " [<ref-list | <object-list]";
+
+struct object_entry {
+ struct pack_idx_entry idx;
+ unsigned long size; /* uncompressed size */
+ struct packed_git *in_pack; /* already in pack */
+ off_t in_pack_offset;
+ struct object_entry *delta; /* delta base object */
+ struct object_entry *delta_child; /* deltified objects who bases me */
+ struct object_entry *delta_sibling; /* other deltified objects who
+ * uses the same base as me
+ */
+ void *delta_data; /* cached delta (uncompressed) */
+ unsigned long delta_size; /* delta data size (uncompressed) */
+ unsigned long z_delta_size; /* delta data size (compressed) */
+ 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 object to delta
+ * objects against.
+ */
+ unsigned char no_try_delta;
+};
+
+/*
+ * Objects we are going to pack are collected in objects array (dynamically
+ * expanded). nr_objects & nr_alloc controls this array. They are stored
+ * in the order we see -- typically rev-list --objects order that gives us
+ * nice "minimum seek" order.
+ */
+static struct object_entry *objects;
+static struct pack_idx_entry **written_list;
+static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
+
+static int non_empty;
+static int reuse_delta = 1, reuse_object = 1;
+static int keep_unreachable, unpack_unreachable, include_tag;
+static int local;
+static int incremental;
+static int ignore_packed_keep;
+static int allow_ofs_delta;
+static const char *base_name;
+static int progress = 1;
+static int window = 10;
+static unsigned long pack_size_limit, pack_size_limit_cfg;
+static int depth = 50;
+static int delta_search_threads;
+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 = 256 * 1024 * 1024;
+static unsigned long cache_max_small_delta_size = 1000;
+
+static unsigned long window_memory_limit = 0;
+
+/*
+ * The object names in objects array are hashed with this hashtable,
+ * to help looking up the entry by object name.
+ * This hashtable is built after all the objects are seen.
+ */
+static int *object_ix;
+static int object_ix_hashsz;
+
+/*
+ * stats
+ */
+static uint32_t written, written_delta;
+static uint32_t reused, reused_delta;
+
+
+static void *get_delta(struct object_entry *entry)
+{
+ unsigned long size, base_size, delta_size;
+ void *buf, *base_buf, *delta_buf;
+ enum object_type type;
+
+ buf = read_sha1_file(entry->idx.sha1, &type, &size);
+ if (!buf)
+ die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+ base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size);
+ if (!base_buf)
+ die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1));
+ delta_buf = diff_delta(base_buf, base_size,
+ buf, size, &delta_size, 0);
+ if (!delta_buf || delta_size != entry->delta_size)
+ die("delta size changed");
+ free(buf);
+ free(base_buf);
+ return delta_buf;
+}
+
+static unsigned long do_compress(void **pptr, unsigned long size)
+{
+ z_stream stream;
+ void *in, *out;
+ unsigned long maxsize;
+
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, pack_compression_level);
+ maxsize = deflateBound(&stream, size);
+
+ in = *pptr;
+ out = xmalloc(maxsize);
+ *pptr = out;
+
+ 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);
+
+ free(in);
+ return stream.total_out;
+}
+
+/*
+ * we are going to reuse the existing object data as is. make
+ * sure it is not corrupt.
+ */
+static int check_pack_inflate(struct packed_git *p,
+ struct pack_window **w_curs,
+ off_t offset,
+ off_t len,
+ unsigned long expect)
+{
+ z_stream stream;
+ unsigned char fakebuf[4096], *in;
+ int st;
+
+ memset(&stream, 0, sizeof(stream));
+ git_inflate_init(&stream);
+ do {
+ in = use_pack(p, w_curs, offset, &stream.avail_in);
+ stream.next_in = in;
+ stream.next_out = fakebuf;
+ stream.avail_out = sizeof(fakebuf);
+ st = git_inflate(&stream, Z_FINISH);
+ offset += stream.next_in - in;
+ } while (st == Z_OK || st == Z_BUF_ERROR);
+ git_inflate_end(&stream);
+ return (st == Z_STREAM_END &&
+ stream.total_out == expect &&
+ stream.total_in == len) ? 0 : -1;
+}
+
+static void copy_pack_data(struct sha1file *f,
+ struct packed_git *p,
+ struct pack_window **w_curs,
+ off_t offset,
+ off_t len)
+{
+ unsigned char *in;
+ unsigned int avail;
+
+ while (len) {
+ in = use_pack(p, w_curs, offset, &avail);
+ if (avail > len)
+ avail = (unsigned int)len;
+ sha1write(f, in, avail);
+ offset += avail;
+ len -= avail;
+ }
+}
+
+static unsigned long write_object(struct sha1file *f,
+ struct object_entry *entry,
+ off_t write_offset)
+{
+ unsigned long size, limit, datalen;
+ void *buf;
+ unsigned char header[10], dheader[10];
+ unsigned hdrlen;
+ enum object_type type;
+ int usable_delta, to_reuse;
+
+ if (!pack_to_stdout)
+ crc32_begin(f);
+
+ type = entry->type;
+
+ /* apply size limit if limited packsize and not first object */
+ if (!pack_size_limit || !nr_written)
+ limit = 0;
+ else if (pack_size_limit <= write_offset)
+ /*
+ * the earlier object did not fit the limit; avoid
+ * mistaking this with unlimited (i.e. limit = 0).
+ */
+ limit = 1;
+ else
+ limit = pack_size_limit - write_offset;
+
+ if (!entry->delta)
+ usable_delta = 0; /* no delta */
+ else if (!pack_size_limit)
+ usable_delta = 1; /* unlimited packfile */
+ else if (entry->delta->idx.offset == (off_t)-1)
+ usable_delta = 0; /* base was written to another pack */
+ else if (entry->delta->idx.offset)
+ usable_delta = 1; /* base already exists in this pack */
+ else
+ usable_delta = 0; /* base could end up in another pack */
+
+ if (!reuse_object)
+ to_reuse = 0; /* explicit */
+ else if (!entry->in_pack)
+ to_reuse = 0; /* can't reuse what we don't have */
+ else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA)
+ /* check_object() decided it for us ... */
+ to_reuse = usable_delta;
+ /* ... but pack split may override that */
+ else if (type != entry->in_pack_type)
+ to_reuse = 0; /* pack has delta which is unusable */
+ else if (entry->delta)
+ to_reuse = 0; /* we want to pack afresh */
+ else
+ to_reuse = 1; /* we have it in-pack undeltified,
+ * and we do not need to deltify it.
+ */
+
+ if (!to_reuse) {
+ no_reuse:
+ if (!usable_delta) {
+ buf = read_sha1_file(entry->idx.sha1, &type, &size);
+ if (!buf)
+ die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+ /*
+ * make sure no cached delta data remains from a
+ * previous attempt before a pack split occurred.
+ */
+ free(entry->delta_data);
+ entry->delta_data = NULL;
+ entry->z_delta_size = 0;
+ } else if (entry->delta_data) {
+ size = entry->delta_size;
+ buf = entry->delta_data;
+ entry->delta_data = NULL;
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ } else {
+ buf = get_delta(entry);
+ size = entry->delta_size;
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ }
+
+ if (entry->z_delta_size)
+ datalen = entry->z_delta_size;
+ else
+ datalen = do_compress(&buf, size);
+
+ /*
+ * The object header is a byte of 'type' followed by zero or
+ * more bytes of length.
+ */
+ hdrlen = encode_in_pack_object_header(type, size, header);
+
+ if (type == OBJ_OFS_DELTA) {
+ /*
+ * Deltas with relative base contain an additional
+ * encoding of the relative offset for the delta
+ * base from this object's position in the pack.
+ */
+ 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) {
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, dheader + pos, sizeof(dheader) - pos);
+ hdrlen += sizeof(dheader) - pos;
+ } else if (type == OBJ_REF_DELTA) {
+ /*
+ * Deltas with a base reference contain
+ * an additional 20 bytes for the base sha1.
+ */
+ if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+ 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(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ }
+ sha1write(f, buf, datalen);
+ free(buf);
+ }
+ else {
+ struct packed_git *p = entry->in_pack;
+ struct pack_window *w_curs = NULL;
+ struct revindex_entry *revidx;
+ off_t offset;
+
+ if (entry->delta)
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ hdrlen = encode_in_pack_object_header(type, entry->size, header);
+
+ offset = entry->in_pack_offset;
+ revidx = find_pack_revindex(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)) {
+ error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
+ unuse_pack(&w_curs);
+ goto no_reuse;
+ }
+
+ offset += entry->in_pack_header_size;
+ datalen -= entry->in_pack_header_size;
+ if (!pack_to_stdout && p->index_version == 1 &&
+ check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
+ error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
+ unuse_pack(&w_curs);
+ goto no_reuse;
+ }
+
+ if (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) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, dheader + pos, sizeof(dheader) - pos);
+ hdrlen += sizeof(dheader) - pos;
+ reused_delta++;
+ } else if (type == OBJ_REF_DELTA) {
+ if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, entry->delta->idx.sha1, 20);
+ hdrlen += 20;
+ reused_delta++;
+ } else {
+ if (limit && hdrlen + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ }
+ copy_pack_data(f, p, &w_curs, offset, datalen);
+ unuse_pack(&w_curs);
+ reused++;
+ }
+ if (usable_delta)
+ written_delta++;
+ written++;
+ if (!pack_to_stdout)
+ entry->idx.crc32 = crc32_end(f);
+ return hdrlen + datalen;
+}
+
+static int write_one(struct sha1file *f,
+ struct object_entry *e,
+ off_t *offset)
+{
+ unsigned long size;
+
+ /* offset is non zero if object is written already. */
+ if (e->idx.offset || e->preferred_base)
+ return -1;
+
+ /* if we are deltified, write out base object first. */
+ if (e->delta && !write_one(f, e->delta, offset))
+ return 0;
+
+ e->idx.offset = *offset;
+ size = write_object(f, e, *offset);
+ if (!size) {
+ e->idx.offset = 0;
+ return 0;
+ }
+ written_list[nr_written++] = &e->idx;
+
+ /* make sure off_t is sufficiently large not to wrap */
+ if (*offset > *offset + size)
+ die("pack too large for current definition of off_t");
+ *offset += size;
+ return 1;
+}
+
+static void write_pack_file(void)
+{
+ uint32_t i = 0, j;
+ struct sha1file *f;
+ off_t offset;
+ struct pack_header hdr;
+ uint32_t nr_remaining = nr_result;
+ time_t last_mtime = 0;
+
+ if (progress > pack_to_stdout)
+ progress_state = start_progress("Writing objects", nr_result);
+ written_list = xmalloc(nr_objects * sizeof(*written_list));
+
+ do {
+ unsigned char sha1[20];
+ char *pack_tmp_name = NULL;
+
+ if (pack_to_stdout) {
+ f = sha1fd_throughput(1, "<stdout>", progress_state);
+ } else {
+ char tmpname[PATH_MAX];
+ int fd;
+ fd = odb_mkstemp(tmpname, sizeof(tmpname),
+ "pack/tmp_pack_XXXXXX");
+ pack_tmp_name = xstrdup(tmpname);
+ f = sha1fd(fd, pack_tmp_name);
+ }
+
+ hdr.hdr_signature = htonl(PACK_SIGNATURE);
+ hdr.hdr_version = htonl(PACK_VERSION);
+ hdr.hdr_entries = htonl(nr_remaining);
+ sha1write(f, &hdr, sizeof(hdr));
+ offset = sizeof(hdr);
+ nr_written = 0;
+ for (; i < nr_objects; i++) {
+ if (!write_one(f, objects + i, &offset))
+ break;
+ display_progress(progress_state, written);
+ }
+
+ /*
+ * Did we write the wrong # entries in the header?
+ * If so, rewrite it like in fast-import
+ */
+ if (pack_to_stdout) {
+ sha1close(f, sha1, CSUM_CLOSE);
+ } else if (nr_written == nr_remaining) {
+ sha1close(f, sha1, CSUM_FSYNC);
+ } else {
+ int fd = sha1close(f, sha1, 0);
+ fixup_pack_header_footer(fd, sha1, pack_tmp_name,
+ nr_written, sha1, offset);
+ close(fd);
+ }
+
+ if (!pack_to_stdout) {
+ struct stat st;
+ const char *idx_tmp_name;
+ char tmpname[PATH_MAX];
+
+ idx_tmp_name = write_idx_file(NULL, written_list,
+ nr_written, sha1);
+
+ snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
+ base_name, sha1_to_hex(sha1));
+ free_pack_by_name(tmpname);
+ if (adjust_shared_perm(pack_tmp_name))
+ die_errno("unable to make temporary pack file readable");
+ if (rename(pack_tmp_name, tmpname))
+ die_errno("unable to rename temporary pack file");
+
+ /*
+ * Packs are runtime accessed in their mtime
+ * order since newer packs are more likely to contain
+ * younger objects. So if we are creating multiple
+ * packs then we should modify the mtime of later ones
+ * to preserve this property.
+ */
+ if (stat(tmpname, &st) < 0) {
+ warning("failed to stat %s: %s",
+ tmpname, strerror(errno));
+ } else if (!last_mtime) {
+ last_mtime = st.st_mtime;
+ } else {
+ struct utimbuf utb;
+ utb.actime = st.st_atime;
+ utb.modtime = --last_mtime;
+ if (utime(tmpname, &utb) < 0)
+ warning("failed utime() on %s: %s",
+ tmpname, strerror(errno));
+ }
+
+ snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
+ base_name, sha1_to_hex(sha1));
+ if (adjust_shared_perm(idx_tmp_name))
+ die_errno("unable to make temporary index file readable");
+ if (rename(idx_tmp_name, tmpname))
+ die_errno("unable to rename temporary index file");
+
+ free((void *) idx_tmp_name);
+ free(pack_tmp_name);
+ puts(sha1_to_hex(sha1));
+ }
+
+ /* mark written objects as written to previous pack */
+ for (j = 0; j < nr_written; j++) {
+ written_list[j]->offset = (off_t)-1;
+ }
+ nr_remaining -= nr_written;
+ } while (nr_remaining && i < nr_objects);
+
+ free(written_list);
+ stop_progress(&progress_state);
+ if (written != nr_result)
+ die("wrote %"PRIu32" objects while expecting %"PRIu32,
+ written, nr_result);
+}
+
+static int locate_object_entry_hash(const unsigned char *sha1)
+{
+ int i;
+ unsigned int ui;
+ memcpy(&ui, sha1, sizeof(unsigned int));
+ i = ui % object_ix_hashsz;
+ while (0 < object_ix[i]) {
+ if (!hashcmp(sha1, objects[object_ix[i] - 1].idx.sha1))
+ return i;
+ if (++i == object_ix_hashsz)
+ i = 0;
+ }
+ return -1 - i;
+}
+
+static struct object_entry *locate_object_entry(const unsigned char *sha1)
+{
+ int i;
+
+ if (!object_ix_hashsz)
+ return NULL;
+
+ i = locate_object_entry_hash(sha1);
+ if (0 <= i)
+ return &objects[object_ix[i]-1];
+ return NULL;
+}
+
+static void rehash_objects(void)
+{
+ uint32_t i;
+ struct object_entry *oe;
+
+ object_ix_hashsz = nr_objects * 3;
+ if (object_ix_hashsz < 1024)
+ object_ix_hashsz = 1024;
+ object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz);
+ memset(object_ix, 0, sizeof(int) * object_ix_hashsz);
+ for (i = 0, oe = objects; i < nr_objects; i++, oe++) {
+ int ix = locate_object_entry_hash(oe->idx.sha1);
+ if (0 <= ix)
+ continue;
+ ix = -1 - ix;
+ object_ix[ix] = i + 1;
+ }
+}
+
+static unsigned name_hash(const char *name)
+{
+ unsigned c, hash = 0;
+
+ if (!name)
+ return 0;
+
+ /*
+ * This effectively just creates a sortable number from the
+ * last sixteen non-whitespace characters. Last characters
+ * count "most", so things that end in ".c" sort together.
+ */
+ while ((c = *name++) != 0) {
+ if (isspace(c))
+ continue;
+ hash = (hash >> 2) + (c << 24);
+ }
+ return hash;
+}
+
+static void setup_delta_attr_check(struct git_attr_check *check)
+{
+ static struct git_attr *attr_delta;
+
+ if (!attr_delta)
+ attr_delta = git_attr("delta");
+
+ 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,
+ 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) {
+ if (exclude) {
+ entry = objects + object_ix[ix] - 1;
+ if (!entry->preferred_base)
+ nr_result--;
+ entry->preferred_base = 1;
+ }
+ return 0;
+ }
+
+ if (!exclude && local && has_loose_object_nonlocal(sha1))
+ return 0;
+
+ for (p = packed_git; p; p = p->next) {
+ off_t offset = find_pack_entry_one(sha1, p);
+ if (offset) {
+ if (!found_pack) {
+ found_offset = offset;
+ found_pack = p;
+ }
+ if (exclude)
+ break;
+ if (incremental)
+ return 0;
+ if (local && !p->pack_local)
+ return 0;
+ if (ignore_packed_keep && p->pack_local && p->pack_keep)
+ return 0;
+ }
+ }
+
+ if (nr_objects >= nr_alloc) {
+ nr_alloc = (nr_alloc + 1024) * 3 / 2;
+ objects = xrealloc(objects, nr_alloc * sizeof(*entry));
+ }
+
+ entry = objects + nr_objects++;
+ memset(entry, 0, sizeof(*entry));
+ hashcpy(entry->idx.sha1, sha1);
+ entry->hash = hash;
+ if (type)
+ entry->type = type;
+ if (exclude)
+ entry->preferred_base = 1;
+ else
+ nr_result++;
+ if (found_pack) {
+ entry->in_pack = found_pack;
+ entry->in_pack_offset = found_offset;
+ }
+
+ if (object_ix_hashsz * 3 <= nr_objects * 4)
+ rehash_objects();
+ else
+ object_ix[-1 - ix] = nr_objects;
+
+ display_progress(progress_state, nr_objects);
+
+ if (name && no_try_delta(name))
+ entry->no_try_delta = 1;
+
+ return 1;
+}
+
+struct pbase_tree_cache {
+ unsigned char sha1[20];
+ int ref;
+ int temporary;
+ void *tree_data;
+ unsigned long tree_size;
+};
+
+static struct pbase_tree_cache *(pbase_tree_cache[256]);
+static int pbase_tree_cache_ix(const unsigned char *sha1)
+{
+ return sha1[0] % ARRAY_SIZE(pbase_tree_cache);
+}
+static int pbase_tree_cache_ix_incr(int ix)
+{
+ return (ix+1) % ARRAY_SIZE(pbase_tree_cache);
+}
+
+static struct pbase_tree {
+ struct pbase_tree *next;
+ /* This is a phony "cache" entry; we are not
+ * going to evict it nor find it through _get()
+ * mechanism -- this is for the toplevel node that
+ * would almost always change with any commit.
+ */
+ struct pbase_tree_cache pcache;
+} *pbase_tree;
+
+static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1)
+{
+ struct pbase_tree_cache *ent, *nent;
+ void *data;
+ unsigned long size;
+ enum object_type type;
+ int neigh;
+ int my_ix = pbase_tree_cache_ix(sha1);
+ int available_ix = -1;
+
+ /* pbase-tree-cache acts as a limited hashtable.
+ * your object will be found at your index or within a few
+ * slots after that slot if it is cached.
+ */
+ for (neigh = 0; neigh < 8; neigh++) {
+ ent = pbase_tree_cache[my_ix];
+ if (ent && !hashcmp(ent->sha1, sha1)) {
+ ent->ref++;
+ return ent;
+ }
+ else if (((available_ix < 0) && (!ent || !ent->ref)) ||
+ ((0 <= available_ix) &&
+ (!ent && pbase_tree_cache[available_ix])))
+ available_ix = my_ix;
+ if (!ent)
+ break;
+ my_ix = pbase_tree_cache_ix_incr(my_ix);
+ }
+
+ /* Did not find one. Either we got a bogus request or
+ * we need to read and perhaps cache.
+ */
+ data = read_sha1_file(sha1, &type, &size);
+ if (!data)
+ return NULL;
+ if (type != OBJ_TREE) {
+ free(data);
+ return NULL;
+ }
+
+ /* We need to either cache or return a throwaway copy */
+
+ if (available_ix < 0)
+ ent = NULL;
+ else {
+ ent = pbase_tree_cache[available_ix];
+ my_ix = available_ix;
+ }
+
+ if (!ent) {
+ nent = xmalloc(sizeof(*nent));
+ nent->temporary = (available_ix < 0);
+ }
+ else {
+ /* evict and reuse */
+ free(ent->tree_data);
+ nent = ent;
+ }
+ hashcpy(nent->sha1, sha1);
+ nent->tree_data = data;
+ nent->tree_size = size;
+ nent->ref = 1;
+ if (!nent->temporary)
+ pbase_tree_cache[my_ix] = nent;
+ return nent;
+}
+
+static void pbase_tree_put(struct pbase_tree_cache *cache)
+{
+ if (!cache->temporary) {
+ cache->ref--;
+ return;
+ }
+ free(cache->tree_data);
+ free(cache);
+}
+
+static int name_cmp_len(const char *name)
+{
+ int i;
+ for (i = 0; name[i] && name[i] != '\n' && name[i] != '/'; i++)
+ ;
+ return i;
+}
+
+static void add_pbase_object(struct tree_desc *tree,
+ const char *name,
+ int cmplen,
+ const char *fullname)
+{
+ struct name_entry entry;
+ int cmp;
+
+ while (tree_entry(tree,&entry)) {
+ if (S_ISGITLINK(entry.mode))
+ continue;
+ cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
+ memcmp(name, entry.path, cmplen);
+ if (cmp > 0)
+ continue;
+ if (cmp < 0)
+ return;
+ if (name[cmplen] != '/') {
+ add_object_entry(entry.sha1,
+ object_type(entry.mode),
+ fullname, 1);
+ return;
+ }
+ if (S_ISDIR(entry.mode)) {
+ struct tree_desc sub;
+ struct pbase_tree_cache *tree;
+ const char *down = name+cmplen+1;
+ int downlen = name_cmp_len(down);
+
+ tree = pbase_tree_get(entry.sha1);
+ if (!tree)
+ return;
+ init_tree_desc(&sub, tree->tree_data, tree->tree_size);
+
+ add_pbase_object(&sub, down, downlen, fullname);
+ pbase_tree_put(tree);
+ }
+ }
+}
+
+static unsigned *done_pbase_paths;
+static int done_pbase_paths_num;
+static int done_pbase_paths_alloc;
+static int done_pbase_path_pos(unsigned hash)
+{
+ int lo = 0;
+ int hi = done_pbase_paths_num;
+ while (lo < hi) {
+ int mi = (hi + lo) / 2;
+ if (done_pbase_paths[mi] == hash)
+ return mi;
+ if (done_pbase_paths[mi] < hash)
+ hi = mi;
+ else
+ lo = mi + 1;
+ }
+ return -lo-1;
+}
+
+static int check_pbase_path(unsigned hash)
+{
+ int pos = (!done_pbase_paths) ? -1 : done_pbase_path_pos(hash);
+ if (0 <= pos)
+ return 1;
+ pos = -pos - 1;
+ if (done_pbase_paths_alloc <= done_pbase_paths_num) {
+ done_pbase_paths_alloc = alloc_nr(done_pbase_paths_alloc);
+ done_pbase_paths = xrealloc(done_pbase_paths,
+ done_pbase_paths_alloc *
+ sizeof(unsigned));
+ }
+ done_pbase_paths_num++;
+ if (pos < done_pbase_paths_num)
+ memmove(done_pbase_paths + pos + 1,
+ done_pbase_paths + pos,
+ (done_pbase_paths_num - pos - 1) * sizeof(unsigned));
+ done_pbase_paths[pos] = hash;
+ return 0;
+}
+
+static void add_preferred_base_object(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, NULL, 1);
+ }
+ else {
+ struct tree_desc tree;
+ init_tree_desc(&tree, it->pcache.tree_data, it->pcache.tree_size);
+ add_pbase_object(&tree, name, cmplen, name);
+ }
+ }
+}
+
+static void add_preferred_base(unsigned char *sha1)
+{
+ struct pbase_tree *it;
+ void *data;
+ unsigned long size;
+ unsigned char tree_sha1[20];
+
+ if (window <= num_preferred_base++)
+ return;
+
+ data = read_object_with_reference(sha1, tree_type, &size, tree_sha1);
+ if (!data)
+ return;
+
+ for (it = pbase_tree; it; it = it->next) {
+ if (!hashcmp(it->pcache.sha1, tree_sha1)) {
+ free(data);
+ return;
+ }
+ }
+
+ it = xcalloc(1, sizeof(*it));
+ it->next = pbase_tree;
+ pbase_tree = it;
+
+ hashcpy(it->pcache.sha1, tree_sha1);
+ it->pcache.tree_data = data;
+ it->pcache.tree_size = size;
+}
+
+static void cleanup_preferred_base(void)
+{
+ struct pbase_tree *it;
+ unsigned i;
+
+ it = pbase_tree;
+ pbase_tree = NULL;
+ while (it) {
+ struct pbase_tree *this = it;
+ it = this->next;
+ free(this->pcache.tree_data);
+ free(this);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pbase_tree_cache); i++) {
+ if (!pbase_tree_cache[i])
+ continue;
+ free(pbase_tree_cache[i]->tree_data);
+ free(pbase_tree_cache[i]);
+ pbase_tree_cache[i] = NULL;
+ }
+
+ free(done_pbase_paths);
+ done_pbase_paths = NULL;
+ done_pbase_paths_num = done_pbase_paths_alloc = 0;
+}
+
+static void check_object(struct object_entry *entry)
+{
+ if (entry->in_pack) {
+ struct packed_git *p = entry->in_pack;
+ struct pack_window *w_curs = NULL;
+ const unsigned char *base_ref = NULL;
+ struct object_entry *base_entry;
+ unsigned long used, used_0;
+ unsigned int avail;
+ off_t ofs;
+ unsigned char *buf, c;
+
+ buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
+
+ /*
+ * We want in_pack_type even if we do not reuse delta
+ * since non-delta representations could still be reused.
+ */
+ used = unpack_object_header_buffer(buf, avail,
+ &entry->in_pack_type,
+ &entry->size);
+ if (used == 0)
+ goto give_up;
+
+ /*
+ * Determine if this is a delta and if so whether we can
+ * reuse it or not. Otherwise let's find out as cheaply as
+ * possible what the actual type and size for this object is.
+ */
+ switch (entry->in_pack_type) {
+ default:
+ /* Not a delta hence we've already got all we need. */
+ entry->type = entry->in_pack_type;
+ entry->in_pack_header_size = used;
+ if (entry->type < OBJ_COMMIT || entry->type > OBJ_BLOB)
+ goto give_up;
+ unuse_pack(&w_curs);
+ return;
+ case OBJ_REF_DELTA:
+ if (reuse_delta && !entry->preferred_base)
+ base_ref = use_pack(p, &w_curs,
+ entry->in_pack_offset + used, NULL);
+ entry->in_pack_header_size = used + 20;
+ break;
+ case OBJ_OFS_DELTA:
+ buf = use_pack(p, &w_curs,
+ entry->in_pack_offset + used, NULL);
+ used_0 = 0;
+ c = buf[used_0++];
+ ofs = c & 127;
+ while (c & 128) {
+ ofs += 1;
+ if (!ofs || MSB(ofs, 7)) {
+ error("delta base offset overflow in pack for %s",
+ sha1_to_hex(entry->idx.sha1));
+ goto give_up;
+ }
+ c = buf[used_0++];
+ ofs = (ofs << 7) + (c & 127);
+ }
+ ofs = entry->in_pack_offset - ofs;
+ if (ofs <= 0 || ofs >= entry->in_pack_offset) {
+ error("delta base offset out of bound for %s",
+ sha1_to_hex(entry->idx.sha1));
+ goto give_up;
+ }
+ if (reuse_delta && !entry->preferred_base) {
+ struct revindex_entry *revidx;
+ revidx = find_pack_revindex(p, ofs);
+ if (!revidx)
+ goto give_up;
+ base_ref = nth_packed_object_sha1(p, revidx->nr);
+ }
+ entry->in_pack_header_size = used + used_0;
+ break;
+ }
+
+ if (base_ref && (base_entry = locate_object_entry(base_ref))) {
+ /*
+ * If base_ref was set above that means we wish to
+ * reuse delta data, and we even found that base
+ * in the list of objects we want to pack. Goodie!
+ *
+ * Depth value does not matter - find_deltas() will
+ * never consider reused delta as the base object to
+ * deltify other objects against, in order to avoid
+ * circular deltas.
+ */
+ entry->type = entry->in_pack_type;
+ entry->delta = base_entry;
+ entry->delta_size = entry->size;
+ entry->delta_sibling = base_entry->delta_child;
+ base_entry->delta_child = entry;
+ unuse_pack(&w_curs);
+ return;
+ }
+
+ if (entry->type) {
+ /*
+ * This must be a delta and we already know what the
+ * final object type is. Let's extract the actual
+ * object size from the delta header.
+ */
+ entry->size = get_size_from_delta(p, &w_curs,
+ entry->in_pack_offset + entry->in_pack_header_size);
+ if (entry->size == 0)
+ goto give_up;
+ unuse_pack(&w_curs);
+ return;
+ }
+
+ /*
+ * No choice but to fall back to the recursive delta walk
+ * with sha1_object_info() to find about the object type
+ * at this point...
+ */
+ give_up:
+ unuse_pack(&w_curs);
+ }
+
+ entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
+ /*
+ * The error condition is checked in prepare_pack(). This is
+ * to permit a missing preferred base object to be ignored
+ * as a preferred base. Doing so can result in a larger
+ * pack file, but the transfer will still take place.
+ */
+}
+
+static int pack_offset_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;
+
+ /* avoid filesystem trashing with loose objects */
+ if (!a->in_pack && !b->in_pack)
+ return hashcmp(a->idx.sha1, b->idx.sha1);
+
+ if (a->in_pack < b->in_pack)
+ return -1;
+ if (a->in_pack > b->in_pack)
+ return 1;
+ return a->in_pack_offset < b->in_pack_offset ? -1 :
+ (a->in_pack_offset > b->in_pack_offset);
+}
+
+static void get_object_details(void)
+{
+ uint32_t i;
+ struct object_entry **sorted_by_offset;
+
+ sorted_by_offset = xcalloc(nr_objects, sizeof(struct object_entry *));
+ for (i = 0; i < nr_objects; i++)
+ sorted_by_offset[i] = objects + i;
+ qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort);
+
+ for (i = 0; i < nr_objects; i++)
+ check_object(sorted_by_offset[i]);
+
+ free(sorted_by_offset);
+}
+
+/*
+ * We search for deltas in a list sorted by type, by filename hash, and then
+ * by size, so that we see progressively smaller and smaller files.
+ * That's because we prefer deltas to be from the bigger file
+ * to the smaller -- deletes are potentially cheaper, but perhaps
+ * more importantly, the bigger file is likely the more recent
+ * one. The deepest deltas are therefore the oldest objects which are
+ * less susceptible to be accessed often.
+ */
+static int type_size_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;
+
+ if (a->type > b->type)
+ return -1;
+ if (a->type < b->type)
+ return 1;
+ if (a->hash > b->hash)
+ return -1;
+ if (a->hash < b->hash)
+ return 1;
+ if (a->preferred_base > b->preferred_base)
+ return -1;
+ if (a->preferred_base < b->preferred_base)
+ return 1;
+ if (a->size > b->size)
+ return -1;
+ if (a->size < b->size)
+ return 1;
+ return a < b ? -1 : (a > b); /* newest first */
+}
+
+struct unpacked {
+ struct object_entry *entry;
+ void *data;
+ struct delta_index *index;
+ unsigned depth;
+};
+
+static int delta_cacheable(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;
+}
+
+#ifndef NO_PTHREADS
+
+static pthread_mutex_t read_mutex;
+#define read_lock() pthread_mutex_lock(&read_mutex)
+#define read_unlock() pthread_mutex_unlock(&read_mutex)
+
+static pthread_mutex_t cache_mutex;
+#define cache_lock() pthread_mutex_lock(&cache_mutex)
+#define cache_unlock() pthread_mutex_unlock(&cache_mutex)
+
+static pthread_mutex_t progress_mutex;
+#define progress_lock() pthread_mutex_lock(&progress_mutex)
+#define progress_unlock() pthread_mutex_unlock(&progress_mutex)
+
+#else
+
+#define read_lock() (void)0
+#define read_unlock() (void)0
+#define cache_lock() (void)0
+#define cache_unlock() (void)0
+#define progress_lock() (void)0
+#define progress_unlock() (void)0
+
+#endif
+
+static int try_delta(struct unpacked *trg, struct unpacked *src,
+ unsigned max_depth, unsigned long *mem_usage)
+{
+ 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;
+
+ /* Don't bother doing diffs between different types */
+ if (trg_entry->type != src_entry->type)
+ return -1;
+
+ /*
+ * We do not bother to try a delta that we discarded
+ * on an earlier try, but only when reusing delta data.
+ */
+ if (reuse_delta && trg_entry->in_pack &&
+ trg_entry->in_pack == src_entry->in_pack &&
+ trg_entry->in_pack_type != OBJ_REF_DELTA &&
+ trg_entry->in_pack_type != OBJ_OFS_DELTA)
+ return 0;
+
+ /* Let's not bust the allowed depth. */
+ if (src->depth >= max_depth)
+ return 0;
+
+ /* Now some size filtering heuristics. */
+ trg_size = trg_entry->size;
+ 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 = (uint64_t)max_size * (max_depth - src->depth) /
+ (max_depth - ref_depth + 1);
+ if (max_size == 0)
+ return 0;
+ 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) {
+ read_lock();
+ trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
+ read_unlock();
+ if (!trg->data)
+ die("object %s cannot be read",
+ sha1_to_hex(trg_entry->idx.sha1));
+ if (sz != trg_size)
+ die("object %s inconsistent object length (%lu vs %lu)",
+ sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
+ *mem_usage += sz;
+ }
+ if (!src->data) {
+ read_lock();
+ src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
+ read_unlock();
+ if (!src->data)
+ die("object %s cannot be read",
+ sha1_to_hex(src_entry->idx.sha1));
+ if (sz != src_size)
+ die("object %s inconsistent object length (%lu vs %lu)",
+ sha1_to_hex(src_entry->idx.sha1), sz, src_size);
+ *mem_usage += sz;
+ }
+ if (!src->index) {
+ src->index = create_delta_index(src->data, src_size);
+ if (!src->index) {
+ static int warned = 0;
+ if (!warned++)
+ warning("suboptimal pack - out of memory");
+ return 0;
+ }
+ *mem_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) {
+ /* Prefer only shallower same-sized deltas. */
+ if (delta_size == trg_entry->delta_size &&
+ src->depth + 1 >= trg->depth) {
+ free(delta_buf);
+ return 0;
+ }
+ }
+
+ /*
+ * Handle memory allocation outside of the cache
+ * accounting lock. Compiler will optimize the strangeness
+ * away when NO_PTHREADS is defined.
+ */
+ free(trg_entry->delta_data);
+ cache_lock();
+ if (trg_entry->delta_data) {
+ delta_cache_size -= trg_entry->delta_size;
+ trg_entry->delta_data = NULL;
+ }
+ if (delta_cacheable(src_size, trg_size, delta_size)) {
+ delta_cache_size += delta_size;
+ cache_unlock();
+ trg_entry->delta_data = xrealloc(delta_buf, delta_size);
+ } else {
+ cache_unlock();
+ free(delta_buf);
+ }
+
+ trg_entry->delta = src_entry;
+ trg_entry->delta_size = delta_size;
+ trg->depth = src->depth + 1;
+
+ return 1;
+}
+
+static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
+{
+ struct object_entry *child = me->delta_child;
+ unsigned int m = n;
+ while (child) {
+ unsigned int c = check_delta_limit(child, n + 1);
+ if (m < c)
+ m = c;
+ child = child->delta_sibling;
+ }
+ return m;
+}
+
+static unsigned long free_unpacked(struct unpacked *n)
+{
+ unsigned long freed_mem = sizeof_delta_index(n->index);
+ free_delta_index(n->index);
+ n->index = NULL;
+ if (n->data) {
+ freed_mem += n->entry->size;
+ free(n->data);
+ n->data = NULL;
+ }
+ n->entry = NULL;
+ n->depth = 0;
+ return freed_mem;
+}
+
+static void find_deltas(struct object_entry **list, unsigned *list_size,
+ int window, int depth, unsigned *processed)
+{
+ uint32_t i, idx = 0, count = 0;
+ struct unpacked *array;
+ unsigned long mem_usage = 0;
+
+ array = xcalloc(window, sizeof(struct unpacked));
+
+ for (;;) {
+ struct object_entry *entry;
+ struct unpacked *n = array + idx;
+ int j, max_depth, best_base = -1;
+
+ progress_lock();
+ if (!*list_size) {
+ progress_unlock();
+ break;
+ }
+ entry = *list++;
+ (*list_size)--;
+ if (!entry->preferred_base) {
+ (*processed)++;
+ display_progress(progress_state, *processed);
+ }
+ progress_unlock();
+
+ mem_usage -= free_unpacked(n);
+ n->entry = entry;
+
+ while (window_memory_limit &&
+ mem_usage > window_memory_limit &&
+ count > 1) {
+ uint32_t tail = (idx + window - count) % window;
+ mem_usage -= free_unpacked(array + tail);
+ count--;
+ }
+
+ /* We do not compute delta to *create* objects we are not
+ * going to pack.
+ */
+ if (entry->preferred_base)
+ goto next;
+
+ /*
+ * If the current object is at pack edge, take the depth the
+ * objects that depend on the current object into account
+ * otherwise they would become too deep.
+ */
+ max_depth = depth;
+ if (entry->delta_child) {
+ max_depth -= check_delta_limit(entry, 0);
+ if (max_depth <= 0)
+ goto next;
+ }
+
+ j = window;
+ while (--j > 0) {
+ int ret;
+ uint32_t other_idx = idx + j;
+ struct unpacked *m;
+ if (other_idx >= window)
+ other_idx -= window;
+ m = array + other_idx;
+ if (!m->entry)
+ break;
+ ret = try_delta(n, m, max_depth, &mem_usage);
+ if (ret < 0)
+ break;
+ else if (ret > 0)
+ best_base = other_idx;
+ }
+
+ /*
+ * If we decided to cache the delta data, then it is best
+ * to compress it right away. First because we have to do
+ * it anyway, and doing it here while we're threaded will
+ * save a lot of time in the non threaded write phase,
+ * as well as allow for caching more deltas within
+ * the same cache size limit.
+ * ...
+ * But only if not writing to stdout, since in that case
+ * the network is most likely throttling writes anyway,
+ * and therefore it is best to go to the write phase ASAP
+ * instead, as we can afford spending more time compressing
+ * between writes at that moment.
+ */
+ if (entry->delta_data && !pack_to_stdout) {
+ entry->z_delta_size = do_compress(&entry->delta_data,
+ entry->delta_size);
+ cache_lock();
+ delta_cache_size -= entry->delta_size;
+ delta_cache_size += entry->z_delta_size;
+ cache_unlock();
+ }
+
+ /* if we made n a delta, and if n is already at max
+ * depth, leaving it in the window is pointless. we
+ * should evict it first.
+ */
+ if (entry->delta && max_depth <= n->depth)
+ continue;
+
+ /*
+ * Move the best delta base up in the window, after the
+ * currently deltified object, to keep it longer. It will
+ * be the first base object to be attempted next.
+ */
+ if (entry->delta) {
+ struct unpacked swap = array[best_base];
+ int dist = (window + idx - best_base) % window;
+ int dst = best_base;
+ while (dist--) {
+ int src = (dst + 1) % window;
+ array[dst] = array[src];
+ dst = src;
+ }
+ array[dst] = swap;
+ }
+
+ next:
+ idx++;
+ if (count + 1 < window)
+ count++;
+ if (idx >= window)
+ idx = 0;
+ }
+
+ for (i = 0; i < window; ++i) {
+ free_delta_index(array[i].index);
+ free(array[i].data);
+ }
+ free(array);
+}
+
+#ifndef NO_PTHREADS
+
+/*
+ * The main thread waits on the condition that (at least) one of the workers
+ * has stopped working (which is indicated in the .working member of
+ * struct thread_params).
+ * When a work thread has completed its work, it sets .working to 0 and
+ * signals the main thread and waits on the condition that .data_ready
+ * becomes 1.
+ */
+
+struct thread_params {
+ pthread_t thread;
+ struct object_entry **list;
+ unsigned list_size;
+ unsigned remaining;
+ int window;
+ int depth;
+ int working;
+ int data_ready;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ unsigned *processed;
+};
+
+static pthread_cond_t progress_cond;
+
+/*
+ * Mutex and conditional variable can't be statically-initialized on Windows.
+ */
+static void init_threaded_search(void)
+{
+ pthread_mutex_init(&read_mutex, NULL);
+ pthread_mutex_init(&cache_mutex, NULL);
+ pthread_mutex_init(&progress_mutex, NULL);
+ pthread_cond_init(&progress_cond, NULL);
+}
+
+static void cleanup_threaded_search(void)
+{
+ pthread_cond_destroy(&progress_cond);
+ pthread_mutex_destroy(&read_mutex);
+ pthread_mutex_destroy(&cache_mutex);
+ pthread_mutex_destroy(&progress_mutex);
+}
+
+static void *threaded_find_deltas(void *arg)
+{
+ struct thread_params *me = arg;
+
+ while (me->remaining) {
+ find_deltas(me->list, &me->remaining,
+ me->window, me->depth, me->processed);
+
+ progress_lock();
+ me->working = 0;
+ pthread_cond_signal(&progress_cond);
+ progress_unlock();
+
+ /*
+ * We must not set ->data_ready before we wait on the
+ * condition because the main thread may have set it to 1
+ * before we get here. In order to be sure that new
+ * work is available if we see 1 in ->data_ready, it
+ * was initialized to 0 before this thread was spawned
+ * and we reset it to 0 right away.
+ */
+ pthread_mutex_lock(&me->mutex);
+ while (!me->data_ready)
+ pthread_cond_wait(&me->cond, &me->mutex);
+ me->data_ready = 0;
+ pthread_mutex_unlock(&me->mutex);
+ }
+ /* leave ->working 1 so that this doesn't get more work assigned */
+ return NULL;
+}
+
+static void ll_find_deltas(struct object_entry **list, unsigned list_size,
+ int window, int depth, unsigned *processed)
+{
+ struct thread_params *p;
+ int i, ret, active_threads = 0;
+
+ init_threaded_search();
+
+ if (!delta_search_threads) /* --threads=0 means autodetect */
+ delta_search_threads = online_cpus();
+ if (delta_search_threads <= 1) {
+ find_deltas(list, &list_size, window, depth, processed);
+ cleanup_threaded_search();
+ return;
+ }
+ if (progress > pack_to_stdout)
+ fprintf(stderr, "Delta compression using up to %d threads.\n",
+ delta_search_threads);
+ p = xcalloc(delta_search_threads, sizeof(*p));
+
+ /* Partition the work amongst work threads. */
+ for (i = 0; i < delta_search_threads; i++) {
+ unsigned sub_size = list_size / (delta_search_threads - i);
+
+ /* don't use too small segments or no deltas will be found */
+ if (sub_size < 2*window && i+1 < delta_search_threads)
+ sub_size = 0;
+
+ p[i].window = window;
+ p[i].depth = depth;
+ p[i].processed = processed;
+ p[i].working = 1;
+ p[i].data_ready = 0;
+
+ /* try to split chunks on "path" boundaries */
+ while (sub_size && sub_size < list_size &&
+ list[sub_size]->hash &&
+ list[sub_size]->hash == list[sub_size-1]->hash)
+ sub_size++;
+
+ p[i].list = list;
+ p[i].list_size = sub_size;
+ p[i].remaining = sub_size;
+
+ list += sub_size;
+ list_size -= sub_size;
+ }
+
+ /* Start work threads. */
+ for (i = 0; i < delta_search_threads; i++) {
+ if (!p[i].list_size)
+ continue;
+ pthread_mutex_init(&p[i].mutex, NULL);
+ pthread_cond_init(&p[i].cond, NULL);
+ ret = pthread_create(&p[i].thread, NULL,
+ threaded_find_deltas, &p[i]);
+ if (ret)
+ die("unable to create thread: %s", strerror(ret));
+ active_threads++;
+ }
+
+ /*
+ * Now let's wait for work completion. Each time a thread is done
+ * with its work, we steal half of the remaining work from the
+ * thread with the largest number of unprocessed objects and give
+ * it to that newly idle thread. This ensure good load balancing
+ * until the remaining object list segments are simply too short
+ * to be worth splitting anymore.
+ */
+ while (active_threads) {
+ struct thread_params *target = NULL;
+ struct thread_params *victim = NULL;
+ unsigned sub_size = 0;
+
+ progress_lock();
+ for (;;) {
+ for (i = 0; !target && i < delta_search_threads; i++)
+ if (!p[i].working)
+ target = &p[i];
+ if (target)
+ break;
+ pthread_cond_wait(&progress_cond, &progress_mutex);
+ }
+
+ for (i = 0; i < delta_search_threads; i++)
+ if (p[i].remaining > 2*window &&
+ (!victim || victim->remaining < p[i].remaining))
+ victim = &p[i];
+ if (victim) {
+ sub_size = victim->remaining / 2;
+ list = victim->list + victim->list_size - sub_size;
+ while (sub_size && list[0]->hash &&
+ list[0]->hash == list[-1]->hash) {
+ list++;
+ sub_size--;
+ }
+ if (!sub_size) {
+ /*
+ * It is possible for some "paths" to have
+ * so many objects that no hash boundary
+ * might be found. Let's just steal the
+ * exact half in that case.
+ */
+ sub_size = victim->remaining / 2;
+ list -= sub_size;
+ }
+ target->list = list;
+ victim->list_size -= sub_size;
+ victim->remaining -= sub_size;
+ }
+ target->list_size = sub_size;
+ target->remaining = sub_size;
+ target->working = 1;
+ progress_unlock();
+
+ pthread_mutex_lock(&target->mutex);
+ target->data_ready = 1;
+ pthread_cond_signal(&target->cond);
+ pthread_mutex_unlock(&target->mutex);
+
+ if (!sub_size) {
+ pthread_join(target->thread, NULL);
+ pthread_cond_destroy(&target->cond);
+ pthread_mutex_destroy(&target->mutex);
+ active_threads--;
+ }
+ }
+ cleanup_threaded_search();
+ free(p);
+}
+
+#else
+#define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p)
+#endif
+
+static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+ unsigned char peeled[20];
+
+ if (!prefixcmp(path, "refs/tags/") && /* is a tag? */
+ !peel_ref(path, peeled) && /* peelable? */
+ !is_null_sha1(peeled) && /* annotated tag? */
+ locate_object_entry(peeled)) /* object packed? */
+ add_object_entry(sha1, OBJ_TAG, NULL, 0);
+ return 0;
+}
+
+static void prepare_pack(int window, int depth)
+{
+ struct object_entry **delta_list;
+ uint32_t i, nr_deltas;
+ unsigned n;
+
+ get_object_details();
+
+ /*
+ * If we're locally repacking then we need to be doubly careful
+ * from now on in order to make sure no stealth corruption gets
+ * propagated to the new pack. Clients receiving streamed packs
+ * should validate everything they get anyway so no need to incur
+ * the additional cost here in that case.
+ */
+ if (!pack_to_stdout)
+ do_check_packed_object_crc = 1;
+
+ if (!nr_objects || !window || !depth)
+ return;
+
+ delta_list = xmalloc(nr_objects * sizeof(*delta_list));
+ nr_deltas = n = 0;
+
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *entry = objects + i;
+
+ if (entry->delta)
+ /* This happens if we decided to reuse existing
+ * delta from a pack. "reuse_delta &&" is implied.
+ */
+ continue;
+
+ if (entry->size < 50)
+ continue;
+
+ if (entry->no_try_delta)
+ continue;
+
+ if (!entry->preferred_base) {
+ nr_deltas++;
+ if (entry->type < 0)
+ die("unable to get type of object %s",
+ sha1_to_hex(entry->idx.sha1));
+ } else {
+ if (entry->type < 0) {
+ /*
+ * This object is not found, but we
+ * don't have to include it anyway.
+ */
+ continue;
+ }
+ }
+
+ delta_list[n++] = entry;
+ }
+
+ if (nr_deltas && n > 1) {
+ unsigned nr_done = 0;
+ if (progress)
+ progress_state = start_progress("Compressing objects",
+ nr_deltas);
+ qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
+ ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
+ stop_progress(&progress_state);
+ if (nr_done != nr_deltas)
+ die("inconsistency with delta count");
+ }
+ free(delta_list);
+}
+
+static int git_pack_config(const char *k, const char *v, void *cb)
+{
+ if (!strcmp(k, "pack.window")) {
+ window = git_config_int(k, v);
+ return 0;
+ }
+ 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;
+ }
+ if (!strcmp(k, "pack.threads")) {
+ delta_search_threads = git_config_int(k, v);
+ if (delta_search_threads < 0)
+ die("invalid number of threads specified (%d)",
+ delta_search_threads);
+#ifdef NO_PTHREADS
+ if (delta_search_threads != 1)
+ warning("no threads support, ignoring %s", k);
+#endif
+ return 0;
+ }
+ if (!strcmp(k, "pack.indexversion")) {
+ pack_idx_default_version = git_config_int(k, v);
+ if (pack_idx_default_version > 2)
+ die("bad pack.indexversion=%"PRIu32,
+ pack_idx_default_version);
+ return 0;
+ }
+ if (!strcmp(k, "pack.packsizelimit")) {
+ pack_size_limit_cfg = git_config_ulong(k, v);
+ return 0;
+ }
+ return git_default_config(k, v, cb);
+}
+
+static void read_object_list_from_stdin(void)
+{
+ char line[40 + 1 + PATH_MAX + 2];
+ unsigned char sha1[20];
+
+ for (;;) {
+ if (!fgets(line, sizeof(line), stdin)) {
+ if (feof(stdin))
+ break;
+ if (!ferror(stdin))
+ die("fgets returned NULL, not EOF, not error!");
+ if (errno != EINTR)
+ die_errno("fgets");
+ clearerr(stdin);
+ continue;
+ }
+ if (line[0] == '-') {
+ if (get_sha1_hex(line+1, sha1))
+ die("expected edge sha1, got garbage:\n %s",
+ line);
+ add_preferred_base(sha1);
+ continue;
+ }
+ if (get_sha1_hex(line, sha1))
+ die("expected sha1, got garbage:\n %s", line);
+
+ add_preferred_base_object(line+41);
+ add_object_entry(sha1, 0, line+41, 0);
+ }
+}
+
+#define OBJECT_ADDED (1u<<20)
+
+static void show_commit(struct commit *commit, void *data)
+{
+ add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
+ commit->object.flags |= OBJECT_ADDED;
+}
+
+static void show_object(struct object *obj, const struct name_path *path, const char *last)
+{
+ char *name = path_name(path, last);
+
+ add_preferred_base_object(name);
+ add_object_entry(obj->sha1, obj->type, name, 0);
+ obj->flags |= OBJECT_ADDED;
+
+ /*
+ * We will have generated the hash from the name,
+ * but not saved a pointer to it - we can free it
+ */
+ free((char *)name);
+}
+
+static void show_edge(struct commit *commit)
+{
+ add_preferred_base(commit->object.sha1);
+}
+
+struct in_pack_object {
+ off_t offset;
+ struct object *object;
+};
+
+struct in_pack {
+ int alloc;
+ int nr;
+ struct in_pack_object *array;
+};
+
+static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack)
+{
+ in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p);
+ in_pack->array[in_pack->nr].object = object;
+ in_pack->nr++;
+}
+
+/*
+ * Compare the objects in the offset order, in order to emulate the
+ * "git rev-list --objects" output that produced the pack originally.
+ */
+static int ofscmp(const void *a_, const void *b_)
+{
+ struct in_pack_object *a = (struct in_pack_object *)a_;
+ struct in_pack_object *b = (struct in_pack_object *)b_;
+
+ if (a->offset < b->offset)
+ return -1;
+ else if (a->offset > b->offset)
+ return 1;
+ else
+ return hashcmp(a->object->sha1, b->object->sha1);
+}
+
+static void add_objects_in_unpacked_packs(struct rev_info *revs)
+{
+ struct packed_git *p;
+ struct in_pack in_pack;
+ uint32_t i;
+
+ memset(&in_pack, 0, sizeof(in_pack));
+
+ for (p = packed_git; p; p = p->next) {
+ const unsigned char *sha1;
+ struct object *o;
+
+ if (!p->pack_local || p->pack_keep)
+ continue;
+ if (open_pack_index(p))
+ die("cannot open pack index");
+
+ ALLOC_GROW(in_pack.array,
+ in_pack.nr + p->num_objects,
+ in_pack.alloc);
+
+ for (i = 0; i < p->num_objects; i++) {
+ sha1 = nth_packed_object_sha1(p, i);
+ o = lookup_unknown_object(sha1);
+ if (!(o->flags & OBJECT_ADDED))
+ mark_in_pack_object(o, p, &in_pack);
+ o->flags |= OBJECT_ADDED;
+ }
+ }
+
+ if (in_pack.nr) {
+ qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]),
+ ofscmp);
+ for (i = 0; i < in_pack.nr; i++) {
+ struct object *o = in_pack.array[i].object;
+ add_object_entry(o->sha1, o->type, "", 0);
+ }
+ }
+ free(in_pack.array);
+}
+
+static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1)
+{
+ static struct packed_git *last_found = (void *)1;
+ struct packed_git *p;
+
+ p = (last_found != (void *)1) ? last_found : packed_git;
+
+ while (p) {
+ if ((!p->pack_local || p->pack_keep) &&
+ find_pack_entry_one(sha1, p)) {
+ last_found = p;
+ return 1;
+ }
+ if (p == last_found)
+ p = packed_git;
+ else
+ p = p->next;
+ if (p == last_found)
+ p = p->next;
+ }
+ return 0;
+}
+
+static void loosen_unused_packed_objects(struct rev_info *revs)
+{
+ struct packed_git *p;
+ uint32_t i;
+ const unsigned char *sha1;
+
+ for (p = packed_git; p; p = p->next) {
+ if (!p->pack_local || p->pack_keep)
+ continue;
+
+ if (open_pack_index(p))
+ die("cannot open pack index");
+
+ for (i = 0; i < p->num_objects; i++) {
+ sha1 = nth_packed_object_sha1(p, i);
+ if (!locate_object_entry(sha1) &&
+ !has_sha1_pack_kept_or_nonlocal(sha1))
+ if (force_object_loose(sha1, p->mtime))
+ die("unable to force loose object");
+ }
+ }
+}
+
+static void get_object_list(int ac, const char **av)
+{
+ struct rev_info revs;
+ char line[1000];
+ int flags = 0;
+
+ init_revisions(&revs, NULL);
+ save_commit_buffer = 0;
+ setup_revisions(ac, av, &revs, NULL);
+
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ int len = strlen(line);
+ if (len && line[len - 1] == '\n')
+ line[--len] = 0;
+ if (!len)
+ break;
+ if (*line == '-') {
+ if (!strcmp(line, "--not")) {
+ flags ^= UNINTERESTING;
+ continue;
+ }
+ die("not a rev '%s'", line);
+ }
+ if (handle_revision_arg(line, &revs, flags, 1))
+ die("bad revision '%s'", line);
+ }
+
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ mark_edges_uninteresting(revs.commits, &revs, show_edge);
+ traverse_commit_list(&revs, show_commit, show_object, NULL);
+
+ if (keep_unreachable)
+ add_objects_in_unpacked_packs(&revs);
+ if (unpack_unreachable)
+ loosen_unused_packed_objects(&revs);
+}
+
+int cmd_pack_objects(int argc, const char **argv, const char *prefix)
+{
+ int use_internal_rev_list = 0;
+ int thin = 0;
+ int all_progress_implied = 0;
+ uint32_t i;
+ const char **rp_av;
+ int rp_ac_alloc = 64;
+ int rp_ac;
+
+ read_replace_refs = 0;
+
+ rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av));
+
+ rp_av[0] = "pack-objects";
+ rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
+ rp_ac = 2;
+
+ git_config(git_pack_config, NULL);
+ if (!pack_compression_seen && core_compression_seen)
+ pack_compression_level = core_compression_level;
+
+ progress = isatty(2);
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg != '-')
+ break;
+
+ if (!strcmp("--non-empty", arg)) {
+ non_empty = 1;
+ continue;
+ }
+ if (!strcmp("--local", arg)) {
+ local = 1;
+ continue;
+ }
+ if (!strcmp("--incremental", arg)) {
+ incremental = 1;
+ continue;
+ }
+ if (!strcmp("--honor-pack-keep", arg)) {
+ ignore_packed_keep = 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=")) {
+ pack_size_limit_cfg = 0;
+ if (!git_parse_ulong(arg+16, &pack_size_limit))
+ usage(pack_usage);
+ continue;
+ }
+ if (!prefixcmp(arg, "--window=")) {
+ char *end;
+ window = strtoul(arg+9, &end, 0);
+ if (!arg[9] || *end)
+ 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, "--threads=")) {
+ char *end;
+ delta_search_threads = strtoul(arg+10, &end, 0);
+ if (!arg[10] || *end || delta_search_threads < 0)
+ usage(pack_usage);
+#ifdef NO_PTHREADS
+ if (delta_search_threads != 1)
+ warning("no threads support, "
+ "ignoring %s", arg);
+#endif
+ continue;
+ }
+ if (!prefixcmp(arg, "--depth=")) {
+ char *end;
+ depth = strtoul(arg+8, &end, 0);
+ if (!arg[8] || *end)
+ usage(pack_usage);
+ continue;
+ }
+ if (!strcmp("--progress", arg)) {
+ progress = 1;
+ continue;
+ }
+ if (!strcmp("--all-progress", arg)) {
+ progress = 2;
+ continue;
+ }
+ if (!strcmp("--all-progress-implied", arg)) {
+ all_progress_implied = 1;
+ continue;
+ }
+ if (!strcmp("-q", arg)) {
+ progress = 0;
+ continue;
+ }
+ if (!strcmp("--no-reuse-delta", arg)) {
+ reuse_delta = 0;
+ continue;
+ }
+ if (!strcmp("--no-reuse-object", arg)) {
+ reuse_object = reuse_delta = 0;
+ continue;
+ }
+ if (!strcmp("--delta-base-offset", arg)) {
+ allow_ofs_delta = 1;
+ continue;
+ }
+ if (!strcmp("--stdout", arg)) {
+ pack_to_stdout = 1;
+ continue;
+ }
+ if (!strcmp("--revs", arg)) {
+ use_internal_rev_list = 1;
+ continue;
+ }
+ if (!strcmp("--keep-unreachable", arg)) {
+ keep_unreachable = 1;
+ continue;
+ }
+ if (!strcmp("--unpack-unreachable", arg)) {
+ unpack_unreachable = 1;
+ continue;
+ }
+ if (!strcmp("--include-tag", arg)) {
+ include_tag = 1;
+ continue;
+ }
+ if (!strcmp("--unpacked", arg) ||
+ !strcmp("--reflog", arg) ||
+ !strcmp("--all", arg)) {
+ use_internal_rev_list = 1;
+ if (rp_ac >= rp_ac_alloc - 1) {
+ rp_ac_alloc = alloc_nr(rp_ac_alloc);
+ rp_av = xrealloc(rp_av,
+ rp_ac_alloc * sizeof(*rp_av));
+ }
+ rp_av[rp_ac++] = arg;
+ continue;
+ }
+ if (!strcmp("--thin", arg)) {
+ use_internal_rev_list = 1;
+ thin = 1;
+ rp_av[1] = "--objects-edge";
+ continue;
+ }
+ if (!prefixcmp(arg, "--index-version=")) {
+ char *c;
+ pack_idx_default_version = strtoul(arg + 16, &c, 10);
+ if (pack_idx_default_version > 2)
+ die("bad %s", arg);
+ if (*c == ',')
+ pack_idx_off32_limit = strtoul(c+1, &c, 0);
+ if (*c || pack_idx_off32_limit & 0x80000000)
+ die("bad %s", arg);
+ continue;
+ }
+ if (!strcmp(arg, "--keep-true-parents")) {
+ grafts_replace_parents = 0;
+ continue;
+ }
+ usage(pack_usage);
+ }
+
+ /* Traditionally "pack-objects [options] base extra" failed;
+ * we would however want to take refs parameter that would
+ * have been given to upstream rev-list ourselves, which means
+ * we somehow want to say what the base name is. So the
+ * syntax would be:
+ *
+ * pack-objects [options] base <refs...>
+ *
+ * in other words, we would treat the first non-option as the
+ * base_name and send everything else to the internal revision
+ * walker.
+ */
+
+ if (!pack_to_stdout)
+ base_name = argv[i++];
+
+ if (pack_to_stdout != !base_name)
+ usage(pack_usage);
+
+ if (!pack_to_stdout && !pack_size_limit)
+ pack_size_limit = pack_size_limit_cfg;
+ if (pack_to_stdout && pack_size_limit)
+ die("--max-pack-size cannot be used to build a pack for transfer.");
+ if (pack_size_limit && pack_size_limit < 1024*1024) {
+ warning("minimum pack size limit is 1 MiB");
+ pack_size_limit = 1024*1024;
+ }
+
+ if (!pack_to_stdout && thin)
+ die("--thin cannot be used to build an indexable pack.");
+
+ if (keep_unreachable && unpack_unreachable)
+ die("--keep-unreachable and --unpack-unreachable are incompatible.");
+
+ if (progress && all_progress_implied)
+ progress = 2;
+
+ prepare_packed_git();
+
+ if (progress)
+ progress_state = start_progress("Counting objects", 0);
+ if (!use_internal_rev_list)
+ read_object_list_from_stdin();
+ else {
+ rp_av[rp_ac] = NULL;
+ get_object_list(rp_ac, rp_av);
+ }
+ cleanup_preferred_base();
+ if (include_tag && nr_result)
+ for_each_ref(add_ref_tag, NULL);
+ stop_progress(&progress_state);
+
+ if (non_empty && !nr_result)
+ return 0;
+ if (nr_result)
+ prepare_pack(window, depth);
+ write_pack_file();
+ if (progress)
+ fprintf(stderr, "Total %"PRIu32" (delta %"PRIu32"),"
+ " reused %"PRIu32" (delta %"PRIu32")\n",
+ written, written_delta, reused, reused_delta);
+ return 0;
+}
--- /dev/null
+/*
+*
+* Copyright 2005, Lukas Sandstrom <lukass@etek.chalmers.se>
+*
+* This file is licensed under the GPL v2.
+*
+*/
+
+#include "cache.h"
+#include "exec_cmd.h"
+
+#define BLKSIZE 512
+
+static const char pack_redundant_usage[] =
+"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
+
+static int load_all_packs, verbose, alt_odb;
+
+struct llist_item {
+ struct llist_item *next;
+ const unsigned char *sha1;
+};
+static struct llist {
+ struct llist_item *front;
+ struct llist_item *back;
+ size_t size;
+} *all_objects; /* all objects which must be present in local packfiles */
+
+static struct pack_list {
+ struct pack_list *next;
+ struct packed_git *pack;
+ struct llist *unique_objects;
+ struct llist *all_objects;
+} *local_packs = NULL, *altodb_packs = NULL;
+
+struct pll {
+ struct pll *next;
+ struct pack_list *pl;
+};
+
+static struct llist_item *free_nodes;
+
+static inline void llist_item_put(struct llist_item *item)
+{
+ item->next = free_nodes;
+ free_nodes = item;
+}
+
+static inline struct llist_item *llist_item_get(void)
+{
+ struct llist_item *new;
+ if ( free_nodes ) {
+ new = free_nodes;
+ free_nodes = free_nodes->next;
+ } else {
+ int i = 1;
+ new = xmalloc(sizeof(struct llist_item) * BLKSIZE);
+ for (; i < BLKSIZE; i++)
+ llist_item_put(&new[i]);
+ }
+ return new;
+}
+
+static void llist_free(struct llist *list)
+{
+ while ((list->back = list->front)) {
+ list->front = list->front->next;
+ llist_item_put(list->back);
+ }
+ free(list);
+}
+
+static inline void llist_init(struct llist **list)
+{
+ *list = xmalloc(sizeof(struct llist));
+ (*list)->front = (*list)->back = NULL;
+ (*list)->size = 0;
+}
+
+static struct llist * llist_copy(struct llist *list)
+{
+ struct llist *ret;
+ struct llist_item *new, *old, *prev;
+
+ llist_init(&ret);
+
+ if ((ret->size = list->size) == 0)
+ return ret;
+
+ new = ret->front = llist_item_get();
+ new->sha1 = list->front->sha1;
+
+ old = list->front->next;
+ while (old) {
+ prev = new;
+ new = llist_item_get();
+ prev->next = new;
+ new->sha1 = old->sha1;
+ old = old->next;
+ }
+ new->next = NULL;
+ ret->back = new;
+
+ return ret;
+}
+
+static inline struct llist_item *llist_insert(struct llist *list,
+ struct llist_item *after,
+ const unsigned char *sha1)
+{
+ struct llist_item *new = llist_item_get();
+ new->sha1 = sha1;
+ new->next = NULL;
+
+ if (after != NULL) {
+ new->next = after->next;
+ after->next = new;
+ if (after == list->back)
+ list->back = new;
+ } else {/* insert in front */
+ if (list->size == 0)
+ list->back = new;
+ else
+ new->next = list->front;
+ list->front = new;
+ }
+ list->size++;
+ return new;
+}
+
+static inline struct llist_item *llist_insert_back(struct llist *list,
+ const unsigned char *sha1)
+{
+ return llist_insert(list, list->back, sha1);
+}
+
+static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
+ const unsigned char *sha1, struct llist_item *hint)
+{
+ struct llist_item *prev = NULL, *l;
+
+ l = (hint == NULL) ? list->front : hint;
+ while (l) {
+ int cmp = hashcmp(l->sha1, sha1);
+ if (cmp > 0) { /* we insert before this entry */
+ return llist_insert(list, prev, sha1);
+ }
+ if (!cmp) { /* already exists */
+ return l;
+ }
+ prev = l;
+ l = l->next;
+ }
+ /* insert at the end */
+ return llist_insert_back(list, sha1);
+}
+
+/* returns a pointer to an item in front of sha1 */
+static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint)
+{
+ struct llist_item *prev, *l;
+
+redo_from_start:
+ l = (hint == NULL) ? list->front : hint;
+ prev = NULL;
+ while (l) {
+ int cmp = hashcmp(l->sha1, sha1);
+ if (cmp > 0) /* not in list, since sorted */
+ return prev;
+ if (!cmp) { /* found */
+ if (prev == NULL) {
+ if (hint != NULL && hint != list->front) {
+ /* we don't know the previous element */
+ hint = NULL;
+ goto redo_from_start;
+ }
+ list->front = l->next;
+ } else
+ prev->next = l->next;
+ if (l == list->back)
+ list->back = prev;
+ llist_item_put(l);
+ list->size--;
+ return prev;
+ }
+ prev = l;
+ l = l->next;
+ }
+ return prev;
+}
+
+/* computes A\B */
+static void llist_sorted_difference_inplace(struct llist *A,
+ struct llist *B)
+{
+ struct llist_item *hint, *b;
+
+ hint = NULL;
+ b = B->front;
+
+ while (b) {
+ hint = llist_sorted_remove(A, b->sha1, hint);
+ b = b->next;
+ }
+}
+
+static inline struct pack_list * pack_list_insert(struct pack_list **pl,
+ struct pack_list *entry)
+{
+ struct pack_list *p = xmalloc(sizeof(struct pack_list));
+ memcpy(p, entry, sizeof(struct pack_list));
+ p->next = *pl;
+ *pl = p;
+ return p;
+}
+
+static inline size_t pack_list_size(struct pack_list *pl)
+{
+ size_t ret = 0;
+ while (pl) {
+ ret++;
+ pl = pl->next;
+ }
+ return ret;
+}
+
+static struct pack_list * pack_list_difference(const struct pack_list *A,
+ const struct pack_list *B)
+{
+ struct pack_list *ret;
+ const struct pack_list *pl;
+
+ if (A == NULL)
+ return NULL;
+
+ pl = B;
+ while (pl != NULL) {
+ if (A->pack == pl->pack)
+ return pack_list_difference(A->next, B);
+ pl = pl->next;
+ }
+ ret = xmalloc(sizeof(struct pack_list));
+ memcpy(ret, A, sizeof(struct pack_list));
+ ret->next = pack_list_difference(A->next, B);
+ return ret;
+}
+
+static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
+{
+ unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
+ const unsigned char *p1_base, *p2_base;
+ struct llist_item *p1_hint = NULL, *p2_hint = NULL;
+
+ p1_base = p1->pack->index_data;
+ p2_base = p2->pack->index_data;
+ p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8);
+ p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8);
+ p1_step = (p1->pack->index_version < 2) ? 24 : 20;
+ p2_step = (p2->pack->index_version < 2) ? 24 : 20;
+
+ while (p1_off < p1->pack->num_objects * p1_step &&
+ p2_off < p2->pack->num_objects * p2_step)
+ {
+ int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
+ /* cmp ~ p1 - p2 */
+ if (cmp == 0) {
+ p1_hint = llist_sorted_remove(p1->unique_objects,
+ p1_base + p1_off, p1_hint);
+ p2_hint = llist_sorted_remove(p2->unique_objects,
+ p1_base + p1_off, p2_hint);
+ p1_off += p1_step;
+ p2_off += p2_step;
+ continue;
+ }
+ if (cmp < 0) { /* p1 has the object, p2 doesn't */
+ p1_off += p1_step;
+ } else { /* p2 has the object, p1 doesn't */
+ p2_off += p2_step;
+ }
+ }
+}
+
+static void pll_free(struct pll *l)
+{
+ struct pll *old;
+ struct pack_list *opl;
+
+ while (l) {
+ old = l;
+ while (l->pl) {
+ opl = l->pl;
+ l->pl = opl->next;
+ free(opl);
+ }
+ l = l->next;
+ free(old);
+ }
+}
+
+/* all the permutations have to be free()d at the same time,
+ * since they refer to each other
+ */
+static struct pll * get_permutations(struct pack_list *list, int n)
+{
+ struct pll *subset, *ret = NULL, *new_pll = NULL, *pll;
+
+ if (list == NULL || pack_list_size(list) < n || n == 0)
+ return NULL;
+
+ if (n == 1) {
+ while (list) {
+ new_pll = xmalloc(sizeof(pll));
+ new_pll->pl = NULL;
+ pack_list_insert(&new_pll->pl, list);
+ new_pll->next = ret;
+ ret = new_pll;
+ list = list->next;
+ }
+ return ret;
+ }
+
+ while (list->next) {
+ subset = get_permutations(list->next, n - 1);
+ while (subset) {
+ new_pll = xmalloc(sizeof(pll));
+ new_pll->pl = subset->pl;
+ pack_list_insert(&new_pll->pl, list);
+ new_pll->next = ret;
+ ret = new_pll;
+ subset = subset->next;
+ }
+ list = list->next;
+ }
+ return ret;
+}
+
+static int is_superset(struct pack_list *pl, struct llist *list)
+{
+ struct llist *diff;
+
+ diff = llist_copy(list);
+
+ while (pl) {
+ llist_sorted_difference_inplace(diff, pl->all_objects);
+ if (diff->size == 0) { /* we're done */
+ llist_free(diff);
+ return 1;
+ }
+ pl = pl->next;
+ }
+ llist_free(diff);
+ return 0;
+}
+
+static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
+{
+ size_t ret = 0;
+ unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
+ const unsigned char *p1_base, *p2_base;
+
+ p1_base = p1->index_data;
+ p2_base = p2->index_data;
+ p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8);
+ p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8);
+ p1_step = (p1->index_version < 2) ? 24 : 20;
+ p2_step = (p2->index_version < 2) ? 24 : 20;
+
+ while (p1_off < p1->num_objects * p1_step &&
+ p2_off < p2->num_objects * p2_step)
+ {
+ int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
+ /* cmp ~ p1 - p2 */
+ if (cmp == 0) {
+ ret++;
+ p1_off += p1_step;
+ p2_off += p2_step;
+ continue;
+ }
+ if (cmp < 0) { /* p1 has the object, p2 doesn't */
+ p1_off += p1_step;
+ } else { /* p2 has the object, p1 doesn't */
+ p2_off += p2_step;
+ }
+ }
+ return ret;
+}
+
+/* another O(n^2) function ... */
+static size_t get_pack_redundancy(struct pack_list *pl)
+{
+ struct pack_list *subset;
+ size_t ret = 0;
+
+ if (pl == NULL)
+ return 0;
+
+ while ((subset = pl->next)) {
+ while (subset) {
+ ret += sizeof_union(pl->pack, subset->pack);
+ subset = subset->next;
+ }
+ pl = pl->next;
+ }
+ return ret;
+}
+
+static inline off_t pack_set_bytecount(struct pack_list *pl)
+{
+ off_t ret = 0;
+ while (pl) {
+ ret += pl->pack->pack_size;
+ ret += pl->pack->index_size;
+ pl = pl->next;
+ }
+ return ret;
+}
+
+static void minimize(struct pack_list **min)
+{
+ struct pack_list *pl, *unique = NULL,
+ *non_unique = NULL, *min_perm = NULL;
+ struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm;
+ struct llist *missing;
+ off_t min_perm_size = 0, perm_size;
+ int n;
+
+ pl = local_packs;
+ while (pl) {
+ if (pl->unique_objects->size)
+ pack_list_insert(&unique, pl);
+ else
+ pack_list_insert(&non_unique, pl);
+ pl = pl->next;
+ }
+ /* find out which objects are missing from the set of unique packs */
+ missing = llist_copy(all_objects);
+ pl = unique;
+ while (pl) {
+ llist_sorted_difference_inplace(missing, pl->all_objects);
+ pl = pl->next;
+ }
+
+ /* return if there are no objects missing from the unique set */
+ if (missing->size == 0) {
+ *min = unique;
+ return;
+ }
+
+ /* find the permutations which contain all missing objects */
+ for (n = 1; n <= pack_list_size(non_unique) && !perm_ok; n++) {
+ perm_all = perm = get_permutations(non_unique, n);
+ while (perm) {
+ if (is_superset(perm->pl, missing)) {
+ new_perm = xmalloc(sizeof(struct pll));
+ memcpy(new_perm, perm, sizeof(struct pll));
+ new_perm->next = perm_ok;
+ perm_ok = new_perm;
+ }
+ perm = perm->next;
+ }
+ if (perm_ok)
+ break;
+ pll_free(perm_all);
+ }
+ if (perm_ok == NULL)
+ die("Internal error: No complete sets found!");
+
+ /* find the permutation with the smallest size */
+ perm = perm_ok;
+ while (perm) {
+ perm_size = pack_set_bytecount(perm->pl);
+ if (!min_perm_size || min_perm_size > perm_size) {
+ min_perm_size = perm_size;
+ min_perm = perm->pl;
+ }
+ perm = perm->next;
+ }
+ *min = min_perm;
+ /* add the unique packs to the list */
+ pl = unique;
+ while (pl) {
+ pack_list_insert(min, pl);
+ pl = pl->next;
+ }
+}
+
+static void load_all_objects(void)
+{
+ struct pack_list *pl = local_packs;
+ struct llist_item *hint, *l;
+
+ llist_init(&all_objects);
+
+ while (pl) {
+ hint = NULL;
+ l = pl->all_objects->front;
+ while (l) {
+ hint = llist_insert_sorted_unique(all_objects,
+ l->sha1, hint);
+ l = l->next;
+ }
+ pl = pl->next;
+ }
+ /* remove objects present in remote packs */
+ pl = altodb_packs;
+ while (pl) {
+ llist_sorted_difference_inplace(all_objects, pl->all_objects);
+ pl = pl->next;
+ }
+}
+
+/* this scales like O(n^2) */
+static void cmp_local_packs(void)
+{
+ struct pack_list *subset, *pl = local_packs;
+
+ while ((subset = pl)) {
+ while ((subset = subset->next))
+ cmp_two_packs(pl, subset);
+ pl = pl->next;
+ }
+}
+
+static void scan_alt_odb_packs(void)
+{
+ struct pack_list *local, *alt;
+
+ alt = altodb_packs;
+ while (alt) {
+ local = local_packs;
+ while (local) {
+ llist_sorted_difference_inplace(local->unique_objects,
+ alt->all_objects);
+ local = local->next;
+ }
+ llist_sorted_difference_inplace(all_objects, alt->all_objects);
+ alt = alt->next;
+ }
+}
+
+static struct pack_list * add_pack(struct packed_git *p)
+{
+ struct pack_list l;
+ unsigned long off = 0, step;
+ const unsigned char *base;
+
+ if (!p->pack_local && !(alt_odb || verbose))
+ return NULL;
+
+ 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;
+ while (off < p->num_objects * step) {
+ llist_insert_back(l.all_objects, base + off);
+ off += step;
+ }
+ /* this list will be pruned in cmp_two_packs later */
+ l.unique_objects = llist_copy(l.all_objects);
+ if (p->pack_local)
+ return pack_list_insert(&local_packs, &l);
+ else
+ return pack_list_insert(&altodb_packs, &l);
+}
+
+static struct pack_list * add_pack_file(const char *filename)
+{
+ struct packed_git *p = packed_git;
+
+ if (strlen(filename) < 40)
+ die("Bad pack filename: %s", filename);
+
+ while (p) {
+ if (strstr(p->pack_name, filename))
+ return add_pack(p);
+ p = p->next;
+ }
+ die("Filename %s not found in packed_git", filename);
+}
+
+static void load_all(void)
+{
+ struct packed_git *p = packed_git;
+
+ while (p) {
+ add_pack(p);
+ p = p->next;
+ }
+}
+
+int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct pack_list *min, *red, *pl;
+ struct llist *ignore;
+ unsigned char *sha1;
+ char buf[42]; /* 40 byte sha1 + \n + \0 */
+
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(pack_redundant_usage);
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (!strcmp(arg, "--all")) {
+ load_all_packs = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--verbose")) {
+ verbose = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--alt-odb")) {
+ alt_odb = 1;
+ continue;
+ }
+ if (*arg == '-')
+ usage(pack_redundant_usage);
+ else
+ break;
+ }
+
+ prepare_packed_git();
+
+ if (load_all_packs)
+ load_all();
+ else
+ while (*(argv + i) != NULL)
+ add_pack_file(*(argv + i++));
+
+ if (local_packs == NULL)
+ die("Zero packs found!");
+
+ load_all_objects();
+
+ cmp_local_packs();
+ if (alt_odb)
+ scan_alt_odb_packs();
+
+ /* ignore objects given on stdin */
+ llist_init(&ignore);
+ if (!isatty(0)) {
+ while (fgets(buf, sizeof(buf), stdin)) {
+ sha1 = xmalloc(20);
+ if (get_sha1_hex(buf, sha1))
+ die("Bad sha1 on stdin: %s", buf);
+ llist_insert_sorted_unique(ignore, sha1, NULL);
+ }
+ }
+ llist_sorted_difference_inplace(all_objects, ignore);
+ pl = local_packs;
+ while (pl) {
+ llist_sorted_difference_inplace(pl->unique_objects, ignore);
+ pl = pl->next;
+ }
+
+ minimize(&min);
+
+ if (verbose) {
+ fprintf(stderr, "There are %lu packs available in alt-odbs.\n",
+ (unsigned long)pack_list_size(altodb_packs));
+ fprintf(stderr, "The smallest (bytewise) set of packs is:\n");
+ pl = min;
+ while (pl) {
+ fprintf(stderr, "\t%s\n", pl->pack->pack_name);
+ pl = pl->next;
+ }
+ fprintf(stderr, "containing %lu duplicate objects "
+ "with a total size of %lukb.\n",
+ (unsigned long)get_pack_redundancy(min),
+ (unsigned long)pack_set_bytecount(min)/1024);
+ fprintf(stderr, "A total of %lu unique objects were considered.\n",
+ (unsigned long)all_objects->size);
+ fprintf(stderr, "Redundant packs (with indexes):\n");
+ }
+ pl = red = pack_list_difference(local_packs, min);
+ while (pl) {
+ printf("%s\n%s\n",
+ sha1_pack_index_name(pl->pack->sha1),
+ pl->pack->pack_name);
+ pl = pl->next;
+ }
+ if (verbose)
+ fprintf(stderr, "%luMB of redundant packs in total.\n",
+ (unsigned long)pack_set_bytecount(red)/(1024*1024));
+
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "parse-options.h"
+#include "pack-refs.h"
+
+static char const * const pack_refs_usage[] = {
+ "git pack-refs [options]",
+ NULL
+};
+
+int cmd_pack_refs(int argc, const char **argv, const char *prefix)
+{
+ unsigned int flags = PACK_REFS_PRUNE;
+ struct option opts[] = {
+ OPT_BIT(0, "all", &flags, "pack everything", PACK_REFS_ALL),
+ OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE),
+ OPT_END(),
+ };
+ if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0))
+ usage_with_options(pack_refs_usage, opts);
+ return pack_refs(flags);
+}
--- /dev/null
+#include "cache.h"
+#include "exec_cmd.h"
+
+static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
+{
+ unsigned char result[20];
+ char name[50];
+
+ if (!patchlen)
+ return;
+
+ git_SHA1_Final(result, c);
+ memcpy(name, sha1_to_hex(id), 41);
+ printf("%s %s\n", sha1_to_hex(result), name);
+ git_SHA1_Init(c);
+}
+
+static int remove_space(char *line)
+{
+ char *src = line;
+ char *dst = line;
+ unsigned char c;
+
+ while ((c = *src++) != '\0') {
+ if (!isspace(c))
+ *dst++ = c;
+ }
+ return dst - line;
+}
+
+static void generate_id_list(void)
+{
+ static unsigned char sha1[20];
+ static char line[1000];
+ git_SHA_CTX ctx;
+ int patchlen = 0;
+
+ git_SHA1_Init(&ctx);
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ unsigned char n[20];
+ char *p = line;
+ int len;
+
+ if (!memcmp(line, "diff-tree ", 10))
+ p += 10;
+ else if (!memcmp(line, "commit ", 7))
+ p += 7;
+
+ if (!get_sha1_hex(p, n)) {
+ flush_current_id(patchlen, sha1, &ctx);
+ hashcpy(sha1, n);
+ patchlen = 0;
+ continue;
+ }
+
+ /* Ignore commit comments */
+ if (!patchlen && memcmp(line, "diff ", 5))
+ continue;
+
+ /* Ignore git-diff index header */
+ if (!memcmp(line, "index ", 6))
+ continue;
+
+ /* Ignore line numbers when computing the SHA1 of the patch */
+ if (!memcmp(line, "@@ -", 4))
+ continue;
+
+ /* Compute the sha without whitespace */
+ len = remove_space(line);
+ patchlen += len;
+ git_SHA1_Update(&ctx, line, len);
+ }
+ flush_current_id(patchlen, sha1, &ctx);
+}
+
+static const char patch_id_usage[] = "git patch-id < patch";
+
+int cmd_patch_id(int argc, const char **argv, const char *prefix)
+{
+ if (argc != 1)
+ usage(patch_id_usage);
+
+ generate_id_list();
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "progress.h"
+#include "parse-options.h"
+
+static const char * const prune_packed_usage[] = {
+ "git prune-packed [-n|--dry-run] [-q|--quiet]",
+ NULL
+};
+
+#define DRY_RUN 01
+#define VERBOSE 02
+
+static struct progress *progress;
+
+static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
+{
+ struct dirent *de;
+ char hex[40];
+
+ sprintf(hex, "%02x", i);
+ while ((de = readdir(dir)) != NULL) {
+ unsigned char sha1[20];
+ if (strlen(de->d_name) != 38)
+ continue;
+ memcpy(hex+2, de->d_name, 38);
+ if (get_sha1_hex(hex, sha1))
+ continue;
+ if (!has_sha1_pack(sha1))
+ continue;
+ memcpy(pathname + len, de->d_name, 38);
+ if (opts & DRY_RUN)
+ printf("rm -f %s\n", pathname);
+ else
+ unlink_or_warn(pathname);
+ display_progress(progress, i + 1);
+ }
+ pathname[len] = 0;
+ rmdir(pathname);
+}
+
+void prune_packed_objects(int opts)
+{
+ int i;
+ static char pathname[PATH_MAX];
+ const char *dir = get_object_directory();
+ int len = strlen(dir);
+
+ if (opts == VERBOSE)
+ progress = start_progress_delay("Removing duplicate objects",
+ 256, 95, 2);
+
+ if (len > PATH_MAX - 42)
+ die("impossible object directory");
+ memcpy(pathname, dir, len);
+ if (len && pathname[len-1] != '/')
+ pathname[len++] = '/';
+ for (i = 0; i < 256; i++) {
+ DIR *d;
+
+ display_progress(progress, i + 1);
+ sprintf(pathname + len, "%02x/", i);
+ d = opendir(pathname);
+ if (!d)
+ continue;
+ prune_dir(i, d, pathname, len + 3, opts);
+ closedir(d);
+ }
+ stop_progress(&progress);
+}
+
+int cmd_prune_packed(int argc, const char **argv, const char *prefix)
+{
+ int opts = isatty(2) ? VERBOSE : 0;
+ const struct option prune_packed_options[] = {
+ OPT_BIT('n', "dry-run", &opts, "dry run", DRY_RUN),
+ OPT_NEGBIT('q', "quiet", &opts, "be quiet", VERBOSE),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, prune_packed_options,
+ prune_packed_usage, 0);
+
+ prune_packed_objects(opts);
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "builtin.h"
+#include "reachable.h"
+#include "parse-options.h"
+#include "dir.h"
+
+static const char * const prune_usage[] = {
+ "git prune [-n] [-v] [--expire <time>] [--] [<head>...]",
+ NULL
+};
+static int show_only;
+static int verbose;
+static unsigned long expire;
+
+static int prune_tmp_object(const char *path, const char *filename)
+{
+ const char *fullpath = mkpath("%s/%s", path, filename);
+ struct stat st;
+ if (lstat(fullpath, &st))
+ return error("Could not stat '%s'", fullpath);
+ if (st.st_mtime > expire)
+ return 0;
+ printf("Removing stale temporary file %s\n", fullpath);
+ if (!show_only)
+ unlink_or_warn(fullpath);
+ return 0;
+}
+
+static int prune_object(char *path, const char *filename, const unsigned char *sha1)
+{
+ const char *fullpath = mkpath("%s/%s", path, filename);
+ struct stat st;
+ if (lstat(fullpath, &st))
+ return error("Could not stat '%s'", fullpath);
+ if (st.st_mtime > expire)
+ return 0;
+ if (show_only || verbose) {
+ enum object_type type = sha1_object_info(sha1, NULL);
+ printf("%s %s\n", sha1_to_hex(sha1),
+ (type > 0) ? typename(type) : "unknown");
+ }
+ if (!show_only)
+ unlink_or_warn(fullpath);
+ return 0;
+}
+
+static int prune_dir(int i, char *path)
+{
+ DIR *dir = opendir(path);
+ struct dirent *de;
+
+ if (!dir)
+ return 0;
+
+ while ((de = readdir(dir)) != NULL) {
+ char name[100];
+ unsigned char sha1[20];
+
+ if (is_dot_or_dotdot(de->d_name))
+ continue;
+ if (strlen(de->d_name) == 38) {
+ sprintf(name, "%02x", i);
+ memcpy(name+2, de->d_name, 39);
+ if (get_sha1_hex(name, sha1) < 0)
+ break;
+
+ /*
+ * Do we know about this object?
+ * It must have been reachable
+ */
+ if (lookup_object(sha1))
+ continue;
+
+ prune_object(path, de->d_name, sha1);
+ continue;
+ }
+ if (!prefixcmp(de->d_name, "tmp_obj_")) {
+ prune_tmp_object(path, de->d_name);
+ continue;
+ }
+ fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+ }
+ if (!show_only)
+ rmdir(path);
+ closedir(dir);
+ return 0;
+}
+
+static void prune_object_dir(const char *path)
+{
+ int i;
+ for (i = 0; i < 256; i++) {
+ static char dir[4096];
+ sprintf(dir, "%s/%02x", path, i);
+ prune_dir(i, dir);
+ }
+}
+
+/*
+ * Write errors (particularly out of space) can result in
+ * failed temporary packs (and more rarely indexes and other
+ * files beginning with "tmp_") accumulating in the object
+ * and the pack directories.
+ */
+static void remove_temporary_files(const char *path)
+{
+ DIR *dir;
+ struct dirent *de;
+
+ dir = opendir(path);
+ if (!dir) {
+ fprintf(stderr, "Unable to open directory %s\n", path);
+ return;
+ }
+ while ((de = readdir(dir)) != NULL)
+ if (!prefixcmp(de->d_name, "tmp_"))
+ prune_tmp_object(path, de->d_name);
+ closedir(dir);
+}
+
+int cmd_prune(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info revs;
+ const struct option options[] = {
+ OPT_BOOLEAN('n', NULL, &show_only,
+ "do not remove, show only"),
+ OPT_BOOLEAN('v', NULL, &verbose,
+ "report pruned objects"),
+ OPT_DATE(0, "expire", &expire,
+ "expire objects older than <time>"),
+ OPT_END()
+ };
+ char *s;
+
+ expire = ULONG_MAX;
+ save_commit_buffer = 0;
+ read_replace_refs = 0;
+ init_revisions(&revs, prefix);
+
+ argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
+ while (argc--) {
+ unsigned char sha1[20];
+ const char *name = *argv++;
+
+ if (!get_sha1(name, sha1)) {
+ struct object *object = parse_object(sha1);
+ if (!object)
+ die("bad object: %s", name);
+ add_pending_object(&revs, object, "");
+ }
+ else
+ die("unrecognized argument: %s", name);
+ }
+ mark_reachable_objects(&revs, 1);
+ prune_object_dir(get_object_directory());
+
+ prune_packed_objects(show_only);
+ remove_temporary_files(get_object_directory());
+ s = xstrdup(mkpath("%s/pack", get_object_directory()));
+ remove_temporary_files(s);
+ free(s);
+ return 0;
+}
--- /dev/null
+/*
+ * "git push"
+ */
+#include "cache.h"
+#include "refs.h"
+#include "run-command.h"
+#include "builtin.h"
+#include "remote.h"
+#include "transport.h"
+#include "parse-options.h"
+
+static const char * const push_usage[] = {
+ "git push [<options>] [<repository> <refspec>...]",
+ NULL,
+};
+
+static int thin;
+static int deleterefs;
+static const char *receivepack;
+
+static const char **refspec;
+static int refspec_nr;
+
+static void add_refspec(const char *ref)
+{
+ int nr = refspec_nr + 1;
+ refspec = xrealloc(refspec, nr * sizeof(char *));
+ refspec[nr-1] = ref;
+ refspec_nr = nr;
+}
+
+static void set_refspecs(const char **refs, int 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;
+ if (deleterefs) {
+ tag = xmalloc(len+1);
+ strcpy(tag, ":refs/tags/");
+ } else {
+ tag = xmalloc(len);
+ strcpy(tag, "refs/tags/");
+ }
+ strcat(tag, refs[i]);
+ ref = tag;
+ } else if (deleterefs && !strchr(ref, ':')) {
+ char *delref;
+ int len = strlen(ref)+1;
+ delref = xmalloc(len+1);
+ strcpy(delref, ":");
+ strcat(delref, ref);
+ ref = delref;
+ } else if (deleterefs)
+ die("--delete only accepts plain target ref names");
+ add_refspec(ref);
+ }
+}
+
+static void setup_push_tracking(void)
+{
+ struct strbuf refspec = STRBUF_INIT;
+ struct branch *branch = branch_get(NULL);
+ if (!branch)
+ die("You are not currently on a branch.");
+ if (!branch->merge_nr || !branch->merge)
+ die("The current branch %s is not tracking anything.",
+ branch->name);
+ if (branch->merge_nr != 1)
+ die("The current branch %s is tracking multiple branches, "
+ "refusing to push.", branch->name);
+ strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
+ add_refspec(refspec.buf);
+}
+
+static void setup_default_push_refspecs(void)
+{
+ switch (push_default) {
+ default:
+ case PUSH_DEFAULT_MATCHING:
+ add_refspec(":");
+ break;
+
+ case PUSH_DEFAULT_TRACKING:
+ setup_push_tracking();
+ break;
+
+ case PUSH_DEFAULT_CURRENT:
+ add_refspec("HEAD");
+ break;
+
+ case PUSH_DEFAULT_NOTHING:
+ die("You didn't specify any refspecs to push, and "
+ "push.default is \"nothing\".");
+ break;
+ }
+}
+
+static int push_with_options(struct transport *transport, int flags)
+{
+ int err;
+ int nonfastforward;
+ if (receivepack)
+ transport_set_option(transport,
+ TRANS_OPT_RECEIVEPACK, receivepack);
+ if (thin)
+ transport_set_option(transport, TRANS_OPT_THIN, "yes");
+
+ if (flags & TRANSPORT_PUSH_VERBOSE)
+ fprintf(stderr, "Pushing to %s\n", transport->url);
+ err = transport_push(transport, refspec_nr, refspec, flags,
+ &nonfastforward);
+ if (err != 0)
+ error("failed to push some refs to '%s'", transport->url);
+
+ err |= transport_disconnect(transport);
+
+ if (!err)
+ return 0;
+
+ if (nonfastforward && advice_push_nonfastforward) {
+ printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
+ "Merge the remote changes before pushing again. See the 'Note about\n"
+ "fast-forwards' section of 'git push --help' for details.\n");
+ }
+
+ return 1;
+}
+
+static int do_push(const char *repo, int flags)
+{
+ int i, errs;
+ struct remote *remote = remote_get(repo);
+ const char **url;
+ int url_nr;
+
+ if (!remote) {
+ if (repo)
+ die("bad repository '%s'", repo);
+ die("No destination configured to push to.");
+ }
+
+ if (remote->mirror)
+ flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+
+ if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
+ if (!strcmp(*refspec, "refs/tags/*"))
+ return error("--all and --tags are incompatible");
+ return error("--all can't be combined with refspecs");
+ }
+
+ if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) {
+ if (!strcmp(*refspec, "refs/tags/*"))
+ return error("--mirror and --tags are incompatible");
+ return error("--mirror can't be combined with refspecs");
+ }
+
+ if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
+ (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
+ return error("--all and --mirror are incompatible");
+ }
+
+ if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) {
+ if (remote->push_refspec_nr) {
+ refspec = remote->push_refspec;
+ refspec_nr = remote->push_refspec_nr;
+ } else if (!(flags & TRANSPORT_PUSH_MIRROR))
+ setup_default_push_refspecs();
+ }
+ errs = 0;
+ if (remote->pushurl_nr) {
+ url = remote->pushurl;
+ url_nr = remote->pushurl_nr;
+ } else {
+ url = remote->url;
+ url_nr = remote->url_nr;
+ }
+ if (url_nr) {
+ for (i = 0; i < url_nr; i++) {
+ struct transport *transport =
+ transport_get(remote, url[i]);
+ if (push_with_options(transport, flags))
+ errs++;
+ }
+ } else {
+ struct transport *transport =
+ transport_get(remote, NULL);
+
+ if (push_with_options(transport, flags))
+ errs++;
+ }
+ return !!errs;
+}
+
+int cmd_push(int argc, const char **argv, const char *prefix)
+{
+ int flags = 0;
+ int tags = 0;
+ int rc;
+ const char *repo = NULL; /* default repository */
+ struct option options[] = {
+ OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET),
+ OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE),
+ OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
+ OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
+ OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
+ (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
+ OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"),
+ OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"),
+ OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
+ OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
+ OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
+ OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
+ OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
+ OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
+ OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
+ TRANSPORT_PUSH_SET_UPSTREAM),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, options, push_usage, 0);
+
+ if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
+ die("--delete is incompatible with --all, --mirror and --tags");
+ if (deleterefs && argc < 2)
+ die("--delete doesn't make sense without any refs");
+
+ if (tags)
+ add_refspec("refs/tags/*");
+
+ if (argc > 0) {
+ repo = argv[0];
+ set_refspecs(argv + 1, argc - 1);
+ }
+
+ rc = do_push(repo, flags);
+ if (rc == -1)
+ usage_with_options(push_usage, options);
+ else
+ return rc;
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+
+#include "cache.h"
+#include "object.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "cache-tree.h"
+#include "unpack-trees.h"
+#include "dir.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "resolve-undo.h"
+
+static int nr_trees;
+static struct tree *trees[MAX_UNPACK_TREES];
+
+static int list_tree(unsigned char *sha1)
+{
+ struct tree *tree;
+
+ if (nr_trees >= MAX_UNPACK_TREES)
+ die("I cannot read more than %d trees", MAX_UNPACK_TREES);
+ tree = parse_tree_indirect(sha1);
+ if (!tree)
+ return -1;
+ trees[nr_trees++] = tree;
+ return 0;
+}
+
+static const char * const read_tree_usage[] = {
+ "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
+ NULL
+};
+
+static int index_output_cb(const struct option *opt, const char *arg,
+ int unset)
+{
+ set_alternate_index_output(arg);
+ return 0;
+}
+
+static int exclude_per_directory_cb(const struct option *opt, const char *arg,
+ int unset)
+{
+ struct dir_struct *dir;
+ struct unpack_trees_options *opts;
+
+ opts = (struct unpack_trees_options *)opt->value;
+
+ if (opts->dir)
+ die("more than one --exclude-per-directory given.");
+
+ dir = xcalloc(1, sizeof(*opts->dir));
+ dir->flags |= DIR_SHOW_IGNORED;
+ dir->exclude_per_dir = arg;
+ opts->dir = dir;
+ /* We do not need to nor want to do read-directory
+ * here; we are merely interested in reusing the
+ * per directory ignore stack mechanism.
+ */
+ return 0;
+}
+
+static void debug_stage(const char *label, struct cache_entry *ce,
+ struct unpack_trees_options *o)
+{
+ printf("%s ", label);
+ if (!ce)
+ printf("(missing)\n");
+ else if (ce == o->df_conflict_entry)
+ printf("(conflict)\n");
+ else
+ printf("%06o #%d %s %.8s\n",
+ ce->ce_mode, ce_stage(ce), ce->name,
+ sha1_to_hex(ce->sha1));
+}
+
+static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o)
+{
+ int i;
+
+ printf("* %d-way merge\n", o->merge_size);
+ debug_stage("index", stages[0], o);
+ for (i = 1; i <= o->merge_size; i++) {
+ char buf[24];
+ sprintf(buf, "ent#%d", i);
+ debug_stage(buf, stages[i], o);
+ }
+ return 0;
+}
+
+static struct lock_file lock_file;
+
+int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
+{
+ int i, newfd, stage = 0;
+ unsigned char sha1[20];
+ struct tree_desc t[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+ int prefix_set = 0;
+ const struct option read_tree_options[] = {
+ { OPTION_CALLBACK, 0, "index-output", NULL, "FILE",
+ "write resulting index to <FILE>",
+ PARSE_OPT_NONEG, index_output_cb },
+ OPT__VERBOSE(&opts.verbose_update),
+ OPT_GROUP("Merging"),
+ OPT_SET_INT('m', NULL, &opts.merge,
+ "perform a merge in addition to a read", 1),
+ OPT_SET_INT(0, "trivial", &opts.trivial_merges_only,
+ "3-way merge if no file level merging required", 1),
+ OPT_SET_INT(0, "aggressive", &opts.aggressive,
+ "3-way merge in presence of adds and removes", 1),
+ OPT_SET_INT(0, "reset", &opts.reset,
+ "same as -m, but discard unmerged entries", 1),
+ { OPTION_STRING, 0, "prefix", &opts.prefix, "<subdirectory>/",
+ "read the tree into the index under <subdirectory>/",
+ PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP },
+ OPT_SET_INT('u', NULL, &opts.update,
+ "update working tree with merge result", 1),
+ { OPTION_CALLBACK, 0, "exclude-per-directory", &opts,
+ "gitignore",
+ "allow explicitly ignored files to be overwritten",
+ PARSE_OPT_NONEG, exclude_per_directory_cb },
+ OPT_SET_INT('i', NULL, &opts.index_only,
+ "don't check the working tree after merging", 1),
+ OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
+ "skip applying sparse checkout filter", 1),
+ OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack,
+ "debug unpack-trees", 1),
+ OPT_END()
+ };
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = -1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+
+ git_config(git_default_config, NULL);
+
+ argc = parse_options(argc, argv, unused_prefix, read_tree_options,
+ read_tree_usage, 0);
+
+ newfd = hold_locked_index(&lock_file, 1);
+
+ prefix_set = opts.prefix ? 1 : 0;
+ if (1 < opts.merge + opts.reset + prefix_set)
+ die("Which one? -m, --reset, or --prefix?");
+
+ if (opts.reset || opts.merge || opts.prefix) {
+ if (read_cache_unmerged() && (opts.prefix || opts.merge))
+ die("You need to resolve your current index first");
+ stage = opts.merge = 1;
+ }
+ resolve_undo_clear();
+
+ for (i = 0; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (get_sha1(arg, sha1))
+ die("Not a valid object name %s", arg);
+ if (list_tree(sha1) < 0)
+ die("failed to unpack tree object %s", arg);
+ stage++;
+ }
+ if (1 < opts.index_only + opts.update)
+ die("-u and -i at the same time makes no sense");
+ if ((opts.update||opts.index_only) && !opts.merge)
+ die("%s is meaningless without -m, --reset, or --prefix",
+ opts.update ? "-u" : "-i");
+ if ((opts.dir && !opts.update))
+ die("--exclude-per-directory is meaningless unless -u");
+ if (opts.merge && !opts.index_only)
+ setup_work_tree();
+
+ if (opts.merge) {
+ if (stage < 2)
+ die("just how do you expect me to merge %d trees?", stage-1);
+ switch (stage - 1) {
+ case 1:
+ opts.fn = opts.prefix ? bind_merge : oneway_merge;
+ break;
+ case 2:
+ opts.fn = twoway_merge;
+ opts.initial_checkout = is_cache_unborn();
+ break;
+ case 3:
+ default:
+ opts.fn = threeway_merge;
+ break;
+ }
+
+ if (stage - 1 >= 3)
+ opts.head_idx = stage - 2;
+ else
+ opts.head_idx = 1;
+ }
+
+ if (opts.debug_unpack)
+ opts.fn = debug_merge;
+
+ cache_tree_free(&active_cache_tree);
+ for (i = 0; i < nr_trees; i++) {
+ struct tree *tree = trees[i];
+ parse_tree(tree);
+ init_tree_desc(t+i, tree->buffer, tree->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return 128;
+
+ if (opts.debug_unpack)
+ return 0; /* do not write the index out */
+
+ /*
+ * When reading only one tree (either the most basic form,
+ * "-m ent" or "--reset ent" form), we can obtain a fully
+ * valid cache-tree because the index must match exactly
+ * what came from the tree.
+ *
+ * The same holds true if we are switching between two trees
+ * using read-tree -m A B. The index must match B after that.
+ */
+ if (nr_trees == 1 && !opts.prefix)
+ prime_cache_tree(&active_cache_tree, trees[0]);
+ else if (nr_trees == 2 && opts.merge)
+ prime_cache_tree(&active_cache_tree, trees[1]);
+
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(&lock_file))
+ die("unable to write new index file");
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "pack.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "sideband.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "commit.h"
+#include "object.h"
+#include "remote.h"
+#include "transport.h"
+
+static const char receive_pack_usage[] = "git receive-pack <git-dir>";
+
+enum deny_action {
+ DENY_UNCONFIGURED,
+ DENY_IGNORE,
+ DENY_WARN,
+ DENY_REFUSE,
+};
+
+static int deny_deletes;
+static int deny_non_fast_forwards;
+static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
+static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
+static int receive_fsck_objects;
+static int receive_unpack_limit = -1;
+static int transfer_unpack_limit = -1;
+static int unpack_limit = 100;
+static int report_status;
+static int use_sideband;
+static int prefer_ofs_delta = 1;
+static int auto_update_server_info;
+static int auto_gc = 1;
+static const char *head_name;
+static int sent_capabilities;
+
+static enum deny_action parse_deny_action(const char *var, const char *value)
+{
+ if (value) {
+ if (!strcasecmp(value, "ignore"))
+ return DENY_IGNORE;
+ if (!strcasecmp(value, "warn"))
+ return DENY_WARN;
+ if (!strcasecmp(value, "refuse"))
+ return DENY_REFUSE;
+ }
+ if (git_config_bool(var, value))
+ return DENY_REFUSE;
+ return DENY_IGNORE;
+}
+
+static int receive_pack_config(const char *var, const char *value, void *cb)
+{
+ if (strcmp(var, "receive.denydeletes") == 0) {
+ deny_deletes = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.denynonfastforwards") == 0) {
+ deny_non_fast_forwards = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.unpacklimit") == 0) {
+ receive_unpack_limit = git_config_int(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "transfer.unpacklimit") == 0) {
+ transfer_unpack_limit = git_config_int(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.fsckobjects") == 0) {
+ receive_fsck_objects = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (!strcmp(var, "receive.denycurrentbranch")) {
+ deny_current_branch = parse_deny_action(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.denydeletecurrent") == 0) {
+ deny_delete_current = parse_deny_action(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
+ prefer_ofs_delta = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.updateserverinfo") == 0) {
+ auto_update_server_info = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.autogc") == 0) {
+ auto_gc = git_config_bool(var, value);
+ return 0;
+ }
+
+ return git_default_config(var, value, cb);
+}
+
+static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+ if (sent_capabilities)
+ packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
+ else
+ packet_write(1, "%s %s%c%s%s\n",
+ sha1_to_hex(sha1), path, 0,
+ " report-status delete-refs side-band-64k",
+ prefer_ofs_delta ? " ofs-delta" : "");
+ sent_capabilities = 1;
+ return 0;
+}
+
+static void write_head_info(void)
+{
+ for_each_ref(show_ref, NULL);
+ if (!sent_capabilities)
+ show_ref("capabilities^{}", null_sha1, 0, NULL);
+
+}
+
+struct command {
+ struct command *next;
+ const char *error_string;
+ unsigned char old_sha1[20];
+ unsigned char new_sha1[20];
+ char ref_name[FLEX_ARRAY]; /* more */
+};
+
+static struct command *commands;
+
+static const char pre_receive_hook[] = "hooks/pre-receive";
+static const char post_receive_hook[] = "hooks/post-receive";
+
+static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
+
+static void report_message(const char *prefix, const char *err, va_list params)
+{
+ int sz = strlen(prefix);
+ char msg[4096];
+
+ strncpy(msg, prefix, sz);
+ sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params);
+ if (sz > (sizeof(msg) - 1))
+ sz = sizeof(msg) - 1;
+ msg[sz++] = '\n';
+
+ if (use_sideband)
+ send_sideband(1, 2, msg, sz, use_sideband);
+ else
+ xwrite(2, msg, sz);
+}
+
+static void rp_warning(const char *err, ...)
+{
+ va_list params;
+ va_start(params, err);
+ report_message("warning: ", err, params);
+ va_end(params);
+}
+
+static void rp_error(const char *err, ...)
+{
+ va_list params;
+ va_start(params, err);
+ report_message("error: ", err, params);
+ va_end(params);
+}
+
+static int copy_to_sideband(int in, int out, void *arg)
+{
+ char data[128];
+ while (1) {
+ ssize_t sz = xread(in, data, sizeof(data));
+ if (sz <= 0)
+ break;
+ send_sideband(1, 2, data, sz, use_sideband);
+ }
+ close(in);
+ return 0;
+}
+
+static int run_receive_hook(const char *hook_name)
+{
+ static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
+ struct command *cmd;
+ struct child_process proc;
+ struct async muxer;
+ const char *argv[2];
+ int have_input = 0, code;
+
+ for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
+ if (!cmd->error_string)
+ have_input = 1;
+ }
+
+ if (!have_input || access(hook_name, X_OK) < 0)
+ return 0;
+
+ argv[0] = hook_name;
+ argv[1] = NULL;
+
+ memset(&proc, 0, sizeof(proc));
+ proc.argv = argv;
+ proc.in = -1;
+ proc.stdout_to_stderr = 1;
+
+ if (use_sideband) {
+ memset(&muxer, 0, sizeof(muxer));
+ muxer.proc = copy_to_sideband;
+ muxer.in = -1;
+ code = start_async(&muxer);
+ if (code)
+ return code;
+ proc.err = muxer.in;
+ }
+
+ code = start_command(&proc);
+ if (code) {
+ if (use_sideband)
+ finish_async(&muxer);
+ return code;
+ }
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!cmd->error_string) {
+ size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
+ sha1_to_hex(cmd->old_sha1),
+ sha1_to_hex(cmd->new_sha1),
+ cmd->ref_name);
+ if (write_in_full(proc.in, buf, n) != n)
+ break;
+ }
+ }
+ close(proc.in);
+ if (use_sideband)
+ finish_async(&muxer);
+ return finish_command(&proc);
+}
+
+static int run_update_hook(struct command *cmd)
+{
+ static const char update_hook[] = "hooks/update";
+ const char *argv[5];
+ struct child_process proc;
+ int code;
+
+ if (access(update_hook, X_OK) < 0)
+ return 0;
+
+ argv[0] = update_hook;
+ argv[1] = cmd->ref_name;
+ argv[2] = sha1_to_hex(cmd->old_sha1);
+ argv[3] = sha1_to_hex(cmd->new_sha1);
+ argv[4] = NULL;
+
+ memset(&proc, 0, sizeof(proc));
+ proc.no_stdin = 1;
+ proc.stdout_to_stderr = 1;
+ proc.err = use_sideband ? -1 : 0;
+ proc.argv = argv;
+
+ code = start_command(&proc);
+ if (code)
+ return code;
+ if (use_sideband)
+ copy_to_sideband(proc.err, -1, NULL);
+ return finish_command(&proc);
+}
+
+static int is_ref_checked_out(const char *ref)
+{
+ if (is_bare_repository())
+ return 0;
+
+ if (!head_name)
+ return 0;
+ return !strcmp(head_name, ref);
+}
+
+static char *refuse_unconfigured_deny_msg[] = {
+ "By default, updating the current branch in a non-bare repository",
+ "is denied, because it will make the index and work tree inconsistent",
+ "with what you pushed, and will require 'git reset --hard' to match",
+ "the work tree to HEAD.",
+ "",
+ "You can set 'receive.denyCurrentBranch' configuration variable to",
+ "'ignore' or 'warn' in the remote repository to allow pushing into",
+ "its current branch; however, this is not recommended unless you",
+ "arranged to update its work tree to match what you pushed in some",
+ "other way.",
+ "",
+ "To squelch this message and still keep the default behaviour, set",
+ "'receive.denyCurrentBranch' configuration variable to 'refuse'."
+};
+
+static void refuse_unconfigured_deny(void)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++)
+ rp_error("%s", refuse_unconfigured_deny_msg[i]);
+}
+
+static char *refuse_unconfigured_deny_delete_current_msg[] = {
+ "By default, deleting the current branch is denied, because the next",
+ "'git clone' won't result in any file checked out, causing confusion.",
+ "",
+ "You can set 'receive.denyDeleteCurrent' configuration variable to",
+ "'warn' or 'ignore' in the remote repository to allow deleting the",
+ "current branch, with or without a warning message.",
+ "",
+ "To squelch this message, you can set it to 'refuse'."
+};
+
+static void refuse_unconfigured_deny_delete_current(void)
+{
+ int i;
+ for (i = 0;
+ i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg);
+ i++)
+ rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
+}
+
+static const char *update(struct command *cmd)
+{
+ const char *name = cmd->ref_name;
+ unsigned char *old_sha1 = cmd->old_sha1;
+ unsigned char *new_sha1 = cmd->new_sha1;
+ struct ref_lock *lock;
+
+ /* only refs/... are allowed */
+ if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
+ rp_error("refusing to create funny ref '%s' remotely", name);
+ return "funny refname";
+ }
+
+ if (is_ref_checked_out(name)) {
+ switch (deny_current_branch) {
+ case DENY_IGNORE:
+ break;
+ case DENY_WARN:
+ rp_warning("updating the current branch");
+ break;
+ case DENY_REFUSE:
+ case DENY_UNCONFIGURED:
+ rp_error("refusing to update checked out branch: %s", name);
+ if (deny_current_branch == DENY_UNCONFIGURED)
+ refuse_unconfigured_deny();
+ return "branch is currently checked out";
+ }
+ }
+
+ if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
+ error("unpack should have generated %s, "
+ "but I can't find it!", sha1_to_hex(new_sha1));
+ return "bad pack";
+ }
+
+ if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
+ if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
+ rp_error("denying ref deletion for %s", name);
+ return "deletion prohibited";
+ }
+
+ if (!strcmp(name, head_name)) {
+ switch (deny_delete_current) {
+ case DENY_IGNORE:
+ break;
+ case DENY_WARN:
+ rp_warning("deleting the current branch");
+ break;
+ case DENY_REFUSE:
+ case DENY_UNCONFIGURED:
+ if (deny_delete_current == DENY_UNCONFIGURED)
+ refuse_unconfigured_deny_delete_current();
+ rp_error("refusing to delete the current branch: %s", name);
+ return "deletion of the current branch prohibited";
+ }
+ }
+ }
+
+ if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
+ !is_null_sha1(old_sha1) &&
+ !prefixcmp(name, "refs/heads/")) {
+ struct object *old_object, *new_object;
+ struct commit *old_commit, *new_commit;
+ struct commit_list *bases, *ent;
+
+ old_object = parse_object(old_sha1);
+ new_object = parse_object(new_sha1);
+
+ if (!old_object || !new_object ||
+ old_object->type != OBJ_COMMIT ||
+ new_object->type != OBJ_COMMIT) {
+ error("bad sha1 objects for %s", name);
+ return "bad ref";
+ }
+ old_commit = (struct commit *)old_object;
+ new_commit = (struct commit *)new_object;
+ bases = get_merge_bases(old_commit, new_commit, 1);
+ for (ent = bases; ent; ent = ent->next)
+ if (!hashcmp(old_sha1, ent->item->object.sha1))
+ break;
+ free_commit_list(bases);
+ if (!ent) {
+ rp_error("denying non-fast-forward %s"
+ " (you should pull first)", name);
+ return "non-fast-forward";
+ }
+ }
+ if (run_update_hook(cmd)) {
+ rp_error("hook declined to update %s", name);
+ return "hook declined";
+ }
+
+ if (is_null_sha1(new_sha1)) {
+ if (!parse_object(old_sha1)) {
+ rp_warning("Allowing deletion of corrupt ref.");
+ old_sha1 = NULL;
+ }
+ if (delete_ref(name, old_sha1, 0)) {
+ rp_error("failed to delete %s", name);
+ return "failed to delete";
+ }
+ return NULL; /* good */
+ }
+ else {
+ lock = lock_any_ref_for_update(name, old_sha1, 0);
+ if (!lock) {
+ rp_error("failed to lock %s", name);
+ return "failed to lock";
+ }
+ if (write_ref_sha1(lock, new_sha1, "push")) {
+ return "failed to write"; /* error() already called */
+ }
+ return NULL; /* good */
+ }
+}
+
+static char update_post_hook[] = "hooks/post-update";
+
+static void run_update_post_hook(struct command *cmd)
+{
+ struct command *cmd_p;
+ int argc;
+ const char **argv;
+ struct child_process proc;
+
+ for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+ if (cmd_p->error_string)
+ continue;
+ argc++;
+ }
+ if (!argc || access(update_post_hook, X_OK) < 0)
+ return;
+ argv = xmalloc(sizeof(*argv) * (2 + argc));
+ argv[0] = update_post_hook;
+
+ for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+ char *p;
+ if (cmd_p->error_string)
+ continue;
+ p = xmalloc(strlen(cmd_p->ref_name) + 1);
+ strcpy(p, cmd_p->ref_name);
+ argv[argc] = p;
+ argc++;
+ }
+ argv[argc] = NULL;
+
+ memset(&proc, 0, sizeof(proc));
+ proc.no_stdin = 1;
+ proc.stdout_to_stderr = 1;
+ proc.err = use_sideband ? -1 : 0;
+ proc.argv = argv;
+
+ if (!start_command(&proc)) {
+ if (use_sideband)
+ copy_to_sideband(proc.err, -1, NULL);
+ finish_command(&proc);
+ }
+}
+
+static void execute_commands(const char *unpacker_error)
+{
+ struct command *cmd = commands;
+ unsigned char sha1[20];
+
+ if (unpacker_error) {
+ while (cmd) {
+ cmd->error_string = "n/a (unpacker error)";
+ cmd = cmd->next;
+ }
+ return;
+ }
+
+ if (run_receive_hook(pre_receive_hook)) {
+ while (cmd) {
+ cmd->error_string = "pre-receive hook declined";
+ cmd = cmd->next;
+ }
+ return;
+ }
+
+ head_name = resolve_ref("HEAD", sha1, 0, NULL);
+
+ while (cmd) {
+ cmd->error_string = update(cmd);
+ cmd = cmd->next;
+ }
+}
+
+static void read_head_info(void)
+{
+ struct command **p = &commands;
+ for (;;) {
+ static char line[1000];
+ unsigned char old_sha1[20], new_sha1[20];
+ struct command *cmd;
+ char *refname;
+ int len, reflen;
+
+ len = packet_read_line(0, line, sizeof(line));
+ if (!len)
+ break;
+ if (line[len-1] == '\n')
+ line[--len] = 0;
+ if (len < 83 ||
+ line[40] != ' ' ||
+ line[81] != ' ' ||
+ get_sha1_hex(line, old_sha1) ||
+ get_sha1_hex(line + 41, new_sha1))
+ die("protocol error: expected old/new/ref, got '%s'",
+ line);
+
+ refname = line + 82;
+ reflen = strlen(refname);
+ if (reflen + 82 < len) {
+ if (strstr(refname + reflen + 1, "report-status"))
+ report_status = 1;
+ if (strstr(refname + reflen + 1, "side-band-64k"))
+ use_sideband = LARGE_PACKET_MAX;
+ }
+ cmd = xmalloc(sizeof(struct command) + len - 80);
+ hashcpy(cmd->old_sha1, old_sha1);
+ hashcpy(cmd->new_sha1, new_sha1);
+ memcpy(cmd->ref_name, line + 82, len - 81);
+ cmd->error_string = NULL;
+ cmd->next = NULL;
+ *p = cmd;
+ p = &cmd->next;
+ }
+}
+
+static const char *parse_pack_header(struct pack_header *hdr)
+{
+ switch (read_pack_header(0, hdr)) {
+ case PH_ERROR_EOF:
+ return "eof before pack header was fully read";
+
+ case PH_ERROR_PACK_SIGNATURE:
+ return "protocol error (pack signature mismatch detected)";
+
+ case PH_ERROR_PROTOCOL:
+ return "protocol error (pack version unsupported)";
+
+ default:
+ return "unknown error in parse_pack_header";
+
+ case 0:
+ return NULL;
+ }
+}
+
+static const char *pack_lockfile;
+
+static const char *unpack(void)
+{
+ struct pack_header hdr;
+ const char *hdr_err;
+ char hdr_arg[38];
+
+ hdr_err = parse_pack_header(&hdr);
+ if (hdr_err)
+ return hdr_err;
+ snprintf(hdr_arg, sizeof(hdr_arg),
+ "--pack_header=%"PRIu32",%"PRIu32,
+ ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
+
+ if (ntohl(hdr.hdr_entries) < unpack_limit) {
+ int code, i = 0;
+ const char *unpacker[4];
+ unpacker[i++] = "unpack-objects";
+ if (receive_fsck_objects)
+ unpacker[i++] = "--strict";
+ unpacker[i++] = hdr_arg;
+ unpacker[i++] = NULL;
+ code = run_command_v_opt(unpacker, RUN_GIT_CMD);
+ if (!code)
+ return NULL;
+ return "unpack-objects abnormal exit";
+ } else {
+ const char *keeper[7];
+ int s, status, i = 0;
+ char keep_arg[256];
+ struct child_process ip;
+
+ s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
+ if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+ strcpy(keep_arg + s, "localhost");
+
+ keeper[i++] = "index-pack";
+ keeper[i++] = "--stdin";
+ if (receive_fsck_objects)
+ keeper[i++] = "--strict";
+ keeper[i++] = "--fix-thin";
+ keeper[i++] = hdr_arg;
+ keeper[i++] = keep_arg;
+ keeper[i++] = NULL;
+ memset(&ip, 0, sizeof(ip));
+ ip.argv = keeper;
+ ip.out = -1;
+ ip.git_cmd = 1;
+ status = start_command(&ip);
+ if (status) {
+ return "index-pack fork failed";
+ }
+ pack_lockfile = index_pack_lockfile(ip.out);
+ close(ip.out);
+ status = finish_command(&ip);
+ if (!status) {
+ reprepare_packed_git();
+ return NULL;
+ }
+ return "index-pack abnormal exit";
+ }
+}
+
+static void report(const char *unpack_status)
+{
+ struct command *cmd;
+ struct strbuf buf = STRBUF_INIT;
+
+ packet_buf_write(&buf, "unpack %s\n",
+ unpack_status ? unpack_status : "ok");
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!cmd->error_string)
+ packet_buf_write(&buf, "ok %s\n",
+ cmd->ref_name);
+ else
+ packet_buf_write(&buf, "ng %s %s\n",
+ cmd->ref_name, cmd->error_string);
+ }
+ packet_buf_flush(&buf);
+
+ if (use_sideband)
+ send_sideband(1, 1, buf.buf, buf.len, use_sideband);
+ else
+ safe_write(1, buf.buf, buf.len);
+ strbuf_release(&buf);
+}
+
+static int delete_only(struct command *cmd)
+{
+ while (cmd) {
+ if (!is_null_sha1(cmd->new_sha1))
+ return 0;
+ cmd = cmd->next;
+ }
+ return 1;
+}
+
+static int add_refs_from_alternate(struct alternate_object_database *e, void *unused)
+{
+ char *other;
+ size_t len;
+ struct remote *remote;
+ struct transport *transport;
+ const struct ref *extra;
+
+ e->name[-1] = '\0';
+ other = xstrdup(make_absolute_path(e->base));
+ e->name[-1] = '/';
+ len = strlen(other);
+
+ while (other[len-1] == '/')
+ other[--len] = '\0';
+ if (len < 8 || memcmp(other + len - 8, "/objects", 8))
+ return 0;
+ /* Is this a git repository with refs? */
+ memcpy(other + len - 8, "/refs", 6);
+ if (!is_directory(other))
+ return 0;
+ other[len - 8] = '\0';
+ remote = remote_get(other);
+ transport = transport_get(remote, other);
+ for (extra = transport_get_remote_refs(transport);
+ extra;
+ extra = extra->next) {
+ add_extra_ref(".have", extra->old_sha1, 0);
+ }
+ transport_disconnect(transport);
+ free(other);
+ return 0;
+}
+
+static void add_alternate_refs(void)
+{
+ foreach_alt_odb(add_refs_from_alternate, NULL);
+}
+
+int cmd_receive_pack(int argc, const char **argv, const char *prefix)
+{
+ int advertise_refs = 0;
+ int stateless_rpc = 0;
+ int i;
+ char *dir = NULL;
+
+ argv++;
+ for (i = 1; i < argc; i++) {
+ const char *arg = *argv++;
+
+ if (*arg == '-') {
+ if (!strcmp(arg, "--advertise-refs")) {
+ advertise_refs = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--stateless-rpc")) {
+ stateless_rpc = 1;
+ continue;
+ }
+
+ usage(receive_pack_usage);
+ }
+ if (dir)
+ usage(receive_pack_usage);
+ dir = xstrdup(arg);
+ }
+ if (!dir)
+ usage(receive_pack_usage);
+
+ setup_path();
+
+ if (!enter_repo(dir, 0))
+ die("'%s' does not appear to be a git repository", dir);
+
+ if (is_repository_shallow())
+ die("attempt to push into a shallow repository");
+
+ git_config(receive_pack_config, NULL);
+
+ if (0 <= transfer_unpack_limit)
+ unpack_limit = transfer_unpack_limit;
+ else if (0 <= receive_unpack_limit)
+ unpack_limit = receive_unpack_limit;
+
+ if (advertise_refs || !stateless_rpc) {
+ add_alternate_refs();
+ write_head_info();
+ clear_extra_refs();
+
+ /* EOF */
+ packet_flush(1);
+ }
+ if (advertise_refs)
+ return 0;
+
+ read_head_info();
+ if (commands) {
+ const char *unpack_status = NULL;
+
+ if (!delete_only(commands))
+ unpack_status = unpack();
+ execute_commands(unpack_status);
+ if (pack_lockfile)
+ unlink_or_warn(pack_lockfile);
+ if (report_status)
+ report(unpack_status);
+ run_receive_hook(post_receive_hook);
+ run_update_post_hook(commands);
+ if (auto_gc) {
+ const char *argv_gc_auto[] = {
+ "gc", "--auto", "--quiet", NULL,
+ };
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+ if (auto_update_server_info)
+ update_server_info(0);
+ }
+ if (use_sideband)
+ packet_flush(1);
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "builtin.h"
+#include "commit.h"
+#include "refs.h"
+#include "dir.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "revision.h"
+#include "reachable.h"
+
+/*
+ * reflog expire
+ */
+
+static const char reflog_expire_usage[] =
+"git reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
+static const char reflog_delete_usage[] =
+"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
+
+static unsigned long default_reflog_expire;
+static unsigned long default_reflog_expire_unreachable;
+
+struct cmd_reflog_expire_cb {
+ struct rev_info revs;
+ int dry_run;
+ int stalefix;
+ int rewrite;
+ int updateref;
+ int verbose;
+ unsigned long expire_total;
+ unsigned long expire_unreachable;
+ int recno;
+};
+
+struct expire_reflog_cb {
+ FILE *newlog;
+ const char *ref;
+ struct commit *ref_commit;
+ struct cmd_reflog_expire_cb *cmd;
+ unsigned char last_kept_sha1[20];
+};
+
+struct collected_reflog {
+ unsigned char sha1[20];
+ char reflog[FLEX_ARRAY];
+};
+struct collect_reflog_cb {
+ struct collected_reflog **e;
+ int alloc;
+ int nr;
+};
+
+#define INCOMPLETE (1u<<10)
+#define STUDYING (1u<<11)
+#define REACHABLE (1u<<12)
+
+static int tree_is_complete(const unsigned char *sha1)
+{
+ struct tree_desc desc;
+ struct name_entry entry;
+ int complete;
+ struct tree *tree;
+
+ tree = lookup_tree(sha1);
+ if (!tree)
+ return 0;
+ if (tree->object.flags & SEEN)
+ return 1;
+ if (tree->object.flags & INCOMPLETE)
+ return 0;
+
+ if (!tree->buffer) {
+ enum object_type type;
+ unsigned long size;
+ void *data = read_sha1_file(sha1, &type, &size);
+ if (!data) {
+ tree->object.flags |= INCOMPLETE;
+ return 0;
+ }
+ tree->buffer = data;
+ tree->size = size;
+ }
+ init_tree_desc(&desc, tree->buffer, tree->size);
+ complete = 1;
+ while (tree_entry(&desc, &entry)) {
+ if (!has_sha1_file(entry.sha1) ||
+ (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
+ tree->object.flags |= INCOMPLETE;
+ complete = 0;
+ }
+ }
+ free(tree->buffer);
+ tree->buffer = NULL;
+
+ if (complete)
+ tree->object.flags |= SEEN;
+ return complete;
+}
+
+static int commit_is_complete(struct commit *commit)
+{
+ struct object_array study;
+ struct object_array found;
+ int is_incomplete = 0;
+ int i;
+
+ /* early return */
+ if (commit->object.flags & SEEN)
+ return 1;
+ if (commit->object.flags & INCOMPLETE)
+ return 0;
+ /*
+ * Find all commits that are reachable and are not marked as
+ * SEEN. Then make sure the trees and blobs contained are
+ * complete. After that, mark these commits also as SEEN.
+ * If some of the objects that are needed to complete this
+ * commit are missing, mark this commit as INCOMPLETE.
+ */
+ memset(&study, 0, sizeof(study));
+ memset(&found, 0, sizeof(found));
+ add_object_array(&commit->object, NULL, &study);
+ add_object_array(&commit->object, NULL, &found);
+ commit->object.flags |= STUDYING;
+ while (study.nr) {
+ struct commit *c;
+ struct commit_list *parent;
+
+ c = (struct commit *)study.objects[--study.nr].item;
+ if (!c->object.parsed && !parse_object(c->object.sha1))
+ c->object.flags |= INCOMPLETE;
+
+ if (c->object.flags & INCOMPLETE) {
+ is_incomplete = 1;
+ break;
+ }
+ else if (c->object.flags & SEEN)
+ continue;
+ for (parent = c->parents; parent; parent = parent->next) {
+ struct commit *p = parent->item;
+ if (p->object.flags & STUDYING)
+ continue;
+ p->object.flags |= STUDYING;
+ add_object_array(&p->object, NULL, &study);
+ add_object_array(&p->object, NULL, &found);
+ }
+ }
+ if (!is_incomplete) {
+ /*
+ * make sure all commits in "found" array have all the
+ * necessary objects.
+ */
+ for (i = 0; i < found.nr; i++) {
+ struct commit *c =
+ (struct commit *)found.objects[i].item;
+ if (!tree_is_complete(c->tree->object.sha1)) {
+ is_incomplete = 1;
+ c->object.flags |= INCOMPLETE;
+ }
+ }
+ if (!is_incomplete) {
+ /* mark all found commits as complete, iow SEEN */
+ for (i = 0; i < found.nr; i++)
+ found.objects[i].item->flags |= SEEN;
+ }
+ }
+ /* clear flags from the objects we traversed */
+ for (i = 0; i < found.nr; i++)
+ found.objects[i].item->flags &= ~STUDYING;
+ if (is_incomplete)
+ commit->object.flags |= INCOMPLETE;
+ else {
+ /*
+ * If we come here, we have (1) traversed the ancestry chain
+ * from the "commit" until we reach SEEN commits (which are
+ * known to be complete), and (2) made sure that the commits
+ * encountered during the above traversal refer to trees that
+ * are complete. Which means that we know *all* the commits
+ * we have seen during this process are complete.
+ */
+ for (i = 0; i < found.nr; i++)
+ found.objects[i].item->flags |= SEEN;
+ }
+ /* free object arrays */
+ free(study.objects);
+ free(found.objects);
+ return !is_incomplete;
+}
+
+static int keep_entry(struct commit **it, unsigned char *sha1)
+{
+ struct commit *commit;
+
+ if (is_null_sha1(sha1))
+ return 1;
+ commit = lookup_commit_reference_gently(sha1, 1);
+ if (!commit)
+ return 0;
+
+ /*
+ * Make sure everything in this commit exists.
+ *
+ * We have walked all the objects reachable from the refs
+ * and cache earlier. The commits reachable by this commit
+ * must meet SEEN commits -- and then we should mark them as
+ * SEEN as well.
+ */
+ if (!commit_is_complete(commit))
+ return 0;
+ *it = commit;
+ return 1;
+}
+
+static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
+{
+ /*
+ * We may or may not have the commit yet - if not, look it
+ * up using the supplied sha1.
+ */
+ if (!commit) {
+ if (is_null_sha1(sha1))
+ return 0;
+
+ commit = lookup_commit_reference_gently(sha1, 1);
+
+ /* Not a commit -- keep it */
+ if (!commit)
+ return 0;
+ }
+
+ /* Reachable from the current ref? Don't prune. */
+ if (commit->object.flags & REACHABLE)
+ return 0;
+ if (in_merge_bases(commit, &cb->ref_commit, 1))
+ return 0;
+
+ /* We can't reach it - prune it. */
+ return 1;
+}
+
+static void mark_reachable(struct commit *commit, unsigned long expire_limit)
+{
+ /*
+ * We need to compute whether the commit on either side of a reflog
+ * entry is reachable from the tip of the ref for all entries.
+ * Mark commits that are reachable from the tip down to the
+ * time threshold first; we know a commit marked thusly is
+ * reachable from the tip without running in_merge_bases()
+ * at all.
+ */
+ struct commit_list *pending = NULL;
+
+ commit_list_insert(commit, &pending);
+ while (pending) {
+ struct commit_list *entry = pending;
+ struct commit_list *parent;
+ pending = entry->next;
+ commit = entry->item;
+ free(entry);
+ if (commit->object.flags & REACHABLE)
+ continue;
+ if (parse_commit(commit))
+ continue;
+ commit->object.flags |= REACHABLE;
+ if (commit->date < expire_limit)
+ continue;
+ parent = commit->parents;
+ while (parent) {
+ commit = parent->item;
+ parent = parent->next;
+ if (commit->object.flags & REACHABLE)
+ continue;
+ commit_list_insert(commit, &pending);
+ }
+ }
+}
+
+static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ struct expire_reflog_cb *cb = cb_data;
+ struct commit *old, *new;
+
+ if (timestamp < cb->cmd->expire_total)
+ goto prune;
+
+ if (cb->cmd->rewrite)
+ osha1 = cb->last_kept_sha1;
+
+ old = new = NULL;
+ if (cb->cmd->stalefix &&
+ (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
+ goto prune;
+
+ if (timestamp < cb->cmd->expire_unreachable) {
+ if (!cb->ref_commit)
+ goto prune;
+ if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1))
+ goto prune;
+ }
+
+ if (cb->cmd->recno && --(cb->cmd->recno) == 0)
+ goto prune;
+
+ if (cb->newlog) {
+ char sign = (tz < 0) ? '-' : '+';
+ int zone = (tz < 0) ? (-tz) : tz;
+ fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s",
+ sha1_to_hex(osha1), sha1_to_hex(nsha1),
+ email, timestamp, sign, zone,
+ message);
+ hashcpy(cb->last_kept_sha1, nsha1);
+ }
+ if (cb->cmd->verbose)
+ printf("keep %s", message);
+ return 0;
+ prune:
+ if (!cb->newlog || cb->cmd->verbose)
+ printf("%sprune %s", cb->newlog ? "" : "would ", message);
+ return 0;
+}
+
+static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+{
+ struct cmd_reflog_expire_cb *cmd = cb_data;
+ struct expire_reflog_cb cb;
+ struct ref_lock *lock;
+ char *log_file, *newlog_path = NULL;
+ int status = 0;
+
+ memset(&cb, 0, sizeof(cb));
+
+ /*
+ * we take the lock for the ref itself to prevent it from
+ * getting updated.
+ */
+ lock = lock_any_ref_for_update(ref, sha1, 0);
+ if (!lock)
+ return error("cannot lock ref '%s'", ref);
+ log_file = git_pathdup("logs/%s", ref);
+ if (!file_exists(log_file))
+ goto finish;
+ if (!cmd->dry_run) {
+ newlog_path = git_pathdup("logs/%s.lock", ref);
+ cb.newlog = fopen(newlog_path, "w");
+ }
+
+ cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
+ cb.ref = ref;
+ cb.cmd = cmd;
+ if (cb.ref_commit)
+ mark_reachable(cb.ref_commit, cmd->expire_total);
+ for_each_reflog_ent(ref, expire_reflog_ent, &cb);
+ if (cb.ref_commit)
+ clear_commit_marks(cb.ref_commit, REACHABLE);
+ finish:
+ if (cb.newlog) {
+ if (fclose(cb.newlog)) {
+ status |= error("%s: %s", strerror(errno),
+ newlog_path);
+ unlink(newlog_path);
+ } else if (cmd->updateref &&
+ (write_in_full(lock->lock_fd,
+ sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
+ write_str_in_full(lock->lock_fd, "\n") != 1 ||
+ close_ref(lock) < 0)) {
+ status |= error("Couldn't write %s",
+ lock->lk->filename);
+ unlink(newlog_path);
+ } else if (rename(newlog_path, log_file)) {
+ status |= error("cannot rename %s to %s",
+ newlog_path, log_file);
+ unlink(newlog_path);
+ } else if (cmd->updateref && commit_ref(lock)) {
+ status |= error("Couldn't set %s", lock->ref_name);
+ } else {
+ adjust_shared_perm(log_file);
+ }
+ }
+ free(newlog_path);
+ free(log_file);
+ unlock_ref(lock);
+ return status;
+}
+
+static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+{
+ struct collected_reflog *e;
+ struct collect_reflog_cb *cb = cb_data;
+ size_t namelen = strlen(ref);
+
+ e = xmalloc(sizeof(*e) + namelen + 1);
+ hashcpy(e->sha1, sha1);
+ memcpy(e->reflog, ref, namelen + 1);
+ ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
+ cb->e[cb->nr++] = e;
+ return 0;
+}
+
+static struct reflog_expire_cfg {
+ struct reflog_expire_cfg *next;
+ unsigned long expire_total;
+ unsigned long expire_unreachable;
+ size_t len;
+ char pattern[FLEX_ARRAY];
+} *reflog_expire_cfg, **reflog_expire_cfg_tail;
+
+static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
+{
+ struct reflog_expire_cfg *ent;
+
+ if (!reflog_expire_cfg_tail)
+ reflog_expire_cfg_tail = &reflog_expire_cfg;
+
+ for (ent = reflog_expire_cfg; ent; ent = ent->next)
+ if (ent->len == len &&
+ !memcmp(ent->pattern, pattern, len))
+ return ent;
+
+ ent = xcalloc(1, (sizeof(*ent) + len));
+ memcpy(ent->pattern, pattern, len);
+ ent->len = len;
+ *reflog_expire_cfg_tail = ent;
+ reflog_expire_cfg_tail = &(ent->next);
+ return ent;
+}
+
+static int parse_expire_cfg_value(const char *var, const char *value, unsigned long *expire)
+{
+ if (!value)
+ return config_error_nonbool(var);
+ if (!strcmp(value, "never") || !strcmp(value, "false")) {
+ *expire = 0;
+ return 0;
+ }
+ *expire = approxidate(value);
+ return 0;
+}
+
+/* expiry timer slot */
+#define EXPIRE_TOTAL 01
+#define EXPIRE_UNREACH 02
+
+static int reflog_expire_config(const char *var, const char *value, void *cb)
+{
+ const char *lastdot = strrchr(var, '.');
+ unsigned long expire;
+ int slot;
+ struct reflog_expire_cfg *ent;
+
+ if (!lastdot || prefixcmp(var, "gc."))
+ return git_default_config(var, value, cb);
+
+ if (!strcmp(lastdot, ".reflogexpire")) {
+ slot = EXPIRE_TOTAL;
+ if (parse_expire_cfg_value(var, value, &expire))
+ return -1;
+ } else if (!strcmp(lastdot, ".reflogexpireunreachable")) {
+ slot = EXPIRE_UNREACH;
+ if (parse_expire_cfg_value(var, value, &expire))
+ return -1;
+ } else
+ return git_default_config(var, value, cb);
+
+ if (lastdot == var + 2) {
+ switch (slot) {
+ case EXPIRE_TOTAL:
+ default_reflog_expire = expire;
+ break;
+ case EXPIRE_UNREACH:
+ default_reflog_expire_unreachable = expire;
+ break;
+ }
+ return 0;
+ }
+
+ ent = find_cfg_ent(var + 3, lastdot - (var+3));
+ if (!ent)
+ return -1;
+ switch (slot) {
+ case EXPIRE_TOTAL:
+ ent->expire_total = expire;
+ break;
+ case EXPIRE_UNREACH:
+ ent->expire_unreachable = expire;
+ break;
+ }
+ return 0;
+}
+
+static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
+{
+ struct reflog_expire_cfg *ent;
+
+ if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
+ return; /* both given explicitly -- nothing to tweak */
+
+ for (ent = reflog_expire_cfg; ent; ent = ent->next) {
+ if (!fnmatch(ent->pattern, ref, 0)) {
+ if (!(slot & EXPIRE_TOTAL))
+ cb->expire_total = ent->expire_total;
+ if (!(slot & EXPIRE_UNREACH))
+ cb->expire_unreachable = ent->expire_unreachable;
+ return;
+ }
+ }
+
+ /*
+ * If unconfigured, make stash never expire
+ */
+ if (!strcmp(ref, "refs/stash")) {
+ if (!(slot & EXPIRE_TOTAL))
+ cb->expire_total = 0;
+ if (!(slot & EXPIRE_UNREACH))
+ cb->expire_unreachable = 0;
+ return;
+ }
+
+ /* Nothing matched -- use the default value */
+ if (!(slot & EXPIRE_TOTAL))
+ cb->expire_total = default_reflog_expire;
+ if (!(slot & EXPIRE_UNREACH))
+ cb->expire_unreachable = default_reflog_expire_unreachable;
+}
+
+static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
+{
+ struct cmd_reflog_expire_cb cb;
+ unsigned long now = time(NULL);
+ int i, status, do_all;
+ int explicit_expiry = 0;
+
+ default_reflog_expire_unreachable = now - 30 * 24 * 3600;
+ default_reflog_expire = now - 90 * 24 * 3600;
+ git_config(reflog_expire_config, NULL);
+
+ save_commit_buffer = 0;
+ do_all = status = 0;
+ memset(&cb, 0, sizeof(cb));
+
+ cb.expire_total = default_reflog_expire;
+ cb.expire_unreachable = default_reflog_expire_unreachable;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
+ cb.dry_run = 1;
+ else if (!prefixcmp(arg, "--expire=")) {
+ cb.expire_total = approxidate(arg + 9);
+ explicit_expiry |= EXPIRE_TOTAL;
+ }
+ else if (!prefixcmp(arg, "--expire-unreachable=")) {
+ cb.expire_unreachable = approxidate(arg + 21);
+ explicit_expiry |= EXPIRE_UNREACH;
+ }
+ else if (!strcmp(arg, "--stale-fix"))
+ cb.stalefix = 1;
+ else if (!strcmp(arg, "--rewrite"))
+ cb.rewrite = 1;
+ else if (!strcmp(arg, "--updateref"))
+ cb.updateref = 1;
+ else if (!strcmp(arg, "--all"))
+ do_all = 1;
+ else if (!strcmp(arg, "--verbose"))
+ cb.verbose = 1;
+ else if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ else if (arg[0] == '-')
+ usage(reflog_expire_usage);
+ else
+ break;
+ }
+
+ /*
+ * We can trust the commits and objects reachable from refs
+ * even in older repository. We cannot trust what's reachable
+ * from reflog if the repository was pruned with older git.
+ */
+ if (cb.stalefix) {
+ init_revisions(&cb.revs, prefix);
+ if (cb.verbose)
+ printf("Marking reachable objects...");
+ mark_reachable_objects(&cb.revs, 0);
+ if (cb.verbose)
+ putchar('\n');
+ }
+
+ if (do_all) {
+ struct collect_reflog_cb collected;
+ int i;
+
+ memset(&collected, 0, sizeof(collected));
+ for_each_reflog(collect_reflog, &collected);
+ for (i = 0; i < collected.nr; i++) {
+ struct collected_reflog *e = collected.e[i];
+ set_reflog_expiry_param(&cb, explicit_expiry, e->reflog);
+ status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
+ free(e);
+ }
+ free(collected.e);
+ }
+
+ for (; i < argc; i++) {
+ char *ref;
+ unsigned char sha1[20];
+ if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) {
+ status |= error("%s points nowhere!", argv[i]);
+ continue;
+ }
+ set_reflog_expiry_param(&cb, explicit_expiry, ref);
+ status |= expire_reflog(ref, sha1, 0, &cb);
+ }
+ return status;
+}
+
+static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ struct cmd_reflog_expire_cb *cb = cb_data;
+ if (!cb->expire_total || timestamp < cb->expire_total)
+ cb->recno++;
+ return 0;
+}
+
+static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
+{
+ struct cmd_reflog_expire_cb cb;
+ int i, status = 0;
+
+ memset(&cb, 0, sizeof(cb));
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
+ cb.dry_run = 1;
+ else if (!strcmp(arg, "--rewrite"))
+ cb.rewrite = 1;
+ else if (!strcmp(arg, "--updateref"))
+ cb.updateref = 1;
+ else if (!strcmp(arg, "--verbose"))
+ cb.verbose = 1;
+ else if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ else if (arg[0] == '-')
+ usage(reflog_delete_usage);
+ else
+ break;
+ }
+
+ if (argc - i < 1)
+ return error("Nothing to delete?");
+
+ for ( ; i < argc; i++) {
+ const char *spec = strstr(argv[i], "@{");
+ unsigned char sha1[20];
+ char *ep, *ref;
+ int recno;
+
+ if (!spec) {
+ status |= error("Not a reflog: %s", argv[i]);
+ continue;
+ }
+
+ if (!dwim_log(argv[i], spec - argv[i], sha1, &ref)) {
+ status |= error("no reflog for '%s'", argv[i]);
+ continue;
+ }
+
+ recno = strtoul(spec + 2, &ep, 10);
+ if (*ep == '}') {
+ cb.recno = -recno;
+ for_each_reflog_ent(ref, count_reflog_ent, &cb);
+ } else {
+ cb.expire_total = approxidate(spec + 2);
+ for_each_reflog_ent(ref, count_reflog_ent, &cb);
+ cb.expire_total = 0;
+ }
+
+ status |= expire_reflog(ref, sha1, 0, &cb);
+ free(ref);
+ }
+ return status;
+}
+
+/*
+ * main "reflog"
+ */
+
+static const char reflog_usage[] =
+"git reflog [ show | expire | delete ]";
+
+int cmd_reflog(int argc, const char **argv, const char *prefix)
+{
+ if (argc > 1 && !strcmp(argv[1], "-h"))
+ usage(reflog_usage);
+
+ /* With no command, we default to showing it. */
+ if (argc < 2 || *argv[1] == '-')
+ return cmd_log_reflog(argc, argv, prefix);
+
+ if (!strcmp(argv[1], "show"))
+ return cmd_log_reflog(argc - 1, argv + 1, prefix);
+
+ if (!strcmp(argv[1], "expire"))
+ return cmd_reflog_expire(argc - 1, argv + 1, prefix);
+
+ if (!strcmp(argv[1], "delete"))
+ return cmd_reflog_delete(argc - 1, argv + 1, prefix);
+
+ /* Not a recognized reflog command..*/
+ usage(reflog_usage);
+}
--- /dev/null
+#include "cache.h"
+#include "parse-options.h"
+#include "transport.h"
+#include "remote.h"
+#include "string-list.h"
+#include "strbuf.h"
+#include "run-command.h"
+#include "refs.h"
+
+static const char * const builtin_remote_usage[] = {
+ "git remote [-v | --verbose]",
+ "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
+ "git remote rename <old> <new>",
+ "git remote rm <name>",
+ "git remote set-head <name> (-a | -d | <branch>)",
+ "git remote [-v | --verbose] show [-n] <name>",
+ "git remote prune [-n | --dry-run] <name>",
+ "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+ "git remote set-url <name> <newurl> [<oldurl>]",
+ "git remote set-url --add <name> <newurl>",
+ "git remote set-url --delete <name> <url>",
+ NULL
+};
+
+static const char * const builtin_remote_add_usage[] = {
+ "git remote add [<options>] <name> <url>",
+ NULL
+};
+
+static const char * const builtin_remote_rename_usage[] = {
+ "git remote rename <old> <new>",
+ NULL
+};
+
+static const char * const builtin_remote_rm_usage[] = {
+ "git remote rm <name>",
+ NULL
+};
+
+static const char * const builtin_remote_sethead_usage[] = {
+ "git remote set-head <name> (-a | -d | <branch>])",
+ NULL
+};
+
+static const char * const builtin_remote_show_usage[] = {
+ "git remote show [<options>] <name>",
+ NULL
+};
+
+static const char * const builtin_remote_prune_usage[] = {
+ "git remote prune [<options>] <name>",
+ NULL
+};
+
+static const char * const builtin_remote_update_usage[] = {
+ "git remote update [<options>] [<group> | <remote>]...",
+ NULL
+};
+
+static const char * const builtin_remote_seturl_usage[] = {
+ "git remote set-url [--push] <name> <newurl> [<oldurl>]",
+ "git remote set-url --add <name> <newurl>",
+ "git remote set-url --delete <name> <url>",
+ NULL
+};
+
+#define GET_REF_STATES (1<<0)
+#define GET_HEAD_NAMES (1<<1)
+#define GET_PUSH_REF_STATES (1<<2)
+
+static int verbose;
+
+static int show_all(void);
+static int prune_remote(const char *remote, int dry_run);
+
+static inline int postfixcmp(const char *string, const char *postfix)
+{
+ int len1 = strlen(string), len2 = strlen(postfix);
+ if (len1 < len2)
+ return 1;
+ return strcmp(string + len1 - len2, postfix);
+}
+
+static int opt_parse_track(const struct option *opt, const char *arg, int not)
+{
+ struct string_list *list = opt->value;
+ if (not)
+ string_list_clear(list, 0);
+ else
+ string_list_append(arg, list);
+ return 0;
+}
+
+static int fetch_remote(const char *name)
+{
+ const char *argv[] = { "fetch", name, NULL, NULL };
+ if (verbose) {
+ argv[1] = "-v";
+ argv[2] = name;
+ }
+ printf("Updating %s\n", name);
+ if (run_command_v_opt(argv, RUN_GIT_CMD))
+ return error("Could not fetch %s", name);
+ return 0;
+}
+
+static int add(int argc, const char **argv)
+{
+ int fetch = 0, mirror = 0;
+ struct string_list track = { NULL, 0, 0 };
+ const char *master = NULL;
+ struct remote *remote;
+ struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+ const char *name, *url;
+ int i;
+
+ struct option options[] = {
+ OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
+ OPT_CALLBACK('t', "track", &track, "branch",
+ "branch(es) to track", opt_parse_track),
+ OPT_STRING('m', "master", &master, "branch", "master branch"),
+ OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage,
+ 0);
+
+ if (argc < 2)
+ usage_with_options(builtin_remote_add_usage, options);
+
+ name = argv[0];
+ url = argv[1];
+
+ remote = remote_get(name);
+ if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
+ remote->fetch_refspec_nr))
+ die("remote %s already exists.", name);
+
+ strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
+ if (!valid_fetch_refspec(buf2.buf))
+ die("'%s' is not a valid remote name", name);
+
+ strbuf_addf(&buf, "remote.%s.url", name);
+ if (git_config_set(buf.buf, url))
+ return 1;
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "remote.%s.fetch", name);
+
+ if (track.nr == 0)
+ string_list_append("*", &track);
+ for (i = 0; i < track.nr; i++) {
+ struct string_list_item *item = track.items + i;
+
+ strbuf_reset(&buf2);
+ strbuf_addch(&buf2, '+');
+ if (mirror)
+ strbuf_addf(&buf2, "refs/%s:refs/%s",
+ item->string, item->string);
+ else
+ strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s",
+ item->string, name, item->string);
+ if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
+ return 1;
+ }
+
+ if (mirror) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "remote.%s.mirror", name);
+ if (git_config_set(buf.buf, "true"))
+ return 1;
+ }
+
+ if (fetch && fetch_remote(name))
+ return 1;
+
+ if (master) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "refs/remotes/%s/HEAD", name);
+
+ strbuf_reset(&buf2);
+ strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
+
+ if (create_symref(buf.buf, buf2.buf, "remote add"))
+ return error("Could not setup master '%s'", master);
+ }
+
+ strbuf_release(&buf);
+ strbuf_release(&buf2);
+ string_list_clear(&track, 0);
+
+ return 0;
+}
+
+struct branch_info {
+ char *remote_name;
+ struct string_list merge;
+ int rebase;
+};
+
+static struct string_list branch_list;
+
+static const char *abbrev_ref(const char *name, const char *prefix)
+{
+ const char *abbrev = skip_prefix(name, prefix);
+ if (abbrev)
+ return abbrev;
+ return name;
+}
+#define abbrev_branch(name) abbrev_ref((name), "refs/heads/")
+
+static int config_read_branches(const char *key, const char *value, void *cb)
+{
+ if (!prefixcmp(key, "branch.")) {
+ const char *orig_key = key;
+ char *name;
+ struct string_list_item *item;
+ struct branch_info *info;
+ enum { REMOTE, MERGE, REBASE } type;
+
+ key += 7;
+ if (!postfixcmp(key, ".remote")) {
+ name = xstrndup(key, strlen(key) - 7);
+ type = REMOTE;
+ } else if (!postfixcmp(key, ".merge")) {
+ name = xstrndup(key, strlen(key) - 6);
+ type = MERGE;
+ } else if (!postfixcmp(key, ".rebase")) {
+ name = xstrndup(key, strlen(key) - 7);
+ type = REBASE;
+ } else
+ return 0;
+
+ item = string_list_insert(name, &branch_list);
+
+ if (!item->util)
+ item->util = xcalloc(sizeof(struct branch_info), 1);
+ info = item->util;
+ if (type == REMOTE) {
+ if (info->remote_name)
+ warning("more than one %s", orig_key);
+ info->remote_name = xstrdup(value);
+ } else if (type == MERGE) {
+ char *space = strchr(value, ' ');
+ value = abbrev_branch(value);
+ while (space) {
+ char *merge;
+ merge = xstrndup(value, space - value);
+ string_list_append(merge, &info->merge);
+ value = abbrev_branch(space + 1);
+ space = strchr(value, ' ');
+ }
+ string_list_append(xstrdup(value), &info->merge);
+ } else
+ info->rebase = git_config_bool(orig_key, value);
+ }
+ return 0;
+}
+
+static void read_branches(void)
+{
+ if (branch_list.nr)
+ return;
+ git_config(config_read_branches, NULL);
+}
+
+struct ref_states {
+ struct remote *remote;
+ struct string_list new, stale, tracked, heads, push;
+ int queried;
+};
+
+static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
+{
+ struct ref *fetch_map = NULL, **tail = &fetch_map;
+ struct ref *ref, *stale_refs;
+ int i;
+
+ for (i = 0; i < states->remote->fetch_refspec_nr; i++)
+ if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
+ die("Could not get fetch map for refspec %s",
+ states->remote->fetch_refspec[i]);
+
+ states->new.strdup_strings = 1;
+ states->tracked.strdup_strings = 1;
+ states->stale.strdup_strings = 1;
+ for (ref = fetch_map; ref; ref = ref->next) {
+ unsigned char sha1[20];
+ if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
+ string_list_append(abbrev_branch(ref->name), &states->new);
+ else
+ string_list_append(abbrev_branch(ref->name), &states->tracked);
+ }
+ stale_refs = get_stale_heads(states->remote, fetch_map);
+ for (ref = stale_refs; ref; ref = ref->next) {
+ struct string_list_item *item =
+ string_list_append(abbrev_branch(ref->name), &states->stale);
+ item->util = xstrdup(ref->name);
+ }
+ free_refs(stale_refs);
+ free_refs(fetch_map);
+
+ sort_string_list(&states->new);
+ sort_string_list(&states->tracked);
+ sort_string_list(&states->stale);
+
+ return 0;
+}
+
+struct push_info {
+ char *dest;
+ int forced;
+ enum {
+ PUSH_STATUS_CREATE = 0,
+ PUSH_STATUS_DELETE,
+ PUSH_STATUS_UPTODATE,
+ PUSH_STATUS_FASTFORWARD,
+ PUSH_STATUS_OUTOFDATE,
+ PUSH_STATUS_NOTQUERIED,
+ } status;
+};
+
+static int get_push_ref_states(const struct ref *remote_refs,
+ struct ref_states *states)
+{
+ struct remote *remote = states->remote;
+ struct ref *ref, *local_refs, *push_map;
+ if (remote->mirror)
+ return 0;
+
+ local_refs = get_local_heads();
+ push_map = copy_ref_list(remote_refs);
+
+ match_refs(local_refs, &push_map, remote->push_refspec_nr,
+ remote->push_refspec, MATCH_REFS_NONE);
+
+ states->push.strdup_strings = 1;
+ for (ref = push_map; ref; ref = ref->next) {
+ struct string_list_item *item;
+ struct push_info *info;
+
+ if (!ref->peer_ref)
+ continue;
+ hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+
+ item = string_list_append(abbrev_branch(ref->peer_ref->name),
+ &states->push);
+ item->util = xcalloc(sizeof(struct push_info), 1);
+ info = item->util;
+ info->forced = ref->force;
+ info->dest = xstrdup(abbrev_branch(ref->name));
+
+ if (is_null_sha1(ref->new_sha1)) {
+ info->status = PUSH_STATUS_DELETE;
+ } else if (!hashcmp(ref->old_sha1, ref->new_sha1))
+ info->status = PUSH_STATUS_UPTODATE;
+ else if (is_null_sha1(ref->old_sha1))
+ info->status = PUSH_STATUS_CREATE;
+ else if (has_sha1_file(ref->old_sha1) &&
+ ref_newer(ref->new_sha1, ref->old_sha1))
+ info->status = PUSH_STATUS_FASTFORWARD;
+ else
+ info->status = PUSH_STATUS_OUTOFDATE;
+ }
+ free_refs(local_refs);
+ free_refs(push_map);
+ return 0;
+}
+
+static int get_push_ref_states_noquery(struct ref_states *states)
+{
+ int i;
+ struct remote *remote = states->remote;
+ struct string_list_item *item;
+ struct push_info *info;
+
+ if (remote->mirror)
+ return 0;
+
+ states->push.strdup_strings = 1;
+ if (!remote->push_refspec_nr) {
+ item = string_list_append("(matching)", &states->push);
+ info = item->util = xcalloc(sizeof(struct push_info), 1);
+ info->status = PUSH_STATUS_NOTQUERIED;
+ info->dest = xstrdup(item->string);
+ }
+ for (i = 0; i < remote->push_refspec_nr; i++) {
+ struct refspec *spec = remote->push + i;
+ if (spec->matching)
+ item = string_list_append("(matching)", &states->push);
+ else if (strlen(spec->src))
+ item = string_list_append(spec->src, &states->push);
+ else
+ item = string_list_append("(delete)", &states->push);
+
+ info = item->util = xcalloc(sizeof(struct push_info), 1);
+ info->forced = spec->force;
+ info->status = PUSH_STATUS_NOTQUERIED;
+ info->dest = xstrdup(spec->dst ? spec->dst : item->string);
+ }
+ return 0;
+}
+
+static int get_head_names(const struct ref *remote_refs, struct ref_states *states)
+{
+ struct ref *ref, *matches;
+ struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+ struct refspec refspec;
+
+ refspec.force = 0;
+ refspec.pattern = 1;
+ refspec.src = refspec.dst = "refs/heads/*";
+ states->heads.strdup_strings = 1;
+ get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+ matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+ fetch_map, 1);
+ for (ref = matches; ref; ref = ref->next)
+ string_list_append(abbrev_branch(ref->name), &states->heads);
+
+ free_refs(fetch_map);
+ free_refs(matches);
+
+ return 0;
+}
+
+struct known_remote {
+ struct known_remote *next;
+ struct remote *remote;
+};
+
+struct known_remotes {
+ struct remote *to_delete;
+ struct known_remote *list;
+};
+
+static int add_known_remote(struct remote *remote, void *cb_data)
+{
+ struct known_remotes *all = cb_data;
+ struct known_remote *r;
+
+ if (!strcmp(all->to_delete->name, remote->name))
+ return 0;
+
+ r = xmalloc(sizeof(*r));
+ r->remote = remote;
+ r->next = all->list;
+ all->list = r;
+ return 0;
+}
+
+struct branches_for_remote {
+ struct remote *remote;
+ struct string_list *branches, *skipped;
+ struct known_remotes *keep;
+};
+
+static int add_branch_for_removal(const char *refname,
+ const unsigned char *sha1, int flags, void *cb_data)
+{
+ struct branches_for_remote *branches = cb_data;
+ struct refspec refspec;
+ struct string_list_item *item;
+ struct known_remote *kr;
+
+ memset(&refspec, 0, sizeof(refspec));
+ refspec.dst = (char *)refname;
+ if (remote_find_tracking(branches->remote, &refspec))
+ return 0;
+
+ /* don't delete a branch if another remote also uses it */
+ for (kr = branches->keep->list; kr; kr = kr->next) {
+ memset(&refspec, 0, sizeof(refspec));
+ refspec.dst = (char *)refname;
+ if (!remote_find_tracking(kr->remote, &refspec))
+ return 0;
+ }
+
+ /* don't delete non-remote refs */
+ if (prefixcmp(refname, "refs/remotes")) {
+ /* advise user how to delete local branches */
+ if (!prefixcmp(refname, "refs/heads/"))
+ string_list_append(abbrev_branch(refname),
+ branches->skipped);
+ /* silently skip over other non-remote refs */
+ return 0;
+ }
+
+ /* make sure that symrefs are deleted */
+ if (flags & REF_ISSYMREF)
+ return unlink(git_path("%s", refname));
+
+ item = string_list_append(refname, branches->branches);
+ item->util = xmalloc(20);
+ hashcpy(item->util, sha1);
+
+ return 0;
+}
+
+struct rename_info {
+ const char *old;
+ const char *new;
+ struct string_list *remote_branches;
+};
+
+static int read_remote_branches(const char *refname,
+ const unsigned char *sha1, int flags, void *cb_data)
+{
+ struct rename_info *rename = cb_data;
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list_item *item;
+ int flag;
+ unsigned char orig_sha1[20];
+ const char *symref;
+
+ strbuf_addf(&buf, "refs/remotes/%s", rename->old);
+ if (!prefixcmp(refname, buf.buf)) {
+ item = string_list_append(xstrdup(refname), rename->remote_branches);
+ symref = resolve_ref(refname, orig_sha1, 1, &flag);
+ if (flag & REF_ISSYMREF)
+ item->util = xstrdup(symref);
+ else
+ item->util = NULL;
+ }
+
+ return 0;
+}
+
+static int migrate_file(struct remote *remote)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int i;
+ char *path = NULL;
+
+ strbuf_addf(&buf, "remote.%s.url", remote->name);
+ for (i = 0; i < remote->url_nr; i++)
+ if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0))
+ return error("Could not append '%s' to '%s'",
+ remote->url[i], buf.buf);
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "remote.%s.push", remote->name);
+ for (i = 0; i < remote->push_refspec_nr; i++)
+ if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0))
+ return error("Could not append '%s' to '%s'",
+ remote->push_refspec[i], buf.buf);
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "remote.%s.fetch", remote->name);
+ for (i = 0; i < remote->fetch_refspec_nr; i++)
+ if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0))
+ return error("Could not append '%s' to '%s'",
+ remote->fetch_refspec[i], buf.buf);
+ if (remote->origin == REMOTE_REMOTES)
+ path = git_path("remotes/%s", remote->name);
+ else if (remote->origin == REMOTE_BRANCHES)
+ path = git_path("branches/%s", remote->name);
+ if (path)
+ unlink_or_warn(path);
+ return 0;
+}
+
+static int mv(int argc, const char **argv)
+{
+ struct option options[] = {
+ OPT_END()
+ };
+ struct remote *oldremote, *newremote;
+ struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
+ struct string_list remote_branches = { NULL, 0, 0, 0 };
+ struct rename_info rename;
+ int i;
+
+ if (argc != 3)
+ usage_with_options(builtin_remote_rename_usage, options);
+
+ rename.old = argv[1];
+ rename.new = argv[2];
+ rename.remote_branches = &remote_branches;
+
+ oldremote = remote_get(rename.old);
+ if (!oldremote)
+ die("No such remote: %s", rename.old);
+
+ if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG)
+ return migrate_file(oldremote);
+
+ newremote = remote_get(rename.new);
+ if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr))
+ die("remote %s already exists.", rename.new);
+
+ strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
+ if (!valid_fetch_refspec(buf.buf))
+ die("'%s' is not a valid remote name", rename.new);
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "remote.%s", rename.old);
+ strbuf_addf(&buf2, "remote.%s", rename.new);
+ if (git_config_rename_section(buf.buf, buf2.buf) < 1)
+ return error("Could not rename config section '%s' to '%s'",
+ buf.buf, buf2.buf);
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "remote.%s.fetch", rename.new);
+ if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
+ return error("Could not remove config section '%s'", buf.buf);
+ for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
+ char *ptr;
+
+ strbuf_reset(&buf2);
+ strbuf_addstr(&buf2, oldremote->fetch_refspec[i]);
+ ptr = strstr(buf2.buf, rename.old);
+ if (ptr)
+ strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old),
+ rename.new, strlen(rename.new));
+ if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
+ return error("Could not append '%s'", buf.buf);
+ }
+
+ read_branches();
+ for (i = 0; i < branch_list.nr; i++) {
+ struct string_list_item *item = branch_list.items + i;
+ struct branch_info *info = item->util;
+ if (info->remote_name && !strcmp(info->remote_name, rename.old)) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "branch.%s.remote", item->string);
+ if (git_config_set(buf.buf, rename.new)) {
+ return error("Could not set '%s'", buf.buf);
+ }
+ }
+ }
+
+ /*
+ * First remove symrefs, then rename the rest, finally create
+ * the new symrefs.
+ */
+ for_each_ref(read_remote_branches, &rename);
+ for (i = 0; i < remote_branches.nr; i++) {
+ struct string_list_item *item = remote_branches.items + i;
+ int flag = 0;
+ unsigned char sha1[20];
+
+ resolve_ref(item->string, sha1, 1, &flag);
+ if (!(flag & REF_ISSYMREF))
+ continue;
+ if (delete_ref(item->string, NULL, REF_NODEREF))
+ die("deleting '%s' failed", item->string);
+ }
+ for (i = 0; i < remote_branches.nr; i++) {
+ struct string_list_item *item = remote_branches.items + i;
+
+ if (item->util)
+ continue;
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, item->string);
+ strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
+ rename.new, strlen(rename.new));
+ strbuf_reset(&buf2);
+ strbuf_addf(&buf2, "remote: renamed %s to %s",
+ item->string, buf.buf);
+ if (rename_ref(item->string, buf.buf, buf2.buf))
+ die("renaming '%s' failed", item->string);
+ }
+ for (i = 0; i < remote_branches.nr; i++) {
+ struct string_list_item *item = remote_branches.items + i;
+
+ if (!item->util)
+ continue;
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, item->string);
+ strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old),
+ rename.new, strlen(rename.new));
+ strbuf_reset(&buf2);
+ strbuf_addstr(&buf2, item->util);
+ strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old),
+ rename.new, strlen(rename.new));
+ strbuf_reset(&buf3);
+ strbuf_addf(&buf3, "remote: renamed %s to %s",
+ item->string, buf.buf);
+ if (create_symref(buf.buf, buf2.buf, buf3.buf))
+ die("creating '%s' failed", buf.buf);
+ }
+ return 0;
+}
+
+static int remove_branches(struct string_list *branches)
+{
+ int i, result = 0;
+ for (i = 0; i < branches->nr; i++) {
+ struct string_list_item *item = branches->items + i;
+ const char *refname = item->string;
+ unsigned char *sha1 = item->util;
+
+ if (delete_ref(refname, sha1, 0))
+ result |= error("Could not remove branch %s", refname);
+ }
+ return result;
+}
+
+static int rm(int argc, const char **argv)
+{
+ struct option options[] = {
+ OPT_END()
+ };
+ struct remote *remote;
+ struct strbuf buf = STRBUF_INIT;
+ struct known_remotes known_remotes = { NULL, NULL };
+ struct string_list branches = { NULL, 0, 0, 1 };
+ struct string_list skipped = { NULL, 0, 0, 1 };
+ struct branches_for_remote cb_data = {
+ NULL, &branches, &skipped, &known_remotes
+ };
+ int i, result;
+
+ if (argc != 2)
+ usage_with_options(builtin_remote_rm_usage, options);
+
+ remote = remote_get(argv[1]);
+ if (!remote)
+ die("No such remote: %s", argv[1]);
+
+ known_remotes.to_delete = remote;
+ for_each_remote(add_known_remote, &known_remotes);
+
+ strbuf_addf(&buf, "remote.%s", remote->name);
+ if (git_config_rename_section(buf.buf, NULL) < 1)
+ return error("Could not remove config section '%s'", buf.buf);
+
+ read_branches();
+ for (i = 0; i < branch_list.nr; i++) {
+ struct string_list_item *item = branch_list.items + i;
+ struct branch_info *info = item->util;
+ if (info->remote_name && !strcmp(info->remote_name, remote->name)) {
+ const char *keys[] = { "remote", "merge", NULL }, **k;
+ for (k = keys; *k; k++) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "branch.%s.%s",
+ item->string, *k);
+ if (git_config_set(buf.buf, NULL)) {
+ strbuf_release(&buf);
+ return -1;
+ }
+ }
+ }
+ }
+
+ /*
+ * We cannot just pass a function to for_each_ref() which deletes
+ * the branches one by one, since for_each_ref() relies on cached
+ * refs, which are invalidated when deleting a branch.
+ */
+ cb_data.remote = remote;
+ result = for_each_ref(add_branch_for_removal, &cb_data);
+ strbuf_release(&buf);
+
+ if (!result)
+ result = remove_branches(&branches);
+ string_list_clear(&branches, 1);
+
+ if (skipped.nr) {
+ fprintf(stderr, skipped.nr == 1 ?
+ "Note: A non-remote branch was not removed; "
+ "to delete it, use:\n" :
+ "Note: Non-remote branches were not removed; "
+ "to delete them, use:\n");
+ for (i = 0; i < skipped.nr; i++)
+ fprintf(stderr, " git branch -d %s\n",
+ skipped.items[i].string);
+ }
+ string_list_clear(&skipped, 0);
+
+ return result;
+}
+
+static void clear_push_info(void *util, const char *string)
+{
+ struct push_info *info = util;
+ free(info->dest);
+ free(info);
+}
+
+static void free_remote_ref_states(struct ref_states *states)
+{
+ string_list_clear(&states->new, 0);
+ string_list_clear(&states->stale, 1);
+ string_list_clear(&states->tracked, 0);
+ string_list_clear(&states->heads, 0);
+ string_list_clear_func(&states->push, clear_push_info);
+}
+
+static int append_ref_to_tracked_list(const char *refname,
+ const unsigned char *sha1, int flags, void *cb_data)
+{
+ struct ref_states *states = cb_data;
+ struct refspec refspec;
+
+ if (flags & REF_ISSYMREF)
+ return 0;
+
+ memset(&refspec, 0, sizeof(refspec));
+ refspec.dst = (char *)refname;
+ if (!remote_find_tracking(states->remote, &refspec))
+ string_list_append(abbrev_branch(refspec.src), &states->tracked);
+
+ return 0;
+}
+
+static int get_remote_ref_states(const char *name,
+ struct ref_states *states,
+ int query)
+{
+ struct transport *transport;
+ const struct ref *remote_refs;
+
+ states->remote = remote_get(name);
+ if (!states->remote)
+ return error("No such remote: %s", name);
+
+ read_branches();
+
+ if (query) {
+ transport = transport_get(states->remote, states->remote->url_nr > 0 ?
+ states->remote->url[0] : NULL);
+ remote_refs = transport_get_remote_refs(transport);
+ transport_disconnect(transport);
+
+ states->queried = 1;
+ if (query & GET_REF_STATES)
+ get_ref_states(remote_refs, states);
+ if (query & GET_HEAD_NAMES)
+ get_head_names(remote_refs, states);
+ if (query & GET_PUSH_REF_STATES)
+ get_push_ref_states(remote_refs, states);
+ } else {
+ for_each_ref(append_ref_to_tracked_list, states);
+ sort_string_list(&states->tracked);
+ get_push_ref_states_noquery(states);
+ }
+
+ return 0;
+}
+
+struct show_info {
+ struct string_list *list;
+ struct ref_states *states;
+ int width, width2;
+ int any_rebase;
+};
+
+static int add_remote_to_show_info(struct string_list_item *item, void *cb_data)
+{
+ struct show_info *info = cb_data;
+ int n = strlen(item->string);
+ if (n > info->width)
+ info->width = n;
+ string_list_insert(item->string, info->list);
+ return 0;
+}
+
+static int show_remote_info_item(struct string_list_item *item, void *cb_data)
+{
+ struct show_info *info = cb_data;
+ struct ref_states *states = info->states;
+ const char *name = item->string;
+
+ if (states->queried) {
+ const char *fmt = "%s";
+ const char *arg = "";
+ if (string_list_has_string(&states->new, name)) {
+ fmt = " new (next fetch will store in remotes/%s)";
+ arg = states->remote->name;
+ } else if (string_list_has_string(&states->tracked, name))
+ arg = " tracked";
+ else if (string_list_has_string(&states->stale, name))
+ arg = " stale (use 'git remote prune' to remove)";
+ else
+ arg = " ???";
+ printf(" %-*s", info->width, name);
+ printf(fmt, arg);
+ printf("\n");
+ } else
+ printf(" %s\n", name);
+
+ return 0;
+}
+
+static int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data)
+{
+ struct show_info *show_info = cb_data;
+ struct ref_states *states = show_info->states;
+ struct branch_info *branch_info = branch_item->util;
+ struct string_list_item *item;
+ int n;
+
+ if (!branch_info->merge.nr || !branch_info->remote_name ||
+ strcmp(states->remote->name, branch_info->remote_name))
+ return 0;
+ if ((n = strlen(branch_item->string)) > show_info->width)
+ show_info->width = n;
+ if (branch_info->rebase)
+ show_info->any_rebase = 1;
+
+ item = string_list_insert(branch_item->string, show_info->list);
+ item->util = branch_info;
+
+ return 0;
+}
+
+static int show_local_info_item(struct string_list_item *item, void *cb_data)
+{
+ struct show_info *show_info = cb_data;
+ struct branch_info *branch_info = item->util;
+ struct string_list *merge = &branch_info->merge;
+ const char *also;
+ int i;
+
+ if (branch_info->rebase && branch_info->merge.nr > 1) {
+ error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
+ item->string);
+ return 0;
+ }
+
+ printf(" %-*s ", show_info->width, item->string);
+ if (branch_info->rebase) {
+ printf("rebases onto remote %s\n", merge->items[0].string);
+ return 0;
+ } else if (show_info->any_rebase) {
+ printf(" merges with remote %s\n", merge->items[0].string);
+ also = " and with remote";
+ } else {
+ printf("merges with remote %s\n", merge->items[0].string);
+ also = " and with remote";
+ }
+ for (i = 1; i < merge->nr; i++)
+ printf(" %-*s %s %s\n", show_info->width, "", also,
+ merge->items[i].string);
+
+ return 0;
+}
+
+static int add_push_to_show_info(struct string_list_item *push_item, void *cb_data)
+{
+ struct show_info *show_info = cb_data;
+ struct push_info *push_info = push_item->util;
+ struct string_list_item *item;
+ int n;
+ if ((n = strlen(push_item->string)) > show_info->width)
+ show_info->width = n;
+ if ((n = strlen(push_info->dest)) > show_info->width2)
+ show_info->width2 = n;
+ item = string_list_append(push_item->string, show_info->list);
+ item->util = push_item->util;
+ return 0;
+}
+
+/*
+ * Sorting comparison for a string list that has push_info
+ * structs in its util field
+ */
+static int cmp_string_with_push(const void *va, const void *vb)
+{
+ const struct string_list_item *a = va;
+ const struct string_list_item *b = vb;
+ const struct push_info *a_push = a->util;
+ const struct push_info *b_push = b->util;
+ int cmp = strcmp(a->string, b->string);
+ return cmp ? cmp : strcmp(a_push->dest, b_push->dest);
+}
+
+static int show_push_info_item(struct string_list_item *item, void *cb_data)
+{
+ struct show_info *show_info = cb_data;
+ struct push_info *push_info = item->util;
+ char *src = item->string, *status = NULL;
+
+ switch (push_info->status) {
+ case PUSH_STATUS_CREATE:
+ status = "create";
+ break;
+ case PUSH_STATUS_DELETE:
+ status = "delete";
+ src = "(none)";
+ break;
+ case PUSH_STATUS_UPTODATE:
+ status = "up to date";
+ break;
+ case PUSH_STATUS_FASTFORWARD:
+ status = "fast-forwardable";
+ break;
+ case PUSH_STATUS_OUTOFDATE:
+ status = "local out of date";
+ break;
+ case PUSH_STATUS_NOTQUERIED:
+ break;
+ }
+ if (status)
+ printf(" %-*s %s to %-*s (%s)\n", show_info->width, src,
+ push_info->forced ? "forces" : "pushes",
+ show_info->width2, push_info->dest, status);
+ else
+ printf(" %-*s %s to %s\n", show_info->width, src,
+ push_info->forced ? "forces" : "pushes",
+ push_info->dest);
+ return 0;
+}
+
+static int show(int argc, const char **argv)
+{
+ int no_query = 0, result = 0, query_flag = 0;
+ struct option options[] = {
+ OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"),
+ OPT_END()
+ };
+ struct ref_states states;
+ struct string_list info_list = { NULL, 0, 0, 0 };
+ struct show_info info;
+
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage,
+ 0);
+
+ if (argc < 1)
+ return show_all();
+
+ if (!no_query)
+ query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES);
+
+ memset(&states, 0, sizeof(states));
+ memset(&info, 0, sizeof(info));
+ info.states = &states;
+ info.list = &info_list;
+ for (; argc; argc--, argv++) {
+ int i;
+ const char **url;
+ int url_nr;
+
+ get_remote_ref_states(*argv, &states, query_flag);
+
+ printf("* remote %s\n", *argv);
+ printf(" Fetch URL: %s\n", states.remote->url_nr > 0 ?
+ states.remote->url[0] : "(no URL)");
+ if (states.remote->pushurl_nr) {
+ url = states.remote->pushurl;
+ url_nr = states.remote->pushurl_nr;
+ } else {
+ url = states.remote->url;
+ url_nr = states.remote->url_nr;
+ }
+ for (i=0; i < url_nr; i++)
+ printf(" Push URL: %s\n", url[i]);
+ if (!i)
+ printf(" Push URL: %s\n", "(no URL)");
+ if (no_query)
+ printf(" HEAD branch: (not queried)\n");
+ else if (!states.heads.nr)
+ printf(" HEAD branch: (unknown)\n");
+ else if (states.heads.nr == 1)
+ printf(" HEAD branch: %s\n", states.heads.items[0].string);
+ else {
+ printf(" HEAD branch (remote HEAD is ambiguous,"
+ " may be one of the following):\n");
+ for (i = 0; i < states.heads.nr; i++)
+ printf(" %s\n", states.heads.items[i].string);
+ }
+
+ /* remote branch info */
+ info.width = 0;
+ for_each_string_list(add_remote_to_show_info, &states.new, &info);
+ for_each_string_list(add_remote_to_show_info, &states.tracked, &info);
+ for_each_string_list(add_remote_to_show_info, &states.stale, &info);
+ if (info.list->nr)
+ printf(" Remote branch%s:%s\n",
+ info.list->nr > 1 ? "es" : "",
+ no_query ? " (status not queried)" : "");
+ for_each_string_list(show_remote_info_item, info.list, &info);
+ string_list_clear(info.list, 0);
+
+ /* git pull info */
+ info.width = 0;
+ info.any_rebase = 0;
+ for_each_string_list(add_local_to_show_info, &branch_list, &info);
+ if (info.list->nr)
+ printf(" Local branch%s configured for 'git pull':\n",
+ info.list->nr > 1 ? "es" : "");
+ for_each_string_list(show_local_info_item, info.list, &info);
+ string_list_clear(info.list, 0);
+
+ /* git push info */
+ if (states.remote->mirror)
+ printf(" Local refs will be mirrored by 'git push'\n");
+
+ info.width = info.width2 = 0;
+ for_each_string_list(add_push_to_show_info, &states.push, &info);
+ qsort(info.list->items, info.list->nr,
+ sizeof(*info.list->items), cmp_string_with_push);
+ if (info.list->nr)
+ printf(" Local ref%s configured for 'git push'%s:\n",
+ info.list->nr > 1 ? "s" : "",
+ no_query ? " (status not queried)" : "");
+ for_each_string_list(show_push_info_item, info.list, &info);
+ string_list_clear(info.list, 0);
+
+ free_remote_ref_states(&states);
+ }
+
+ return result;
+}
+
+static int set_head(int argc, const char **argv)
+{
+ int i, opt_a = 0, opt_d = 0, result = 0;
+ struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+ char *head_name = NULL;
+
+ struct option options[] = {
+ OPT_BOOLEAN('a', "auto", &opt_a,
+ "set refs/remotes/<name>/HEAD according to remote"),
+ OPT_BOOLEAN('d', "delete", &opt_d,
+ "delete refs/remotes/<name>/HEAD"),
+ OPT_END()
+ };
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage,
+ 0);
+ if (argc)
+ strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+
+ if (!opt_a && !opt_d && argc == 2) {
+ head_name = xstrdup(argv[1]);
+ } else if (opt_a && !opt_d && argc == 1) {
+ struct ref_states states;
+ memset(&states, 0, sizeof(states));
+ get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
+ if (!states.heads.nr)
+ result |= error("Cannot determine remote HEAD");
+ else if (states.heads.nr > 1) {
+ result |= error("Multiple remote HEAD branches. "
+ "Please choose one explicitly with:");
+ for (i = 0; i < states.heads.nr; i++)
+ fprintf(stderr, " git remote set-head %s %s\n",
+ argv[0], states.heads.items[i].string);
+ } else
+ head_name = xstrdup(states.heads.items[0].string);
+ free_remote_ref_states(&states);
+ } else if (opt_d && !opt_a && argc == 1) {
+ if (delete_ref(buf.buf, NULL, REF_NODEREF))
+ result |= error("Could not delete %s", buf.buf);
+ } else
+ usage_with_options(builtin_remote_sethead_usage, options);
+
+ if (head_name) {
+ unsigned char sha1[20];
+ strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+ /* make sure it's valid */
+ if (!resolve_ref(buf2.buf, sha1, 1, NULL))
+ result |= error("Not a valid ref: %s", buf2.buf);
+ else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
+ result |= error("Could not setup %s", buf.buf);
+ if (opt_a)
+ printf("%s/HEAD set to %s\n", argv[0], head_name);
+ free(head_name);
+ }
+
+ strbuf_release(&buf);
+ strbuf_release(&buf2);
+ return result;
+}
+
+static int prune(int argc, const char **argv)
+{
+ int dry_run = 0, result = 0;
+ struct option options[] = {
+ OPT__DRY_RUN(&dry_run),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage,
+ 0);
+
+ if (argc < 1)
+ usage_with_options(builtin_remote_prune_usage, options);
+
+ for (; argc; argc--, argv++)
+ result |= prune_remote(*argv, dry_run);
+
+ return result;
+}
+
+static int prune_remote(const char *remote, int dry_run)
+{
+ int result = 0, i;
+ struct ref_states states;
+ const char *dangling_msg = dry_run
+ ? " %s will become dangling!\n"
+ : " %s has become dangling!\n";
+
+ memset(&states, 0, sizeof(states));
+ get_remote_ref_states(remote, &states, GET_REF_STATES);
+
+ if (states.stale.nr) {
+ printf("Pruning %s\n", remote);
+ printf("URL: %s\n",
+ states.remote->url_nr
+ ? states.remote->url[0]
+ : "(no URL)");
+ }
+
+ for (i = 0; i < states.stale.nr; i++) {
+ const char *refname = states.stale.items[i].util;
+
+ if (!dry_run)
+ result |= delete_ref(refname, NULL, 0);
+
+ printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
+ abbrev_ref(refname, "refs/remotes/"));
+ warn_dangling_symref(stdout, dangling_msg, refname);
+ }
+
+ free_remote_ref_states(&states);
+ return result;
+}
+
+static int get_remote_default(const char *key, const char *value, void *priv)
+{
+ if (strcmp(key, "remotes.default") == 0) {
+ int *found = priv;
+ *found = 1;
+ }
+ return 0;
+}
+
+static int update(int argc, const char **argv)
+{
+ int i, prune = 0;
+ struct option options[] = {
+ OPT_BOOLEAN('p', "prune", &prune,
+ "prune remotes after fetching"),
+ OPT_END()
+ };
+ const char **fetch_argv;
+ int fetch_argc = 0;
+ int default_defined = 0;
+
+ fetch_argv = xmalloc(sizeof(char *) * (argc+5));
+
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+ PARSE_OPT_KEEP_ARGV0);
+
+ fetch_argv[fetch_argc++] = "fetch";
+
+ if (prune)
+ fetch_argv[fetch_argc++] = "--prune";
+ if (verbose)
+ fetch_argv[fetch_argc++] = "-v";
+ fetch_argv[fetch_argc++] = "--multiple";
+ if (argc < 2)
+ fetch_argv[fetch_argc++] = "default";
+ for (i = 1; i < argc; i++)
+ fetch_argv[fetch_argc++] = argv[i];
+
+ if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) {
+ git_config(get_remote_default, &default_defined);
+ if (!default_defined)
+ fetch_argv[fetch_argc-1] = "--all";
+ }
+
+ fetch_argv[fetch_argc] = NULL;
+
+ return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
+}
+
+static int set_url(int argc, const char **argv)
+{
+ int i, push_mode = 0, add_mode = 0, delete_mode = 0;
+ int matches = 0, negative_matches = 0;
+ const char *remotename = NULL;
+ const char *newurl = NULL;
+ const char *oldurl = NULL;
+ struct remote *remote;
+ regex_t old_regex;
+ const char **urlset;
+ int urlset_nr;
+ struct strbuf name_buf = STRBUF_INIT;
+ struct option options[] = {
+ OPT_BOOLEAN('\0', "push", &push_mode,
+ "manipulate push URLs"),
+ OPT_BOOLEAN('\0', "add", &add_mode,
+ "add URL"),
+ OPT_BOOLEAN('\0', "delete", &delete_mode,
+ "delete URLs"),
+ OPT_END()
+ };
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+ PARSE_OPT_KEEP_ARGV0);
+
+ if (add_mode && delete_mode)
+ die("--add --delete doesn't make sense");
+
+ if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3))
+ usage_with_options(builtin_remote_seturl_usage, options);
+
+ remotename = argv[1];
+ newurl = argv[2];
+ if (argc > 3)
+ oldurl = argv[3];
+
+ if (delete_mode)
+ oldurl = newurl;
+
+ if (!remote_is_configured(remotename))
+ die("No such remote '%s'", remotename);
+ remote = remote_get(remotename);
+
+ if (push_mode) {
+ strbuf_addf(&name_buf, "remote.%s.pushurl", remotename);
+ urlset = remote->pushurl;
+ urlset_nr = remote->pushurl_nr;
+ } else {
+ strbuf_addf(&name_buf, "remote.%s.url", remotename);
+ urlset = remote->url;
+ urlset_nr = remote->url_nr;
+ }
+
+ /* Special cases that add new entry. */
+ if ((!oldurl && !delete_mode) || add_mode) {
+ if (add_mode)
+ git_config_set_multivar(name_buf.buf, newurl,
+ "^$", 0);
+ else
+ git_config_set(name_buf.buf, newurl);
+ strbuf_release(&name_buf);
+ return 0;
+ }
+
+ /* Old URL specified. Demand that one matches. */
+ if (regcomp(&old_regex, oldurl, REG_EXTENDED))
+ die("Invalid old URL pattern: %s", oldurl);
+
+ for (i = 0; i < urlset_nr; i++)
+ if (!regexec(&old_regex, urlset[i], 0, NULL, 0))
+ matches++;
+ else
+ negative_matches++;
+ if (!delete_mode && !matches)
+ die("No such URL found: %s", oldurl);
+ if (delete_mode && !negative_matches && !push_mode)
+ die("Will not delete all non-push URLs");
+
+ regfree(&old_regex);
+
+ if (!delete_mode)
+ git_config_set_multivar(name_buf.buf, newurl, oldurl, 0);
+ else
+ git_config_set_multivar(name_buf.buf, NULL, oldurl, 1);
+ return 0;
+}
+
+static int get_one_entry(struct remote *remote, void *priv)
+{
+ struct string_list *list = priv;
+ struct strbuf url_buf = STRBUF_INIT;
+ const char **url;
+ int i, url_nr;
+
+ if (remote->url_nr > 0) {
+ strbuf_addf(&url_buf, "%s (fetch)", remote->url[0]);
+ string_list_append(remote->name, list)->util =
+ strbuf_detach(&url_buf, NULL);
+ } else
+ string_list_append(remote->name, list)->util = NULL;
+ if (remote->pushurl_nr) {
+ url = remote->pushurl;
+ url_nr = remote->pushurl_nr;
+ } else {
+ url = remote->url;
+ url_nr = remote->url_nr;
+ }
+ for (i = 0; i < url_nr; i++)
+ {
+ strbuf_addf(&url_buf, "%s (push)", url[i]);
+ string_list_append(remote->name, list)->util =
+ strbuf_detach(&url_buf, NULL);
+ }
+
+ return 0;
+}
+
+static int show_all(void)
+{
+ struct string_list list = { NULL, 0, 0 };
+ int result;
+
+ list.strdup_strings = 1;
+ result = for_each_remote(get_one_entry, &list);
+
+ if (!result) {
+ int i;
+
+ sort_string_list(&list);
+ for (i = 0; i < list.nr; i++) {
+ struct string_list_item *item = list.items + i;
+ if (verbose)
+ printf("%s\t%s\n", item->string,
+ item->util ? (const char *)item->util : "");
+ else {
+ if (i && !strcmp((item - 1)->string, item->string))
+ continue;
+ printf("%s\n", item->string);
+ }
+ }
+ }
+ string_list_clear(&list, 1);
+ return result;
+}
+
+int cmd_remote(int argc, const char **argv, const char *prefix)
+{
+ struct option options[] = {
+ OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"),
+ OPT_END()
+ };
+ int result;
+
+ argc = parse_options(argc, argv, prefix, options, builtin_remote_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (argc < 1)
+ result = show_all();
+ else if (!strcmp(argv[0], "add"))
+ result = add(argc, argv);
+ else if (!strcmp(argv[0], "rename"))
+ result = mv(argc, argv);
+ else if (!strcmp(argv[0], "rm"))
+ result = rm(argc, argv);
+ else if (!strcmp(argv[0], "set-head"))
+ result = set_head(argc, argv);
+ else if (!strcmp(argv[0], "set-url"))
+ result = set_url(argc, argv);
+ else if (!strcmp(argv[0], "show"))
+ result = show(argc, argv);
+ else if (!strcmp(argv[0], "prune"))
+ result = prune(argc, argv);
+ else if (!strcmp(argv[0], "update"))
+ result = update(argc, argv);
+ else {
+ error("Unknown subcommand: %s", argv[0]);
+ usage_with_options(builtin_remote_usage, options);
+ }
+
+ return result ? 1 : 0;
+}
--- /dev/null
+/*
+ * Builtin "git replace"
+ *
+ * Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org>
+ *
+ * Based on builtin-tag.c by Kristian Høgsberg <krh@redhat.com>
+ * and Carlos Rica <jasampler@gmail.com> that was itself based on
+ * git-tag.sh and mktag.c by Linus Torvalds.
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "refs.h"
+#include "parse-options.h"
+
+static const char * const git_replace_usage[] = {
+ "git replace [-f] <object> <replacement>",
+ "git replace -d <object>...",
+ "git replace -l [<pattern>]",
+ NULL
+};
+
+static int show_reference(const char *refname, const unsigned char *sha1,
+ int flag, void *cb_data)
+{
+ const char *pattern = cb_data;
+
+ if (!fnmatch(pattern, refname, 0))
+ printf("%s\n", refname);
+
+ return 0;
+}
+
+static int list_replace_refs(const char *pattern)
+{
+ if (pattern == NULL)
+ pattern = "*";
+
+ for_each_replace_ref(show_reference, (void *) pattern);
+
+ return 0;
+}
+
+typedef int (*each_replace_name_fn)(const char *name, const char *ref,
+ const unsigned char *sha1);
+
+static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
+{
+ const char **p;
+ char ref[PATH_MAX];
+ int had_error = 0;
+ unsigned char sha1[20];
+
+ for (p = argv; *p; p++) {
+ if (snprintf(ref, sizeof(ref), "refs/replace/%s", *p)
+ >= sizeof(ref)) {
+ error("replace ref name too long: %.*s...", 50, *p);
+ had_error = 1;
+ continue;
+ }
+ if (!resolve_ref(ref, sha1, 1, NULL)) {
+ error("replace ref '%s' not found.", *p);
+ had_error = 1;
+ continue;
+ }
+ if (fn(*p, ref, sha1))
+ had_error = 1;
+ }
+ return had_error;
+}
+
+static int delete_replace_ref(const char *name, const char *ref,
+ const unsigned char *sha1)
+{
+ if (delete_ref(ref, sha1, 0))
+ return 1;
+ printf("Deleted replace ref '%s'\n", name);
+ return 0;
+}
+
+static int replace_object(const char *object_ref, const char *replace_ref,
+ int force)
+{
+ unsigned char object[20], prev[20], repl[20];
+ char ref[PATH_MAX];
+ struct ref_lock *lock;
+
+ if (get_sha1(object_ref, object))
+ die("Failed to resolve '%s' as a valid ref.", object_ref);
+ if (get_sha1(replace_ref, repl))
+ die("Failed to resolve '%s' as a valid ref.", replace_ref);
+
+ if (snprintf(ref, sizeof(ref),
+ "refs/replace/%s",
+ sha1_to_hex(object)) > sizeof(ref) - 1)
+ die("replace ref name too long: %.*s...", 50, ref);
+ if (check_ref_format(ref))
+ die("'%s' is not a valid ref name.", ref);
+
+ if (!resolve_ref(ref, prev, 1, NULL))
+ hashclr(prev);
+ else if (!force)
+ die("replace ref '%s' already exists", ref);
+
+ lock = lock_any_ref_for_update(ref, prev, 0);
+ if (!lock)
+ die("%s: cannot lock the ref", ref);
+ if (write_ref_sha1(lock, repl, NULL) < 0)
+ die("%s: cannot update the ref", ref);
+
+ return 0;
+}
+
+int cmd_replace(int argc, const char **argv, const char *prefix)
+{
+ int list = 0, delete = 0, force = 0;
+ struct option options[] = {
+ OPT_BOOLEAN('l', NULL, &list, "list replace refs"),
+ OPT_BOOLEAN('d', NULL, &delete, "delete replace refs"),
+ OPT_BOOLEAN('f', NULL, &force, "replace the ref if it exists"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
+
+ if (list && delete)
+ usage_msg_opt("-l and -d cannot be used together",
+ git_replace_usage, options);
+
+ if (force && (list || delete))
+ usage_msg_opt("-f cannot be used with -d or -l",
+ git_replace_usage, options);
+
+ /* Delete refs */
+ if (delete) {
+ if (argc < 1)
+ usage_msg_opt("-d needs at least one argument",
+ git_replace_usage, options);
+ return for_each_replace_name(argv, delete_replace_ref);
+ }
+
+ /* Replace object */
+ if (!list && argc) {
+ if (argc != 2)
+ usage_msg_opt("bad number of arguments",
+ git_replace_usage, options);
+ return replace_object(argv[0], argv[1], force);
+ }
+
+ /* List refs, even if "list" is not set */
+ if (argc > 1)
+ usage_msg_opt("only one pattern can be given with -l",
+ git_replace_usage, options);
+ if (force)
+ usage_msg_opt("-f needs some arguments",
+ git_replace_usage, options);
+
+ return list_replace_refs(argv[0]);
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "string-list.h"
+#include "rerere.h"
+#include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
+
+static const char git_rerere_usage[] =
+"git rerere [clear | status | diff | gc]";
+
+/* these values are days */
+static int cutoff_noresolve = 15;
+static int cutoff_resolve = 60;
+
+static time_t rerere_created_at(const char *name)
+{
+ struct stat st;
+ return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
+}
+
+static void unlink_rr_item(const char *name)
+{
+ unlink(rerere_path(name, "thisimage"));
+ unlink(rerere_path(name, "preimage"));
+ unlink(rerere_path(name, "postimage"));
+ rmdir(git_path("rr-cache/%s", name));
+}
+
+static int git_rerere_gc_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "gc.rerereresolved"))
+ cutoff_resolve = git_config_int(var, value);
+ else if (!strcmp(var, "gc.rerereunresolved"))
+ cutoff_noresolve = git_config_int(var, value);
+ else
+ return git_default_config(var, value, cb);
+ return 0;
+}
+
+static void garbage_collect(struct string_list *rr)
+{
+ struct string_list to_remove = { NULL, 0, 0, 1 };
+ DIR *dir;
+ struct dirent *e;
+ int i, cutoff;
+ time_t now = time(NULL), then;
+
+ git_config(git_rerere_gc_config, NULL);
+ dir = opendir(git_path("rr-cache"));
+ if (!dir)
+ die_errno("unable to open rr-cache directory");
+ while ((e = readdir(dir))) {
+ if (is_dot_or_dotdot(e->d_name))
+ continue;
+ then = rerere_created_at(e->d_name);
+ if (!then)
+ continue;
+ cutoff = (has_rerere_resolution(e->d_name)
+ ? cutoff_resolve : cutoff_noresolve);
+ if (then < now - cutoff * 86400)
+ string_list_append(e->d_name, &to_remove);
+ }
+ for (i = 0; i < to_remove.nr; i++)
+ unlink_rr_item(to_remove.items[i].string);
+ string_list_clear(&to_remove, 0);
+}
+
+static int outf(void *dummy, mmbuffer_t *ptr, int nbuf)
+{
+ int i;
+ for (i = 0; i < nbuf; i++)
+ if (write_in_full(1, ptr[i].ptr, ptr[i].size) != ptr[i].size)
+ return -1;
+ return 0;
+}
+
+static int diff_two(const char *file1, const char *label1,
+ const char *file2, const char *label2)
+{
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+ mmfile_t minus, plus;
+
+ if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
+ return 1;
+
+ printf("--- a/%s\n+++ b/%s\n", label1, label2);
+ fflush(stdout);
+ memset(&xpp, 0, sizeof(xpp));
+ xpp.flags = XDF_NEED_MINIMAL;
+ memset(&xecfg, 0, sizeof(xecfg));
+ xecfg.ctxlen = 3;
+ ecb.outf = outf;
+ xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+
+ free(minus.ptr);
+ free(plus.ptr);
+ return 0;
+}
+
+int cmd_rerere(int argc, const char **argv, const char *prefix)
+{
+ struct string_list merge_rr = { NULL, 0, 0, 1 };
+ int i, fd, flags = 0;
+
+ if (2 < argc) {
+ if (!strcmp(argv[1], "-h"))
+ usage(git_rerere_usage);
+ if (!strcmp(argv[1], "--rerere-autoupdate"))
+ flags = RERERE_AUTOUPDATE;
+ else if (!strcmp(argv[1], "--no-rerere-autoupdate"))
+ flags = RERERE_NOAUTOUPDATE;
+ if (flags) {
+ argc--;
+ argv++;
+ }
+ }
+ if (argc < 2)
+ return rerere(flags);
+
+ if (!strcmp(argv[1], "forget")) {
+ const char **pathspec = get_pathspec(prefix, argv + 2);
+ return rerere_forget(pathspec);
+ }
+
+ fd = setup_rerere(&merge_rr, flags);
+ if (fd < 0)
+ return 0;
+
+ if (!strcmp(argv[1], "clear")) {
+ for (i = 0; i < merge_rr.nr; i++) {
+ const char *name = (const char *)merge_rr.items[i].util;
+ if (!has_rerere_resolution(name))
+ unlink_rr_item(name);
+ }
+ unlink_or_warn(git_path("rr-cache/MERGE_RR"));
+ } else if (!strcmp(argv[1], "gc"))
+ garbage_collect(&merge_rr);
+ else if (!strcmp(argv[1], "status"))
+ for (i = 0; i < merge_rr.nr; i++)
+ printf("%s\n", merge_rr.items[i].string);
+ else if (!strcmp(argv[1], "diff"))
+ for (i = 0; i < merge_rr.nr; i++) {
+ const char *path = merge_rr.items[i].string;
+ const char *name = (const char *)merge_rr.items[i].util;
+ diff_two(rerere_path(name, "preimage"), path, path, path);
+ }
+ else
+ usage(git_rerere_usage);
+
+ string_list_clear(&merge_rr, 1);
+ return 0;
+}
--- /dev/null
+/*
+ * "git reset" builtin command
+ *
+ * Copyright (c) 2007 Carlos Rica
+ *
+ * Based on git-reset.sh, which is
+ *
+ * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
+ */
+#include "cache.h"
+#include "tag.h"
+#include "object.h"
+#include "commit.h"
+#include "run-command.h"
+#include "refs.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "tree.h"
+#include "branch.h"
+#include "parse-options.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+
+static const char * const git_reset_usage[] = {
+ "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
+ "git reset [--mixed] <commit> [--] <paths>...",
+ NULL
+};
+
+enum reset_type { MIXED, SOFT, HARD, MERGE, NONE };
+static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL };
+
+static char *args_to_str(const char **argv)
+{
+ char *buf = NULL;
+ unsigned long len, space = 0, nr = 0;
+
+ for (; *argv; argv++) {
+ len = strlen(*argv);
+ ALLOC_GROW(buf, nr + 1 + len, space);
+ if (nr)
+ buf[nr++] = ' ';
+ memcpy(buf + nr, *argv, len);
+ nr += len;
+ }
+ ALLOC_GROW(buf, nr + 1, space);
+ buf[nr] = '\0';
+
+ return buf;
+}
+
+static inline int is_merge(void)
+{
+ return !access(git_path("MERGE_HEAD"), F_OK);
+}
+
+static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
+{
+ int nr = 1;
+ int newfd;
+ struct tree_desc desc[2];
+ struct unpack_trees_options opts;
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.fn = oneway_merge;
+ opts.merge = 1;
+ if (!quiet)
+ opts.verbose_update = 1;
+ switch (reset_type) {
+ case MERGE:
+ opts.update = 1;
+ break;
+ case HARD:
+ opts.update = 1;
+ /* fallthrough */
+ default:
+ opts.reset = 1;
+ }
+
+ newfd = hold_locked_index(lock, 1);
+
+ read_cache_unmerged();
+
+ if (!fill_tree_descriptor(desc + nr - 1, sha1))
+ return error("Failed to find tree of %s.", sha1_to_hex(sha1));
+ if (unpack_trees(nr, desc, &opts))
+ return -1;
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(lock))
+ return error("Could not write new index file.");
+
+ return 0;
+}
+
+static void print_new_head_line(struct commit *commit)
+{
+ const char *hex, *body;
+
+ hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
+ printf("HEAD is now at %s", hex);
+ body = strstr(commit->buffer, "\n\n");
+ if (body) {
+ const char *eol;
+ size_t len;
+ body += 2;
+ eol = strchr(body, '\n');
+ len = eol ? eol - body : strlen(body);
+ printf(" %.*s\n", (int) len, body);
+ }
+ else
+ printf("\n");
+}
+
+static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
+{
+ int result;
+
+ if (!index_lock) {
+ index_lock = xcalloc(1, sizeof(struct lock_file));
+ fd = hold_locked_index(index_lock, 1);
+ }
+
+ if (read_cache() < 0)
+ return error("Could not read index");
+
+ result = refresh_index(&the_index, (flags), NULL, NULL,
+ "Unstaged changes after reset:") ? 1 : 0;
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(index_lock))
+ return error ("Could not refresh index");
+ return result;
+}
+
+static void update_index_from_diff(struct diff_queue_struct *q,
+ struct diff_options *opt, void *data)
+{
+ int i;
+ int *discard_flag = data;
+
+ /* do_diff_cache() mangled the index */
+ discard_cache();
+ *discard_flag = 1;
+ read_cache();
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filespec *one = q->queue[i]->one;
+ if (one->mode) {
+ struct cache_entry *ce;
+ ce = make_cache_entry(one->mode, one->sha1, one->path,
+ 0, 0);
+ if (!ce)
+ die("make_cache_entry failed for path '%s'",
+ one->path);
+ add_cache_entry(ce, ADD_CACHE_OK_TO_ADD |
+ ADD_CACHE_OK_TO_REPLACE);
+ } else
+ remove_file_from_cache(one->path);
+ }
+}
+
+static int interactive_reset(const char *revision, const char **argv,
+ const char *prefix)
+{
+ const char **pathspec = NULL;
+
+ if (*argv)
+ pathspec = get_pathspec(prefix, argv);
+
+ return run_add_interactive(revision, "--patch=reset", pathspec);
+}
+
+static int read_from_tree(const char *prefix, const char **argv,
+ unsigned char *tree_sha1, int refresh_flags)
+{
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ int index_fd, index_was_discarded = 0;
+ struct diff_options opt;
+
+ memset(&opt, 0, sizeof(opt));
+ diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
+ opt.output_format = DIFF_FORMAT_CALLBACK;
+ opt.format_callback = update_index_from_diff;
+ opt.format_callback_data = &index_was_discarded;
+
+ index_fd = hold_locked_index(lock, 1);
+ index_was_discarded = 0;
+ read_cache();
+ if (do_diff_cache(tree_sha1, &opt))
+ return 1;
+ diffcore_std(&opt);
+ diff_flush(&opt);
+ diff_tree_release_paths(&opt);
+
+ if (!index_was_discarded)
+ /* The index is still clobbered from do_diff_cache() */
+ discard_cache();
+ return update_index_refresh(index_fd, lock, refresh_flags);
+}
+
+static void prepend_reflog_action(const char *action, char *buf, size_t size)
+{
+ const char *sep = ": ";
+ const char *rla = getenv("GIT_REFLOG_ACTION");
+ if (!rla)
+ rla = sep = "";
+ if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size)
+ warning("Reflog action message too long: %.*s...", 50, buf);
+}
+
+int cmd_reset(int argc, const char **argv, const char *prefix)
+{
+ int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
+ int patch_mode = 0;
+ const char *rev = "HEAD";
+ unsigned char sha1[20], *orig = NULL, sha1_orig[20],
+ *old_orig = NULL, sha1_old_orig[20];
+ struct commit *commit;
+ char *reflog_action, msg[1024];
+ const struct option options[] = {
+ OPT__QUIET(&quiet),
+ OPT_SET_INT(0, "mixed", &reset_type,
+ "reset HEAD and index", MIXED),
+ OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
+ OPT_SET_INT(0, "hard", &reset_type,
+ "reset HEAD, index and working tree", HARD),
+ OPT_SET_INT(0, "merge", &reset_type,
+ "reset HEAD, index and working tree", MERGE),
+ OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, options, git_reset_usage,
+ PARSE_OPT_KEEP_DASHDASH);
+ reflog_action = args_to_str(argv);
+ setenv("GIT_REFLOG_ACTION", reflog_action, 0);
+
+ /*
+ * Possible arguments are:
+ *
+ * git reset [-opts] <rev> <paths>...
+ * git reset [-opts] <rev> -- <paths>...
+ * git reset [-opts] -- <paths>...
+ * git reset [-opts] <paths>...
+ *
+ * At this point, argv[i] points immediately after [-opts].
+ */
+
+ if (i < argc) {
+ if (!strcmp(argv[i], "--")) {
+ i++; /* reset to HEAD, possibly with paths */
+ } else if (i + 1 < argc && !strcmp(argv[i+1], "--")) {
+ rev = argv[i];
+ i += 2;
+ }
+ /*
+ * Otherwise, argv[i] could be either <rev> or <paths> and
+ * has to be unambiguous.
+ */
+ else if (!get_sha1(argv[i], sha1)) {
+ /*
+ * Ok, argv[i] looks like a rev; it should not
+ * be a filename.
+ */
+ verify_non_filename(prefix, argv[i]);
+ rev = argv[i++];
+ } else {
+ /* Otherwise we treat this as a filename */
+ verify_filename(prefix, argv[i]);
+ }
+ }
+
+ if (get_sha1(rev, sha1))
+ die("Failed to resolve '%s' as a valid ref.", rev);
+
+ commit = lookup_commit_reference(sha1);
+ if (!commit)
+ die("Could not parse object '%s'.", rev);
+ hashcpy(sha1, commit->object.sha1);
+
+ if (patch_mode) {
+ if (reset_type != NONE)
+ die("--patch is incompatible with --{hard,mixed,soft}");
+ return interactive_reset(rev, argv + i, prefix);
+ }
+
+ /* git reset tree [--] paths... can be used to
+ * load chosen paths from the tree into the index without
+ * affecting the working tree nor HEAD. */
+ if (i < argc) {
+ if (reset_type == MIXED)
+ warning("--mixed option is deprecated with paths.");
+ else if (reset_type != NONE)
+ die("Cannot do %s reset with paths.",
+ reset_type_names[reset_type]);
+ return read_from_tree(prefix, argv + i, sha1,
+ quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+ }
+ if (reset_type == NONE)
+ reset_type = MIXED; /* by default */
+
+ if (reset_type == HARD || reset_type == MERGE)
+ setup_work_tree();
+
+ if (reset_type == MIXED && is_bare_repository())
+ die("%s reset is not allowed in a bare repository",
+ reset_type_names[reset_type]);
+
+ /* Soft reset does not touch the index file nor the working tree
+ * at all, but requires them in a good order. Other resets reset
+ * the index file to the tree object we are switching to. */
+ if (reset_type == SOFT) {
+ if (is_merge() || read_cache() < 0 || unmerged_cache())
+ die("Cannot do a soft reset in the middle of a merge.");
+ }
+ else if (reset_index_file(sha1, reset_type, quiet))
+ die("Could not reset index file to revision '%s'.", rev);
+
+ /* Any resets update HEAD to the head being switched to,
+ * saving the previous head in ORIG_HEAD before. */
+ if (!get_sha1("ORIG_HEAD", sha1_old_orig))
+ old_orig = sha1_old_orig;
+ if (!get_sha1("HEAD", sha1_orig)) {
+ orig = sha1_orig;
+ prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg));
+ update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
+ }
+ else if (old_orig)
+ delete_ref("ORIG_HEAD", old_orig, 0);
+ prepend_reflog_action("updating HEAD", msg, sizeof(msg));
+ update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR);
+
+ switch (reset_type) {
+ case HARD:
+ if (!update_ref_status && !quiet)
+ print_new_head_line(commit);
+ break;
+ case SOFT: /* Nothing else to do. */
+ break;
+ case MIXED: /* Report what has not been updated. */
+ update_index_refresh(0, NULL,
+ quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
+ break;
+ }
+
+ remove_branch_state();
+
+ free(reflog_action);
+
+ return update_ref_status;
+}
--- /dev/null
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "builtin.h"
+#include "log-tree.h"
+#include "graph.h"
+#include "bisect.h"
+
+static const char rev_list_usage[] =
+"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
+" limiting output:\n"
+" --max-count=nr\n"
+" --max-age=epoch\n"
+" --min-age=epoch\n"
+" --sparse\n"
+" --no-merges\n"
+" --remove-empty\n"
+" --all\n"
+" --branches\n"
+" --tags\n"
+" --remotes\n"
+" --stdin\n"
+" --quiet\n"
+" ordering output:\n"
+" --topo-order\n"
+" --date-order\n"
+" --reverse\n"
+" formatting output:\n"
+" --parents\n"
+" --children\n"
+" --objects | --objects-edge\n"
+" --unpacked\n"
+" --header | --pretty\n"
+" --abbrev=nr | --no-abbrev\n"
+" --abbrev-commit\n"
+" --left-right\n"
+" special purpose:\n"
+" --bisect\n"
+" --bisect-vars\n"
+" --bisect-all"
+;
+
+static void finish_commit(struct commit *commit, void *data);
+static void show_commit(struct commit *commit, void *data)
+{
+ struct rev_list_info *info = data;
+ struct rev_info *revs = info->revs;
+
+ graph_show_commit(revs->graph);
+
+ if (info->show_timestamp)
+ printf("%lu ", commit->date);
+ if (info->header_prefix)
+ fputs(info->header_prefix, stdout);
+
+ if (!revs->graph) {
+ if (commit->object.flags & BOUNDARY)
+ putchar('-');
+ else if (commit->object.flags & UNINTERESTING)
+ putchar('^');
+ else if (revs->left_right) {
+ if (commit->object.flags & SYMMETRIC_LEFT)
+ putchar('<');
+ else
+ putchar('>');
+ }
+ }
+ if (revs->abbrev_commit && revs->abbrev)
+ fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev),
+ stdout);
+ else
+ fputs(sha1_to_hex(commit->object.sha1), stdout);
+ if (revs->print_parents) {
+ struct commit_list *parents = commit->parents;
+ while (parents) {
+ printf(" %s", sha1_to_hex(parents->item->object.sha1));
+ parents = parents->next;
+ }
+ }
+ if (revs->children.name) {
+ struct commit_list *children;
+
+ children = lookup_decoration(&revs->children, &commit->object);
+ while (children) {
+ printf(" %s", sha1_to_hex(children->item->object.sha1));
+ children = children->next;
+ }
+ }
+ show_decorations(revs, commit);
+ if (revs->commit_format == CMIT_FMT_ONELINE)
+ putchar(' ');
+ else
+ putchar('\n');
+
+ if (revs->verbose_header && commit->buffer) {
+ struct strbuf buf = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ ctx.abbrev = revs->abbrev;
+ ctx.date_mode = revs->date_mode;
+ pretty_print_commit(revs->commit_format, commit, &buf, &ctx);
+ if (revs->graph) {
+ if (buf.len) {
+ if (revs->commit_format != CMIT_FMT_ONELINE)
+ graph_show_oneline(revs->graph);
+
+ graph_show_commit_msg(revs->graph, &buf);
+
+ /*
+ * Add a newline after the commit message.
+ *
+ * Usually, this newline produces a blank
+ * padding line between entries, in which case
+ * we need to add graph padding on this line.
+ *
+ * However, the commit message may not end in a
+ * newline. In this case the newline simply
+ * ends the last line of the commit message,
+ * and we don't need any graph output. (This
+ * always happens with CMIT_FMT_ONELINE, and it
+ * happens with CMIT_FMT_USERFORMAT when the
+ * format doesn't explicitly end in a newline.)
+ */
+ if (buf.len && buf.buf[buf.len - 1] == '\n')
+ graph_show_padding(revs->graph);
+ putchar('\n');
+ } else {
+ /*
+ * If the message buffer is empty, just show
+ * the rest of the graph output for this
+ * commit.
+ */
+ if (graph_show_remainder(revs->graph))
+ putchar('\n');
+ }
+ } else {
+ if (buf.len)
+ printf("%s%c", buf.buf, info->hdr_termination);
+ }
+ strbuf_release(&buf);
+ } else {
+ if (graph_show_remainder(revs->graph))
+ putchar('\n');
+ }
+ maybe_flush_or_die(stdout, "stdout");
+ finish_commit(commit, data);
+}
+
+static void finish_commit(struct commit *commit, void *data)
+{
+ if (commit->parents) {
+ free_commit_list(commit->parents);
+ commit->parents = NULL;
+ }
+ free(commit->buffer);
+ commit->buffer = NULL;
+}
+
+static void finish_object(struct object *obj, const struct name_path *path, const char *name)
+{
+ if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
+ die("missing blob object '%s'", sha1_to_hex(obj->sha1));
+}
+
+static void show_object(struct object *obj, const struct name_path *path, const char *component)
+{
+ char *name = path_name(path, component);
+ /* An object with name "foo\n0000000..." can be used to
+ * confuse downstream "git pack-objects" very badly.
+ */
+ const char *ep = strchr(name, '\n');
+
+ finish_object(obj, path, name);
+ if (ep) {
+ printf("%s %.*s\n", sha1_to_hex(obj->sha1),
+ (int) (ep - name),
+ name);
+ }
+ else
+ printf("%s %s\n", sha1_to_hex(obj->sha1), name);
+ free(name);
+}
+
+static void show_edge(struct commit *commit)
+{
+ printf("-%s\n", sha1_to_hex(commit->object.sha1));
+}
+
+static inline int log2i(int n)
+{
+ int log2 = 0;
+
+ for (; n > 1; n >>= 1)
+ log2++;
+
+ return log2;
+}
+
+static inline int exp2i(int n)
+{
+ return 1 << n;
+}
+
+/*
+ * Estimate the number of bisect steps left (after the current step)
+ *
+ * For any x between 0 included and 2^n excluded, the probability for
+ * n - 1 steps left looks like:
+ *
+ * P(2^n + x) == (2^n - x) / (2^n + x)
+ *
+ * and P(2^n + x) < 0.5 means 2^n < 3x
+ */
+int estimate_bisect_steps(int all)
+{
+ int n, x, e;
+
+ if (all < 3)
+ return 0;
+
+ n = log2i(all);
+ e = exp2i(n);
+ x = all - e;
+
+ return (e < 3 * x) ? n : n - 1;
+}
+
+void print_commit_list(struct commit_list *list,
+ const char *format_cur,
+ const char *format_last)
+{
+ for ( ; list; list = list->next) {
+ const char *format = list->next ? format_cur : format_last;
+ printf(format, sha1_to_hex(list->item->object.sha1));
+ }
+}
+
+static void show_tried_revs(struct commit_list *tried)
+{
+ printf("bisect_tried='");
+ print_commit_list(tried, "%s|", "%s");
+ printf("'\n");
+}
+
+static void print_var_str(const char *var, const char *val)
+{
+ printf("%s='%s'\n", var, val);
+}
+
+static void print_var_int(const char *var, int val)
+{
+ printf("%s=%d\n", var, val);
+}
+
+static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
+{
+ int cnt, flags = info->bisect_show_flags;
+ char hex[41] = "";
+ struct commit_list *tried;
+ struct rev_info *revs = info->revs;
+
+ if (!revs->commits && !(flags & BISECT_SHOW_TRIED))
+ return 1;
+
+ revs->commits = filter_skipped(revs->commits, &tried,
+ flags & BISECT_SHOW_ALL,
+ NULL, NULL);
+
+ /*
+ * revs->commits can reach "reaches" commits among
+ * "all" commits. If it is good, then there are
+ * (all-reaches) commits left to be bisected.
+ * On the other hand, if it is bad, then the set
+ * to bisect is "reaches".
+ * A bisect set of size N has (N-1) commits further
+ * to test, as we already know one bad one.
+ */
+ cnt = all - reaches;
+ if (cnt < reaches)
+ cnt = reaches;
+
+ if (revs->commits)
+ strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1));
+
+ if (flags & BISECT_SHOW_ALL) {
+ traverse_commit_list(revs, show_commit, show_object, info);
+ printf("------\n");
+ }
+
+ if (flags & BISECT_SHOW_TRIED)
+ show_tried_revs(tried);
+
+ print_var_str("bisect_rev", hex);
+ print_var_int("bisect_nr", cnt - 1);
+ print_var_int("bisect_good", all - reaches - 1);
+ print_var_int("bisect_bad", reaches - 1);
+ print_var_int("bisect_all", all);
+ print_var_int("bisect_steps", estimate_bisect_steps(all));
+
+ return 0;
+}
+
+int cmd_rev_list(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info revs;
+ struct rev_list_info info;
+ int i;
+ int bisect_list = 0;
+ int bisect_show_vars = 0;
+ int bisect_find_all = 0;
+ int quiet = 0;
+
+ git_config(git_default_config, NULL);
+ init_revisions(&revs, prefix);
+ revs.abbrev = 0;
+ revs.commit_format = CMIT_FMT_UNSPECIFIED;
+ argc = setup_revisions(argc, argv, &revs, NULL);
+
+ memset(&info, 0, sizeof(info));
+ info.revs = &revs;
+ if (revs.bisect)
+ bisect_list = 1;
+
+ quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
+ for (i = 1 ; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (!strcmp(arg, "--header")) {
+ revs.verbose_header = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--timestamp")) {
+ info.show_timestamp = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--bisect")) {
+ bisect_list = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--bisect-all")) {
+ bisect_list = 1;
+ bisect_find_all = 1;
+ info.bisect_show_flags = BISECT_SHOW_ALL;
+ revs.show_decorations = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--bisect-vars")) {
+ bisect_list = 1;
+ bisect_show_vars = 1;
+ continue;
+ }
+ usage(rev_list_usage);
+
+ }
+ if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
+ /* The command line has a --pretty */
+ info.hdr_termination = '\n';
+ if (revs.commit_format == CMIT_FMT_ONELINE)
+ info.header_prefix = "";
+ else
+ info.header_prefix = "commit ";
+ }
+ else if (revs.verbose_header)
+ /* Only --header was specified */
+ revs.commit_format = CMIT_FMT_RAW;
+
+ if ((!revs.commits &&
+ (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
+ !revs.pending.nr)) ||
+ revs.diff)
+ usage(rev_list_usage);
+
+ save_commit_buffer = (revs.verbose_header ||
+ revs.grep_filter.pattern_list ||
+ revs.grep_filter.header_list);
+ if (bisect_list)
+ revs.limited = 1;
+
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ if (revs.tree_objects)
+ mark_edges_uninteresting(revs.commits, &revs, show_edge);
+
+ if (bisect_list) {
+ int reaches = reaches, all = all;
+
+ revs.commits = find_bisection(revs.commits, &reaches, &all,
+ bisect_find_all);
+
+ if (bisect_show_vars)
+ return show_bisect_vars(&info, reaches, all);
+ }
+
+ traverse_commit_list(&revs,
+ quiet ? finish_commit : show_commit,
+ quiet ? finish_object : show_object,
+ &info);
+
+ return 0;
+}
--- /dev/null
+/*
+ * rev-parse.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "quote.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+#define DO_REVS 1
+#define DO_NOREV 2
+#define DO_FLAGS 4
+#define DO_NONFLAGS 8
+static int filter = ~0;
+
+static const char *def;
+
+#define NORMAL 0
+#define REVERSED 1
+static int show_type = NORMAL;
+
+#define SHOW_SYMBOLIC_ASIS 1
+#define SHOW_SYMBOLIC_FULL 2
+static int symbolic;
+static int abbrev;
+static int abbrev_ref;
+static int abbrev_ref_strict;
+static int output_sq;
+
+/*
+ * Some arguments are relevant "revision" arguments,
+ * others are about output format or other details.
+ * This sorts it all out.
+ */
+static int is_rev_argument(const char *arg)
+{
+ static const char *rev_args[] = {
+ "--all",
+ "--bisect",
+ "--dense",
+ "--branches=",
+ "--branches",
+ "--header",
+ "--max-age=",
+ "--max-count=",
+ "--min-age=",
+ "--no-merges",
+ "--objects",
+ "--objects-edge",
+ "--parents",
+ "--pretty",
+ "--remotes=",
+ "--remotes",
+ "--glob=",
+ "--sparse",
+ "--tags=",
+ "--tags",
+ "--topo-order",
+ "--date-order",
+ "--unpacked",
+ NULL
+ };
+ const char **p = rev_args;
+
+ /* accept -<digit>, like traditional "head" */
+ if ((*arg == '-') && isdigit(arg[1]))
+ return 1;
+
+ for (;;) {
+ const char *str = *p++;
+ int len;
+ if (!str)
+ return 0;
+ len = strlen(str);
+ if (!strcmp(arg, str) ||
+ (str[len-1] == '=' && !strncmp(arg, str, len)))
+ return 1;
+ }
+}
+
+/* Output argument as a string, either SQ or normal */
+static void show(const char *arg)
+{
+ if (output_sq) {
+ int sq = '\'', ch;
+
+ putchar(sq);
+ while ((ch = *arg++)) {
+ if (ch == sq)
+ fputs("'\\'", stdout);
+ putchar(ch);
+ }
+ putchar(sq);
+ putchar(' ');
+ }
+ else
+ puts(arg);
+}
+
+/* Like show(), but with a negation prefix according to type */
+static void show_with_type(int type, const char *arg)
+{
+ if (type != show_type)
+ putchar('^');
+ show(arg);
+}
+
+/* Output a revision, only if filter allows it */
+static void show_rev(int type, const unsigned char *sha1, const char *name)
+{
+ if (!(filter & DO_REVS))
+ return;
+ def = NULL;
+
+ if ((symbolic || abbrev_ref) && name) {
+ if (symbolic == SHOW_SYMBOLIC_FULL || abbrev_ref) {
+ unsigned char discard[20];
+ char *full;
+
+ switch (dwim_ref(name, strlen(name), discard, &full)) {
+ case 0:
+ /*
+ * Not found -- not a ref. We could
+ * emit "name" here, but symbolic-full
+ * users are interested in finding the
+ * refs spelled in full, and they would
+ * need to filter non-refs if we did so.
+ */
+ break;
+ case 1: /* happy */
+ if (abbrev_ref)
+ full = shorten_unambiguous_ref(full,
+ abbrev_ref_strict);
+ show_with_type(type, full);
+ break;
+ default: /* ambiguous */
+ error("refname '%s' is ambiguous", name);
+ break;
+ }
+ } else {
+ show_with_type(type, name);
+ }
+ }
+ else if (abbrev)
+ show_with_type(type, find_unique_abbrev(sha1, abbrev));
+ else
+ show_with_type(type, sha1_to_hex(sha1));
+}
+
+/* Output a flag, only if filter allows it. */
+static int show_flag(const char *arg)
+{
+ if (!(filter & DO_FLAGS))
+ return 0;
+ if (filter & (is_rev_argument(arg) ? DO_REVS : DO_NOREV)) {
+ show(arg);
+ return 1;
+ }
+ return 0;
+}
+
+static int show_default(void)
+{
+ const char *s = def;
+
+ if (s) {
+ unsigned char sha1[20];
+
+ def = NULL;
+ if (!get_sha1(s, sha1)) {
+ show_rev(NORMAL, sha1, s);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ show_rev(NORMAL, sha1, refname);
+ return 0;
+}
+
+static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ show_rev(REVERSED, sha1, refname);
+ return 0;
+}
+
+static void show_datestring(const char *flag, const char *datestr)
+{
+ static char buffer[100];
+
+ /* date handling requires both flags and revs */
+ if ((filter & (DO_FLAGS | DO_REVS)) != (DO_FLAGS | DO_REVS))
+ return;
+ snprintf(buffer, sizeof(buffer), "%s%lu", flag, approxidate(datestr));
+ show(buffer);
+}
+
+static int show_file(const char *arg)
+{
+ show_default();
+ if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) {
+ show(arg);
+ return 1;
+ }
+ return 0;
+}
+
+static int try_difference(const char *arg)
+{
+ char *dotdot;
+ unsigned char sha1[20];
+ unsigned char end[20];
+ const char *next;
+ const char *this;
+ int symmetric;
+
+ if (!(dotdot = strstr(arg, "..")))
+ return 0;
+ next = dotdot + 2;
+ this = arg;
+ symmetric = (*next == '.');
+
+ *dotdot = 0;
+ next += symmetric;
+
+ if (!*next)
+ next = "HEAD";
+ if (dotdot == arg)
+ this = "HEAD";
+ if (!get_sha1(this, sha1) && !get_sha1(next, end)) {
+ show_rev(NORMAL, end, next);
+ show_rev(symmetric ? NORMAL : REVERSED, sha1, this);
+ if (symmetric) {
+ struct commit_list *exclude;
+ struct commit *a, *b;
+ a = lookup_commit_reference(sha1);
+ b = lookup_commit_reference(end);
+ exclude = get_merge_bases(a, b, 1);
+ while (exclude) {
+ struct commit_list *n = exclude->next;
+ show_rev(REVERSED,
+ exclude->item->object.sha1,NULL);
+ free(exclude);
+ exclude = n;
+ }
+ }
+ return 1;
+ }
+ *dotdot = '.';
+ return 0;
+}
+
+static int try_parent_shorthands(const char *arg)
+{
+ char *dotdot;
+ unsigned char sha1[20];
+ struct commit *commit;
+ struct commit_list *parents;
+ int parents_only;
+
+ if ((dotdot = strstr(arg, "^!")))
+ parents_only = 0;
+ else if ((dotdot = strstr(arg, "^@")))
+ parents_only = 1;
+
+ if (!dotdot || dotdot[2])
+ return 0;
+
+ *dotdot = 0;
+ if (get_sha1(arg, sha1))
+ return 0;
+
+ if (!parents_only)
+ show_rev(NORMAL, sha1, arg);
+ commit = lookup_commit_reference(sha1);
+ for (parents = commit->parents; parents; parents = parents->next)
+ show_rev(parents_only ? NORMAL : REVERSED,
+ parents->item->object.sha1, arg);
+
+ return 1;
+}
+
+static int parseopt_dump(const struct option *o, const char *arg, int unset)
+{
+ struct strbuf *parsed = o->value;
+ if (unset)
+ strbuf_addf(parsed, " --no-%s", o->long_name);
+ else if (o->short_name)
+ strbuf_addf(parsed, " -%c", o->short_name);
+ else
+ strbuf_addf(parsed, " --%s", o->long_name);
+ if (arg) {
+ strbuf_addch(parsed, ' ');
+ sq_quote_buf(parsed, arg);
+ }
+ return 0;
+}
+
+static const char *skipspaces(const char *s)
+{
+ while (isspace(*s))
+ s++;
+ return s;
+}
+
+static int cmd_parseopt(int argc, const char **argv, const char *prefix)
+{
+ static int keep_dashdash = 0, stop_at_non_option = 0;
+ static char const * const parseopt_usage[] = {
+ "git rev-parse --parseopt [options] -- [<args>...]",
+ NULL
+ };
+ static struct option parseopt_opts[] = {
+ OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash,
+ "keep the `--` passed as an arg"),
+ OPT_BOOLEAN(0, "stop-at-non-option", &stop_at_non_option,
+ "stop parsing after the "
+ "first non-option argument"),
+ OPT_END(),
+ };
+
+ struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
+ const char **usage = NULL;
+ struct option *opts = NULL;
+ int onb = 0, osz = 0, unb = 0, usz = 0;
+
+ strbuf_addstr(&parsed, "set --");
+ argc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage,
+ PARSE_OPT_KEEP_DASHDASH);
+ if (argc < 1 || strcmp(argv[0], "--"))
+ usage_with_options(parseopt_usage, parseopt_opts);
+
+ /* get the usage up to the first line with a -- on it */
+ for (;;) {
+ if (strbuf_getline(&sb, stdin, '\n') == EOF)
+ die("premature end of input");
+ ALLOC_GROW(usage, unb + 1, usz);
+ if (!strcmp("--", sb.buf)) {
+ if (unb < 1)
+ die("no usage string given before the `--' separator");
+ usage[unb] = NULL;
+ break;
+ }
+ usage[unb++] = strbuf_detach(&sb, NULL);
+ }
+
+ /* parse: (<short>|<short>,<long>|<long>)[=?]? SP+ <help> */
+ while (strbuf_getline(&sb, stdin, '\n') != EOF) {
+ const char *s;
+ struct option *o;
+
+ if (!sb.len)
+ continue;
+
+ ALLOC_GROW(opts, onb + 1, osz);
+ memset(opts + onb, 0, sizeof(opts[onb]));
+
+ o = &opts[onb++];
+ s = strchr(sb.buf, ' ');
+ if (!s || *sb.buf == ' ') {
+ o->type = OPTION_GROUP;
+ o->help = xstrdup(skipspaces(sb.buf));
+ continue;
+ }
+
+ o->type = OPTION_CALLBACK;
+ o->help = xstrdup(skipspaces(s));
+ o->value = &parsed;
+ o->flags = PARSE_OPT_NOARG;
+ o->callback = &parseopt_dump;
+ while (s > sb.buf && strchr("*=?!", s[-1])) {
+ switch (*--s) {
+ case '=':
+ o->flags &= ~PARSE_OPT_NOARG;
+ break;
+ case '?':
+ o->flags &= ~PARSE_OPT_NOARG;
+ o->flags |= PARSE_OPT_OPTARG;
+ break;
+ case '!':
+ o->flags |= PARSE_OPT_NONEG;
+ break;
+ case '*':
+ o->flags |= PARSE_OPT_HIDDEN;
+ break;
+ }
+ }
+
+ if (s - sb.buf == 1) /* short option only */
+ o->short_name = *sb.buf;
+ else if (sb.buf[1] != ',') /* long option only */
+ o->long_name = xmemdupz(sb.buf, s - sb.buf);
+ else {
+ o->short_name = *sb.buf;
+ o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
+ }
+ }
+ strbuf_release(&sb);
+
+ /* put an OPT_END() */
+ ALLOC_GROW(opts, onb + 1, osz);
+ memset(opts + onb, 0, sizeof(opts[onb]));
+ argc = parse_options(argc, argv, prefix, opts, usage,
+ keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0 |
+ stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0);
+
+ strbuf_addf(&parsed, " --");
+ sq_quote_argv(&parsed, argv, 0);
+ puts(parsed.buf);
+ return 0;
+}
+
+static int cmd_sq_quote(int argc, const char **argv)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ if (argc)
+ sq_quote_argv(&buf, argv, 0);
+ printf("%s\n", buf.buf);
+ strbuf_release(&buf);
+
+ return 0;
+}
+
+static void die_no_single_rev(int quiet)
+{
+ if (quiet)
+ exit(1);
+ else
+ die("Needed a single revision");
+}
+
+static const char builtin_rev_parse_usage[] =
+"git rev-parse --parseopt [options] -- [<args>...]\n"
+" or: git rev-parse --sq-quote [<arg>...]\n"
+" or: git rev-parse [options] [<arg>...]\n"
+"\n"
+"Run \"git rev-parse --parseopt -h\" for more information on the first usage.";
+
+int cmd_rev_parse(int argc, const char **argv, const char *prefix)
+{
+ int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
+ unsigned char sha1[20];
+ const char *name = NULL;
+
+ if (argc > 1 && !strcmp("--parseopt", argv[1]))
+ return cmd_parseopt(argc - 1, argv + 1, prefix);
+
+ if (argc > 1 && !strcmp("--sq-quote", argv[1]))
+ return cmd_sq_quote(argc - 2, argv + 2);
+
+ if (argc == 2 && !strcmp("--local-env-vars", argv[1])) {
+ int i;
+ for (i = 0; local_repo_env[i]; i++)
+ printf("%s\n", local_repo_env[i]);
+ return 0;
+ }
+
+ if (argc > 1 && !strcmp("-h", argv[1]))
+ usage(builtin_rev_parse_usage);
+
+ prefix = setup_git_directory();
+ git_config(git_default_config, NULL);
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (as_is) {
+ if (show_file(arg) && as_is < 2)
+ verify_filename(prefix, arg);
+ continue;
+ }
+ if (!strcmp(arg,"-n")) {
+ if (++i >= argc)
+ die("-n requires an argument");
+ if ((filter & DO_FLAGS) && (filter & DO_REVS)) {
+ show(arg);
+ show(argv[i]);
+ }
+ continue;
+ }
+ if (!prefixcmp(arg, "-n")) {
+ if ((filter & DO_FLAGS) && (filter & DO_REVS))
+ show(arg);
+ continue;
+ }
+
+ if (*arg == '-') {
+ if (!strcmp(arg, "--")) {
+ as_is = 2;
+ /* Pass on the "--" if we show anything but files.. */
+ if (filter & (DO_FLAGS | DO_REVS))
+ show_file(arg);
+ continue;
+ }
+ if (!strcmp(arg, "--default")) {
+ def = argv[i+1];
+ i++;
+ continue;
+ }
+ if (!strcmp(arg, "--revs-only")) {
+ filter &= ~DO_NOREV;
+ continue;
+ }
+ if (!strcmp(arg, "--no-revs")) {
+ filter &= ~DO_REVS;
+ continue;
+ }
+ if (!strcmp(arg, "--flags")) {
+ filter &= ~DO_NONFLAGS;
+ continue;
+ }
+ if (!strcmp(arg, "--no-flags")) {
+ filter &= ~DO_FLAGS;
+ continue;
+ }
+ if (!strcmp(arg, "--verify")) {
+ filter &= ~(DO_FLAGS|DO_NOREV);
+ verify = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) {
+ quiet = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--short") ||
+ !prefixcmp(arg, "--short=")) {
+ filter &= ~(DO_FLAGS|DO_NOREV);
+ verify = 1;
+ abbrev = DEFAULT_ABBREV;
+ if (arg[7] == '=')
+ abbrev = strtoul(arg + 8, NULL, 10);
+ if (abbrev < MINIMUM_ABBREV)
+ abbrev = MINIMUM_ABBREV;
+ else if (40 <= abbrev)
+ abbrev = 40;
+ continue;
+ }
+ if (!strcmp(arg, "--sq")) {
+ output_sq = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--not")) {
+ show_type ^= REVERSED;
+ continue;
+ }
+ if (!strcmp(arg, "--symbolic")) {
+ symbolic = SHOW_SYMBOLIC_ASIS;
+ continue;
+ }
+ if (!strcmp(arg, "--symbolic-full-name")) {
+ symbolic = SHOW_SYMBOLIC_FULL;
+ continue;
+ }
+ if (!prefixcmp(arg, "--abbrev-ref") &&
+ (!arg[12] || arg[12] == '=')) {
+ abbrev_ref = 1;
+ abbrev_ref_strict = warn_ambiguous_refs;
+ if (arg[12] == '=') {
+ if (!strcmp(arg + 13, "strict"))
+ abbrev_ref_strict = 1;
+ else if (!strcmp(arg + 13, "loose"))
+ abbrev_ref_strict = 0;
+ else
+ die("unknown mode for %s", arg);
+ }
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ for_each_ref(show_reference, NULL);
+ continue;
+ }
+ if (!strcmp(arg, "--bisect")) {
+ for_each_ref_in("refs/bisect/bad", show_reference, NULL);
+ for_each_ref_in("refs/bisect/good", anti_reference, NULL);
+ continue;
+ }
+ if (!prefixcmp(arg, "--branches=")) {
+ for_each_glob_ref_in(show_reference, arg + 11,
+ "refs/heads/", NULL);
+ continue;
+ }
+ if (!strcmp(arg, "--branches")) {
+ for_each_branch_ref(show_reference, NULL);
+ continue;
+ }
+ if (!prefixcmp(arg, "--tags=")) {
+ for_each_glob_ref_in(show_reference, arg + 7,
+ "refs/tags/", NULL);
+ continue;
+ }
+ if (!strcmp(arg, "--tags")) {
+ for_each_tag_ref(show_reference, NULL);
+ continue;
+ }
+ if (!prefixcmp(arg, "--glob=")) {
+ for_each_glob_ref(show_reference, arg + 7, NULL);
+ continue;
+ }
+ if (!prefixcmp(arg, "--remotes=")) {
+ for_each_glob_ref_in(show_reference, arg + 10,
+ "refs/remotes/", NULL);
+ continue;
+ }
+ if (!strcmp(arg, "--remotes")) {
+ for_each_remote_ref(show_reference, NULL);
+ continue;
+ }
+ if (!strcmp(arg, "--show-toplevel")) {
+ const char *work_tree = get_git_work_tree();
+ if (work_tree)
+ puts(work_tree);
+ continue;
+ }
+ if (!strcmp(arg, "--show-prefix")) {
+ if (prefix)
+ puts(prefix);
+ continue;
+ }
+ 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) {
+ pfx++;
+ printf("../");
+ }
+ }
+ putchar('\n');
+ continue;
+ }
+ if (!strcmp(arg, "--git-dir")) {
+ const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
+ static char cwd[PATH_MAX];
+ int len;
+ if (gitdir) {
+ puts(gitdir);
+ continue;
+ }
+ if (!prefix) {
+ puts(".git");
+ continue;
+ }
+ if (!getcwd(cwd, PATH_MAX))
+ die_errno("unable to get current working directory");
+ len = strlen(cwd);
+ printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
+ continue;
+ }
+ if (!strcmp(arg, "--is-inside-git-dir")) {
+ printf("%s\n", is_inside_git_dir() ? "true"
+ : "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;
+ }
+ if (!prefixcmp(arg, "--after=")) {
+ show_datestring("--max-age=", arg+8);
+ continue;
+ }
+ if (!prefixcmp(arg, "--before=")) {
+ show_datestring("--min-age=", arg+9);
+ continue;
+ }
+ if (!prefixcmp(arg, "--until=")) {
+ show_datestring("--min-age=", arg+8);
+ continue;
+ }
+ if (show_flag(arg) && verify)
+ die_no_single_rev(quiet);
+ continue;
+ }
+
+ /* Not a flag argument */
+ if (try_difference(arg))
+ continue;
+ if (try_parent_shorthands(arg))
+ continue;
+ name = arg;
+ type = NORMAL;
+ if (*arg == '^') {
+ name++;
+ type = REVERSED;
+ }
+ if (!get_sha1(name, sha1)) {
+ if (verify)
+ revs_count++;
+ else
+ show_rev(type, sha1, name);
+ continue;
+ }
+ if (verify)
+ die_no_single_rev(quiet);
+ as_is = 1;
+ if (!show_file(arg))
+ continue;
+ verify_filename(prefix, arg);
+ }
+ if (verify) {
+ if (revs_count == 1) {
+ show_rev(type, sha1, name);
+ return 0;
+ } else if (revs_count == 0 && show_default())
+ return 0;
+ die_no_single_rev(quiet);
+ } else
+ show_default();
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "builtin.h"
+#include "object.h"
+#include "commit.h"
+#include "tag.h"
+#include "wt-status.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "utf8.h"
+#include "parse-options.h"
+#include "cache-tree.h"
+#include "diff.h"
+#include "revision.h"
+#include "rerere.h"
+#include "merge-recursive.h"
+
+/*
+ * This implements the builtins revert and cherry-pick.
+ *
+ * Copyright (c) 2007 Johannes E. Schindelin
+ *
+ * Based on git-revert.sh, which is
+ *
+ * Copyright (c) 2005 Linus Torvalds
+ * Copyright (c) 2005 Junio C Hamano
+ */
+
+static const char * const revert_usage[] = {
+ "git revert [options] <commit-ish>",
+ NULL
+};
+
+static const char * const cherry_pick_usage[] = {
+ "git cherry-pick [options] <commit-ish>",
+ NULL
+};
+
+static int edit, no_replay, no_commit, mainline, signoff;
+static enum { REVERT, CHERRY_PICK } action;
+static struct commit *commit;
+static const char *commit_name;
+static int allow_rerere_auto;
+
+static const char *me;
+
+#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
+
+static void parse_args(int argc, const char **argv)
+{
+ const char * const * usage_str =
+ action == REVERT ? revert_usage : cherry_pick_usage;
+ unsigned char sha1[20];
+ int noop;
+ struct option options[] = {
+ OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
+ OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
+ OPT_BOOLEAN('x', NULL, &no_replay, "append commit name when cherry-picking"),
+ OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
+ OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
+ OPT_INTEGER('m', "mainline", &mainline, "parent number"),
+ OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
+ OPT_END(),
+ };
+
+ if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1)
+ usage_with_options(usage_str, options);
+
+ commit_name = argv[0];
+ if (get_sha1(commit_name, sha1))
+ die ("Cannot find '%s'", commit_name);
+ commit = lookup_commit_reference(sha1);
+ if (!commit)
+ exit(1);
+}
+
+static char *get_oneline(const char *message)
+{
+ char *result;
+ const char *p = message, *abbrev, *eol;
+ int abbrev_len, oneline_len;
+
+ if (!p)
+ die ("Could not read commit message of %s",
+ sha1_to_hex(commit->object.sha1));
+ while (*p && (*p != '\n' || p[1] != '\n'))
+ p++;
+
+ if (*p) {
+ p += 2;
+ for (eol = p + 1; *eol && *eol != '\n'; eol++)
+ ; /* do nothing */
+ } else
+ eol = p;
+ abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
+ abbrev_len = strlen(abbrev);
+ oneline_len = eol - p;
+ result = xmalloc(abbrev_len + 5 + oneline_len);
+ memcpy(result, abbrev, abbrev_len);
+ memcpy(result + abbrev_len, "... ", 4);
+ memcpy(result + abbrev_len + 4, p, oneline_len);
+ result[abbrev_len + 4 + oneline_len] = '\0';
+ return result;
+}
+
+static char *get_encoding(const char *message)
+{
+ const char *p = message, *eol;
+
+ if (!p)
+ die ("Could not read commit message of %s",
+ sha1_to_hex(commit->object.sha1));
+ while (*p && *p != '\n') {
+ for (eol = p + 1; *eol && *eol != '\n'; eol++)
+ ; /* do nothing */
+ if (!prefixcmp(p, "encoding ")) {
+ char *result = xmalloc(eol - 8 - p);
+ strlcpy(result, p + 9, eol - 8 - p);
+ return result;
+ }
+ p = eol;
+ if (*p == '\n')
+ p++;
+ }
+ return NULL;
+}
+
+static struct lock_file msg_file;
+static int msg_fd;
+
+static void add_to_msg(const char *string)
+{
+ int len = strlen(string);
+ if (write_in_full(msg_fd, string, len) < 0)
+ die_errno ("Could not write to MERGE_MSG");
+}
+
+static void add_message_to_msg(const char *message)
+{
+ const char *p = message;
+ while (*p && (*p != '\n' || p[1] != '\n'))
+ p++;
+
+ if (!*p)
+ add_to_msg(sha1_to_hex(commit->object.sha1));
+
+ p += 2;
+ add_to_msg(p);
+ return;
+}
+
+static void set_author_ident_env(const char *message)
+{
+ const char *p = message;
+ if (!p)
+ die ("Could not read commit message of %s",
+ sha1_to_hex(commit->object.sha1));
+ while (*p && *p != '\n') {
+ const char *eol;
+
+ for (eol = p; *eol && *eol != '\n'; eol++)
+ ; /* do nothing */
+ if (!prefixcmp(p, "author ")) {
+ char *line, *pend, *email, *timestamp;
+
+ p += 7;
+ line = xmemdupz(p, eol - p);
+ email = strchr(line, '<');
+ if (!email)
+ die ("Could not extract author email from %s",
+ sha1_to_hex(commit->object.sha1));
+ if (email == line)
+ pend = line;
+ else
+ for (pend = email; pend != line + 1 &&
+ isspace(pend[-1]); pend--);
+ ; /* do nothing */
+ *pend = '\0';
+ email++;
+ timestamp = strchr(email, '>');
+ if (!timestamp)
+ die ("Could not extract author time from %s",
+ sha1_to_hex(commit->object.sha1));
+ *timestamp = '\0';
+ for (timestamp++; *timestamp && isspace(*timestamp);
+ timestamp++)
+ ; /* do nothing */
+ setenv("GIT_AUTHOR_NAME", line, 1);
+ setenv("GIT_AUTHOR_EMAIL", email, 1);
+ setenv("GIT_AUTHOR_DATE", timestamp, 1);
+ free(line);
+ return;
+ }
+ p = eol;
+ if (*p == '\n')
+ p++;
+ }
+ die ("No author information found in %s",
+ sha1_to_hex(commit->object.sha1));
+}
+
+static char *help_msg(const char *name)
+{
+ struct strbuf helpbuf = STRBUF_INIT;
+ char *msg = getenv("GIT_CHERRY_PICK_HELP");
+
+ if (msg)
+ return msg;
+
+ strbuf_addstr(&helpbuf, " After resolving the conflicts,\n"
+ "mark the corrected paths with 'git add <paths>' or 'git rm <paths>'\n"
+ "and commit the result");
+
+ if (action == CHERRY_PICK) {
+ strbuf_addf(&helpbuf, " with: \n"
+ "\n"
+ " git commit -c %s\n",
+ name);
+ }
+ else
+ strbuf_addch(&helpbuf, '.');
+ return strbuf_detach(&helpbuf, NULL);
+}
+
+static struct tree *empty_tree(void)
+{
+ struct tree *tree = xcalloc(1, sizeof(struct tree));
+
+ tree->object.parsed = 1;
+ tree->object.type = OBJ_TREE;
+ pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
+ return tree;
+}
+
+static NORETURN void die_dirty_index(const char *me)
+{
+ if (read_cache_unmerged()) {
+ die_resolve_conflict(me);
+ } else {
+ if (advice_commit_before_merge)
+ die("Your local changes would be overwritten by %s.\n"
+ "Please, commit your changes or stash them to proceed.", me);
+ else
+ die("Your local changes would be overwritten by %s.\n", me);
+ }
+}
+
+static int revert_or_cherry_pick(int argc, const char **argv)
+{
+ unsigned char head[20];
+ struct commit *base, *next, *parent;
+ int i, index_fd, clean;
+ char *oneline, *reencoded_message = NULL;
+ const char *message, *encoding;
+ char *defmsg = git_pathdup("MERGE_MSG");
+ struct merge_options o;
+ struct tree *result, *next_tree, *base_tree, *head_tree;
+ static struct lock_file index_lock;
+
+ git_config(git_default_config, NULL);
+ me = action == REVERT ? "revert" : "cherry-pick";
+ setenv(GIT_REFLOG_ACTION, me, 0);
+ parse_args(argc, argv);
+
+ /* this is copied from the shell script, but it's never triggered... */
+ if (action == REVERT && !no_replay)
+ die("revert is incompatible with replay");
+
+ if (read_cache() < 0)
+ die("git %s: failed to read the index", me);
+ if (no_commit) {
+ /*
+ * We do not intend to commit immediately. We just want to
+ * merge the differences in, so let's compute the tree
+ * that represents the "current" state for merge-recursive
+ * to work on.
+ */
+ if (write_cache_as_tree(head, 0, NULL))
+ die ("Your index file is unmerged.");
+ } else {
+ if (get_sha1("HEAD", head))
+ die ("You do not have a valid HEAD");
+ if (index_differs_from("HEAD", 0))
+ die_dirty_index(me);
+ }
+ discard_cache();
+
+ index_fd = hold_locked_index(&index_lock, 1);
+
+ if (!commit->parents) {
+ if (action == REVERT)
+ die ("Cannot revert a root commit");
+ parent = NULL;
+ }
+ else if (commit->parents->next) {
+ /* Reverting or cherry-picking a merge commit */
+ int cnt;
+ struct commit_list *p;
+
+ if (!mainline)
+ die("Commit %s is a merge but no -m option was given.",
+ sha1_to_hex(commit->object.sha1));
+
+ for (cnt = 1, p = commit->parents;
+ cnt != mainline && p;
+ cnt++)
+ p = p->next;
+ if (cnt != mainline || !p)
+ die("Commit %s does not have parent %d",
+ sha1_to_hex(commit->object.sha1), mainline);
+ parent = p->item;
+ } else if (0 < mainline)
+ die("Mainline was specified but commit %s is not a merge.",
+ sha1_to_hex(commit->object.sha1));
+ else
+ parent = commit->parents->item;
+
+ if (!(message = commit->buffer))
+ die ("Cannot get commit message for %s",
+ sha1_to_hex(commit->object.sha1));
+
+ if (parent && parse_commit(parent) < 0)
+ die("%s: cannot parse parent commit %s",
+ me, sha1_to_hex(parent->object.sha1));
+
+ /*
+ * "commit" is an existing commit. We would want to apply
+ * the difference it introduces since its first parent "prev"
+ * on top of the current HEAD if we are cherry-pick. Or the
+ * reverse of it if we are revert.
+ */
+
+ msg_fd = hold_lock_file_for_update(&msg_file, defmsg,
+ LOCK_DIE_ON_ERROR);
+
+ encoding = get_encoding(message);
+ if (!encoding)
+ encoding = "UTF-8";
+ if (!git_commit_encoding)
+ git_commit_encoding = "UTF-8";
+ if ((reencoded_message = reencode_string(message,
+ git_commit_encoding, encoding)))
+ message = reencoded_message;
+
+ oneline = get_oneline(message);
+
+ if (action == REVERT) {
+ char *oneline_body = strchr(oneline, ' ');
+
+ base = commit;
+ next = parent;
+ add_to_msg("Revert \"");
+ add_to_msg(oneline_body + 1);
+ add_to_msg("\"\n\nThis reverts commit ");
+ add_to_msg(sha1_to_hex(commit->object.sha1));
+
+ if (commit->parents->next) {
+ add_to_msg(", reversing\nchanges made to ");
+ add_to_msg(sha1_to_hex(parent->object.sha1));
+ }
+ add_to_msg(".\n");
+ } else {
+ base = parent;
+ next = commit;
+ set_author_ident_env(message);
+ add_message_to_msg(message);
+ if (no_replay) {
+ add_to_msg("(cherry picked from commit ");
+ add_to_msg(sha1_to_hex(commit->object.sha1));
+ add_to_msg(")\n");
+ }
+ }
+
+ read_cache();
+ init_merge_options(&o);
+ o.branch1 = "HEAD";
+ o.branch2 = oneline;
+
+ head_tree = parse_tree_indirect(head);
+ next_tree = next ? next->tree : empty_tree();
+ base_tree = base ? base->tree : empty_tree();
+
+ clean = merge_trees(&o,
+ head_tree,
+ next_tree, base_tree, &result);
+
+ if (active_cache_changed &&
+ (write_cache(index_fd, active_cache, active_nr) ||
+ commit_locked_index(&index_lock)))
+ die("%s: Unable to write new index file", me);
+ rollback_lock_file(&index_lock);
+
+ if (!clean) {
+ add_to_msg("\nConflicts:\n\n");
+ for (i = 0; i < active_nr;) {
+ struct cache_entry *ce = active_cache[i++];
+ if (ce_stage(ce)) {
+ add_to_msg("\t");
+ add_to_msg(ce->name);
+ add_to_msg("\n");
+ while (i < active_nr && !strcmp(ce->name,
+ active_cache[i]->name))
+ i++;
+ }
+ }
+ if (commit_lock_file(&msg_file) < 0)
+ die ("Error wrapping up %s", defmsg);
+ fprintf(stderr, "Automatic %s failed.%s\n",
+ me, help_msg(commit_name));
+ rerere(allow_rerere_auto);
+ exit(1);
+ }
+ if (commit_lock_file(&msg_file) < 0)
+ die ("Error wrapping up %s", defmsg);
+ fprintf(stderr, "Finished one %s.\n", me);
+
+ /*
+ *
+ * If we are cherry-pick, and if the merge did not result in
+ * hand-editing, we will hit this commit and inherit the original
+ * author date and name.
+ * If we are revert, or if our cherry-pick results in a hand merge,
+ * we had better say that the current user is responsible for that.
+ */
+
+ if (!no_commit) {
+ /* 6 is max possible length of our args array including NULL */
+ const char *args[6];
+ int i = 0;
+ args[i++] = "commit";
+ args[i++] = "-n";
+ if (signoff)
+ args[i++] = "-s";
+ if (!edit) {
+ args[i++] = "-F";
+ args[i++] = defmsg;
+ }
+ args[i] = NULL;
+ return execv_git_cmd(args);
+ }
+ free(reencoded_message);
+ free(defmsg);
+
+ return 0;
+}
+
+int cmd_revert(int argc, const char **argv, const char *prefix)
+{
+ if (isatty(0))
+ edit = 1;
+ no_replay = 1;
+ action = REVERT;
+ return revert_or_cherry_pick(argc, argv);
+}
+
+int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
+{
+ no_replay = 0;
+ action = CHERRY_PICK;
+ return revert_or_cherry_pick(argc, argv);
+}
--- /dev/null
+/*
+ * "git rm" builtin command
+ *
+ * Copyright (C) Linus Torvalds 2006
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "cache-tree.h"
+#include "tree-walk.h"
+#include "parse-options.h"
+
+static const char * const builtin_rm_usage[] = {
+ "git rm [options] [--] <file>...",
+ NULL
+};
+
+static struct {
+ int nr, alloc;
+ const char **name;
+} list;
+
+static void add_list(const char *name)
+{
+ if (list.nr >= list.alloc) {
+ list.alloc = alloc_nr(list.alloc);
+ list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
+ }
+ list.name[list.nr++] = name;
+}
+
+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
+ * tree_desc based traversal if we wanted to, but I am
+ * lazy, and who cares if removal of files is a tad
+ * slower than the theoretical maximum speed?
+ */
+ int i, no_head;
+ int errs = 0;
+
+ no_head = is_null_sha1(head);
+ for (i = 0; i < list.nr; i++) {
+ struct stat st;
+ int pos;
+ struct cache_entry *ce;
+ 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; /* removing unmerged entry */
+ ce = active_cache[pos];
+
+ if (lstat(ce->name, &st) < 0) {
+ if (errno != ENOENT)
+ warning("'%s': %s", ce->name, strerror(errno));
+ /* It already vanished from the working tree */
+ continue;
+ }
+ else if (S_ISDIR(st.st_mode)) {
+ /* if a file was removed and it is now a
+ * directory, that is the same as ENOENT as
+ * far as git is concerned; we do not track
+ * directories.
+ */
+ continue;
+ }
+
+ /*
+ * "rm" of a path that has changes need to be treated
+ * carefully not to allow losing local changes
+ * accidentally. A local change could be (1) file in
+ * work tree is different since the index; and/or (2)
+ * the user staged a content that is different from
+ * the current commit in the index.
+ *
+ * In such a case, you would need to --force the
+ * removal. However, "rm --cached" (remove only from
+ * the index) is safe if the index matches the file in
+ * the work tree or the HEAD commit, as it means that
+ * the content being removed is available elsewhere.
+ */
+
+ /*
+ * Is the index different from the file in the work tree?
+ */
+ if (ce_match_stat(ce, &st, 0))
+ local_changes = 1;
+
+ /*
+ * Is the index different from the HEAD commit? By
+ * definition, before the very initial commit,
+ * anything staged in the index is treated by the same
+ * way as changed from the HEAD.
+ */
+ if (no_head
+ || get_tree_entry(head, name, sha1, &mode)
+ || ce->ce_mode != create_ce_mode(mode)
+ || hashcmp(ce->sha1, sha1))
+ staged_changes = 1;
+
+ /*
+ * If the index does not match the file in the work
+ * tree and if it does not match the HEAD commit
+ * either, (1) "git rm" without --cached definitely
+ * will lose information; (2) "git rm --cached" will
+ * lose information unless it is about removing an
+ * "intent to add" entry.
+ */
+ if (local_changes && staged_changes) {
+ if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
+ errs = error("'%s' has staged content different "
+ "from both the file and the HEAD\n"
+ "(use -f to force removal)", name);
+ }
+ else if (!index_only) {
+ if (staged_changes)
+ errs = error("'%s' has changes staged in the index\n"
+ "(use --cached to keep the file, "
+ "or -f to force removal)", name);
+ if (local_changes)
+ errs = error("'%s' has local modifications\n"
+ "(use --cached to keep the file, "
+ "or -f to force removal)", name);
+ }
+ }
+ return errs;
+}
+
+static struct lock_file lock_file;
+
+static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
+static int ignore_unmatch = 0;
+
+static struct option builtin_rm_options[] = {
+ OPT__DRY_RUN(&show_only),
+ OPT__QUIET(&quiet),
+ OPT_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"),
+ OPT_BOOLEAN('f', "force", &force, "override the up-to-date check"),
+ OPT_BOOLEAN('r', NULL, &recursive, "allow recursive removal"),
+ OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
+ "exit with a zero status even if nothing matched"),
+ OPT_END(),
+};
+
+int cmd_rm(int argc, const char **argv, const char *prefix)
+{
+ int i, newfd;
+ const char **pathspec;
+ char *seen;
+
+ git_config(git_default_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, builtin_rm_options,
+ builtin_rm_usage, 0);
+ if (!argc)
+ usage_with_options(builtin_rm_usage, builtin_rm_options);
+
+ if (!index_only)
+ setup_work_tree();
+
+ newfd = hold_locked_index(&lock_file, 1);
+
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ pathspec = get_pathspec(prefix, argv);
+ refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL);
+
+ seen = NULL;
+ for (i = 0; pathspec[i] ; i++)
+ /* nothing */;
+ seen = xcalloc(i, 1);
+
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
+ continue;
+ add_list(ce->name);
+ }
+
+ if (pathspec) {
+ const char *match;
+ int seen_any = 0;
+ for (i = 0; (match = pathspec[i]) != NULL ; i++) {
+ if (!seen[i]) {
+ if (!ignore_unmatch) {
+ die("pathspec '%s' did not match any files",
+ match);
+ }
+ }
+ else {
+ seen_any = 1;
+ }
+ if (!recursive && seen[i] == MATCHED_RECURSIVELY)
+ die("not removing '%s' recursively without -r",
+ *match ? match : ".");
+ }
+
+ if (! seen_any)
+ exit(0);
+ }
+
+ /*
+ * If not forced, the file, the index and the HEAD (if exists)
+ * must match; but the file can already been removed, since
+ * this sequence is a natural "novice" way:
+ *
+ * rm F; git rm F
+ *
+ * Further, if HEAD commit exists, "diff-index --cached" must
+ * report no changes unless forced.
+ */
+ if (!force) {
+ unsigned char sha1[20];
+ if (get_sha1("HEAD", sha1))
+ hashclr(sha1);
+ if (check_local_mod(sha1, index_only))
+ exit(1);
+ }
+
+ /*
+ * First remove the names from the index: we won't commit
+ * the index unless all of them succeed.
+ */
+ for (i = 0; i < list.nr; i++) {
+ const char *path = list.name[i];
+ if (!quiet)
+ printf("rm '%s'\n", path);
+
+ if (remove_file_from_cache(path))
+ die("git rm: unable to remove %s", path);
+ }
+
+ if (show_only)
+ return 0;
+
+ /*
+ * Then, unless we used "--cached", remove the filenames from
+ * the workspace. If we fail to remove the first one, we
+ * abort the "git rm" (but once we've successfully removed
+ * any file at all, we'll go ahead and commit to it all:
+ * by then we've already committed ourselves and can't fail
+ * in the middle)
+ */
+ if (!index_only) {
+ int removed = 0;
+ for (i = 0; i < list.nr; i++) {
+ const char *path = list.name[i];
+ if (!remove_path(path)) {
+ removed = 1;
+ continue;
+ }
+ if (!removed)
+ die_errno("git rm: '%s'", path);
+ }
+ }
+
+ if (active_cache_changed) {
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(&lock_file))
+ die("Unable to write new index file");
+ }
+
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "sideband.h"
+#include "run-command.h"
+#include "remote.h"
+#include "send-pack.h"
+#include "quote.h"
+#include "transport.h"
+
+static const char send_pack_usage[] =
+"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+" --all and explicit <ref> specification are mutually exclusive.";
+
+static struct send_pack_args args;
+
+static int feed_object(const unsigned char *sha1, int fd, int negative)
+{
+ char buf[42];
+
+ if (negative && !has_sha1_file(sha1))
+ return 1;
+
+ memcpy(buf + negative, sha1_to_hex(sha1), 40);
+ if (negative)
+ buf[0] = '^';
+ buf[40 + negative] = '\n';
+ return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs");
+}
+
+/*
+ * Make a pack stream and spit it out into file descriptor fd
+ */
+static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args)
+{
+ /*
+ * The child becomes pack-objects --revs; we feed
+ * the revision parameters to it via its stdin and
+ * let its stdout go back to the other end.
+ */
+ const char *argv[] = {
+ "pack-objects",
+ "--all-progress-implied",
+ "--revs",
+ "--stdout",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ };
+ struct child_process po;
+ int i;
+
+ i = 4;
+ if (args->use_thin_pack)
+ argv[i++] = "--thin";
+ if (args->use_ofs_delta)
+ argv[i++] = "--delta-base-offset";
+ if (args->quiet)
+ argv[i++] = "-q";
+ memset(&po, 0, sizeof(po));
+ po.argv = argv;
+ po.in = -1;
+ po.out = args->stateless_rpc ? -1 : fd;
+ po.git_cmd = 1;
+ if (start_command(&po))
+ die_errno("git pack-objects failed");
+
+ /*
+ * We feed the pack-objects we just spawned with revision
+ * parameters by writing to the pipe.
+ */
+ for (i = 0; i < extra->nr; i++)
+ if (!feed_object(extra->array[i], po.in, 1))
+ break;
+
+ while (refs) {
+ if (!is_null_sha1(refs->old_sha1) &&
+ !feed_object(refs->old_sha1, po.in, 1))
+ break;
+ if (!is_null_sha1(refs->new_sha1) &&
+ !feed_object(refs->new_sha1, po.in, 0))
+ break;
+ refs = refs->next;
+ }
+
+ close(po.in);
+
+ if (args->stateless_rpc) {
+ char *buf = xmalloc(LARGE_PACKET_MAX);
+ while (1) {
+ ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX);
+ if (n <= 0)
+ break;
+ send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX);
+ }
+ free(buf);
+ close(po.out);
+ po.out = -1;
+ }
+
+ if (finish_command(&po))
+ return error("pack-objects died with strange error");
+ return 0;
+}
+
+static int receive_status(int in, struct ref *refs)
+{
+ struct ref *hint;
+ char line[1000];
+ int ret = 0;
+ int len = packet_read_line(in, line, sizeof(line));
+ if (len < 10 || memcmp(line, "unpack ", 7))
+ return error("did not receive remote status");
+ if (memcmp(line, "unpack ok\n", 10)) {
+ char *p = line + strlen(line) - 1;
+ if (*p == '\n')
+ *p = '\0';
+ error("unpack failed: %s", line + 7);
+ ret = -1;
+ }
+ hint = NULL;
+ while (1) {
+ char *refname;
+ char *msg;
+ len = packet_read_line(in, line, sizeof(line));
+ if (!len)
+ break;
+ if (len < 3 ||
+ (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
+ fprintf(stderr, "protocol error: %s\n", line);
+ ret = -1;
+ break;
+ }
+
+ line[strlen(line)-1] = '\0';
+ refname = line + 3;
+ msg = strchr(refname, ' ');
+ if (msg)
+ *msg++ = '\0';
+
+ /* first try searching at our hint, falling back to all refs */
+ if (hint)
+ hint = find_ref_by_name(hint, refname);
+ if (!hint)
+ hint = find_ref_by_name(refs, refname);
+ if (!hint) {
+ warning("remote reported status on unknown ref: %s",
+ refname);
+ continue;
+ }
+ if (hint->status != REF_STATUS_EXPECTING_REPORT) {
+ warning("remote reported status on unexpected ref: %s",
+ refname);
+ continue;
+ }
+
+ if (line[0] == 'o' && line[1] == 'k')
+ hint->status = REF_STATUS_OK;
+ else {
+ hint->status = REF_STATUS_REMOTE_REJECT;
+ ret = -1;
+ }
+ if (msg)
+ hint->remote_status = xstrdup(msg);
+ /* start our next search from the next ref */
+ hint = hint->next;
+ }
+ return ret;
+}
+
+static void print_helper_status(struct ref *ref)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ for (; ref; ref = ref->next) {
+ const char *msg = NULL;
+ const char *res;
+
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ res = "error";
+ msg = "no match";
+ break;
+
+ case REF_STATUS_OK:
+ res = "ok";
+ break;
+
+ case REF_STATUS_UPTODATE:
+ res = "ok";
+ msg = "up to date";
+ break;
+
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ res = "error";
+ msg = "non-fast forward";
+ break;
+
+ case REF_STATUS_REJECT_NODELETE:
+ case REF_STATUS_REMOTE_REJECT:
+ res = "error";
+ break;
+
+ case REF_STATUS_EXPECTING_REPORT:
+ default:
+ continue;
+ }
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%s %s", res, ref->name);
+ if (ref->remote_status)
+ msg = ref->remote_status;
+ if (msg) {
+ strbuf_addch(&buf, ' ');
+ quote_two_c_style(&buf, "", msg, 0);
+ }
+ strbuf_addch(&buf, '\n');
+
+ safe_write(1, buf.buf, buf.len);
+ }
+ strbuf_release(&buf);
+}
+
+static int sideband_demux(int in, int out, void *data)
+{
+ int *fd = data;
+ int ret = recv_sideband("send-pack", fd[0], out);
+ close(out);
+ return ret;
+}
+
+int send_pack(struct send_pack_args *args,
+ int fd[], struct child_process *conn,
+ struct ref *remote_refs,
+ struct extra_have_objects *extra_have)
+{
+ int in = fd[0];
+ int out = fd[1];
+ struct strbuf req_buf = STRBUF_INIT;
+ struct ref *ref;
+ int new_refs;
+ int allow_deleting_refs = 0;
+ int status_report = 0;
+ int use_sideband = 0;
+ unsigned cmds_sent = 0;
+ int ret;
+ struct async demux;
+
+ /* Does the other end support the reporting? */
+ if (server_supports("report-status"))
+ status_report = 1;
+ if (server_supports("delete-refs"))
+ allow_deleting_refs = 1;
+ if (server_supports("ofs-delta"))
+ args->use_ofs_delta = 1;
+ if (server_supports("side-band-64k"))
+ use_sideband = 1;
+
+ if (!remote_refs) {
+ fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
+ "Perhaps you should specify a branch such as 'master'.\n");
+ return 0;
+ }
+
+ /*
+ * Finally, tell the other end!
+ */
+ new_refs = 0;
+ for (ref = remote_refs; ref; ref = ref->next) {
+ if (!ref->peer_ref && !args->send_mirror)
+ continue;
+
+ /* Check for statuses set by set_ref_status_for_push() */
+ switch (ref->status) {
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ case REF_STATUS_UPTODATE:
+ continue;
+ default:
+ ; /* do nothing */
+ }
+
+ if (ref->deletion && !allow_deleting_refs) {
+ ref->status = REF_STATUS_REJECT_NODELETE;
+ continue;
+ }
+
+ if (!ref->deletion)
+ new_refs++;
+
+ if (args->dry_run) {
+ ref->status = REF_STATUS_OK;
+ } else {
+ char *old_hex = sha1_to_hex(ref->old_sha1);
+ char *new_hex = sha1_to_hex(ref->new_sha1);
+
+ if (!cmds_sent && (status_report || use_sideband)) {
+ packet_buf_write(&req_buf, "%s %s %s%c%s%s",
+ old_hex, new_hex, ref->name, 0,
+ status_report ? " report-status" : "",
+ use_sideband ? " side-band-64k" : "");
+ }
+ else
+ packet_buf_write(&req_buf, "%s %s %s",
+ old_hex, new_hex, ref->name);
+ ref->status = status_report ?
+ REF_STATUS_EXPECTING_REPORT :
+ REF_STATUS_OK;
+ cmds_sent++;
+ }
+ }
+
+ if (args->stateless_rpc) {
+ if (!args->dry_run && cmds_sent) {
+ packet_buf_flush(&req_buf);
+ send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
+ }
+ } else {
+ safe_write(out, req_buf.buf, req_buf.len);
+ packet_flush(out);
+ }
+ strbuf_release(&req_buf);
+
+ if (use_sideband && cmds_sent) {
+ memset(&demux, 0, sizeof(demux));
+ demux.proc = sideband_demux;
+ demux.data = fd;
+ demux.out = -1;
+ if (start_async(&demux))
+ die("receive-pack: unable to fork off sideband demultiplexer");
+ in = demux.out;
+ }
+
+ if (new_refs && cmds_sent) {
+ if (pack_objects(out, remote_refs, extra_have, args) < 0) {
+ for (ref = remote_refs; ref; ref = ref->next)
+ ref->status = REF_STATUS_NONE;
+ if (use_sideband)
+ finish_async(&demux);
+ return -1;
+ }
+ }
+ if (args->stateless_rpc && cmds_sent)
+ packet_flush(out);
+
+ if (status_report && cmds_sent)
+ ret = receive_status(in, remote_refs);
+ else
+ ret = 0;
+ if (args->stateless_rpc)
+ packet_flush(out);
+
+ if (use_sideband && cmds_sent) {
+ if (finish_async(&demux)) {
+ error("error in sideband demultiplexer");
+ ret = -1;
+ }
+ close(demux.out);
+ }
+
+ if (ret < 0)
+ return ret;
+ for (ref = remote_refs; ref; ref = ref->next) {
+ switch (ref->status) {
+ case REF_STATUS_NONE:
+ case REF_STATUS_UPTODATE:
+ case REF_STATUS_OK:
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int cmd_send_pack(int argc, const char **argv, const char *prefix)
+{
+ int i, nr_refspecs = 0;
+ const char **refspecs = NULL;
+ const char *remote_name = NULL;
+ struct remote *remote = NULL;
+ const char *dest = NULL;
+ int fd[2];
+ struct child_process *conn;
+ struct extra_have_objects extra_have;
+ struct ref *remote_refs, *local_refs;
+ int ret;
+ int helper_status = 0;
+ int send_all = 0;
+ const char *receivepack = "git-receive-pack";
+ int flags;
+ int nonfastforward = 0;
+
+ argv++;
+ for (i = 1; i < argc; i++, argv++) {
+ const char *arg = *argv;
+
+ if (*arg == '-') {
+ if (!prefixcmp(arg, "--receive-pack=")) {
+ receivepack = arg + 15;
+ continue;
+ }
+ if (!prefixcmp(arg, "--exec=")) {
+ receivepack = arg + 7;
+ continue;
+ }
+ if (!prefixcmp(arg, "--remote=")) {
+ remote_name = arg + 9;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ send_all = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--dry-run")) {
+ args.dry_run = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--mirror")) {
+ args.send_mirror = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--force")) {
+ args.force_update = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--verbose")) {
+ args.verbose = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--thin")) {
+ args.use_thin_pack = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--stateless-rpc")) {
+ args.stateless_rpc = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--helper-status")) {
+ helper_status = 1;
+ continue;
+ }
+ usage(send_pack_usage);
+ }
+ if (!dest) {
+ dest = arg;
+ continue;
+ }
+ refspecs = (const char **) argv;
+ nr_refspecs = argc - i;
+ break;
+ }
+ if (!dest)
+ usage(send_pack_usage);
+ /*
+ * --all and --mirror are incompatible; neither makes sense
+ * with any refspecs.
+ */
+ if ((refspecs && (send_all || args.send_mirror)) ||
+ (send_all && args.send_mirror))
+ usage(send_pack_usage);
+
+ if (remote_name) {
+ remote = remote_get(remote_name);
+ if (!remote_has_url(remote, dest)) {
+ die("Destination %s is not a uri for %s",
+ dest, remote_name);
+ }
+ }
+
+ if (args.stateless_rpc) {
+ conn = NULL;
+ fd[0] = 0;
+ fd[1] = 1;
+ } else {
+ conn = git_connect(fd, dest, receivepack,
+ args.verbose ? CONNECT_VERBOSE : 0);
+ }
+
+ memset(&extra_have, 0, sizeof(extra_have));
+
+ get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL,
+ &extra_have);
+
+ transport_verify_remote_names(nr_refspecs, refspecs);
+
+ local_refs = get_local_heads();
+
+ flags = MATCH_REFS_NONE;
+
+ if (send_all)
+ flags |= MATCH_REFS_ALL;
+ if (args.send_mirror)
+ flags |= MATCH_REFS_MIRROR;
+
+ /* match them up */
+ if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
+ return -1;
+
+ set_ref_status_for_push(remote_refs, args.send_mirror,
+ args.force_update);
+
+ ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
+
+ if (helper_status)
+ print_helper_status(remote_refs);
+
+ close(fd[1]);
+ close(fd[0]);
+
+ ret |= finish_connect(conn);
+
+ if (!helper_status)
+ transport_print_push_status(dest, remote_refs, args.verbose, 0, &nonfastforward);
+
+ if (!args.dry_run && remote) {
+ struct ref *ref;
+ for (ref = remote_refs; ref; ref = ref->next)
+ transport_update_tracking_ref(remote, ref, args.verbose);
+ }
+
+ if (!ret && !transport_refs_pushed(remote_refs))
+ fprintf(stderr, "Everything up-to-date\n");
+
+ return ret;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "string-list.h"
+#include "revision.h"
+#include "utf8.h"
+#include "mailmap.h"
+#include "shortlog.h"
+#include "parse-options.h"
+
+static char const * const shortlog_usage[] = {
+ "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]",
+ "",
+ "[rev-opts] are documented in git-rev-list(1)",
+ NULL
+};
+
+static int compare_by_number(const void *a1, const void *a2)
+{
+ const struct string_list_item *i1 = a1, *i2 = a2;
+ const struct string_list *l1 = i1->util, *l2 = i2->util;
+
+ if (l1->nr < l2->nr)
+ return 1;
+ else if (l1->nr == l2->nr)
+ return 0;
+ else
+ return -1;
+}
+
+const char *format_subject(struct strbuf *sb, const char *msg,
+ const char *line_separator);
+
+static void insert_one_record(struct shortlog *log,
+ const char *author,
+ const char *oneline)
+{
+ const char *dot3 = log->common_repo_prefix;
+ char *buffer, *p;
+ struct string_list_item *item;
+ char namebuf[1024];
+ char emailbuf[1024];
+ size_t len;
+ const char *eol;
+ const char *boemail, *eoemail;
+ struct strbuf subject = STRBUF_INIT;
+
+ boemail = strchr(author, '<');
+ if (!boemail)
+ return;
+ eoemail = strchr(boemail, '>');
+ if (!eoemail)
+ return;
+
+ /* copy author name to namebuf, to support matching on both name and email */
+ memcpy(namebuf, author, boemail - author);
+ len = boemail - author;
+ while (len > 0 && isspace(namebuf[len-1]))
+ len--;
+ namebuf[len] = 0;
+
+ /* copy email name to emailbuf, to allow email replacement as well */
+ memcpy(emailbuf, boemail+1, eoemail - boemail);
+ emailbuf[eoemail - boemail - 1] = 0;
+
+ if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
+ while (author < boemail && isspace(*author))
+ author++;
+ for (len = 0;
+ len < sizeof(namebuf) - 1 && author + len < boemail;
+ len++)
+ namebuf[len] = author[len];
+ while (0 < len && isspace(namebuf[len-1]))
+ len--;
+ namebuf[len] = '\0';
+ }
+ else
+ len = strlen(namebuf);
+
+ if (log->email) {
+ size_t room = sizeof(namebuf) - len - 1;
+ int maillen = strlen(emailbuf);
+ snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
+ }
+
+ item = string_list_insert(namebuf, &log->list);
+ if (item->util == NULL)
+ item->util = xcalloc(1, sizeof(struct string_list));
+
+ /* Skip any leading whitespace, including any blank lines. */
+ while (*oneline && isspace(*oneline))
+ oneline++;
+ eol = strchr(oneline, '\n');
+ if (!eol)
+ eol = oneline + strlen(oneline);
+ if (!prefixcmp(oneline, "[PATCH")) {
+ char *eob = strchr(oneline, ']');
+ if (eob && (!eol || eob < eol))
+ oneline = eob + 1;
+ }
+ while (*oneline && isspace(*oneline) && *oneline != '\n')
+ oneline++;
+ format_subject(&subject, oneline, " ");
+ buffer = strbuf_detach(&subject, NULL);
+
+ if (dot3) {
+ int dot3len = strlen(dot3);
+ if (dot3len > 5) {
+ while ((p = strstr(buffer, dot3)) != NULL) {
+ int taillen = strlen(p) - dot3len;
+ memcpy(p, "/.../", 5);
+ memmove(p + 5, p + dot3len, taillen + 1);
+ }
+ }
+ }
+
+ string_list_append(buffer, item->util);
+}
+
+static void read_from_stdin(struct shortlog *log)
+{
+ char author[1024], oneline[1024];
+
+ while (fgets(author, sizeof(author), stdin) != NULL) {
+ if (!(author[0] == 'A' || author[0] == 'a') ||
+ prefixcmp(author + 1, "uthor: "))
+ continue;
+ while (fgets(oneline, sizeof(oneline), stdin) &&
+ oneline[0] != '\n')
+ ; /* discard headers */
+ while (fgets(oneline, sizeof(oneline), stdin) &&
+ oneline[0] == '\n')
+ ; /* discard blanks */
+ insert_one_record(log, author + 8, oneline);
+ }
+}
+
+void shortlog_add_commit(struct shortlog *log, struct commit *commit)
+{
+ const char *author = NULL, *buffer;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf ufbuf = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+
+ pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx);
+ buffer = buf.buf;
+ while (*buffer && *buffer != '\n') {
+ const char *eol = strchr(buffer, '\n');
+
+ if (eol == NULL)
+ eol = buffer + strlen(buffer);
+ else
+ eol++;
+
+ if (!prefixcmp(buffer, "author "))
+ author = buffer + 7;
+ buffer = eol;
+ }
+ if (!author)
+ die("Missing author: %s",
+ sha1_to_hex(commit->object.sha1));
+ if (log->user_format) {
+ struct pretty_print_context ctx = {0};
+ ctx.abbrev = DEFAULT_ABBREV;
+ ctx.subject = "";
+ ctx.after_subject = "";
+ ctx.date_mode = DATE_NORMAL;
+ pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx);
+ buffer = ufbuf.buf;
+ } else if (*buffer) {
+ buffer++;
+ }
+ insert_one_record(log, author, !*buffer ? "<none>" : buffer);
+ strbuf_release(&ufbuf);
+ strbuf_release(&buf);
+}
+
+static void get_from_rev(struct rev_info *rev, struct shortlog *log)
+{
+ struct commit *commit;
+
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
+ while ((commit = get_revision(rev)) != NULL)
+ shortlog_add_commit(log, commit);
+}
+
+static int parse_uint(char const **arg, int comma, int defval)
+{
+ unsigned long ul;
+ int ret;
+ char *endp;
+
+ ul = strtoul(*arg, &endp, 10);
+ if (*endp && *endp != comma)
+ return -1;
+ if (ul > INT_MAX)
+ return -1;
+ ret = *arg == endp ? defval : (int)ul;
+ *arg = *endp ? endp + 1 : endp;
+ return ret;
+}
+
+static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]";
+#define DEFAULT_WRAPLEN 76
+#define DEFAULT_INDENT1 6
+#define DEFAULT_INDENT2 9
+
+static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
+{
+ struct shortlog *log = opt->value;
+
+ log->wrap_lines = !unset;
+ if (unset)
+ return 0;
+ if (!arg) {
+ log->wrap = DEFAULT_WRAPLEN;
+ log->in1 = DEFAULT_INDENT1;
+ log->in2 = DEFAULT_INDENT2;
+ return 0;
+ }
+
+ log->wrap = parse_uint(&arg, ',', DEFAULT_WRAPLEN);
+ log->in1 = parse_uint(&arg, ',', DEFAULT_INDENT1);
+ log->in2 = parse_uint(&arg, '\0', DEFAULT_INDENT2);
+ if (log->wrap < 0 || log->in1 < 0 || log->in2 < 0)
+ return error(wrap_arg_usage);
+ if (log->wrap &&
+ ((log->in1 && log->wrap <= log->in1) ||
+ (log->in2 && log->wrap <= log->in2)))
+ return error(wrap_arg_usage);
+ return 0;
+}
+
+void shortlog_init(struct shortlog *log)
+{
+ memset(log, 0, sizeof(*log));
+
+ read_mailmap(&log->mailmap, &log->common_repo_prefix);
+
+ log->list.strdup_strings = 1;
+ log->wrap = DEFAULT_WRAPLEN;
+ log->in1 = DEFAULT_INDENT1;
+ log->in2 = DEFAULT_INDENT2;
+}
+
+int cmd_shortlog(int argc, const char **argv, const char *prefix)
+{
+ static struct shortlog log;
+ static struct rev_info rev;
+ int nongit;
+
+ static const struct option options[] = {
+ OPT_BOOLEAN('n', "numbered", &log.sort_by_number,
+ "sort output according to the number of commits per author"),
+ OPT_BOOLEAN('s', "summary", &log.summary,
+ "Suppress commit descriptions, only provides commit count"),
+ OPT_BOOLEAN('e', "email", &log.email,
+ "Show the email address of each author"),
+ { OPTION_CALLBACK, 'w', NULL, &log, "w[,i1[,i2]]",
+ "Linewrap output", PARSE_OPT_OPTARG, &parse_wrap_args },
+ OPT_END(),
+ };
+
+ struct parse_opt_ctx_t ctx;
+
+ prefix = setup_git_directory_gently(&nongit);
+ git_config(git_default_config, NULL);
+ shortlog_init(&log);
+ init_revisions(&rev, prefix);
+ parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
+ PARSE_OPT_KEEP_ARGV0);
+
+ for (;;) {
+ switch (parse_options_step(&ctx, options, shortlog_usage)) {
+ case PARSE_OPT_HELP:
+ exit(129);
+ case PARSE_OPT_DONE:
+ goto parse_done;
+ }
+ parse_revision_opt(&rev, &ctx, options, shortlog_usage);
+ }
+parse_done:
+ argc = parse_options_end(&ctx);
+
+ if (setup_revisions(argc, argv, &rev, NULL) != 1) {
+ error("unrecognized argument: %s", argv[1]);
+ usage_with_options(shortlog_usage, options);
+ }
+
+ log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
+
+ /* assume HEAD if from a tty */
+ if (!nongit && !rev.pending.nr && isatty(0))
+ add_head_to_pending(&rev);
+ if (rev.pending.nr == 0) {
+ if (isatty(0))
+ fprintf(stderr, "(reading log message from standard input)\n");
+ read_from_stdin(&log);
+ }
+ else
+ get_from_rev(&rev, &log);
+
+ shortlog_output(&log);
+ return 0;
+}
+
+static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s,
+ const struct shortlog *log)
+{
+ int col = strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap);
+ if (col != log->wrap)
+ strbuf_addch(sb, '\n');
+}
+
+void shortlog_output(struct shortlog *log)
+{
+ int i, j;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (log->sort_by_number)
+ qsort(log->list.items, log->list.nr, sizeof(struct string_list_item),
+ compare_by_number);
+ for (i = 0; i < log->list.nr; i++) {
+ struct string_list *onelines = log->list.items[i].util;
+
+ if (log->summary) {
+ printf("%6d\t%s\n", onelines->nr, log->list.items[i].string);
+ } else {
+ printf("%s (%d):\n", log->list.items[i].string, onelines->nr);
+ for (j = onelines->nr - 1; j >= 0; j--) {
+ const char *msg = onelines->items[j].string;
+
+ if (log->wrap_lines) {
+ strbuf_reset(&sb);
+ add_wrapped_shortlog_msg(&sb, msg, log);
+ fwrite(sb.buf, sb.len, 1, stdout);
+ }
+ else
+ printf(" %s\n", msg);
+ }
+ putchar('\n');
+ }
+
+ onelines->strdup_strings = 1;
+ string_list_clear(onelines, 0);
+ free(onelines);
+ log->list.items[i].util = NULL;
+ }
+
+ strbuf_release(&sb);
+ log->list.strdup_strings = 1;
+ string_list_clear(&log->list, 1);
+ clear_mailmap(&log->mailmap);
+}
--- /dev/null
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "builtin.h"
+#include "color.h"
+#include "parse-options.h"
+
+static const char* show_branch_usage[] = {
+ "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...",
+ "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]",
+ NULL
+};
+
+static int showbranch_use_color = -1;
+static char column_colors[][COLOR_MAXLEN] = {
+ GIT_COLOR_RED,
+ GIT_COLOR_GREEN,
+ GIT_COLOR_YELLOW,
+ GIT_COLOR_BLUE,
+ GIT_COLOR_MAGENTA,
+ GIT_COLOR_CYAN,
+};
+
+#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))
+
+static int default_num;
+static int default_alloc;
+static const char **default_arg;
+
+#define UNINTERESTING 01
+
+#define REV_SHIFT 2
+#define MAX_REVS (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
+
+#define DEFAULT_REFLOG 4
+
+static const char *get_color_code(int idx)
+{
+ if (showbranch_use_color)
+ return column_colors[idx];
+ return "";
+}
+
+static const char *get_color_reset_code(void)
+{
+ if (showbranch_use_color)
+ return GIT_COLOR_RESET;
+ return "";
+}
+
+static struct commit *interesting(struct commit_list *list)
+{
+ while (list) {
+ struct commit *commit = list->item;
+ list = list->next;
+ if (commit->object.flags & UNINTERESTING)
+ continue;
+ return commit;
+ }
+ return NULL;
+}
+
+static struct commit *pop_one_commit(struct commit_list **list_p)
+{
+ struct commit *commit;
+ struct commit_list *list;
+ list = *list_p;
+ commit = list->item;
+ *list_p = list->next;
+ free(list);
+ return commit;
+}
+
+struct commit_name {
+ const char *head_name; /* which head's ancestor? */
+ int generation; /* how many parents away from head_name */
+};
+
+/* Name the commit as nth generation ancestor of head_name;
+ * we count only the first-parent relationship for naming purposes.
+ */
+static void name_commit(struct commit *commit, const char *head_name, int nth)
+{
+ struct commit_name *name;
+ if (!commit->util)
+ commit->util = xmalloc(sizeof(struct commit_name));
+ name = commit->util;
+ name->head_name = head_name;
+ name->generation = nth;
+}
+
+/* Parent is the first parent of the commit. We may name it
+ * as (n+1)th generation ancestor of the same head_name as
+ * commit is nth generation ancestor of, if that generation
+ * number is better than the name it already has.
+ */
+static void name_parent(struct commit *commit, struct commit *parent)
+{
+ struct commit_name *commit_name = commit->util;
+ struct commit_name *parent_name = parent->util;
+ if (!commit_name)
+ return;
+ if (!parent_name ||
+ commit_name->generation + 1 < parent_name->generation)
+ name_commit(parent, commit_name->head_name,
+ commit_name->generation + 1);
+}
+
+static int name_first_parent_chain(struct commit *c)
+{
+ int i = 0;
+ while (c) {
+ struct commit *p;
+ if (!c->util)
+ break;
+ if (!c->parents)
+ break;
+ p = c->parents->item;
+ if (!p->util) {
+ name_parent(c, p);
+ i++;
+ }
+ else
+ break;
+ c = p;
+ }
+ return i;
+}
+
+static void name_commits(struct commit_list *list,
+ struct commit **rev,
+ char **ref_name,
+ int num_rev)
+{
+ struct commit_list *cl;
+ struct commit *c;
+ int i;
+
+ /* First give names to the given heads */
+ for (cl = list; cl; cl = cl->next) {
+ c = cl->item;
+ if (c->util)
+ continue;
+ for (i = 0; i < num_rev; i++) {
+ if (rev[i] == c) {
+ name_commit(c, ref_name[i], 0);
+ break;
+ }
+ }
+ }
+
+ /* Then commits on the first parent ancestry chain */
+ do {
+ i = 0;
+ for (cl = list; cl; cl = cl->next) {
+ i += name_first_parent_chain(cl->item);
+ }
+ } while (i);
+
+ /* Finally, any unnamed commits */
+ do {
+ i = 0;
+ for (cl = list; cl; cl = cl->next) {
+ struct commit_list *parents;
+ struct commit_name *n;
+ int nth;
+ c = cl->item;
+ if (!c->util)
+ continue;
+ n = c->util;
+ parents = c->parents;
+ nth = 0;
+ while (parents) {
+ struct commit *p = parents->item;
+ char newname[1000], *en;
+ parents = parents->next;
+ nth++;
+ if (p->util)
+ continue;
+ en = newname;
+ switch (n->generation) {
+ case 0:
+ en += sprintf(en, "%s", n->head_name);
+ break;
+ case 1:
+ en += sprintf(en, "%s^", n->head_name);
+ break;
+ default:
+ en += sprintf(en, "%s~%d",
+ n->head_name, n->generation);
+ break;
+ }
+ if (nth == 1)
+ en += sprintf(en, "^");
+ else
+ en += sprintf(en, "^%d", nth);
+ name_commit(p, xstrdup(newname), 0);
+ i++;
+ name_first_parent_chain(p);
+ }
+ }
+ } while (i);
+}
+
+static int mark_seen(struct commit *commit, struct commit_list **seen_p)
+{
+ if (!commit->object.flags) {
+ commit_list_insert(commit, seen_p);
+ return 1;
+ }
+ return 0;
+}
+
+static void join_revs(struct commit_list **list_p,
+ struct commit_list **seen_p,
+ int num_rev, int extra)
+{
+ int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+ int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+
+ while (*list_p) {
+ struct commit_list *parents;
+ int still_interesting = !!interesting(*list_p);
+ struct commit *commit = pop_one_commit(list_p);
+ int flags = commit->object.flags & all_mask;
+
+ if (!still_interesting && extra <= 0)
+ break;
+
+ mark_seen(commit, seen_p);
+ if ((flags & all_revs) == all_revs)
+ flags |= UNINTERESTING;
+ parents = commit->parents;
+
+ while (parents) {
+ struct commit *p = parents->item;
+ int this_flag = p->object.flags;
+ parents = parents->next;
+ if ((this_flag & flags) == flags)
+ continue;
+ if (!p->object.parsed)
+ parse_commit(p);
+ if (mark_seen(p, seen_p) && !still_interesting)
+ extra--;
+ p->object.flags |= flags;
+ insert_by_date(p, list_p);
+ }
+ }
+
+ /*
+ * Postprocess to complete well-poisoning.
+ *
+ * At this point we have all the commits we have seen in
+ * seen_p list. Mark anything that can be reached from
+ * uninteresting commits not interesting.
+ */
+ for (;;) {
+ int changed = 0;
+ struct commit_list *s;
+ for (s = *seen_p; s; s = s->next) {
+ struct commit *c = s->item;
+ struct commit_list *parents;
+
+ if (((c->object.flags & all_revs) != all_revs) &&
+ !(c->object.flags & UNINTERESTING))
+ continue;
+
+ /* The current commit is either a merge base or
+ * already uninteresting one. Mark its parents
+ * as uninteresting commits _only_ if they are
+ * already parsed. No reason to find new ones
+ * here.
+ */
+ parents = c->parents;
+ while (parents) {
+ struct commit *p = parents->item;
+ parents = parents->next;
+ if (!(p->object.flags & UNINTERESTING)) {
+ p->object.flags |= UNINTERESTING;
+ changed = 1;
+ }
+ }
+ }
+ if (!changed)
+ break;
+ }
+}
+
+static void show_one_commit(struct commit *commit, int no_name)
+{
+ struct strbuf pretty = STRBUF_INIT;
+ const char *pretty_str = "(unavailable)";
+ struct commit_name *name = commit->util;
+
+ if (commit->object.parsed) {
+ struct pretty_print_context ctx = {0};
+ pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx);
+ pretty_str = pretty.buf;
+ }
+ if (!prefixcmp(pretty_str, "[PATCH] "))
+ pretty_str += 8;
+
+ if (!no_name) {
+ if (name && name->head_name) {
+ printf("[%s", name->head_name);
+ if (name->generation) {
+ if (name->generation == 1)
+ printf("^");
+ else
+ printf("~%d", name->generation);
+ }
+ printf("] ");
+ }
+ else
+ printf("[%s] ",
+ find_unique_abbrev(commit->object.sha1, 7));
+ }
+ puts(pretty_str);
+ strbuf_release(&pretty);
+}
+
+static char *ref_name[MAX_REVS + 1];
+static int ref_name_cnt;
+
+static const char *find_digit_prefix(const char *s, int *v)
+{
+ const char *p;
+ int ver;
+ char ch;
+
+ for (p = s, ver = 0;
+ '0' <= (ch = *p) && ch <= '9';
+ p++)
+ ver = ver * 10 + ch - '0';
+ *v = ver;
+ return p;
+}
+
+
+static int version_cmp(const char *a, const char *b)
+{
+ while (1) {
+ int va, vb;
+
+ a = find_digit_prefix(a, &va);
+ b = find_digit_prefix(b, &vb);
+ if (va != vb)
+ return va - vb;
+
+ while (1) {
+ int ca = *a;
+ int cb = *b;
+ if ('0' <= ca && ca <= '9')
+ ca = 0;
+ if ('0' <= cb && cb <= '9')
+ cb = 0;
+ if (ca != cb)
+ return ca - cb;
+ if (!ca)
+ break;
+ a++;
+ b++;
+ }
+ if (!*a && !*b)
+ return 0;
+ }
+}
+
+static int compare_ref_name(const void *a_, const void *b_)
+{
+ const char * const*a = a_, * const*b = b_;
+ return version_cmp(*a, *b);
+}
+
+static void sort_ref_range(int bottom, int top)
+{
+ qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]),
+ compare_ref_name);
+}
+
+static int append_ref(const char *refname, const unsigned char *sha1,
+ int allow_dups)
+{
+ struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ int i;
+
+ if (!commit)
+ return 0;
+
+ if (!allow_dups) {
+ /* Avoid adding the same thing twice */
+ for (i = 0; i < ref_name_cnt; i++)
+ if (!strcmp(refname, ref_name[i]))
+ return 0;
+ }
+ if (MAX_REVS <= ref_name_cnt) {
+ warning("ignoring %s; cannot handle more than %d refs",
+ refname, MAX_REVS);
+ return 0;
+ }
+ ref_name[ref_name_cnt++] = xstrdup(refname);
+ ref_name[ref_name_cnt] = NULL;
+ return 0;
+}
+
+static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ unsigned char tmp[20];
+ int ofs = 11;
+ if (prefixcmp(refname, "refs/heads/"))
+ return 0;
+ /* If both heads/foo and tags/foo exists, get_sha1 would
+ * get confused.
+ */
+ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+ ofs = 5;
+ return append_ref(refname + ofs, sha1, 0);
+}
+
+static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ unsigned char tmp[20];
+ int ofs = 13;
+ if (prefixcmp(refname, "refs/remotes/"))
+ return 0;
+ /* If both heads/foo and tags/foo exists, get_sha1 would
+ * get confused.
+ */
+ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+ ofs = 5;
+ return append_ref(refname + ofs, sha1, 0);
+}
+
+static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ if (prefixcmp(refname, "refs/tags/"))
+ return 0;
+ return append_ref(refname + 5, sha1, 0);
+}
+
+static const char *match_ref_pattern = NULL;
+static int match_ref_slash = 0;
+static int count_slash(const char *s)
+{
+ int cnt = 0;
+ while (*s)
+ if (*s++ == '/')
+ cnt++;
+ return cnt;
+}
+
+static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ /* we want to allow pattern hold/<asterisk> to show all
+ * branches under refs/heads/hold/, and v0.99.9? to show
+ * refs/tags/v0.99.9a and friends.
+ */
+ const char *tail;
+ int slash = count_slash(refname);
+ for (tail = refname; *tail && match_ref_slash < slash; )
+ if (*tail++ == '/')
+ slash--;
+ if (!*tail)
+ return 0;
+ if (fnmatch(match_ref_pattern, tail, 0))
+ return 0;
+ if (!prefixcmp(refname, "refs/heads/"))
+ return append_head_ref(refname, sha1, flag, cb_data);
+ if (!prefixcmp(refname, "refs/tags/"))
+ return append_tag_ref(refname, sha1, flag, cb_data);
+ return append_ref(refname, sha1, 0);
+}
+
+static void snarf_refs(int head, int remotes)
+{
+ if (head) {
+ int orig_cnt = ref_name_cnt;
+ for_each_ref(append_head_ref, NULL);
+ sort_ref_range(orig_cnt, ref_name_cnt);
+ }
+ if (remotes) {
+ int orig_cnt = ref_name_cnt;
+ for_each_ref(append_remote_ref, NULL);
+ sort_ref_range(orig_cnt, ref_name_cnt);
+ }
+}
+
+static int rev_is_head(char *head, int headlen, char *name,
+ unsigned char *head_sha1, unsigned char *sha1)
+{
+ if ((!head[0]) ||
+ (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
+ return 0;
+ if (!prefixcmp(head, "refs/heads/"))
+ head += 11;
+ if (!prefixcmp(name, "refs/heads/"))
+ name += 11;
+ else if (!prefixcmp(name, "heads/"))
+ name += 6;
+ return !strcmp(head, name);
+}
+
+static int show_merge_base(struct commit_list *seen, int num_rev)
+{
+ int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+ int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+ int exit_status = 1;
+
+ while (seen) {
+ struct commit *commit = pop_one_commit(&seen);
+ int flags = commit->object.flags & all_mask;
+ if (!(flags & UNINTERESTING) &&
+ ((flags & all_revs) == all_revs)) {
+ puts(sha1_to_hex(commit->object.sha1));
+ exit_status = 0;
+ commit->object.flags |= UNINTERESTING;
+ }
+ }
+ return exit_status;
+}
+
+static int show_independent(struct commit **rev,
+ int num_rev,
+ char **ref_name,
+ unsigned int *rev_mask)
+{
+ int i;
+
+ for (i = 0; i < num_rev; i++) {
+ struct commit *commit = rev[i];
+ unsigned int flag = rev_mask[i];
+
+ if (commit->object.flags == flag)
+ puts(sha1_to_hex(commit->object.sha1));
+ commit->object.flags |= UNINTERESTING;
+ }
+ return 0;
+}
+
+static void append_one_rev(const char *av)
+{
+ unsigned char revkey[20];
+ if (!get_sha1(av, revkey)) {
+ append_ref(av, revkey, 0);
+ return;
+ }
+ if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
+ /* glob style match */
+ int saved_matches = ref_name_cnt;
+ match_ref_pattern = av;
+ match_ref_slash = count_slash(av);
+ for_each_ref(append_matching_ref, NULL);
+ if (saved_matches == ref_name_cnt &&
+ ref_name_cnt < MAX_REVS)
+ error("no matching refs with %s", av);
+ if (saved_matches + 1 < ref_name_cnt)
+ sort_ref_range(saved_matches, ref_name_cnt);
+ return;
+ }
+ die("bad sha1 reference %s", av);
+}
+
+static int git_show_branch_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "showbranch.default")) {
+ if (!value)
+ return config_error_nonbool(var);
+ /*
+ * default_arg is now passed to parse_options(), so we need to
+ * mimic the real argv a bit better.
+ */
+ if (!default_num) {
+ default_alloc = 20;
+ default_arg = xcalloc(default_alloc, sizeof(*default_arg));
+ default_arg[default_num++] = "show-branch";
+ } else if (default_alloc <= default_num + 1) {
+ default_alloc = default_alloc * 3 / 2 + 20;
+ default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
+ }
+ default_arg[default_num++] = xstrdup(value);
+ default_arg[default_num] = NULL;
+ return 0;
+ }
+
+ if (!strcmp(var, "color.showbranch")) {
+ showbranch_use_color = git_config_colorbool(var, value, -1);
+ return 0;
+ }
+
+ return git_color_default_config(var, value, cb);
+}
+
+static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
+{
+ /* If the commit is tip of the named branches, do not
+ * omit it.
+ * Otherwise, if it is a merge that is reachable from only one
+ * tip, it is not that interesting.
+ */
+ int i, flag, count;
+ for (i = 0; i < n; i++)
+ if (rev[i] == commit)
+ return 0;
+ flag = commit->object.flags;
+ for (i = count = 0; i < n; i++) {
+ if (flag & (1u << (i + REV_SHIFT)))
+ count++;
+ }
+ if (count == 1)
+ return 1;
+ return 0;
+}
+
+static int reflog = 0;
+
+static int parse_reflog_param(const struct option *opt, const char *arg,
+ int unset)
+{
+ char *ep;
+ const char **base = (const char **)opt->value;
+ if (!arg)
+ arg = "";
+ reflog = strtoul(arg, &ep, 10);
+ if (*ep == ',')
+ *base = ep + 1;
+ else if (*ep)
+ return error("unrecognized reflog param '%s'", arg);
+ else
+ *base = NULL;
+ if (reflog <= 0)
+ reflog = DEFAULT_REFLOG;
+ return 0;
+}
+
+int cmd_show_branch(int ac, const char **av, const char *prefix)
+{
+ struct commit *rev[MAX_REVS], *commit;
+ char *reflog_msg[MAX_REVS];
+ struct commit_list *list = NULL, *seen = NULL;
+ unsigned int rev_mask[MAX_REVS];
+ int num_rev, i, extra = 0;
+ int all_heads = 0, all_remotes = 0;
+ int all_mask, all_revs;
+ int lifo = 1;
+ char head[128];
+ const char *head_p;
+ int head_len;
+ unsigned char head_sha1[20];
+ int merge_base = 0;
+ int independent = 0;
+ int no_name = 0;
+ int sha1_name = 0;
+ int shown_merge_point = 0;
+ int with_current_branch = 0;
+ int head_at = -1;
+ int topics = 0;
+ int dense = 1;
+ const char *reflog_base = NULL;
+ struct option builtin_show_branch_options[] = {
+ OPT_BOOLEAN('a', "all", &all_heads,
+ "show remote-tracking and local branches"),
+ OPT_BOOLEAN('r', "remotes", &all_remotes,
+ "show remote-tracking branches"),
+ OPT__COLOR(&showbranch_use_color,
+ "color '*!+-' corresponding to the branch"),
+ { OPTION_INTEGER, 0, "more", &extra, "n",
+ "show <n> more commits after the common ancestor",
+ PARSE_OPT_OPTARG, NULL, (intptr_t)1 },
+ OPT_SET_INT(0, "list", &extra, "synonym to more=-1", -1),
+ OPT_BOOLEAN(0, "no-name", &no_name, "suppress naming strings"),
+ OPT_BOOLEAN(0, "current", &with_current_branch,
+ "include the current branch"),
+ OPT_BOOLEAN(0, "sha1-name", &sha1_name,
+ "name commits with their object names"),
+ OPT_BOOLEAN(0, "merge-base", &merge_base,
+ "show possible merge bases"),
+ OPT_BOOLEAN(0, "independent", &independent,
+ "show refs unreachable from any other ref"),
+ OPT_BOOLEAN(0, "topo-order", &lifo,
+ "show commits in topological order"),
+ OPT_BOOLEAN(0, "topics", &topics,
+ "show only commits not on the first branch"),
+ OPT_SET_INT(0, "sparse", &dense,
+ "show merges reachable from only one tip", 0),
+ OPT_SET_INT(0, "date-order", &lifo,
+ "show commits where no parent comes before its "
+ "children", 0),
+ { OPTION_CALLBACK, 'g', "reflog", &reflog_base, "<n>[,<base>]",
+ "show <n> most recent ref-log entries starting at "
+ "base",
+ PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+ parse_reflog_param },
+ OPT_END()
+ };
+
+ git_config(git_show_branch_config, NULL);
+
+ if (showbranch_use_color == -1)
+ showbranch_use_color = git_use_color_default;
+
+ /* If nothing is specified, try the default first */
+ if (ac == 1 && default_num) {
+ ac = default_num;
+ av = default_arg;
+ }
+
+ ac = parse_options(ac, av, prefix, builtin_show_branch_options,
+ show_branch_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+ if (all_heads)
+ all_remotes = 1;
+
+ if (extra || reflog) {
+ /* "listing" mode is incompatible with
+ * independent nor merge-base modes.
+ */
+ if (independent || merge_base)
+ usage_with_options(show_branch_usage,
+ builtin_show_branch_options);
+ if (reflog && ((0 < extra) || all_heads || all_remotes))
+ /*
+ * Asking for --more in reflog mode does not
+ * make sense. --list is Ok.
+ *
+ * Also --all and --remotes do not make sense either.
+ */
+ die("--reflog is incompatible with --all, --remotes, "
+ "--independent or --merge-base");
+ }
+
+ /* If nothing is specified, show all branches by default */
+ if (ac + all_heads + all_remotes == 0)
+ all_heads = 1;
+
+ if (reflog) {
+ unsigned char sha1[20];
+ char nth_desc[256];
+ char *ref;
+ int base = 0;
+
+ if (ac == 0) {
+ static const char *fake_av[2];
+ const char *refname;
+
+ refname = resolve_ref("HEAD", sha1, 1, NULL);
+ fake_av[0] = xstrdup(refname);
+ fake_av[1] = NULL;
+ av = fake_av;
+ ac = 1;
+ }
+ if (ac != 1)
+ die("--reflog option needs one branch name");
+
+ if (MAX_REVS < reflog)
+ die("Only %d entries can be shown at one time.",
+ MAX_REVS);
+ if (!dwim_ref(*av, strlen(*av), sha1, &ref))
+ die("No such ref %s", *av);
+
+ /* Has the base been specified? */
+ if (reflog_base) {
+ char *ep;
+ base = strtoul(reflog_base, &ep, 10);
+ if (*ep) {
+ /* Ah, that is a date spec... */
+ unsigned long at;
+ at = approxidate(reflog_base);
+ read_ref_at(ref, at, -1, sha1, NULL,
+ NULL, NULL, &base);
+ }
+ }
+
+ for (i = 0; i < reflog; i++) {
+ char *logmsg, *m;
+ const char *msg;
+ unsigned long timestamp;
+ int tz;
+
+ if (read_ref_at(ref, 0, base+i, sha1, &logmsg,
+ ×tamp, &tz, NULL)) {
+ reflog = i;
+ break;
+ }
+ msg = strchr(logmsg, '\t');
+ if (!msg)
+ msg = "(none)";
+ else
+ msg++;
+ m = xmalloc(strlen(msg) + 200);
+ sprintf(m, "(%s) %s",
+ show_date(timestamp, tz, 1),
+ msg);
+ reflog_msg[i] = m;
+ free(logmsg);
+ sprintf(nth_desc, "%s@{%d}", *av, base+i);
+ append_ref(nth_desc, sha1, 1);
+ }
+ }
+ else if (all_heads + all_remotes)
+ snarf_refs(all_heads, all_remotes);
+ else {
+ while (0 < ac) {
+ append_one_rev(*av);
+ ac--; av++;
+ }
+ }
+
+ head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+ if (head_p) {
+ head_len = strlen(head_p);
+ memcpy(head, head_p, head_len + 1);
+ }
+ else {
+ head_len = 0;
+ head[0] = 0;
+ }
+
+ if (with_current_branch && head_p) {
+ int has_head = 0;
+ for (i = 0; !has_head && i < ref_name_cnt; i++) {
+ /* We are only interested in adding the branch
+ * HEAD points at.
+ */
+ if (rev_is_head(head,
+ head_len,
+ ref_name[i],
+ head_sha1, NULL))
+ has_head++;
+ }
+ if (!has_head) {
+ int offset = !prefixcmp(head, "refs/heads/") ? 11 : 0;
+ append_one_rev(head + offset);
+ }
+ }
+
+ if (!ref_name_cnt) {
+ fprintf(stderr, "No revs to be shown.\n");
+ exit(0);
+ }
+
+ for (num_rev = 0; ref_name[num_rev]; num_rev++) {
+ unsigned char revkey[20];
+ unsigned int flag = 1u << (num_rev + REV_SHIFT);
+
+ if (MAX_REVS <= num_rev)
+ die("cannot handle more than %d revs.", MAX_REVS);
+ if (get_sha1(ref_name[num_rev], revkey))
+ die("'%s' is not a valid ref.", ref_name[num_rev]);
+ commit = lookup_commit_reference(revkey);
+ if (!commit)
+ die("cannot find commit %s (%s)",
+ ref_name[num_rev], revkey);
+ parse_commit(commit);
+ mark_seen(commit, &seen);
+
+ /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
+ * and so on. REV_SHIFT bits from bit 0 are used for
+ * internal bookkeeping.
+ */
+ commit->object.flags |= flag;
+ if (commit->object.flags == flag)
+ insert_by_date(commit, &list);
+ rev[num_rev] = commit;
+ }
+ for (i = 0; i < num_rev; i++)
+ rev_mask[i] = rev[i]->object.flags;
+
+ if (0 <= extra)
+ join_revs(&list, &seen, num_rev, extra);
+
+ sort_by_date(&seen);
+
+ if (merge_base)
+ return show_merge_base(seen, num_rev);
+
+ if (independent)
+ return show_independent(rev, num_rev, ref_name, rev_mask);
+
+ /* Show list; --more=-1 means list-only */
+ if (1 < num_rev || extra < 0) {
+ for (i = 0; i < num_rev; i++) {
+ int j;
+ int is_head = rev_is_head(head,
+ head_len,
+ ref_name[i],
+ head_sha1,
+ rev[i]->object.sha1);
+ if (extra < 0)
+ printf("%c [%s] ",
+ is_head ? '*' : ' ', ref_name[i]);
+ else {
+ for (j = 0; j < i; j++)
+ putchar(' ');
+ printf("%s%c%s [%s] ",
+ get_color_code(i % COLUMN_COLORS_MAX),
+ is_head ? '*' : '!',
+ get_color_reset_code(), ref_name[i]);
+ }
+
+ if (!reflog) {
+ /* header lines never need name */
+ show_one_commit(rev[i], 1);
+ }
+ else
+ puts(reflog_msg[i]);
+
+ if (is_head)
+ head_at = i;
+ }
+ if (0 <= extra) {
+ for (i = 0; i < num_rev; i++)
+ putchar('-');
+ putchar('\n');
+ }
+ }
+ if (extra < 0)
+ exit(0);
+
+ /* Sort topologically */
+ sort_in_topological_order(&seen, lifo);
+
+ /* Give names to commits */
+ if (!sha1_name && !no_name)
+ name_commits(seen, rev, ref_name, num_rev);
+
+ all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+ all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+
+ while (seen) {
+ struct commit *commit = pop_one_commit(&seen);
+ int this_flag = commit->object.flags;
+ int is_merge_point = ((this_flag & all_revs) == all_revs);
+
+ shown_merge_point |= is_merge_point;
+
+ if (1 < num_rev) {
+ int is_merge = !!(commit->parents &&
+ commit->parents->next);
+ if (topics &&
+ !is_merge_point &&
+ (this_flag & (1u << REV_SHIFT)))
+ continue;
+ if (dense && is_merge &&
+ omit_in_dense(commit, rev, num_rev))
+ continue;
+ for (i = 0; i < num_rev; i++) {
+ int mark;
+ if (!(this_flag & (1u << (i + REV_SHIFT))))
+ mark = ' ';
+ else if (is_merge)
+ mark = '-';
+ else if (i == head_at)
+ mark = '*';
+ else
+ mark = '+';
+ printf("%s%c%s",
+ get_color_code(i % COLUMN_COLORS_MAX),
+ mark, get_color_reset_code());
+ }
+ putchar(' ');
+ }
+ show_one_commit(commit, no_name);
+
+ if (shown_merge_point && --extra < 0)
+ break;
+ }
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+static const char * const show_ref_usage[] = {
+ "git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] ",
+ "git show-ref --exclude-existing[=pattern] < ref-list",
+ NULL
+};
+
+static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
+ quiet, hash_only, abbrev, exclude_arg;
+static const char **pattern;
+static const char *exclude_existing_arg;
+
+static void show_one(const char *refname, const unsigned char *sha1)
+{
+ const char *hex = find_unique_abbrev(sha1, abbrev);
+ if (hash_only)
+ printf("%s\n", hex);
+ else
+ printf("%s %s\n", hex, refname);
+}
+
+static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+{
+ struct object *obj;
+ const char *hex;
+ unsigned char peeled[20];
+
+ if (tags_only || heads_only) {
+ int match;
+
+ match = heads_only && !prefixcmp(refname, "refs/heads/");
+ match |= tags_only && !prefixcmp(refname, "refs/tags/");
+ if (!match)
+ return 0;
+ }
+ if (pattern) {
+ int reflen = strlen(refname);
+ const char **p = pattern, *m;
+ while ((m = *p++) != NULL) {
+ int len = strlen(m);
+ if (len > reflen)
+ continue;
+ if (memcmp(m, refname + reflen - len, len))
+ continue;
+ if (len == reflen)
+ goto match;
+ /* "--verify" requires an exact match */
+ if (verify)
+ continue;
+ if (refname[reflen - len - 1] == '/')
+ goto match;
+ }
+ return 0;
+ }
+
+match:
+ found_match++;
+
+ /* This changes the semantics slightly that even under quiet we
+ * detect and return error if the repository is corrupt and
+ * ref points at a nonexistent object.
+ */
+ if (!has_sha1_file(sha1))
+ die("git show-ref: bad ref %s (%s)", refname,
+ sha1_to_hex(sha1));
+
+ if (quiet)
+ return 0;
+
+ show_one(refname, sha1);
+
+ if (!deref_tags)
+ return 0;
+
+ if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) {
+ if (!is_null_sha1(peeled)) {
+ hex = find_unique_abbrev(peeled, abbrev);
+ printf("%s %s^{}\n", hex, refname);
+ }
+ }
+ else {
+ obj = parse_object(sha1);
+ if (!obj)
+ die("git show-ref: bad ref %s (%s)", refname,
+ sha1_to_hex(sha1));
+ if (obj->type == OBJ_TAG) {
+ obj = deref_tag(obj, refname, 0);
+ if (!obj)
+ die("git show-ref: bad tag at ref %s (%s)", refname,
+ sha1_to_hex(sha1));
+ hex = find_unique_abbrev(obj->sha1, abbrev);
+ printf("%s %s^{}\n", hex, refname);
+ }
+ }
+ return 0;
+}
+
+static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+{
+ struct string_list *list = (struct string_list *)cbdata;
+ string_list_insert(refname, list);
+ return 0;
+}
+
+/*
+ * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
+ * and
+ * (1) strip "^{}" at the end of line if any;
+ * (2) ignore if match is provided and does not head-match refname;
+ * (3) warn if refname is not a well-formed refname and skip;
+ * (4) ignore if refname is a ref that exists in the local repository;
+ * (5) otherwise output the line.
+ */
+static int exclude_existing(const char *match)
+{
+ static struct string_list existing_refs = { NULL, 0, 0, 0 };
+ char buf[1024];
+ int matchlen = match ? strlen(match) : 0;
+
+ for_each_ref(add_existing, &existing_refs);
+ while (fgets(buf, sizeof(buf), stdin)) {
+ char *ref;
+ int len = strlen(buf);
+
+ if (len > 0 && buf[len - 1] == '\n')
+ buf[--len] = '\0';
+ if (3 <= len && !strcmp(buf + len - 3, "^{}")) {
+ len -= 3;
+ buf[len] = '\0';
+ }
+ for (ref = buf + len; buf < ref; ref--)
+ if (isspace(ref[-1]))
+ break;
+ if (match) {
+ int reflen = buf + len - ref;
+ if (reflen < matchlen)
+ continue;
+ if (strncmp(ref, match, matchlen))
+ continue;
+ }
+ if (check_ref_format(ref)) {
+ warning("ref '%s' ignored", ref);
+ continue;
+ }
+ if (!string_list_has_string(&existing_refs, ref)) {
+ printf("%s\n", buf);
+ }
+ }
+ return 0;
+}
+
+static int hash_callback(const struct option *opt, const char *arg, int unset)
+{
+ hash_only = 1;
+ /* Use full length SHA1 if no argument */
+ if (!arg)
+ return 0;
+ return parse_opt_abbrev_cb(opt, arg, unset);
+}
+
+static int exclude_existing_callback(const struct option *opt, const char *arg,
+ int unset)
+{
+ exclude_arg = 1;
+ *(const char **)opt->value = arg;
+ return 0;
+}
+
+static int help_callback(const struct option *opt, const char *arg, int unset)
+{
+ return -1;
+}
+
+static const struct option show_ref_options[] = {
+ OPT_BOOLEAN(0, "tags", &tags_only, "only show tags (can be combined with heads)"),
+ OPT_BOOLEAN(0, "heads", &heads_only, "only show heads (can be combined with tags)"),
+ OPT_BOOLEAN(0, "verify", &verify, "stricter reference checking, "
+ "requires exact ref path"),
+ { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL,
+ "show the HEAD reference",
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ OPT_BOOLEAN(0, "head", &show_head, "show the HEAD reference"),
+ OPT_BOOLEAN('d', "dereference", &deref_tags,
+ "dereference tags into object IDs"),
+ { OPTION_CALLBACK, 's', "hash", &abbrev, "n",
+ "only show SHA1 hash using <n> digits",
+ PARSE_OPT_OPTARG, &hash_callback },
+ OPT__ABBREV(&abbrev),
+ OPT__QUIET(&quiet),
+ { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg,
+ "pattern", "show refs from stdin that aren't in local repository",
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback },
+ { OPTION_CALLBACK, 0, "help-all", NULL, NULL, "show usage",
+ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
+ OPT_END()
+};
+
+int cmd_show_ref(int argc, const char **argv, const char *prefix)
+{
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(show_ref_usage, show_ref_options);
+
+ argc = parse_options(argc, argv, prefix, show_ref_options,
+ show_ref_usage, PARSE_OPT_NO_INTERNAL_HELP);
+
+ if (exclude_arg)
+ return exclude_existing(exclude_existing_arg);
+
+ pattern = argv;
+ if (!*pattern)
+ pattern = NULL;
+
+ if (verify) {
+ if (!pattern)
+ die("--verify requires a reference");
+ while (*pattern) {
+ unsigned char sha1[20];
+
+ if (!prefixcmp(*pattern, "refs/") &&
+ resolve_ref(*pattern, sha1, 1, NULL)) {
+ if (!quiet)
+ show_one(*pattern, sha1);
+ }
+ else if (!quiet)
+ die("'%s' - not a valid ref", *pattern);
+ else
+ return 1;
+ pattern++;
+ }
+ return 0;
+ }
+
+ if (show_head)
+ head_ref(show_ref, NULL);
+ for_each_ref(show_ref, NULL);
+ if (!found_match) {
+ if (verify && !quiet)
+ die("No match");
+ return 1;
+ }
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+
+/*
+ * Returns the length of a line, without trailing spaces.
+ *
+ * If the line ends with newline, it will be removed too.
+ */
+static size_t cleanup(char *line, size_t len)
+{
+ while (len) {
+ unsigned char c = line[len - 1];
+ if (!isspace(c))
+ break;
+ len--;
+ }
+
+ return len;
+}
+
+/*
+ * 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 does not have a newline at the end, one is added.
+ *
+ * Enable skip_comments to skip every line starting with "#".
+ */
+void stripspace(struct strbuf *sb, int skip_comments)
+{
+ int empties = 0;
+ size_t i, j, len, newlen;
+ char *eol;
+
+ /* We may have to add a newline. */
+ strbuf_grow(sb, 1);
+
+ for (i = j = 0; i < sb->len; i += len, j += newlen) {
+ eol = memchr(sb->buf + i, '\n', sb->len - i);
+ len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
+
+ if (skip_comments && len && sb->buf[i] == '#') {
+ newlen = 0;
+ continue;
+ }
+ newlen = cleanup(sb->buf + i, len);
+
+ /* Not just an empty line? */
+ if (newlen) {
+ if (empties > 0 && j > 0)
+ sb->buf[j++] = '\n';
+ empties = 0;
+ memmove(sb->buf + j, sb->buf + i, newlen);
+ sb->buf[newlen + j++] = '\n';
+ } else {
+ empties++;
+ }
+ }
+
+ strbuf_setlen(sb, j);
+}
+
+int cmd_stripspace(int argc, const char **argv, const char *prefix)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int strip_comments = 0;
+
+ if (argc == 2 && (!strcmp(argv[1], "-s") ||
+ !strcmp(argv[1], "--strip-comments")))
+ strip_comments = 1;
+ else if (argc > 1)
+ usage("git stripspace [-s | --strip-comments] < <stream>");
+
+ if (strbuf_read(&buf, 0, 1024) < 0)
+ die_errno("could not read the input");
+
+ stripspace(&buf, strip_comments);
+
+ write_or_die(1, buf.buf, buf.len);
+ strbuf_release(&buf);
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "refs.h"
+#include "parse-options.h"
+
+static const char * const git_symbolic_ref_usage[] = {
+ "git symbolic-ref [options] name [ref]",
+ NULL
+};
+
+static void check_symref(const char *HEAD, int quiet)
+{
+ unsigned char sha1[20];
+ int flag;
+ const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
+
+ if (!refs_heads_master)
+ die("No such ref: %s", HEAD);
+ else if (!(flag & REF_ISSYMREF)) {
+ if (!quiet)
+ die("ref %s is not a symbolic ref", HEAD);
+ else
+ exit(1);
+ }
+ puts(refs_heads_master);
+}
+
+int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
+{
+ int quiet = 0;
+ const char *msg = NULL;
+ struct option options[] = {
+ OPT__QUIET(&quiet),
+ OPT_STRING('m', NULL, &msg, "reason", "reason of the update"),
+ OPT_END(),
+ };
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, options,
+ git_symbolic_ref_usage, 0);
+ if (msg &&!*msg)
+ die("Refusing to perform update with empty message");
+ switch (argc) {
+ case 1:
+ check_symref(argv[0], quiet);
+ break;
+ case 2:
+ if (!strcmp(argv[0], "HEAD") &&
+ prefixcmp(argv[1], "refs/"))
+ die("Refusing to point HEAD outside of refs/");
+ create_symref(argv[0], argv[1], msg);
+ break;
+ default:
+ usage_with_options(git_symbolic_ref_usage, options);
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * Builtin "git tag"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
+ * Carlos Rica <jasampler@gmail.com>
+ * Based on git-tag.sh and mktag.c by Linus Torvalds.
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "refs.h"
+#include "tag.h"
+#include "run-command.h"
+#include "parse-options.h"
+
+static const char * const git_tag_usage[] = {
+ "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
+ "git tag -d <tagname>...",
+ "git tag -l [-n[<num>]] [<pattern>]",
+ "git tag -v <tagname>...",
+ NULL
+};
+
+static char signingkey[1000];
+
+struct tag_filter {
+ const char *pattern;
+ int lines;
+ struct commit_list *with_commit;
+};
+
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+
+static int show_reference(const char *refname, const unsigned char *sha1,
+ int flag, void *cb_data)
+{
+ struct tag_filter *filter = cb_data;
+
+ if (!fnmatch(filter->pattern, refname, 0)) {
+ int i;
+ unsigned long size;
+ enum object_type type;
+ char *buf, *sp, *eol;
+ size_t len;
+
+ if (filter->with_commit) {
+ struct commit *commit;
+
+ commit = lookup_commit_reference_gently(sha1, 1);
+ if (!commit)
+ return 0;
+ if (!is_descendant_of(commit, filter->with_commit))
+ return 0;
+ }
+
+ if (!filter->lines) {
+ printf("%s\n", refname);
+ return 0;
+ }
+ printf("%-15s ", refname);
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf || !size)
+ return 0;
+
+ /* skip header */
+ sp = strstr(buf, "\n\n");
+ if (!sp) {
+ free(buf);
+ return 0;
+ }
+ /* only take up to "lines" lines, and strip the signature */
+ for (i = 0, sp += 2;
+ i < filter->lines && sp < buf + size &&
+ prefixcmp(sp, PGP_SIGNATURE "\n");
+ i++) {
+ if (i)
+ printf("\n ");
+ eol = memchr(sp, '\n', size - (sp - buf));
+ len = eol ? eol - sp : size - (sp - buf);
+ fwrite(sp, len, 1, stdout);
+ if (!eol)
+ break;
+ sp = eol + 1;
+ }
+ putchar('\n');
+ free(buf);
+ }
+
+ return 0;
+}
+
+static int list_tags(const char *pattern, int lines,
+ struct commit_list *with_commit)
+{
+ struct tag_filter filter;
+
+ if (pattern == NULL)
+ pattern = "*";
+
+ filter.pattern = pattern;
+ filter.lines = lines;
+ filter.with_commit = with_commit;
+
+ for_each_tag_ref(show_reference, (void *) &filter);
+
+ return 0;
+}
+
+typedef int (*each_tag_name_fn)(const char *name, const char *ref,
+ const unsigned char *sha1);
+
+static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
+{
+ const char **p;
+ char ref[PATH_MAX];
+ int had_error = 0;
+ unsigned char sha1[20];
+
+ for (p = argv; *p; p++) {
+ if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p)
+ >= sizeof(ref)) {
+ error("tag name too long: %.*s...", 50, *p);
+ had_error = 1;
+ continue;
+ }
+ if (!resolve_ref(ref, sha1, 1, NULL)) {
+ error("tag '%s' not found.", *p);
+ had_error = 1;
+ continue;
+ }
+ if (fn(*p, ref, sha1))
+ had_error = 1;
+ }
+ return had_error;
+}
+
+static int delete_tag(const char *name, const char *ref,
+ const unsigned char *sha1)
+{
+ if (delete_ref(ref, sha1, 0))
+ return 1;
+ printf("Deleted tag '%s' (was %s)\n", name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ return 0;
+}
+
+static int verify_tag(const char *name, const char *ref,
+ const unsigned char *sha1)
+{
+ const char *argv_verify_tag[] = {"git-verify-tag",
+ "-v", "SHA1_HEX", NULL};
+ argv_verify_tag[2] = sha1_to_hex(sha1);
+
+ if (run_command_v_opt(argv_verify_tag, 0))
+ return error("could not verify the tag '%s'", name);
+ return 0;
+}
+
+static int do_sign(struct strbuf *buffer)
+{
+ struct child_process gpg;
+ const char *args[4];
+ char *bracket;
+ int len;
+ int i, j;
+
+ if (!*signingkey) {
+ if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
+ sizeof(signingkey)) > sizeof(signingkey) - 1)
+ return error("committer info too long.");
+ bracket = strchr(signingkey, '>');
+ if (bracket)
+ bracket[1] = '\0';
+ }
+
+ /* When the username signingkey is bad, program could be terminated
+ * because gpg exits without reading and then write gets SIGPIPE. */
+ signal(SIGPIPE, SIG_IGN);
+
+ memset(&gpg, 0, sizeof(gpg));
+ gpg.argv = args;
+ gpg.in = -1;
+ gpg.out = -1;
+ args[0] = "gpg";
+ args[1] = "-bsau";
+ args[2] = signingkey;
+ args[3] = NULL;
+
+ if (start_command(&gpg))
+ return error("could not run gpg.");
+
+ if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
+ close(gpg.in);
+ close(gpg.out);
+ finish_command(&gpg);
+ return error("gpg did not accept the tag data");
+ }
+ close(gpg.in);
+ len = strbuf_read(buffer, gpg.out, 1024);
+ close(gpg.out);
+
+ if (finish_command(&gpg) || !len || len < 0)
+ return error("gpg failed to sign the tag");
+
+ /* Strip CR from the line endings, in case we are on Windows. */
+ for (i = j = 0; i < buffer->len; i++)
+ if (buffer->buf[i] != '\r') {
+ if (i != j)
+ buffer->buf[j] = buffer->buf[i];
+ j++;
+ }
+ strbuf_setlen(buffer, j);
+
+ return 0;
+}
+
+static const char tag_template[] =
+ "\n"
+ "#\n"
+ "# Write a tag message\n"
+ "#\n";
+
+static void set_signingkey(const char *value)
+{
+ if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))
+ die("signing key value too long (%.10s...)", value);
+}
+
+static int git_tag_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "user.signingkey")) {
+ if (!value)
+ return config_error_nonbool(var);
+ set_signingkey(value);
+ return 0;
+ }
+
+ return git_default_config(var, value, cb);
+}
+
+static void write_tag_body(int fd, const unsigned char *sha1)
+{
+ unsigned long size;
+ enum object_type type;
+ char *buf, *sp, *eob;
+ size_t len;
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ return;
+ /* skip header */
+ sp = strstr(buf, "\n\n");
+
+ if (!sp || !size || type != OBJ_TAG) {
+ free(buf);
+ return;
+ }
+ sp += 2; /* skip the 2 LFs */
+ eob = strstr(sp, "\n" PGP_SIGNATURE "\n");
+ if (eob)
+ len = eob - sp;
+ else
+ len = buf + size - sp;
+ write_or_die(fd, sp, len);
+
+ free(buf);
+}
+
+static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
+{
+ if (sign && do_sign(buf) < 0)
+ return error("unable to sign the tag");
+ if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0)
+ return error("unable to write tag file");
+ return 0;
+}
+
+static void create_tag(const unsigned char *object, const char *tag,
+ struct strbuf *buf, int message, int sign,
+ unsigned char *prev, unsigned char *result)
+{
+ enum object_type type;
+ char header_buf[1024];
+ int header_len;
+ char *path = NULL;
+
+ type = sha1_object_info(object, NULL);
+ if (type <= OBJ_NONE)
+ die("bad object type.");
+
+ header_len = snprintf(header_buf, sizeof(header_buf),
+ "object %s\n"
+ "type %s\n"
+ "tag %s\n"
+ "tagger %s\n\n",
+ sha1_to_hex(object),
+ typename(type),
+ tag,
+ git_committer_info(IDENT_ERROR_ON_NO_NAME));
+
+ if (header_len > sizeof(header_buf) - 1)
+ die("tag header too big.");
+
+ if (!message) {
+ int fd;
+
+ /* write the template message before editing: */
+ path = git_pathdup("TAG_EDITMSG");
+ fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+ if (fd < 0)
+ die_errno("could not create file '%s'", path);
+
+ if (!is_null_sha1(prev))
+ write_tag_body(fd, prev);
+ else
+ write_or_die(fd, tag_template, strlen(tag_template));
+ close(fd);
+
+ if (launch_editor(path, buf, NULL)) {
+ fprintf(stderr,
+ "Please supply the message using either -m or -F option.\n");
+ exit(1);
+ }
+ }
+
+ stripspace(buf, 1);
+
+ if (!message && !buf->len)
+ die("no tag message?");
+
+ strbuf_insert(buf, 0, header_buf, header_len);
+
+ if (build_tag_object(buf, sign, result) < 0) {
+ if (path)
+ fprintf(stderr, "The tag message has been left in %s\n",
+ path);
+ exit(128);
+ }
+ if (path) {
+ unlink_or_warn(path);
+ free(path);
+ }
+}
+
+struct msg_arg {
+ int given;
+ struct strbuf buf;
+};
+
+static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
+{
+ struct msg_arg *msg = opt->value;
+
+ if (!arg)
+ return -1;
+ if (msg->buf.len)
+ strbuf_addstr(&(msg->buf), "\n\n");
+ strbuf_addstr(&(msg->buf), arg);
+ msg->given = 1;
+ return 0;
+}
+
+int cmd_tag(int argc, const char **argv, const char *prefix)
+{
+ struct strbuf buf = STRBUF_INIT;
+ unsigned char object[20], prev[20];
+ char ref[PATH_MAX];
+ const char *object_ref, *tag;
+ struct ref_lock *lock;
+
+ int annotate = 0, sign = 0, force = 0, lines = -1,
+ list = 0, delete = 0, verify = 0;
+ const char *msgfile = NULL, *keyid = NULL;
+ struct msg_arg msg = { 0, STRBUF_INIT };
+ struct commit_list *with_commit = NULL;
+ struct option options[] = {
+ OPT_BOOLEAN('l', NULL, &list, "list tag names"),
+ { OPTION_INTEGER, 'n', NULL, &lines, "n",
+ "print <n> lines of each tag message",
+ PARSE_OPT_OPTARG, NULL, 1 },
+ OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
+ OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
+
+ OPT_GROUP("Tag creation options"),
+ OPT_BOOLEAN('a', NULL, &annotate,
+ "annotated tag, needs a message"),
+ OPT_CALLBACK('m', NULL, &msg, "msg",
+ "message for the tag", parse_msg_arg),
+ OPT_FILENAME('F', NULL, &msgfile, "message in a file"),
+ OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
+ OPT_STRING('u', NULL, &keyid, "key-id",
+ "use another key to sign the tag"),
+ OPT_BOOLEAN('f', "force", &force, "replace the tag if exists"),
+
+ OPT_GROUP("Tag listing options"),
+ {
+ OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
+ "print only tags that contain the commit",
+ PARSE_OPT_LASTARG_DEFAULT,
+ parse_opt_with_commit, (intptr_t)"HEAD",
+ },
+ OPT_END()
+ };
+
+ git_config(git_tag_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
+
+ if (keyid) {
+ sign = 1;
+ set_signingkey(keyid);
+ }
+ if (sign)
+ annotate = 1;
+ if (argc == 0 && !(delete || verify))
+ list = 1;
+
+ if ((annotate || msg.given || msgfile || force) &&
+ (list || delete || verify))
+ usage_with_options(git_tag_usage, options);
+
+ if (list + delete + verify > 1)
+ usage_with_options(git_tag_usage, options);
+ if (list)
+ return list_tags(argv[0], lines == -1 ? 0 : lines,
+ with_commit);
+ if (lines != -1)
+ die("-n option is only allowed with -l.");
+ if (with_commit)
+ die("--contains option is only allowed with -l.");
+ if (delete)
+ return for_each_tag_name(argv, delete_tag);
+ if (verify)
+ return for_each_tag_name(argv, verify_tag);
+
+ if (msg.given || msgfile) {
+ if (msg.given && msgfile)
+ die("only one -F or -m option is allowed.");
+ annotate = 1;
+ if (msg.given)
+ strbuf_addbuf(&buf, &(msg.buf));
+ else {
+ if (!strcmp(msgfile, "-")) {
+ if (strbuf_read(&buf, 0, 1024) < 0)
+ die_errno("cannot read '%s'", msgfile);
+ } else {
+ if (strbuf_read_file(&buf, msgfile, 1024) < 0)
+ die_errno("could not open or read '%s'",
+ msgfile);
+ }
+ }
+ }
+
+ tag = argv[0];
+
+ object_ref = argc == 2 ? argv[1] : "HEAD";
+ if (argc > 2)
+ die("too many params");
+
+ if (get_sha1(object_ref, object))
+ die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+ if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1)
+ die("tag name too long: %.*s...", 50, tag);
+ if (check_ref_format(ref))
+ die("'%s' is not a valid tag name.", tag);
+
+ if (!resolve_ref(ref, prev, 1, NULL))
+ hashclr(prev);
+ else if (!force)
+ die("tag '%s' already exists", tag);
+
+ if (annotate)
+ create_tag(object, tag, &buf, msg.given || msgfile,
+ sign, prev, object);
+
+ lock = lock_any_ref_for_update(ref, prev, 0);
+ if (!lock)
+ die("%s: cannot lock the ref", ref);
+ if (write_ref_sha1(lock, object, NULL) < 0)
+ die("%s: cannot update the ref", ref);
+ if (force && hashcmp(prev, object))
+ printf("Updated tag '%s' (was %s)\n", tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
+
+ strbuf_release(&buf);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (c) 2005, 2006 Rene Scharfe
+ */
+#include "cache.h"
+#include "commit.h"
+#include "tar.h"
+#include "builtin.h"
+#include "quote.h"
+
+static const char tar_tree_usage[] =
+"git tar-tree [--remote=<repo>] <tree-ish> [basedir]\n"
+"*** Note that this command is now deprecated; use \"git archive\" instead.";
+
+static const char builtin_get_tar_commit_id_usage[] =
+"git get-tar-commit-id < <tarfile>";
+
+int cmd_tar_tree(int argc, const char **argv, const char *prefix)
+{
+ /*
+ * "git tar-tree" is now a wrapper around "git archive --format=tar"
+ *
+ * $0 --remote=<repo> arg... ==>
+ * git archive --format=tar --remote=<repo> arg...
+ * $0 tree-ish ==>
+ * git archive --format=tar tree-ish
+ * $0 tree-ish basedir ==>
+ * git archive --format-tar --prefix=basedir tree-ish
+ */
+ int i;
+ const char **nargv = xcalloc(sizeof(*nargv), argc + 3);
+ char *basedir_arg;
+ int nargc = 0;
+
+ nargv[nargc++] = "archive";
+ nargv[nargc++] = "--format=tar";
+
+ if (2 <= argc && !prefixcmp(argv[1], "--remote=")) {
+ nargv[nargc++] = argv[1];
+ argv++;
+ argc--;
+ }
+
+ /*
+ * Because it's just a compatibility wrapper, tar-tree supports only
+ * the old behaviour of reading attributes from the work tree.
+ */
+ nargv[nargc++] = "--worktree-attributes";
+
+ switch (argc) {
+ default:
+ usage(tar_tree_usage);
+ break;
+ case 3:
+ /* base-path */
+ basedir_arg = xmalloc(strlen(argv[2]) + 11);
+ sprintf(basedir_arg, "--prefix=%s/", argv[2]);
+ nargv[nargc++] = basedir_arg;
+ /* fallthru */
+ case 2:
+ /* tree-ish */
+ nargv[nargc++] = argv[1];
+ }
+ nargv[nargc] = NULL;
+
+ fprintf(stderr,
+ "*** \"git tar-tree\" is now deprecated.\n"
+ "*** Running \"git archive\" instead.\n***");
+ for (i = 0; i < nargc; i++) {
+ fputc(' ', stderr);
+ sq_quote_print(stderr, nargv[i]);
+ }
+ fputc('\n', stderr);
+ return cmd_archive(nargc, nargv, prefix);
+}
+
+/* ustar header + extended global header content */
+#define RECORDSIZE (512)
+#define HEADERSIZE (2 * RECORDSIZE)
+
+int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
+{
+ char buffer[HEADERSIZE];
+ struct ustar_header *header = (struct ustar_header *)buffer;
+ char *content = buffer + RECORDSIZE;
+ ssize_t n;
+
+ if (argc != 1)
+ usage(builtin_get_tar_commit_id_usage);
+
+ n = read_in_full(0, buffer, HEADERSIZE);
+ if (n < HEADERSIZE)
+ die("git get-tar-commit-id: read error");
+ if (header->typeflag[0] != 'g')
+ return 1;
+ if (memcmp(content, "52 comment=", 11))
+ return 1;
+
+ n = write_in_full(1, content + 11, 41);
+ if (n < 41)
+ die_errno("git get-tar-commit-id: write error");
+
+ return 0;
+}
--- /dev/null
+#include "cache.h"
+#include "blob.h"
+#include "exec_cmd.h"
+
+static char *create_temp_file(unsigned char *sha1)
+{
+ static char path[50];
+ void *buf;
+ enum object_type type;
+ unsigned long size;
+ int fd;
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf || type != OBJ_BLOB)
+ die("unable to read blob object %s", sha1_to_hex(sha1));
+
+ strcpy(path, ".merge_file_XXXXXX");
+ fd = xmkstemp(path);
+ if (write_in_full(fd, buf, size) != size)
+ die_errno("unable to write temp-file");
+ close(fd);
+ return path;
+}
+
+int cmd_unpack_file(int argc, const char **argv, const char *prefix)
+{
+ unsigned char sha1[20];
+
+ if (argc != 2 || !strcmp(argv[1], "-h"))
+ usage("git unpack-file <sha1>");
+ if (get_sha1(argv[1], sha1))
+ die("Not a valid object name %s", argv[1]);
+
+ git_config(git_default_config, NULL);
+
+ puts(create_temp_file(sha1));
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "object.h"
+#include "delta.h"
+#include "pack.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "progress.h"
+#include "decorate.h"
+#include "fsck.h"
+
+static int dry_run, quiet, recover, has_errors, strict;
+static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict] < pack-file";
+
+/* We always read in 4kB chunks. */
+static unsigned char buffer[4096];
+static unsigned int offset, len;
+static off_t consumed_bytes;
+static git_SHA_CTX ctx;
+
+/*
+ * When running under --strict mode, objects whose reachability are
+ * suspect are kept in core without getting written in the object
+ * store.
+ */
+struct obj_buffer {
+ char *buffer;
+ unsigned long size;
+};
+
+static struct decoration obj_decorate;
+
+static struct obj_buffer *lookup_object_buffer(struct object *base)
+{
+ return lookup_decoration(&obj_decorate, base);
+}
+
+static void add_object_buffer(struct object *object, char *buffer, unsigned long size)
+{
+ struct obj_buffer *obj;
+ obj = xcalloc(1, sizeof(struct obj_buffer));
+ obj->buffer = buffer;
+ obj->size = size;
+ if (add_decoration(&obj_decorate, object, obj))
+ die("object %s tried to add buffer twice!", sha1_to_hex(object->sha1));
+}
+
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void *fill(int min)
+{
+ if (min <= len)
+ return buffer + offset;
+ if (min > sizeof(buffer))
+ die("cannot fill %d bytes", min);
+ if (offset) {
+ git_SHA1_Update(&ctx, buffer, offset);
+ memmove(buffer, buffer + offset, len);
+ offset = 0;
+ }
+ do {
+ ssize_t ret = xread(0, buffer + len, sizeof(buffer) - len);
+ if (ret <= 0) {
+ if (!ret)
+ die("early EOF");
+ die_errno("read error on input");
+ }
+ len += ret;
+ } while (len < min);
+ return buffer;
+}
+
+static void use(int bytes)
+{
+ if (bytes > len)
+ die("used more bytes than were available");
+ len -= bytes;
+ offset += bytes;
+
+ /* make sure off_t is sufficiently large not to wrap */
+ if (consumed_bytes > consumed_bytes + bytes)
+ die("pack too large for current definition of off_t");
+ consumed_bytes += bytes;
+}
+
+static void *get_data(unsigned long size)
+{
+ z_stream stream;
+ void *buf = xmalloc(size);
+
+ memset(&stream, 0, sizeof(stream));
+
+ stream.next_out = buf;
+ stream.avail_out = size;
+ stream.next_in = fill(1);
+ stream.avail_in = len;
+ git_inflate_init(&stream);
+
+ for (;;) {
+ int ret = git_inflate(&stream, 0);
+ use(len - stream.avail_in);
+ if (stream.total_out == size && ret == Z_STREAM_END)
+ break;
+ if (ret != Z_OK) {
+ error("inflate returned %d\n", ret);
+ free(buf);
+ buf = NULL;
+ if (!recover)
+ exit(1);
+ has_errors = 1;
+ break;
+ }
+ stream.next_in = fill(1);
+ stream.avail_in = len;
+ }
+ git_inflate_end(&stream);
+ return buf;
+}
+
+struct delta_info {
+ unsigned char base_sha1[20];
+ unsigned nr;
+ off_t base_offset;
+ unsigned long size;
+ void *delta;
+ struct delta_info *next;
+};
+
+static struct delta_info *delta_list;
+
+static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
+ off_t base_offset,
+ void *delta, unsigned long size)
+{
+ struct delta_info *info = xmalloc(sizeof(*info));
+
+ hashcpy(info->base_sha1, base_sha1);
+ info->base_offset = base_offset;
+ info->size = size;
+ info->delta = delta;
+ info->nr = nr;
+ info->next = delta_list;
+ delta_list = info;
+}
+
+struct obj_info {
+ off_t offset;
+ unsigned char sha1[20];
+ struct object *obj;
+};
+
+#define FLAG_OPEN (1u<<20)
+#define FLAG_WRITTEN (1u<<21)
+
+static struct obj_info *obj_list;
+static unsigned nr_objects;
+
+/*
+ * Called only from check_object() after it verified this object
+ * is Ok.
+ */
+static void write_cached_object(struct object *obj)
+{
+ unsigned char sha1[20];
+ struct obj_buffer *obj_buf = lookup_object_buffer(obj);
+ if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0)
+ die("failed to write object %s", sha1_to_hex(obj->sha1));
+ obj->flags |= FLAG_WRITTEN;
+}
+
+/*
+ * At the very end of the processing, write_rest() scans the objects
+ * that have reachability requirements and calls this function.
+ * Verify its reachability and validity recursively and write it out.
+ */
+static int check_object(struct object *obj, int type, void *data)
+{
+ if (!obj)
+ return 1;
+
+ if (obj->flags & FLAG_WRITTEN)
+ return 0;
+
+ if (type != OBJ_ANY && obj->type != type)
+ die("object type mismatch");
+
+ if (!(obj->flags & FLAG_OPEN)) {
+ unsigned long size;
+ int type = sha1_object_info(obj->sha1, &size);
+ if (type != obj->type || type <= 0)
+ die("object of unexpected type");
+ obj->flags |= FLAG_WRITTEN;
+ return 0;
+ }
+
+ if (fsck_object(obj, 1, fsck_error_function))
+ die("Error in object");
+ if (fsck_walk(obj, check_object, NULL))
+ die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
+ write_cached_object(obj);
+ return 0;
+}
+
+static void write_rest(void)
+{
+ unsigned i;
+ for (i = 0; i < nr_objects; i++) {
+ if (obj_list[i].obj)
+ check_object(obj_list[i].obj, OBJ_ANY, NULL);
+ }
+}
+
+static void added_object(unsigned nr, enum object_type type,
+ void *data, unsigned long size);
+
+/*
+ * Write out nr-th object from the list, now we know the contents
+ * of it. Under --strict, this buffers structured objects in-core,
+ * to be checked at the end.
+ */
+static void write_object(unsigned nr, enum object_type type,
+ void *buf, unsigned long size)
+{
+ if (!strict) {
+ if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
+ die("failed to write object");
+ added_object(nr, type, buf, size);
+ free(buf);
+ obj_list[nr].obj = NULL;
+ } else if (type == OBJ_BLOB) {
+ struct blob *blob;
+ if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
+ die("failed to write object");
+ added_object(nr, type, buf, size);
+ free(buf);
+
+ blob = lookup_blob(obj_list[nr].sha1);
+ if (blob)
+ blob->object.flags |= FLAG_WRITTEN;
+ else
+ die("invalid blob object");
+ obj_list[nr].obj = NULL;
+ } else {
+ struct object *obj;
+ int eaten;
+ hash_sha1_file(buf, size, typename(type), obj_list[nr].sha1);
+ added_object(nr, type, buf, size);
+ obj = parse_object_buffer(obj_list[nr].sha1, type, size, buf, &eaten);
+ if (!obj)
+ die("invalid %s", typename(type));
+ add_object_buffer(obj, buf, size);
+ obj->flags |= FLAG_OPEN;
+ obj_list[nr].obj = obj;
+ }
+}
+
+static void resolve_delta(unsigned nr, enum object_type type,
+ void *base, unsigned long base_size,
+ void *delta, unsigned long delta_size)
+{
+ void *result;
+ unsigned long result_size;
+
+ result = patch_delta(base, base_size,
+ delta, delta_size,
+ &result_size);
+ if (!result)
+ die("failed to apply delta");
+ free(delta);
+ write_object(nr, type, result, result_size);
+}
+
+/*
+ * We now know the contents of an object (which is nr-th in the pack);
+ * resolve all the deltified objects that are based on it.
+ */
+static void added_object(unsigned nr, enum object_type type,
+ void *data, unsigned long size)
+{
+ struct delta_info **p = &delta_list;
+ struct delta_info *info;
+
+ while ((info = *p) != NULL) {
+ if (!hashcmp(info->base_sha1, obj_list[nr].sha1) ||
+ info->base_offset == obj_list[nr].offset) {
+ *p = info->next;
+ p = &delta_list;
+ resolve_delta(info->nr, type, data, size,
+ info->delta, info->size);
+ free(info);
+ continue;
+ }
+ p = &info->next;
+ }
+}
+
+static void unpack_non_delta_entry(enum object_type type, unsigned long size,
+ unsigned nr)
+{
+ void *buf = get_data(size);
+
+ if (!dry_run && buf)
+ write_object(nr, type, buf, size);
+ else
+ free(buf);
+}
+
+static int resolve_against_held(unsigned nr, const unsigned char *base,
+ void *delta_data, unsigned long delta_size)
+{
+ struct object *obj;
+ struct obj_buffer *obj_buffer;
+ obj = lookup_object(base);
+ if (!obj)
+ return 0;
+ obj_buffer = lookup_object_buffer(obj);
+ if (!obj_buffer)
+ return 0;
+ resolve_delta(nr, obj->type, obj_buffer->buffer,
+ obj_buffer->size, delta_data, delta_size);
+ return 1;
+}
+
+static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
+ unsigned nr)
+{
+ void *delta_data, *base;
+ unsigned long base_size;
+ unsigned char base_sha1[20];
+
+ if (type == OBJ_REF_DELTA) {
+ hashcpy(base_sha1, fill(20));
+ use(20);
+ delta_data = get_data(delta_size);
+ if (dry_run || !delta_data) {
+ free(delta_data);
+ return;
+ }
+ if (has_sha1_file(base_sha1))
+ ; /* Ok we have this one */
+ else if (resolve_against_held(nr, base_sha1,
+ delta_data, delta_size))
+ return; /* we are done */
+ else {
+ /* cannot resolve yet --- queue it */
+ hashcpy(obj_list[nr].sha1, null_sha1);
+ add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size);
+ return;
+ }
+ } else {
+ unsigned base_found = 0;
+ unsigned char *pack, c;
+ off_t base_offset;
+ unsigned lo, mid, hi;
+
+ pack = fill(1);
+ c = *pack;
+ use(1);
+ base_offset = c & 127;
+ while (c & 128) {
+ base_offset += 1;
+ if (!base_offset || MSB(base_offset, 7))
+ die("offset value overflow for delta base object");
+ pack = fill(1);
+ c = *pack;
+ use(1);
+ base_offset = (base_offset << 7) + (c & 127);
+ }
+ base_offset = obj_list[nr].offset - base_offset;
+ if (base_offset <= 0 || base_offset >= obj_list[nr].offset)
+ die("offset value out of bound for delta base object");
+
+ delta_data = get_data(delta_size);
+ if (dry_run || !delta_data) {
+ free(delta_data);
+ return;
+ }
+ lo = 0;
+ hi = nr;
+ while (lo < hi) {
+ mid = (lo + hi)/2;
+ if (base_offset < obj_list[mid].offset) {
+ hi = mid;
+ } else if (base_offset > obj_list[mid].offset) {
+ lo = mid + 1;
+ } else {
+ hashcpy(base_sha1, obj_list[mid].sha1);
+ base_found = !is_null_sha1(base_sha1);
+ break;
+ }
+ }
+ if (!base_found) {
+ /*
+ * The delta base object is itself a delta that
+ * has not been resolved yet.
+ */
+ hashcpy(obj_list[nr].sha1, null_sha1);
+ add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size);
+ return;
+ }
+ }
+
+ if (resolve_against_held(nr, base_sha1, delta_data, delta_size))
+ return;
+
+ base = read_sha1_file(base_sha1, &type, &base_size);
+ if (!base) {
+ error("failed to read delta-pack base object %s",
+ sha1_to_hex(base_sha1));
+ if (!recover)
+ exit(1);
+ has_errors = 1;
+ return;
+ }
+ resolve_delta(nr, type, base, base_size, delta_data, delta_size);
+ free(base);
+}
+
+static void unpack_one(unsigned nr)
+{
+ unsigned shift;
+ unsigned char *pack;
+ unsigned long size, c;
+ enum object_type type;
+
+ obj_list[nr].offset = consumed_bytes;
+
+ pack = fill(1);
+ c = *pack;
+ use(1);
+ type = (c >> 4) & 7;
+ size = (c & 15);
+ shift = 4;
+ while (c & 0x80) {
+ pack = fill(1);
+ c = *pack;
+ use(1);
+ size += (c & 0x7f) << shift;
+ shift += 7;
+ }
+
+ switch (type) {
+ case OBJ_COMMIT:
+ case OBJ_TREE:
+ case OBJ_BLOB:
+ case OBJ_TAG:
+ unpack_non_delta_entry(type, size, nr);
+ return;
+ case OBJ_REF_DELTA:
+ case OBJ_OFS_DELTA:
+ unpack_delta_entry(type, size, nr);
+ return;
+ default:
+ error("bad object type %d", type);
+ has_errors = 1;
+ if (recover)
+ return;
+ exit(1);
+ }
+}
+
+static void unpack_all(void)
+{
+ int i;
+ struct progress *progress = NULL;
+ struct pack_header *hdr = fill(sizeof(struct pack_header));
+
+ nr_objects = ntohl(hdr->hdr_entries);
+
+ if (ntohl(hdr->hdr_signature) != PACK_SIGNATURE)
+ die("bad pack file");
+ if (!pack_version_ok(hdr->hdr_version))
+ die("unknown pack file version %"PRIu32,
+ ntohl(hdr->hdr_version));
+ use(sizeof(struct pack_header));
+
+ if (!quiet)
+ progress = start_progress("Unpacking objects", nr_objects);
+ obj_list = xcalloc(nr_objects, sizeof(*obj_list));
+ for (i = 0; i < nr_objects; i++) {
+ unpack_one(i);
+ display_progress(progress, i + 1);
+ }
+ stop_progress(&progress);
+
+ if (delta_list)
+ die("unresolved deltas left after unpacking");
+}
+
+int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ unsigned char sha1[20];
+
+ read_replace_refs = 0;
+
+ git_config(git_default_config, NULL);
+
+ quiet = !isatty(2);
+
+ for (i = 1 ; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!strcmp(arg, "-n")) {
+ dry_run = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-q")) {
+ quiet = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-r")) {
+ recover = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--strict")) {
+ strict = 1;
+ continue;
+ }
+ if (!prefixcmp(arg, "--pack_header=")) {
+ struct pack_header *hdr;
+ char *c;
+
+ hdr = (struct pack_header *)buffer;
+ hdr->hdr_signature = htonl(PACK_SIGNATURE);
+ hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+ if (*c != ',')
+ die("bad %s", arg);
+ hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+ if (*c)
+ die("bad %s", arg);
+ len = sizeof(*hdr);
+ continue;
+ }
+ usage(unpack_usage);
+ }
+
+ /* We don't take any non-flag arguments now.. Maybe some day */
+ usage(unpack_usage);
+ }
+ git_SHA1_Init(&ctx);
+ unpack_all();
+ git_SHA1_Update(&ctx, buffer, offset);
+ git_SHA1_Final(sha1, &ctx);
+ if (strict)
+ write_rest();
+ if (hashcmp(fill(20), sha1))
+ die("final sha1 did not match");
+ use(20);
+
+ /* Write the last part of the buffer to stdout */
+ while (len) {
+ int ret = xwrite(1, buffer + offset, len);
+ if (ret <= 0)
+ break;
+ len -= ret;
+ offset += ret;
+ }
+
+ /* All done */
+ return has_errors;
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "quote.h"
+#include "cache-tree.h"
+#include "tree-walk.h"
+#include "builtin.h"
+#include "refs.h"
+#include "resolve-undo.h"
+
+/*
+ * Default to not allowing changes to the list of files. The
+ * tool doesn't actually care, but this makes it harder to add
+ * files to the revision control by mistake by doing something
+ * like "git update-index *" and suddenly having all the object
+ * files be revision controlled.
+ */
+static int allow_add;
+static int allow_remove;
+static int allow_replace;
+static int info_only;
+static int force_remove;
+static int verbose;
+static int mark_valid_only;
+static int mark_skip_worktree_only;
+#define MARK_FLAG 1
+#define UNMARK_FLAG 2
+
+__attribute__((format (printf, 1, 2)))
+static void report(const char *fmt, ...)
+{
+ va_list vp;
+
+ if (!verbose)
+ return;
+
+ va_start(vp, fmt);
+ vprintf(fmt, vp);
+ putchar('\n');
+ va_end(vp);
+}
+
+static int mark_ce_flags(const char *path, int flag, int mark)
+{
+ int namelen = strlen(path);
+ int pos = cache_name_pos(path, namelen);
+ if (0 <= pos) {
+ if (mark)
+ active_cache[pos]->ce_flags |= flag;
+ else
+ active_cache[pos]->ce_flags &= ~flag;
+ cache_tree_invalidate_path(active_cache_tree, path);
+ active_cache_changed = 1;
+ return 0;
+ }
+ return -1;
+}
+
+static int remove_one_path(const char *path)
+{
+ if (!allow_remove)
+ return error("%s: does not exist and --remove not passed", path);
+ if (remove_file_from_cache(path))
+ return error("%s: cannot remove from the index", path);
+ return 0;
+}
+
+/*
+ * Handle a path that couldn't be lstat'ed. It's either:
+ * - missing file (ENOENT or ENOTDIR). That's ok if we're
+ * supposed to be removing it and the removal actually
+ * succeeds.
+ * - permission error. That's never ok.
+ */
+static int process_lstat_error(const char *path, int err)
+{
+ if (err == ENOENT || err == ENOTDIR)
+ return remove_one_path(path);
+ return error("lstat(\"%s\"): %s", path, strerror(errno));
+}
+
+static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
+{
+ int option, size;
+ struct cache_entry *ce;
+
+ /* Was the old index entry already up-to-date? */
+ if (old && !ce_stage(old) && !ce_match_stat(old, st, 0))
+ return 0;
+
+ size = cache_entry_size(len);
+ ce = xcalloc(1, size);
+ memcpy(ce->name, path, len);
+ ce->ce_flags = len;
+ fill_stat_cache_info(ce, st);
+ ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
+
+ if (index_path(ce->sha1, path, st, !info_only))
+ return -1;
+ option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+ option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+ if (add_cache_entry(ce, option))
+ return error("%s: cannot add to the index - missing --add option?", path);
+ return 0;
+}
+
+/*
+ * Handle a path that was a directory. Four cases:
+ *
+ * - it's already a gitlink in the index, and we keep it that
+ * way, and update it if we can (if we cannot find the HEAD,
+ * we're going to keep it unchanged in the index!)
+ *
+ * - it's a *file* in the index, in which case it should be
+ * removed as a file if removal is allowed, since it doesn't
+ * exist as such any more. If removal isn't allowed, it's
+ * an error.
+ *
+ * (NOTE! This is old and arguably fairly strange behaviour.
+ * We might want to make this an error unconditionally, and
+ * use "--force-remove" if you actually want to force removal).
+ *
+ * - it used to exist as a subdirectory (ie multiple files with
+ * this particular prefix) in the index, in which case it's wrong
+ * to try to update it as a directory.
+ *
+ * - it doesn't exist at all in the index, but it is a valid
+ * git directory, and it should be *added* as a gitlink.
+ */
+static int process_directory(const char *path, int len, struct stat *st)
+{
+ unsigned char sha1[20];
+ int pos = cache_name_pos(path, len);
+
+ /* Exact match: file or existing gitlink */
+ if (pos >= 0) {
+ struct cache_entry *ce = active_cache[pos];
+ if (S_ISGITLINK(ce->ce_mode)) {
+
+ /* Do nothing to the index if there is no HEAD! */
+ if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
+ return 0;
+
+ return add_one_path(ce, path, len, st);
+ }
+ /* Should this be an unconditional error? */
+ return remove_one_path(path);
+ }
+
+ /* Inexact match: is there perhaps a subdirectory match? */
+ pos = -pos-1;
+ while (pos < active_nr) {
+ struct cache_entry *ce = active_cache[pos++];
+
+ if (strncmp(ce->name, path, len))
+ break;
+ if (ce->name[len] > '/')
+ break;
+ if (ce->name[len] < '/')
+ continue;
+
+ /* Subdirectory match - error out */
+ return error("%s: is a directory - add individual files instead", path);
+ }
+
+ /* No match - should we add it as a gitlink? */
+ if (!resolve_gitlink_ref(path, "HEAD", sha1))
+ return add_one_path(NULL, path, len, st);
+
+ /* Error out. */
+ return error("%s: is a directory - add files inside instead", path);
+}
+
+static int process_path(const char *path)
+{
+ int pos, len;
+ struct stat st;
+ struct cache_entry *ce;
+
+ len = strlen(path);
+ if (has_symlink_leading_path(path, len))
+ return error("'%s' is beyond a symbolic link", path);
+
+ pos = cache_name_pos(path, len);
+ ce = pos < 0 ? NULL : active_cache[pos];
+ if (ce && ce_skip_worktree(ce)) {
+ /*
+ * working directory version is assumed "good"
+ * so updating it does not make sense.
+ * On the other hand, removing it from index should work
+ */
+ if (allow_remove && remove_file_from_cache(path))
+ return error("%s: cannot remove from the index", path);
+ return 0;
+ }
+
+ /*
+ * First things first: get the stat information, to decide
+ * what to do about the pathname!
+ */
+ if (lstat(path, &st) < 0)
+ return process_lstat_error(path, errno);
+
+ if (S_ISDIR(st.st_mode))
+ return process_directory(path, len, &st);
+
+ /*
+ * Process a regular file
+ */
+ if (ce && S_ISGITLINK(ce->ce_mode))
+ return error("%s is already a gitlink, not replacing", path);
+
+ return add_one_path(ce, path, len, &st);
+}
+
+static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
+ const char *path, int stage)
+{
+ int size, len, option;
+ struct cache_entry *ce;
+
+ if (!verify_path(path))
+ return error("Invalid path '%s'", path);
+
+ len = strlen(path);
+ size = cache_entry_size(len);
+ ce = xcalloc(1, size);
+
+ hashcpy(ce->sha1, sha1);
+ memcpy(ce->name, path, len);
+ ce->ce_flags = create_ce_flags(len, stage);
+ ce->ce_mode = create_ce_mode(mode);
+ if (assume_unchanged)
+ ce->ce_flags |= CE_VALID;
+ option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+ option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+ if (add_cache_entry(ce, option))
+ return error("%s: cannot add to the index - missing --add option?",
+ path);
+ report("add '%s'", path);
+ return 0;
+}
+
+static void chmod_path(int flip, const char *path)
+{
+ int pos;
+ struct cache_entry *ce;
+ unsigned int mode;
+
+ pos = cache_name_pos(path, strlen(path));
+ if (pos < 0)
+ goto fail;
+ ce = active_cache[pos];
+ mode = ce->ce_mode;
+ if (!S_ISREG(mode))
+ goto fail;
+ switch (flip) {
+ case '+':
+ ce->ce_mode |= 0111; break;
+ case '-':
+ ce->ce_mode &= ~0111; break;
+ default:
+ goto fail;
+ }
+ cache_tree_invalidate_path(active_cache_tree, path);
+ active_cache_changed = 1;
+ report("chmod %cx '%s'", flip, path);
+ return;
+ fail:
+ die("git update-index: cannot chmod %cx '%s'", flip, path);
+}
+
+static void update_one(const char *path, const char *prefix, int prefix_length)
+{
+ const char *p = prefix_path(prefix, prefix_length, path);
+ if (!verify_path(p)) {
+ fprintf(stderr, "Ignoring path %s\n", path);
+ goto free_return;
+ }
+ if (mark_valid_only) {
+ if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG))
+ die("Unable to mark file %s", path);
+ goto free_return;
+ }
+ if (mark_skip_worktree_only) {
+ if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG))
+ die("Unable to mark file %s", path);
+ goto free_return;
+ }
+
+ if (force_remove) {
+ if (remove_file_from_cache(p))
+ die("git update-index: unable to remove %s", path);
+ report("remove '%s'", path);
+ goto free_return;
+ }
+ if (process_path(p))
+ die("Unable to process path %s", path);
+ report("add '%s'", path);
+ free_return:
+ if (p < path || p > path + strlen(path))
+ free((char *)p);
+}
+
+static void read_index_info(int line_termination)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf uq = STRBUF_INIT;
+
+ while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+ char *ptr, *tab;
+ char *path_name;
+ unsigned char sha1[20];
+ unsigned int mode;
+ unsigned long ul;
+ int stage;
+
+ /* This reads lines formatted in one of three formats:
+ *
+ * (1) mode SP sha1 TAB path
+ * The first format is what "git apply --index-info"
+ * reports, and used to reconstruct a partial tree
+ * that is used for phony merge base tree when falling
+ * back on 3-way merge.
+ *
+ * (2) mode SP type SP sha1 TAB path
+ * The second format is to stuff "git ls-tree" output
+ * into the index file.
+ *
+ * (3) mode SP sha1 SP stage TAB path
+ * This format is to put higher order stages into the
+ * index file and matches "git ls-files --stage" output.
+ */
+ errno = 0;
+ ul = strtoul(buf.buf, &ptr, 8);
+ if (ptr == buf.buf || *ptr != ' '
+ || errno || (unsigned int) ul != ul)
+ goto bad_line;
+ mode = ul;
+
+ tab = strchr(ptr, '\t');
+ if (!tab || tab - ptr < 41)
+ goto bad_line;
+
+ if (tab[-2] == ' ' && '0' <= tab[-1] && tab[-1] <= '3') {
+ stage = tab[-1] - '0';
+ ptr = tab + 1; /* point at the head of path */
+ tab = tab - 2; /* point at tail of sha1 */
+ }
+ else {
+ stage = 0;
+ ptr = tab + 1; /* point at the head of path */
+ }
+
+ if (get_sha1_hex(tab - 40, sha1) || tab[-41] != ' ')
+ goto bad_line;
+
+ path_name = ptr;
+ if (line_termination && path_name[0] == '"') {
+ strbuf_reset(&uq);
+ if (unquote_c_style(&uq, path_name, NULL)) {
+ die("git update-index: bad quoting of path name");
+ }
+ path_name = uq.buf;
+ }
+
+ if (!verify_path(path_name)) {
+ fprintf(stderr, "Ignoring path %s\n", path_name);
+ continue;
+ }
+
+ if (!mode) {
+ /* mode == 0 means there is no such path -- remove */
+ if (remove_file_from_cache(path_name))
+ die("git update-index: unable to remove %s",
+ ptr);
+ }
+ else {
+ /* mode ' ' sha1 '\t' name
+ * ptr[-1] points at tab,
+ * ptr[-41] is at the beginning of sha1
+ */
+ ptr[-42] = ptr[-1] = 0;
+ if (add_cacheinfo(mode, sha1, path_name, stage))
+ die("git update-index: unable to update %s",
+ path_name);
+ }
+ continue;
+
+ bad_line:
+ die("malformed index info %s", buf.buf);
+ }
+ strbuf_release(&buf);
+ strbuf_release(&uq);
+}
+
+static const char update_index_usage[] =
+"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+
+static unsigned char head_sha1[20];
+static unsigned char merge_head_sha1[20];
+
+static struct cache_entry *read_one_ent(const char *which,
+ unsigned char *ent, const char *path,
+ int namelen, int stage)
+{
+ unsigned mode;
+ unsigned char sha1[20];
+ int size;
+ struct cache_entry *ce;
+
+ if (get_tree_entry(ent, path, sha1, &mode)) {
+ if (which)
+ error("%s: not in %s branch.", path, which);
+ return NULL;
+ }
+ if (mode == S_IFDIR) {
+ if (which)
+ error("%s: not a blob in %s branch.", path, which);
+ return NULL;
+ }
+ size = cache_entry_size(namelen);
+ ce = xcalloc(1, size);
+
+ hashcpy(ce->sha1, sha1);
+ memcpy(ce->name, path, namelen);
+ ce->ce_flags = create_ce_flags(namelen, stage);
+ ce->ce_mode = create_ce_mode(mode);
+ return ce;
+}
+
+static int unresolve_one(const char *path)
+{
+ int namelen = strlen(path);
+ int pos;
+ int ret = 0;
+ struct cache_entry *ce_2 = NULL, *ce_3 = NULL;
+
+ /* See if there is such entry in the index. */
+ pos = cache_name_pos(path, namelen);
+ if (0 <= pos) {
+ /* already merged */
+ pos = unmerge_cache_entry_at(pos);
+ if (pos < active_nr) {
+ struct cache_entry *ce = active_cache[pos];
+ if (ce_stage(ce) &&
+ ce_namelen(ce) == namelen &&
+ !memcmp(ce->name, path, namelen))
+ return 0;
+ }
+ /* no resolve-undo information; fall back */
+ } else {
+ /* If there isn't, either it is unmerged, or
+ * resolved as "removed" by mistake. We do not
+ * want to do anything in the former case.
+ */
+ pos = -pos-1;
+ if (pos < active_nr) {
+ struct cache_entry *ce = active_cache[pos];
+ if (ce_namelen(ce) == namelen &&
+ !memcmp(ce->name, path, namelen)) {
+ fprintf(stderr,
+ "%s: skipping still unmerged path.\n",
+ path);
+ goto free_return;
+ }
+ }
+ }
+
+ /* Grab blobs from given path from HEAD and MERGE_HEAD,
+ * stuff HEAD version in stage #2,
+ * stuff MERGE_HEAD version in stage #3.
+ */
+ ce_2 = read_one_ent("our", head_sha1, path, namelen, 2);
+ ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3);
+
+ if (!ce_2 || !ce_3) {
+ ret = -1;
+ goto free_return;
+ }
+ if (!hashcmp(ce_2->sha1, ce_3->sha1) &&
+ ce_2->ce_mode == ce_3->ce_mode) {
+ fprintf(stderr, "%s: identical in both, skipping.\n",
+ path);
+ goto free_return;
+ }
+
+ remove_file_from_cache(path);
+ if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
+ error("%s: cannot add our version to the index.", path);
+ ret = -1;
+ goto free_return;
+ }
+ if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD))
+ return 0;
+ error("%s: cannot add their version to the index.", path);
+ ret = -1;
+ free_return:
+ free(ce_2);
+ free(ce_3);
+ return ret;
+}
+
+static void read_head_pointers(void)
+{
+ if (read_ref("HEAD", head_sha1))
+ die("No HEAD -- no initial commit yet?");
+ if (read_ref("MERGE_HEAD", merge_head_sha1)) {
+ fprintf(stderr, "Not in the middle of a merge.\n");
+ exit(0);
+ }
+}
+
+static int do_unresolve(int ac, const char **av,
+ const char *prefix, int prefix_length)
+{
+ int i;
+ int err = 0;
+
+ /* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we
+ * are not doing a merge, so exit with success status.
+ */
+ read_head_pointers();
+
+ for (i = 1; i < ac; i++) {
+ const char *arg = av[i];
+ const char *p = prefix_path(prefix, prefix_length, arg);
+ err |= unresolve_one(p);
+ if (p < arg || p > arg + strlen(arg))
+ free((char *)p);
+ }
+ return err;
+}
+
+static int do_reupdate(int ac, const char **av,
+ const char *prefix, int prefix_length)
+{
+ /* Read HEAD and run update-index on paths that are
+ * merged and already different between index and HEAD.
+ */
+ int pos;
+ int has_head = 1;
+ const char **pathspec = get_pathspec(prefix, av + 1);
+
+ if (read_ref("HEAD", head_sha1))
+ /* If there is no HEAD, that means it is an initial
+ * commit. Update everything in the index.
+ */
+ has_head = 0;
+ redo:
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+ struct cache_entry *old = NULL;
+ int save_nr;
+
+ if (ce_stage(ce) || !ce_path_match(ce, pathspec))
+ continue;
+ if (has_head)
+ old = read_one_ent(NULL, head_sha1,
+ ce->name, ce_namelen(ce), 0);
+ if (old && ce->ce_mode == old->ce_mode &&
+ !hashcmp(ce->sha1, old->sha1)) {
+ free(old);
+ continue; /* unchanged */
+ }
+ /* Be careful. The working tree may not have the
+ * path anymore, in which case, under 'allow_remove',
+ * or worse yet 'allow_replace', active_nr may decrease.
+ */
+ save_nr = active_nr;
+ update_one(ce->name + prefix_length, prefix, prefix_length);
+ if (save_nr != active_nr)
+ goto redo;
+ }
+ return 0;
+}
+
+int cmd_update_index(int argc, const char **argv, const char *prefix)
+{
+ int i, newfd, entries, has_errors = 0, line_termination = '\n';
+ int allow_options = 1;
+ int read_from_stdin = 0;
+ int prefix_length = prefix ? strlen(prefix) : 0;
+ char set_executable_bit = 0;
+ unsigned int refresh_flags = 0;
+ int lock_error = 0;
+ struct lock_file *lock_file;
+
+ git_config(git_default_config, NULL);
+
+ /* We can't free this memory, it becomes part of a linked list parsed atexit() */
+ lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ newfd = hold_locked_index(lock_file, 0);
+ if (newfd < 0)
+ lock_error = errno;
+
+ entries = read_cache();
+ if (entries < 0)
+ die("cache corrupted");
+
+ for (i = 1 ; i < argc; i++) {
+ const char *path = argv[i];
+ const char *p;
+
+ if (allow_options && *path == '-') {
+ if (!strcmp(path, "--")) {
+ allow_options = 0;
+ continue;
+ }
+ if (!strcmp(path, "-q")) {
+ refresh_flags |= REFRESH_QUIET;
+ continue;
+ }
+ if (!strcmp(path, "--ignore-submodules")) {
+ refresh_flags |= REFRESH_IGNORE_SUBMODULES;
+ continue;
+ }
+ if (!strcmp(path, "--add")) {
+ allow_add = 1;
+ continue;
+ }
+ if (!strcmp(path, "--replace")) {
+ allow_replace = 1;
+ continue;
+ }
+ if (!strcmp(path, "--remove")) {
+ allow_remove = 1;
+ continue;
+ }
+ if (!strcmp(path, "--unmerged")) {
+ refresh_flags |= REFRESH_UNMERGED;
+ continue;
+ }
+ if (!strcmp(path, "--refresh")) {
+ setup_work_tree();
+ has_errors |= refresh_cache(refresh_flags);
+ continue;
+ }
+ if (!strcmp(path, "--really-refresh")) {
+ setup_work_tree();
+ has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags);
+ continue;
+ }
+ if (!strcmp(path, "--cacheinfo")) {
+ unsigned char sha1[20];
+ unsigned int mode;
+
+ if (i+3 >= argc)
+ die("git update-index: --cacheinfo <mode> <sha1> <path>");
+
+ if (strtoul_ui(argv[i+1], 8, &mode) ||
+ get_sha1_hex(argv[i+2], sha1) ||
+ add_cacheinfo(mode, sha1, argv[i+3], 0))
+ die("git update-index: --cacheinfo"
+ " cannot add %s", argv[i+3]);
+ i += 3;
+ continue;
+ }
+ if (!strcmp(path, "--chmod=-x") ||
+ !strcmp(path, "--chmod=+x")) {
+ if (argc <= i+1)
+ die("git update-index: %s <path>", path);
+ set_executable_bit = path[8];
+ continue;
+ }
+ if (!strcmp(path, "--assume-unchanged")) {
+ mark_valid_only = MARK_FLAG;
+ continue;
+ }
+ if (!strcmp(path, "--no-assume-unchanged")) {
+ mark_valid_only = UNMARK_FLAG;
+ continue;
+ }
+ if (!strcmp(path, "--no-skip-worktree")) {
+ mark_skip_worktree_only = UNMARK_FLAG;
+ continue;
+ }
+ if (!strcmp(path, "--skip-worktree")) {
+ mark_skip_worktree_only = MARK_FLAG;
+ continue;
+ }
+ if (!strcmp(path, "--info-only")) {
+ info_only = 1;
+ continue;
+ }
+ if (!strcmp(path, "--force-remove")) {
+ force_remove = 1;
+ continue;
+ }
+ if (!strcmp(path, "-z")) {
+ line_termination = 0;
+ continue;
+ }
+ if (!strcmp(path, "--stdin")) {
+ if (i != argc - 1)
+ die("--stdin must be at the end");
+ read_from_stdin = 1;
+ break;
+ }
+ if (!strcmp(path, "--index-info")) {
+ if (i != argc - 1)
+ die("--index-info must be at the end");
+ allow_add = allow_replace = allow_remove = 1;
+ read_index_info(line_termination);
+ break;
+ }
+ if (!strcmp(path, "--unresolve")) {
+ has_errors = do_unresolve(argc - i, argv + i,
+ prefix, prefix_length);
+ if (has_errors)
+ active_cache_changed = 0;
+ goto finish;
+ }
+ if (!strcmp(path, "--again") || !strcmp(path, "-g")) {
+ setup_work_tree();
+ has_errors = do_reupdate(argc - i, argv + i,
+ prefix, prefix_length);
+ if (has_errors)
+ active_cache_changed = 0;
+ goto finish;
+ }
+ if (!strcmp(path, "--ignore-missing")) {
+ refresh_flags |= REFRESH_IGNORE_MISSING;
+ continue;
+ }
+ if (!strcmp(path, "--verbose")) {
+ verbose = 1;
+ continue;
+ }
+ if (!strcmp(path, "--clear-resolve-undo")) {
+ resolve_undo_clear();
+ continue;
+ }
+ if (!strcmp(path, "-h") || !strcmp(path, "--help"))
+ usage(update_index_usage);
+ die("unknown option %s", path);
+ }
+ setup_work_tree();
+ p = prefix_path(prefix, prefix_length, path);
+ update_one(p, NULL, 0);
+ if (set_executable_bit)
+ chmod_path(set_executable_bit, p);
+ if (p < path || p > path + strlen(path))
+ free((char *)p);
+ }
+ if (read_from_stdin) {
+ struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
+
+ setup_work_tree();
+ while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+ const char *p;
+ if (line_termination && buf.buf[0] == '"') {
+ strbuf_reset(&nbuf);
+ if (unquote_c_style(&nbuf, buf.buf, NULL))
+ die("line is badly quoted");
+ strbuf_swap(&buf, &nbuf);
+ }
+ p = prefix_path(prefix, prefix_length, buf.buf);
+ update_one(p, NULL, 0);
+ if (set_executable_bit)
+ chmod_path(set_executable_bit, p);
+ if (p < buf.buf || p > buf.buf + buf.len)
+ free((char *)p);
+ }
+ strbuf_release(&nbuf);
+ strbuf_release(&buf);
+ }
+
+ finish:
+ if (active_cache_changed) {
+ if (newfd < 0) {
+ if (refresh_flags & REFRESH_QUIET)
+ exit(128);
+ unable_to_lock_index_die(get_index_file(), lock_error);
+ }
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("Unable to write new index file");
+ }
+
+ rollback_lock_file(lock_file);
+
+ return has_errors ? 1 : 0;
+}
--- /dev/null
+#include "cache.h"
+#include "refs.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static const char * const git_update_ref_usage[] = {
+ "git update-ref [options] -d <refname> [<oldval>]",
+ "git update-ref [options] <refname> <newval> [<oldval>]",
+ NULL
+};
+
+int cmd_update_ref(int argc, const char **argv, const char *prefix)
+{
+ const char *refname, *oldval, *msg=NULL;
+ unsigned char sha1[20], oldsha1[20];
+ int delete = 0, no_deref = 0, flags = 0;
+ struct option options[] = {
+ OPT_STRING( 'm', NULL, &msg, "reason", "reason of the update"),
+ OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"),
+ OPT_BOOLEAN( 0 , "no-deref", &no_deref,
+ "update <refname> not the one it points to"),
+ OPT_END(),
+ };
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, options, git_update_ref_usage,
+ 0);
+ if (msg && !*msg)
+ die("Refusing to perform update with empty message.");
+
+ if (delete) {
+ if (argc < 1 || argc > 2)
+ usage_with_options(git_update_ref_usage, options);
+ refname = argv[0];
+ oldval = argv[1];
+ } else {
+ const char *value;
+ if (argc < 2 || argc > 3)
+ usage_with_options(git_update_ref_usage, options);
+ refname = argv[0];
+ value = argv[1];
+ oldval = argv[2];
+ if (get_sha1(value, sha1))
+ die("%s: not a valid SHA1", value);
+ }
+
+ hashclr(oldsha1); /* all-zero hash in case oldval is the empty string */
+ if (oldval && *oldval && get_sha1(oldval, oldsha1))
+ die("%s: not a valid old SHA1", oldval);
+
+ if (no_deref)
+ flags = REF_NODEREF;
+ if (delete)
+ return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
+ else
+ return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
+ flags, DIE_ON_ERR);
+}
--- /dev/null
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+
+static const char * const update_server_info_usage[] = {
+ "git update-server-info [--force]",
+ NULL
+};
+
+int cmd_update_server_info(int argc, const char **argv, const char *prefix)
+{
+ int force = 0;
+ struct option options[] = {
+ OPT_BOOLEAN('f', "force", &force,
+ "update the info files from scratch"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ update_server_info_usage, 0);
+ if (argc > 0)
+ usage_with_options(update_server_info_usage, options);
+
+ return !!update_server_info(force);
+}
--- /dev/null
+/*
+ * Copyright (c) 2006 Franck Bui-Huu
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "archive.h"
+#include "pkt-line.h"
+#include "sideband.h"
+
+static const char upload_archive_usage[] =
+ "git upload-archive <repo>";
+
+static const char deadchild[] =
+"git upload-archive: archiver died with error";
+
+static const char lostchild[] =
+"git upload-archive: archiver process was lost";
+
+#define MAX_ARGS (64)
+
+static int run_upload_archive(int argc, const char **argv, const char *prefix)
+{
+ const char *sent_argv[MAX_ARGS];
+ const char *arg_cmd = "argument ";
+ char *p, buf[4096];
+ int sent_argc;
+ int len;
+
+ if (argc != 2)
+ usage(upload_archive_usage);
+
+ if (strlen(argv[1]) + 1 > sizeof(buf))
+ die("insanely long repository name");
+
+ strcpy(buf, argv[1]); /* enter-repo smudges its argument */
+
+ if (!enter_repo(buf, 0))
+ die("'%s' does not appear to be a git repository", buf);
+
+ /* put received options in sent_argv[] */
+ sent_argc = 1;
+ sent_argv[0] = "git-upload-archive";
+ for (p = buf;;) {
+ /* This will die if not enough free space in buf */
+ len = packet_read_line(0, p, (buf + sizeof buf) - p);
+ if (len == 0)
+ break; /* got a flush */
+ if (sent_argc > MAX_ARGS - 2)
+ die("Too many options (>%d)", MAX_ARGS - 2);
+
+ if (p[len-1] == '\n') {
+ p[--len] = 0;
+ }
+ if (len < strlen(arg_cmd) ||
+ strncmp(arg_cmd, p, strlen(arg_cmd)))
+ die("'argument' token or flush expected");
+
+ len -= strlen(arg_cmd);
+ memmove(p, p + strlen(arg_cmd), len);
+ sent_argv[sent_argc++] = p;
+ p += len;
+ *p++ = 0;
+ }
+ sent_argv[sent_argc] = NULL;
+
+ /* parse all options sent by the client */
+ return write_archive(sent_argc, sent_argv, prefix, 0);
+}
+
+__attribute__((format (printf, 1, 2)))
+static void error_clnt(const char *fmt, ...)
+{
+ char buf[1024];
+ va_list params;
+ int len;
+
+ va_start(params, fmt);
+ len = vsprintf(buf, fmt, params);
+ va_end(params);
+ send_sideband(1, 3, buf, len, LARGE_PACKET_MAX);
+ die("sent error to the client: %s", buf);
+}
+
+static ssize_t process_input(int child_fd, int band)
+{
+ char buf[16384];
+ ssize_t sz = read(child_fd, buf, sizeof(buf));
+ if (sz < 0) {
+ if (errno != EAGAIN && errno != EINTR)
+ error_clnt("read error: %s\n", strerror(errno));
+ return sz;
+ }
+ send_sideband(1, band, buf, sz, LARGE_PACKET_MAX);
+ return sz;
+}
+
+int cmd_upload_archive(int argc, const char **argv, const char *prefix)
+{
+ pid_t writer;
+ int fd1[2], fd2[2];
+ /*
+ * Set up sideband subprocess.
+ *
+ * We (parent) monitor and read from child, sending its fd#1 and fd#2
+ * multiplexed out to our fd#1. If the child dies, we tell the other
+ * end over channel #3.
+ */
+ if (pipe(fd1) < 0 || pipe(fd2) < 0) {
+ int err = errno;
+ packet_write(1, "NACK pipe failed on the remote side\n");
+ die("upload-archive: %s", strerror(err));
+ }
+ writer = fork();
+ if (writer < 0) {
+ int err = errno;
+ packet_write(1, "NACK fork failed on the remote side\n");
+ die("upload-archive: %s", strerror(err));
+ }
+ if (!writer) {
+ /* child - connect fd#1 and fd#2 to the pipe */
+ dup2(fd1[1], 1);
+ dup2(fd2[1], 2);
+ close(fd1[1]); close(fd2[1]);
+ close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
+
+ exit(run_upload_archive(argc, argv, prefix));
+ }
+
+ /* parent - read from child, multiplex and send out to fd#1 */
+ close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
+ packet_write(1, "ACK\n");
+ packet_flush(1);
+
+ while (1) {
+ struct pollfd pfd[2];
+ int status;
+
+ pfd[0].fd = fd1[0];
+ pfd[0].events = POLLIN;
+ pfd[1].fd = fd2[0];
+ pfd[1].events = POLLIN;
+ if (poll(pfd, 2, -1) < 0) {
+ if (errno != EINTR) {
+ error("poll failed resuming: %s",
+ strerror(errno));
+ sleep(1);
+ }
+ continue;
+ }
+ if (pfd[1].revents & POLLIN)
+ /* Status stream ready */
+ if (process_input(pfd[1].fd, 2))
+ continue;
+ if (pfd[0].revents & POLLIN)
+ /* Data stream ready */
+ if (process_input(pfd[0].fd, 1))
+ continue;
+
+ if (waitpid(writer, &status, 0) < 0)
+ error_clnt("%s", lostchild);
+ else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+ error_clnt("%s", deadchild);
+ packet_flush(1);
+ break;
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Eric Biederman, 2005
+ */
+#include "cache.h"
+#include "exec_cmd.h"
+
+static const char var_usage[] = "git var (-l | <variable>)";
+
+static const char *editor(int flag)
+{
+ const char *pgm = git_editor();
+
+ if (!pgm && flag & IDENT_ERROR_ON_NO_NAME)
+ die("Terminal is dumb, but EDITOR unset");
+
+ return pgm;
+}
+
+static const char *pager(int flag)
+{
+ const char *pgm = git_pager(1);
+
+ if (!pgm)
+ pgm = "cat";
+ return pgm;
+}
+
+struct git_var {
+ const char *name;
+ const char *(*read)(int);
+};
+static struct git_var git_vars[] = {
+ { "GIT_COMMITTER_IDENT", git_committer_info },
+ { "GIT_AUTHOR_IDENT", git_author_info },
+ { "GIT_EDITOR", editor },
+ { "GIT_PAGER", pager },
+ { "", NULL },
+};
+
+static void list_vars(void)
+{
+ struct git_var *ptr;
+ const char *val;
+
+ for (ptr = git_vars; ptr->read; ptr++)
+ if ((val = ptr->read(0)))
+ printf("%s=%s\n", ptr->name, val);
+}
+
+static const char *read_var(const char *var)
+{
+ struct git_var *ptr;
+ const char *val;
+ val = NULL;
+ for (ptr = git_vars; ptr->read; ptr++) {
+ if (strcmp(var, ptr->name) == 0) {
+ val = ptr->read(IDENT_ERROR_ON_NO_NAME);
+ break;
+ }
+ }
+ return val;
+}
+
+static int show_config(const char *var, const char *value, void *cb)
+{
+ if (value)
+ printf("%s=%s\n", var, value);
+ else
+ printf("%s\n", var);
+ return git_default_config(var, value, cb);
+}
+
+int cmd_var(int argc, const char **argv, const char *prefix)
+{
+ const char *val;
+ int nongit;
+ if (argc != 2) {
+ usage(var_usage);
+ }
+
+ setup_git_directory_gently(&nongit);
+ val = NULL;
+
+ if (strcmp(argv[1], "-l") == 0) {
+ git_config(show_config, NULL);
+ list_vars();
+ return 0;
+ }
+ git_config(git_default_config, NULL);
+ val = read_var(argv[1]);
+ if (!val)
+ usage(var_usage);
+
+ printf("%s\n", val);
+
+ return 0;
+}
--- /dev/null
+#include "builtin.h"
+#include "cache.h"
+#include "pack.h"
+#include "pack-revindex.h"
+#include "parse-options.h"
+
+#define MAX_CHAIN 50
+
+#define VERIFY_PACK_VERBOSE 01
+#define VERIFY_PACK_STAT_ONLY 02
+
+static void show_pack_info(struct packed_git *p, unsigned int flags)
+{
+ uint32_t nr_objects, i;
+ int cnt;
+ int stat_only = flags & VERIFY_PACK_STAT_ONLY;
+ unsigned long chain_histogram[MAX_CHAIN+1], baseobjects;
+
+ nr_objects = p->num_objects;
+ memset(chain_histogram, 0, sizeof(chain_histogram));
+ baseobjects = 0;
+
+ for (i = 0; i < nr_objects; i++) {
+ const unsigned char *sha1;
+ unsigned char base_sha1[20];
+ const char *type;
+ unsigned long size;
+ unsigned long store_size;
+ off_t offset;
+ unsigned int delta_chain_length;
+
+ sha1 = nth_packed_object_sha1(p, i);
+ if (!sha1)
+ die("internal error pack-check nth-packed-object");
+ offset = nth_packed_object_offset(p, i);
+ type = packed_object_info_detail(p, offset, &size, &store_size,
+ &delta_chain_length,
+ base_sha1);
+ if (!stat_only)
+ printf("%s ", sha1_to_hex(sha1));
+ if (!delta_chain_length) {
+ if (!stat_only)
+ printf("%-6s %lu %lu %"PRIuMAX"\n",
+ type, size, store_size, (uintmax_t)offset);
+ baseobjects++;
+ }
+ else {
+ if (!stat_only)
+ printf("%-6s %lu %lu %"PRIuMAX" %u %s\n",
+ type, size, store_size, (uintmax_t)offset,
+ delta_chain_length, sha1_to_hex(base_sha1));
+ if (delta_chain_length <= MAX_CHAIN)
+ chain_histogram[delta_chain_length]++;
+ else
+ chain_histogram[0]++;
+ }
+ }
+
+ if (baseobjects)
+ printf("non delta: %lu object%s\n",
+ baseobjects, baseobjects > 1 ? "s" : "");
+
+ for (cnt = 1; cnt <= MAX_CHAIN; cnt++) {
+ if (!chain_histogram[cnt])
+ continue;
+ printf("chain length = %d: %lu object%s\n", cnt,
+ chain_histogram[cnt],
+ chain_histogram[cnt] > 1 ? "s" : "");
+ }
+ if (chain_histogram[0])
+ printf("chain length > %d: %lu object%s\n", MAX_CHAIN,
+ chain_histogram[0],
+ chain_histogram[0] > 1 ? "s" : "");
+}
+
+static int verify_one_pack(const char *path, unsigned int flags)
+{
+ char arg[PATH_MAX];
+ int len;
+ int verbose = flags & VERIFY_PACK_VERBOSE;
+ int stat_only = flags & VERIFY_PACK_STAT_ONLY;
+ struct packed_git *pack;
+ int err;
+
+ len = strlcpy(arg, path, PATH_MAX);
+ if (len >= PATH_MAX)
+ return error("name too long: %s", path);
+
+ /*
+ * In addition to "foo.idx" we accept "foo.pack" and "foo";
+ * normalize these forms to "foo.idx" for add_packed_git().
+ */
+ if (has_extension(arg, ".pack")) {
+ strcpy(arg + len - 5, ".idx");
+ len--;
+ } else if (!has_extension(arg, ".idx")) {
+ if (len + 4 >= PATH_MAX)
+ return error("name too long: %s.idx", arg);
+ strcpy(arg + len, ".idx");
+ len += 4;
+ }
+
+ /*
+ * add_packed_git() uses our buffer (containing "foo.idx") to
+ * build the pack filename ("foo.pack"). Make sure it fits.
+ */
+ if (len + 1 >= PATH_MAX) {
+ arg[len - 4] = '\0';
+ return error("name too long: %s.pack", arg);
+ }
+
+ pack = add_packed_git(arg, len, 1);
+ if (!pack)
+ return error("packfile %s not found.", arg);
+
+ install_packed_git(pack);
+
+ if (!stat_only)
+ err = verify_pack(pack);
+ else
+ err = open_pack_index(pack);
+
+ if (verbose || stat_only) {
+ if (err)
+ printf("%s: bad\n", pack->pack_name);
+ else {
+ show_pack_info(pack, flags);
+ if (!stat_only)
+ printf("%s: ok\n", pack->pack_name);
+ }
+ }
+
+ return err;
+}
+
+static const char * const verify_pack_usage[] = {
+ "git verify-pack [-v|--verbose] [-s|--stat-only] <pack>...",
+ NULL
+};
+
+int cmd_verify_pack(int argc, const char **argv, const char *prefix)
+{
+ int err = 0;
+ unsigned int flags = 0;
+ int i;
+ const struct option verify_pack_options[] = {
+ OPT_BIT('v', "verbose", &flags, "verbose",
+ VERIFY_PACK_VERBOSE),
+ OPT_BIT('s', "stat-only", &flags, "show statistics only",
+ VERIFY_PACK_STAT_ONLY),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, verify_pack_options,
+ verify_pack_usage, 0);
+ if (argc < 1)
+ usage_with_options(verify_pack_usage, verify_pack_options);
+ for (i = 0; i < argc; i++) {
+ if (verify_one_pack(argv[i], flags))
+ err = 1;
+ discard_revindex();
+ }
+
+ return err;
+}
--- /dev/null
+/*
+ * Builtin "git verify-tag"
+ *
+ * Copyright (c) 2007 Carlos Rica <jasampler@gmail.com>
+ *
+ * Based on git-verify-tag.sh
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "tag.h"
+#include "run-command.h"
+#include <signal.h>
+#include "parse-options.h"
+
+static const char * const verify_tag_usage[] = {
+ "git verify-tag [-v|--verbose] <tag>...",
+ NULL
+};
+
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+
+static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
+{
+ struct child_process gpg;
+ const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
+ char path[PATH_MAX], *eol;
+ size_t len;
+ int fd, ret;
+
+ fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
+ if (fd < 0)
+ return error("could not create temporary file '%s': %s",
+ path, strerror(errno));
+ if (write_in_full(fd, buf, size) < 0)
+ return error("failed writing temporary file '%s': %s",
+ path, strerror(errno));
+ close(fd);
+
+ /* find the length without signature */
+ len = 0;
+ while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) {
+ eol = memchr(buf + len, '\n', size - len);
+ len += eol ? eol - (buf + len) + 1 : size - len;
+ }
+ if (verbose)
+ write_in_full(1, buf, len);
+
+ memset(&gpg, 0, sizeof(gpg));
+ gpg.argv = args_gpg;
+ gpg.in = -1;
+ args_gpg[2] = path;
+ if (start_command(&gpg)) {
+ unlink(path);
+ return error("could not run gpg.");
+ }
+
+ write_in_full(gpg.in, buf, len);
+ close(gpg.in);
+ ret = finish_command(&gpg);
+
+ unlink_or_warn(path);
+
+ return ret;
+}
+
+static int verify_tag(const char *name, int verbose)
+{
+ enum object_type type;
+ unsigned char sha1[20];
+ char *buf;
+ unsigned long size;
+ int ret;
+
+ if (get_sha1(name, sha1))
+ return error("tag '%s' not found.", name);
+
+ type = sha1_object_info(sha1, NULL);
+ if (type != OBJ_TAG)
+ return error("%s: cannot verify a non-tag object of type %s.",
+ name, typename(type));
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ return error("%s: unable to read file.", name);
+
+ ret = run_gpg_verify(buf, size, verbose);
+
+ free(buf);
+ return ret;
+}
+
+int cmd_verify_tag(int argc, const char **argv, const char *prefix)
+{
+ int i = 1, verbose = 0, had_error = 0;
+ const struct option verify_tag_options[] = {
+ OPT__VERBOSE(&verbose),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, verify_tag_options,
+ verify_tag_usage, PARSE_OPT_KEEP_ARGV0);
+ if (argc <= i)
+ usage_with_options(verify_tag_usage, verify_tag_options);
+
+ /* sometimes the program was terminated because this signal
+ * was received in the process of writing the gpg input: */
+ signal(SIGPIPE, SIG_IGN);
+ while (i < argc)
+ if (verify_tag(argv[i++], verbose))
+ had_error = 1;
+ return had_error;
+}
--- /dev/null
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "builtin.h"
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+#include "parse-options.h"
+
+static const char * const write_tree_usage[] = {
+ "git write-tree [--missing-ok] [--prefix=<prefix>/]",
+ NULL
+};
+
+int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
+{
+ int flags = 0, ret;
+ const char *prefix = NULL;
+ unsigned char sha1[20];
+ const char *me = "git-write-tree";
+ struct option write_tree_options[] = {
+ OPT_BIT(0, "missing-ok", &flags, "allow missing objects",
+ WRITE_TREE_MISSING_OK),
+ { OPTION_STRING, 0, "prefix", &prefix, "<prefix>/",
+ "write tree object for a subdirectory <prefix>" ,
+ PARSE_OPT_LITERAL_ARGHELP },
+ { OPTION_BIT, 0, "ignore-cache-tree", &flags, NULL,
+ "only useful for debugging",
+ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, NULL,
+ WRITE_TREE_IGNORE_CACHE_TREE },
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, unused_prefix, write_tree_options,
+ write_tree_usage, 0);
+
+ ret = write_cache_as_tree(sha1, flags, prefix);
+ switch (ret) {
+ case 0:
+ printf("%s\n", sha1_to_hex(sha1));
+ break;
+ case WRITE_TREE_UNREADABLE_INDEX:
+ die("%s: error reading the index", me);
+ break;
+ case WRITE_TREE_UNMERGED_INDEX:
+ die("%s: error building trees", me);
+ break;
+ case WRITE_TREE_PREFIX_ERROR:
+ die("%s: prefix %s not found", me, prefix);
+ break;
+ }
+ return ret;
+}
int longest_ancestor_length(const char *path, const char *prefix_list);
char *strip_path_suffix(const char *path, const char *suffix);
int daemon_avoid_alias(const char *path);
+int offset_1st_component(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 *);
goto auto_color;
}
+ if (!var)
+ return -1;
+
/* Missing or explicit false to turn off colorization */
if (!git_config_bool(var, value))
return 0;
return fd;
}
+#undef fopen
+FILE *mingw_fopen (const char *filename, const char *otype)
+{
+ if (!strcmp(filename, "/dev/null"))
+ filename = "nul";
+ return fopen(filename, otype);
+}
+
+#undef freopen
+FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
+{
+ if (filename && !strcmp(filename, "/dev/null"))
+ filename = "nul";
+ return freopen(filename, otype, stream);
+}
+
/*
* The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
* Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
int mingw_open (const char *filename, int oflags, ...);
#define open mingw_open
+FILE *mingw_fopen (const char *filename, const char *otype);
+#define fopen mingw_fopen
+
+FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream);
+#define freopen mingw_freopen
+
char *mingw_getcwd(char *pointer, int len);
#define getcwd mingw_getcwd
#define STR_(s) # s
#define STR(s) STR_(s)
+static void get_host_and_port(char **host, const char **port)
+{
+ char *colon, *end;
+
+ if (*host[0] == '[') {
+ end = strchr(*host + 1, ']');
+ if (end) {
+ *end = 0;
+ end++;
+ (*host)++;
+ } else
+ end = *host;
+ } else
+ end = *host;
+ colon = strchr(end, ':');
+
+ if (colon) {
+ *colon = 0;
+ *port = colon + 1;
+ }
+}
+
#ifndef NO_IPV6
static const char *ai_name(const struct addrinfo *ai)
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, ']');
- if (end) {
- *end = 0;
- end++;
- host++;
- } else
- end = host;
- } else
- end = host;
- colon = strchr(end, ':');
-
- if (colon) {
- *colon = 0;
- port = colon + 1;
- if (!*port)
- port = "<none>";
- }
+ get_host_and_port(&host, &port);
+ if (!*port)
+ port = "<none>";
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
static int git_tcp_connect_sock(char *host, int flags)
{
int sockfd = -1, saved_errno = 0;
- char *colon, *end;
- char *port = STR(DEFAULT_GIT_PORT), *ep;
+ const char *port = STR(DEFAULT_GIT_PORT);
+ char *ep;
struct hostent *he;
struct sockaddr_in sa;
char **ap;
unsigned int nport;
int cnt;
- if (host[0] == '[') {
- end = strchr(host + 1, ']');
- if (end) {
- *end = 0;
- end++;
- host++;
- } else
- end = host;
- } else
- end = host;
- colon = strchr(end, ':');
-
- if (colon) {
- *colon = 0;
- port = colon + 1;
- }
+ get_host_and_port(&host, &port);
if (flags & CONNECT_VERBOSE)
fprintf(stderr, "Looking up %s ... ", host);
static void git_proxy_connect(int fd[2], char *host)
{
const char *port = STR(DEFAULT_GIT_PORT);
- char *colon, *end;
const char *argv[4];
struct child_process proxy;
- if (host[0] == '[') {
- end = strchr(host + 1, ']');
- if (end) {
- *end = 0;
- end++;
- host++;
- } else
- end = host;
- } else
- end = host;
- colon = strchr(end, ':');
-
- if (colon) {
- *colon = 0;
- port = colon + 1;
- }
+ get_host_and_port(&host, &port);
argv[0] = git_proxy_command;
argv[1] = host;
DIFF_OPT_SET(options, FOLLOW_RENAMES);
else if (!strcmp(arg, "--color"))
DIFF_OPT_SET(options, COLOR_DIFF);
+ else if (!prefixcmp(arg, "--color=")) {
+ int value = git_config_colorbool(NULL, arg+8, -1);
+ if (value == 0)
+ DIFF_OPT_CLR(options, COLOR_DIFF);
+ else if (value > 0)
+ DIFF_OPT_SET(options, COLOR_DIFF);
+ else
+ return error("option `color' expects \"always\", \"auto\", or \"never\"");
+ }
else if (!strcmp(arg, "--no-color"))
DIFF_OPT_CLR(options, COLOR_DIFF);
else if (!strcmp(arg, "--color-words")) {
!(prefix = strip_path_suffix(argv0_path, BINDIR)) &&
!(prefix = strip_path_suffix(argv0_path, "git"))) {
prefix = PREFIX;
- fprintf(stderr, "RUNTIME_PREFIX requested, "
+ trace_printf("RUNTIME_PREFIX requested, "
"but prefix computation failed. "
"Using static fallback '%s'.\n", prefix);
}
start_packfile();
}
-static size_t encode_header(
- enum object_type type,
- uintmax_t size,
- unsigned char *hdr)
-{
- int n = 1;
- unsigned char c;
-
- if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
- die("bad type %d", type);
-
- c = (type << 4) | (size & 15);
- size >>= 4;
- while (size) {
- *hdr++ = c | 0x80;
- c = size & 0x7f;
- size >>= 7;
- n++;
- }
- *hdr = c;
- return n;
-}
-
static int store_object(
enum object_type type,
struct strbuf *dat,
delta_count_by_type[type]++;
e->depth = last->depth + 1;
- hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr);
+ hdrlen = encode_in_pack_object_header(OBJ_OFS_DELTA, deltalen, hdr);
sha1write(pack_file, hdr, hdrlen);
pack_size += hdrlen;
pack_size += sizeof(hdr) - pos;
} else {
e->depth = 0;
- hdrlen = encode_header(type, dat->len, hdr);
+ hdrlen = encode_in_pack_object_header(type, dat->len, hdr);
sha1write(pack_file, hdr, hdrlen);
pack_size += hdrlen;
}
memset(&s, 0, sizeof(s));
deflateInit(&s, pack_compression_level);
- hdrlen = encode_header(OBJ_BLOB, len, out_buf);
+ hdrlen = encode_in_pack_object_header(OBJ_BLOB, len, out_buf);
if (out_sz <= hdrlen)
die("impossibly large object header");
s,signoff add a Signed-off-by line to the commit message
u,utf8 recode into utf8 (default)
k,keep pass -k flag to git-mailinfo
+keep-cr pass --keep-cr flag to git-mailsplit for mbox format
+no-keep-cr do not pass --keep-cr flag to git-mailsplit independent of am.keepcr
c,scissors strip everything before a scissors line
whitespace= pass it through git-apply
ignore-space-change pass it through git-apply
split_patches () {
case "$patch_format" in
mbox)
- case "$rebasing" in
- '')
- keep_cr= ;;
- ?*)
- keep_cr=--keep-cr ;;
- esac
+ if test -n "$rebasing" || test t = "$keepcr"
+ then
+ keep_cr=--keep-cr
+ else
+ keep_cr=
+ fi
git mailsplit -d"$prec" -o"$dotest" -b $keep_cr -- "$@" > "$dotest/last" ||
clean_abort
;;
prec=4
dotest="$GIT_DIR/rebase-apply"
-sign= utf8=t keep= skip= interactive= resolved= rebasing= abort=
+sign= utf8=t keep= keepcr= skip= interactive= resolved= rebasing= abort=
resolvemsg= resume= scissors= no_inbody_headers=
git_apply_opt=
committer_date_is_author_date=
ignore_date=
allow_rerere_autoupdate=
+if test "$(git config --bool --get am.keepcr)" = true
+then
+ keepcr=t
+fi
+
while test $# != 0
do
case "$1" in
allow_rerere_autoupdate="$1" ;;
-q|--quiet)
GIT_QUIET=t ;;
+ --keep-cr)
+ keepcr=t ;;
+ --no-keep-cr)
+ keepcr=f ;;
--)
shift; break ;;
*)
echo "$sign" >"$dotest/sign"
echo "$utf8" >"$dotest/utf8"
echo "$keep" >"$dotest/keep"
+ echo "$keepcr" >"$dotest/keepcr"
echo "$scissors" >"$dotest/scissors"
echo "$no_inbody_headers" >"$dotest/no_inbody_headers"
echo "$GIT_QUIET" >"$dotest/quiet"
then
keep=-k
fi
+case "$(cat "$dotest/keepcr")" in
+t)
+ keepcr=--keep-cr ;;
+f)
+ keepcr=--no-keep-cr ;;
+esac
case "$(cat "$dotest/scissors")" in
t)
scissors=--scissors ;;
$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, $opt_r);
+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, $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]
- [-r remote] [CVS_module]
+ [-r remote] [-R] [CVS_module]
END
exit(1);
}
}
}
-my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:";
+my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:R";
read_repo_config($opts);
Getopt::Long::Configure( 'no_ignore_case', 'bundling' );
write_author_info("$git_dir/cvs-authors");
}
+# open .git/cvs-revisions, if requested
+open my $revision_map, '>>', "$git_dir/cvs-revisions"
+ or die "Can't open $git_dir/cvs-revisions for appending: $!\n"
+ if defined $opt_R;
+
#
# run cvsps into a file unless we are getting
}
my ($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
-my (@old,@new,@skipped,%ignorebranch);
+my (@old,@new,@skipped,%ignorebranch,@commit_revisions);
# commits that cvsps cannot place anywhere...
$ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
system('git' , 'update-ref', "$remote/$branch", $cid) == 0
or die "Cannot write branch $branch for update: $!\n";
+ if ($revision_map) {
+ print $revision_map "@$_ $cid\n" for @commit_revisions;
+ }
+ @commit_revisions = ();
+
if ($tag) {
my ($xtag) = $tag;
$xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
push(@skipped, $fn);
next;
}
+ push @commit_revisions, [$fn, $rev];
print "Fetching $fn v $rev\n" if $opt_v;
my ($tmpname, $size) = $cvs->file($fn,$rev);
if ($size == -1) {
unlink($tmpname);
} elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
my $fn = $1;
+ my $rev = $2;
$fn =~ s#^/+##;
+ push @commit_revisions, [$fn, $rev];
push(@old,$fn);
print "Delete $fn\n" if $opt_v;
} elsif ($state == 9 and /^\s*$/) {
status=1
fi
-echo "The following changes since commit $baserev:"
-git shortlog --max-count=1 $baserev | sed -e 's/^\(.\)/ \1/'
+git show -s --format='The following changes since commit %H:
-echo "are available in the git repository at:"
-echo
+ %s (%ci)
+
+are available in the git repository at:' $baserev
echo " $url $branch"
echo
test $summary_limit = 0 && return
- if rev=$(git rev-parse -q --verify "$1^0")
+ if rev=$(git rev-parse -q --verify --default HEAD ${1+"$1"})
then
head=$rev
- shift
+ test $# = 0 || shift
+ elif test -z "$1" -o "$1" = "HEAD"
+ then
+ return
else
- head=HEAD
+ head="HEAD"
fi
if [ -n "$files" ]
$| = 1; # unbuffer STDOUT
sub fatal (@) { print STDERR "@_\n"; exit 1 }
-require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
-require SVN::Ra;
-require SVN::Delta;
-if ($SVN::Core::VERSION lt '1.1.0') {
- fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)";
+sub _req_svn {
+ require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
+ require SVN::Ra;
+ require SVN::Delta;
+ if ($SVN::Core::VERSION lt '1.1.0') {
+ fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)";
+ }
}
my $can_compress = eval { require Compress::Zlib; 1};
push @Git::SVN::Ra::ISA, 'SVN::Ra';
}
sub version {
+ ::_req_svn();
print "git-svn version $VERSION (svn $SVN::Core::VERSION)\n";
exit 0;
}
command_noisy(@init_db);
$_repository = Git->repository(Repository => ".git");
}
- command_noisy('config', 'core.autocrlf', 'false');
my $set;
my $pfx = "svn-remote.$Git::SVN::default_repo_id";
foreach my $i (keys %icv) {
$src=~s/^http:/https:/;
}
+ ::_req_svn();
+
my $ctx = SVN::Client->new(
auth => Git::SVN::Ra::_auth_providers(),
log_msg => sub {
if ($@) {
$result .= "Repository Root: (offline)\n";
}
+ ::_req_svn();
$result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" &&
($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir");
$result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n";
for my $ticket ( @tickets ) {
my ($uuid, $path, $rev) = split /:/, $ticket;
if ( $uuid eq $self->ra_uuid ) {
- my $url = $self->rewrite_root || $self->{url};
+ my $url = $self->{url};
my $repos_root = $url;
my $branch_from = $path;
$branch_from =~ s{^/}{};
# are now marked as merge, we can add the tip as a parent.
my @merges = split "\n", $mergeinfo;
my @merge_tips;
- my $url = $self->rewrite_root || $self->{url};
+ my $url = $self->{url};
my $uuid = $self->ra_uuid;
my %ranges;
for my $merge ( @merges ) {
"$new_parents[$i]..$new_parents[$j]",
);
if ( !$revs ) {
- undef($new_parents[$i]);
+ undef($new_parents[$j]);
}
}
}
$url =~ s!/+$!!;
return $RA if ($RA && $RA->{url} eq $url);
+ ::_req_svn();
+
SVN::_Core::svn_config_ensure($config_dir, undef);
my ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers);
my $config = SVN::Core::config_get_config($config_dir);
{
int handled = 0;
+ if (!getenv("GIT_ASKPASS") && getenv("SSH_ASKPASS"))
+ setenv("GIT_ASKPASS", getenv("SSH_ASKPASS"), 1);
+
while (*argc > 0) {
const char *cmd = (*argv)[0];
if (cmd[0] != '-')
{ "fsck-objects", cmd_fsck, RUN_SETUP },
{ "gc", cmd_gc, RUN_SETUP },
{ "get-tar-commit-id", cmd_get_tar_commit_id },
- { "grep", cmd_grep, RUN_SETUP | USE_PAGER },
+ { "grep", cmd_grep, USE_PAGER },
{ "hash-object", cmd_hash_object },
{ "help", cmd_help },
{ "index-pack", cmd_index_pack },
# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
sub to_utf8 {
my $str = shift;
+ return undef unless defined $str;
if (utf8::valid($str)) {
utf8::decode($str);
return $str;
# correct, but quoted slashes look too horrible in bookmarks
sub esc_param {
my $str = shift;
+ return undef unless defined $str;
$str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
$str =~ s/ /\+/g;
return $str;
# quote unsafe chars in whole URL, so some charactrs cannot be quoted
sub esc_url {
my $str = shift;
+ return undef unless defined $str;
$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
$str =~ s/\+/%2B/g;
$str =~ s/ /\+/g;
my $str = shift;
my %opts = @_;
+ return undef unless defined $str;
+
$str = to_utf8($str);
$str = $cgi->escapeHTML($str);
if ($opts{'-nbsp'}) {
my $str = shift;
my %opts = @_;
+ return undef unless defined $str;
+
$str = to_utf8($str);
$str = $cgi->escapeHTML($str);
if ($opts{'-nbsp'}) {
"</html>";
}
-# die_error(<http_status_code>, <error_message>)
+# die_error(<http_status_code>, <error_message>[, <detailed_html_description>])
# Example: die_error(404, 'Hash not found')
# By convention, use the following status codes (as defined in RFC 2616):
# 400: Invalid or missing CGI parameters, or
# or down for maintenance). Generally, this is a temporary state.
sub die_error {
my $status = shift || 500;
- my $error = shift || "Internal server error";
+ my $error = esc_html(shift) || "Internal Server Error";
my $extra = shift;
my %http_responses = (
#include "run-command.h"
#ifdef NO_OPENSSL
typedef void *SSL;
+#else
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
#endif
struct store_conf {
int use_ssl;
int ssl_verify;
int use_html;
+ char *auth_method;
+};
+
+static struct imap_server_conf server = {
+ NULL, /* name */
+ NULL, /* tunnel */
+ NULL, /* host */
+ 0, /* port */
+ NULL, /* user */
+ NULL, /* pass */
+ 0, /* use_ssl */
+ 1, /* ssl_verify */
+ 0, /* use_html */
+ NULL, /* auth_method */
};
struct imap_store_conf {
LITERALPLUS,
NAMESPACE,
STARTTLS,
+ AUTH_CRAM_MD5,
};
static const char *cap_list[] = {
"LITERAL+",
"NAMESPACE",
"STARTTLS",
+ "AUTH=CRAM-MD5",
};
#define RESP_OK 0
free(ctx);
}
+#ifndef NO_OPENSSL
+
+/*
+ * hexchar() and cram() functions are based on the code from the isync
+ * project (http://isync.sf.net/).
+ */
+static char hexchar(unsigned int b)
+{
+ return b < 10 ? '0' + b : 'a' + (b - 10);
+}
+
+#define ENCODED_SIZE(n) (4*((n+2)/3))
+static char *cram(const char *challenge_64, const char *user, const char *pass)
+{
+ int i, resp_len, encoded_len, decoded_len;
+ HMAC_CTX hmac;
+ unsigned char hash[16];
+ char hex[33];
+ char *response, *response_64, *challenge;
+
+ /*
+ * length of challenge_64 (i.e. base-64 encoded string) is a good
+ * enough upper bound for challenge (decoded result).
+ */
+ encoded_len = strlen(challenge_64);
+ challenge = xmalloc(encoded_len);
+ decoded_len = EVP_DecodeBlock((unsigned char *)challenge,
+ (unsigned char *)challenge_64, encoded_len);
+ if (decoded_len < 0)
+ die("invalid challenge %s", challenge_64);
+ HMAC_Init(&hmac, (unsigned char *)pass, strlen(pass), EVP_md5());
+ HMAC_Update(&hmac, (unsigned char *)challenge, decoded_len);
+ HMAC_Final(&hmac, hash, NULL);
+ HMAC_CTX_cleanup(&hmac);
+
+ hex[32] = 0;
+ for (i = 0; i < 16; i++) {
+ hex[2 * i] = hexchar((hash[i] >> 4) & 0xf);
+ hex[2 * i + 1] = hexchar(hash[i] & 0xf);
+ }
+
+ /* response: "<user> <digest in hex>" */
+ resp_len = strlen(user) + 1 + strlen(hex) + 1;
+ response = xmalloc(resp_len);
+ sprintf(response, "%s %s", user, hex);
+
+ response_64 = xmalloc(ENCODED_SIZE(resp_len) + 1);
+ encoded_len = EVP_EncodeBlock((unsigned char *)response_64,
+ (unsigned char *)response, resp_len);
+ if (encoded_len < 0)
+ die("EVP_EncodeBlock error");
+ response_64[encoded_len] = '\0';
+ return (char *)response_64;
+}
+
+#else
+
+static char *cram(const char *challenge_64, const char *user, const char *pass)
+{
+ die("If you want to use CRAM-MD5 authenticate method, "
+ "you have to build git-imap-send with OpenSSL library.");
+}
+
+#endif
+
+static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const char *prompt)
+{
+ int ret;
+ char *response;
+
+ response = cram(prompt, server.user, server.pass);
+
+ ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
+ if (ret != strlen(response))
+ return error("IMAP error: sending response failed\n");
+
+ free(response);
+
+ return 0;
+}
+
static struct store *imap_open_store(struct imap_server_conf *srvc)
{
struct imap_store *ctx;
if (!imap->buf.sock.ssl)
imap_warn("*** IMAP Warning *** Password is being "
"sent in the clear\n");
- if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) {
- fprintf(stderr, "IMAP error: LOGIN failed\n");
- goto bail;
+
+ if (srvc->auth_method) {
+ struct imap_cmd_cb cb;
+
+ if (!strcmp(srvc->auth_method, "CRAM-MD5")) {
+ if (!CAP(AUTH_CRAM_MD5)) {
+ fprintf(stderr, "You specified"
+ "CRAM-MD5 as authentication method, "
+ "but %s doesn't support it.\n", srvc->host);
+ goto bail;
+ }
+ /* CRAM-MD5 */
+
+ memset(&cb, 0, sizeof(cb));
+ cb.cont = auth_cram_md5;
+ if (imap_exec(ctx, &cb, "AUTHENTICATE CRAM-MD5") != RESP_OK) {
+ fprintf(stderr, "IMAP error: AUTHENTICATE CRAM-MD5 failed\n");
+ goto bail;
+ }
+ } else {
+ fprintf(stderr, "Unknown authentication method:%s\n", srvc->host);
+ goto bail;
+ }
+ } else {
+ if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) {
+ fprintf(stderr, "IMAP error: LOGIN failed\n");
+ goto bail;
+ }
}
} /* !preauth */
return 1;
}
-static struct imap_server_conf server = {
- NULL, /* name */
- NULL, /* tunnel */
- NULL, /* host */
- 0, /* port */
- NULL, /* user */
- NULL, /* pass */
- 0, /* use_ssl */
- 1, /* ssl_verify */
- 0, /* use_html */
-};
-
static char *imap_folder;
static int git_imap_config(const char *key, const char *val, void *cb)
server.port = git_config_int(key, val);
else if (!strcmp("tunnel", key))
server.tunnel = xstrdup(val);
+ else if (!strcmp("authmethod", key))
+ server.auth_method = xstrdup(val);
+
return 0;
}
merge:1;
};
-static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
-{
- unsigned long size;
- enum object_type type;
-
- if (!hashcmp(sha1, null_sha1)) {
- mm->ptr = xstrdup("");
- mm->size = 0;
- return;
- }
-
- mm->ptr = read_sha1_file(sha1, &type, &size);
- if (!mm->ptr || type != OBJ_BLOB)
- die("unable to read blob object %s", sha1_to_hex(sha1));
- mm->size = size;
-}
-
static int merge_3way(struct merge_options *o,
mmbuffer_t *result_buf,
struct diff_filespec *one,
name2 = xstrdup(mkpath("%s", branch2));
}
- fill_mm(one->sha1, &orig);
- fill_mm(a->sha1, &src1);
- fill_mm(b->sha1, &src2);
+ read_mmblob(&orig, one->sha1);
+ read_mmblob(&src1, a->sha1);
+ read_mmblob(&src2, b->sha1);
merge_status = ll_merge(result_buf, a->path, &orig,
&src1, name1, &src2, name2,
}
return NULL;
}
+
+/*
+ * The per-object header is a pretty dense thing, which is
+ * - first byte: low four bits are "size", then three bits of "type",
+ * and the high bit is "size continues".
+ * - each byte afterwards: low seven bits are size continuation,
+ * with the high bit being "size continues"
+ */
+int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned char *hdr)
+{
+ int n = 1;
+ unsigned char c;
+
+ if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
+ die("bad type %d", type);
+
+ c = (type << 4) | (size & 15);
+ size >>= 4;
+ while (size) {
+ *hdr++ = c | 0x80;
+ c = size & 0x7f;
+ size >>= 7;
+ n++;
+ }
+ *hdr = c;
+ return n;
+}
extern int verify_pack(struct packed_git *);
extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
extern char *index_pack_lockfile(int fd);
+extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned char *);
#define PH_ERROR_EOF (-1)
#define PH_ERROR_PACK_SIGNATURE (-2)
#include "parse-options.h"
#include "cache.h"
#include "commit.h"
+#include "color.h"
static int parse_options_usage(const char * const *usagestr,
const struct option *opts);
return 0;
}
+int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
+ int unset)
+{
+ int value;
+
+ if (!arg)
+ arg = unset ? "never" : (const char *)opt->defval;
+ value = git_config_colorbool(NULL, arg, -1);
+ if (value < 0)
+ return opterror(opt,
+ "expects \"always\", \"auto\", or \"never\"", 0);
+ *(int *)opt->value = value;
+ return 0;
+}
+
int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
int unset)
{
PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
#define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \
"FILE", (h) }
+#define OPT_COLOR_FLAG(s, l, v, h) \
+ { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \
+ parse_opt_color_flag_cb, (intptr_t)"always" }
+
/* parse_options() will filter out the processed options and leave the
* non-option arguments in argv[].
/*----- some often used options -----*/
extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
+extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
extern int parse_opt_with_commit(const struct option *, const char *, int);
extern int parse_opt_tertiary(const struct option *, const char *, int);
{ OPTION_CALLBACK, 0, "abbrev", (var), "n", \
"use <n> digits to display SHA-1s", \
PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
+#define OPT__COLOR(var, h) \
+ OPT_COLOR_FLAG(0, "color", (var), (h))
#endif
if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
validate_headref("HEAD") == 0) {
- setenv(GIT_DIR_ENVIRONMENT, ".", 1);
+ set_git_dir(".");
check_repository_format();
return path;
}
}
}
}
+
+int offset_1st_component(const char *path)
+{
+ if (has_dos_drive_prefix(path))
+ return 2 + is_dir_sep(path[2]);
+ return is_dir_sep(path[0]);
+}
($self->{hash_object_pid}, $self->{hash_object_in},
$self->{hash_object_out}, $self->{hash_object_ctx}) =
- command_bidi_pipe(qw(hash-object -w --stdin-paths));
+ command_bidi_pipe(qw(hash-object -w --stdin-paths --no-filters));
}
sub _close_hash_and_insert_object {
if (normalize_path_copy(sanitized, sanitized))
goto error_out;
if (is_absolute_path(orig)) {
- size_t len, total;
+ size_t root_len, len, total;
const char *work_tree = get_git_work_tree();
if (!work_tree)
goto error_out;
len = strlen(work_tree);
+ root_len = offset_1st_component(work_tree);
total = strlen(sanitized) + 1;
if (strncmp(sanitized, work_tree, len) ||
- (sanitized[len] != '\0' && sanitized[len] != '/')) {
+ (len > root_len && sanitized[len] != '\0' && sanitized[len] != '/')) {
error_out:
die("'%s' is outside repository", orig);
}
static char cwd[PATH_MAX+1];
const char *gitdirenv;
const char *gitfile_dir;
- int len, offset, ceil_offset;
+ int len, offset, ceil_offset, root_len;
/*
* Let's assume that we are in a git repository.
if (!work_tree_env)
inside_work_tree = 0;
if (offset != len) {
- cwd[offset] = '\0';
- setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
+ root_len = offset_1st_component(cwd);
+ cwd[offset > root_len ? offset : root_len] = '\0';
+ set_git_dir(cwd);
} else
- setenv(GIT_DIR_ENVIRONMENT, ".", 1);
+ set_git_dir(".");
check_repository_format_gently(nongit_ok);
return NULL;
}
inside_git_dir = 0;
if (!work_tree_env)
inside_work_tree = 1;
- git_work_tree_cfg = xstrndup(cwd, offset);
+ root_len = offset_1st_component(cwd);
+ git_work_tree_cfg = xstrndup(cwd, offset > root_len ? offset : root_len);
if (check_repository_format_gently(nongit_ok))
return NULL;
if (offset == len)
const unsigned char null_sha1[20];
-static inline int offset_1st_component(const char *path)
-{
- if (has_dos_drive_prefix(path))
- return 2 + (path[2] == '/');
- return *path == '/';
-}
-
int safe_create_leading_directories(char *path)
{
char *pos = path + offset_1st_component(path);
! test -f template-blank/.git/info/exclude
'
+test_expect_success 'init with init.templatedir set' '
+ mkdir templatedir-source &&
+ echo Content >templatedir-source/file &&
+ (
+ HOME="`pwd`" &&
+ export HOME &&
+ test_config="${HOME}/.gitconfig" &&
+ git config -f "$test_config" init.templatedir "${HOME}/templatedir-source" &&
+ mkdir templatedir-set &&
+ cd templatedir-set &&
+ unset GIT_CONFIG_NOGLOBAL &&
+ unset GIT_TEMPLATE_DIR &&
+ NO_SET_GIT_TEMPLATE_DIR=t &&
+ export NO_SET_GIT_TEMPLATE_DIR &&
+ git init
+ ) &&
+ test_cmp templatedir-source/file templatedir-set/.git/file
+'
+
test_expect_success 'init --bare/--shared overrides system/global config' '
(
HOME="`pwd`" &&
echo example | test_must_fail git hash-object --stdin-paths --path=foo
'
-test_expect_success "Can't use --stdin-paths with --no-filters" '
- echo example | test_must_fail git hash-object --stdin-paths --no-filters
-'
-
test_expect_success "Can't use --path with --no-filters" '
test_must_fail git hash-object --no-filters --path=foo
'
git config --unset core.autocrlf
'
+test_expect_success 'check that --no-filters option works with --stdin-paths' '
+ echo fooQ | tr Q "\\015" >file0 &&
+ cp file0 file1 &&
+ echo "file0 -crlf" >.gitattributes &&
+ echo "file1 crlf" >>.gitattributes &&
+ git config core.autocrlf true &&
+ file0_sha=$(git hash-object file0) &&
+ file1_sha=$(git hash-object file1) &&
+ test "$file0_sha" != "$file1_sha" &&
+ nofilters_file1=$(echo "file1" | git hash-object --stdin-paths --no-filters) &&
+ test "$file0_sha" = "$nofilters_file1" &&
+ git config --unset core.autocrlf
+'
+
pop_repo
for args in "-w --stdin" "--stdin -w"; do
--- /dev/null
+#!/bin/sh
+
+test_description='Test Git when git repository is located at root
+
+This test requires write access in root. Do not bother if you do not
+have a throwaway chroot or VM.
+
+Script t1509/prepare-chroot.sh may help you setup chroot, then you
+can chroot in and execute this test from there.
+'
+
+. ./test-lib.sh
+
+test_cmp_val() {
+ echo "$1" > expected
+ echo "$2" > result
+ test_cmp expected result
+}
+
+test_vars() {
+ test_expect_success "$1: gitdir" '
+ test_cmp_val "'"$2"'" "$(git rev-parse --git-dir)"
+ '
+
+ test_expect_success "$1: worktree" '
+ test_cmp_val "'"$3"'" "$(git rev-parse --show-toplevel)"
+ '
+
+ test_expect_success "$1: prefix" '
+ test_cmp_val "'"$4"'" "$(git rev-parse --show-prefix)"
+ '
+}
+
+test_foobar_root() {
+ test_expect_success 'add relative' '
+ test -z "$(cd / && git ls-files)" &&
+ git add foo/foome &&
+ git add foo/bar/barme &&
+ git add me &&
+ ( cd / && git ls-files --stage ) > result &&
+ test_cmp /ls.expected result &&
+ rm "$(git rev-parse --git-dir)/index"
+ '
+
+ test_expect_success 'add absolute' '
+ test -z "$(cd / && git ls-files)" &&
+ git add /foo/foome &&
+ git add /foo/bar/barme &&
+ git add /me &&
+ ( cd / && git ls-files --stage ) > result &&
+ test_cmp /ls.expected result &&
+ rm "$(git rev-parse --git-dir)/index"
+ '
+
+}
+
+test_foobar_foo() {
+ test_expect_success 'add relative' '
+ test -z "$(cd / && git ls-files)" &&
+ git add foome &&
+ git add bar/barme &&
+ git add ../me &&
+ ( cd / && git ls-files --stage ) > result &&
+ test_cmp /ls.expected result &&
+ rm "$(git rev-parse --git-dir)/index"
+ '
+
+ test_expect_success 'add absolute' '
+ test -z "$(cd / && git ls-files)" &&
+ git add /foo/foome &&
+ git add /foo/bar/barme &&
+ git add /me &&
+ ( cd / && git ls-files --stage ) > result &&
+ test_cmp /ls.expected result &&
+ rm "$(git rev-parse --git-dir)/index"
+ '
+}
+
+test_foobar_foobar() {
+ test_expect_success 'add relative' '
+ test -z "$(cd / && git ls-files)" &&
+ git add ../foome &&
+ git add barme &&
+ git add ../../me &&
+ ( cd / && git ls-files --stage ) > result &&
+ test_cmp /ls.expected result &&
+ rm "$(git rev-parse --git-dir)/index"
+ '
+
+ test_expect_success 'add absolute' '
+ test -z "$(cd / && git ls-files)" &&
+ git add /foo/foome &&
+ git add /foo/bar/barme &&
+ git add /me &&
+ ( cd / && git ls-files --stage ) > result &&
+ test_cmp /ls.expected result &&
+ rm "$(git rev-parse --git-dir)/index"
+ '
+}
+
+if ! test_have_prereq POSIXPERM || ! [ -w / ]; then
+ say "Dangerous test skipped. Read this test if you want to execute it"
+ test_done
+fi
+
+if [ "$IKNOWWHATIAMDOING" != "YES" ]; then
+ say "You must set env var IKNOWWHATIAMDOING=YES in order to run this test"
+ test_done
+fi
+
+if [ "$UID" = 0 ]; then
+ say "No you can't run this with root"
+ test_done
+fi
+
+ONE_SHA1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d
+
+test_expect_success 'setup' '
+ rm -rf /foo
+ mkdir /foo &&
+ mkdir /foo/bar &&
+ echo 1 > /foo/foome &&
+ echo 1 > /foo/bar/barme &&
+ echo 1 > /me
+'
+
+say "GIT_DIR absolute, GIT_WORK_TREE set"
+
+test_expect_success 'go to /' 'cd /'
+
+cat >ls.expected <<EOF
+100644 $ONE_SHA1 0 foo/bar/barme
+100644 $ONE_SHA1 0 foo/foome
+100644 $ONE_SHA1 0 me
+EOF
+
+export GIT_DIR="$TRASH_DIRECTORY/.git"
+export GIT_WORK_TREE=/
+
+test_vars 'abs gitdir, root' "$GIT_DIR" "/" ""
+test_foobar_root
+
+test_expect_success 'go to /foo' 'cd /foo'
+
+test_vars 'abs gitdir, foo' "$GIT_DIR" "/" "foo/"
+test_foobar_foo
+
+test_expect_success 'go to /foo/bar' 'cd /foo/bar'
+
+test_vars 'abs gitdir, foo/bar' "$GIT_DIR" "/" "foo/bar/"
+test_foobar_foobar
+
+say "GIT_DIR relative, GIT_WORK_TREE set"
+
+test_expect_success 'go to /' 'cd /'
+
+export GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git"
+export GIT_WORK_TREE=/
+
+test_vars 'rel gitdir, root' "$GIT_DIR" "/" ""
+test_foobar_root
+
+test_expect_success 'go to /foo' 'cd /foo'
+
+export GIT_DIR="../$TRASH_DIRECTORY/.git"
+export GIT_WORK_TREE=/
+
+test_vars 'rel gitdir, foo' "$TRASH_DIRECTORY/.git" "/" "foo/"
+test_foobar_foo
+
+test_expect_success 'go to /foo/bar' 'cd /foo/bar'
+
+export GIT_DIR="../../$TRASH_DIRECTORY/.git"
+export GIT_WORK_TREE=/
+
+test_vars 'rel gitdir, foo/bar' "$TRASH_DIRECTORY/.git" "/" "foo/bar/"
+test_foobar_foobar
+
+say "GIT_DIR relative, GIT_WORK_TREE relative"
+
+test_expect_success 'go to /' 'cd /'
+
+export GIT_DIR="$(echo $TRASH_DIRECTORY|sed 's,^/,,')/.git"
+export GIT_WORK_TREE=.
+
+test_vars 'rel gitdir, root' "$GIT_DIR" "/" ""
+test_foobar_root
+
+test_expect_success 'go to /' 'cd /foo'
+
+export GIT_DIR="../$TRASH_DIRECTORY/.git"
+export GIT_WORK_TREE=..
+
+test_vars 'rel gitdir, foo' "$TRASH_DIRECTORY/.git" "/" "foo/"
+test_foobar_foo
+
+test_expect_success 'go to /foo/bar' 'cd /foo/bar'
+
+export GIT_DIR="../../$TRASH_DIRECTORY/.git"
+export GIT_WORK_TREE=../..
+
+test_vars 'rel gitdir, foo/bar' "$TRASH_DIRECTORY/.git" "/" "foo/bar/"
+test_foobar_foobar
+
+say ".git at root"
+
+unset GIT_DIR
+unset GIT_WORK_TREE
+
+test_expect_success 'go to /' 'cd /'
+test_expect_success 'setup' '
+ rm -rf /.git
+ echo "Initialized empty Git repository in /.git/" > expected &&
+ git init > result &&
+ test_cmp expected result
+'
+
+test_vars 'auto gitdir, root' ".git" "/" ""
+test_foobar_root
+
+test_expect_success 'go to /foo' 'cd /foo'
+test_vars 'auto gitdir, foo' "/.git" "/" "foo/"
+test_foobar_foo
+
+test_expect_success 'go to /foo/bar' 'cd /foo/bar'
+test_vars 'auto gitdir, foo/bar' "/.git" "/" "foo/bar/"
+test_foobar_foobar
+
+test_expect_success 'cleanup' 'rm -rf /.git'
+
+say "auto bare gitdir"
+
+# DESTROYYYYY!!!!!
+test_expect_success 'setup' '
+ rm -rf /refs /objects /info /hooks
+ rm /*
+ cd / &&
+ echo "Initialized empty Git repository in /" > expected &&
+ git init --bare > result &&
+ test_cmp expected result
+'
+
+test_vars 'auto gitdir, root' "." "" ""
+
+test_expect_success 'go to /foo' 'cd /foo'
+
+test_vars 'auto gitdir, root' "/" "" ""
+
+test_done
--- /dev/null
+*.o
+*~
+*.bak
+*.c
+*.h
+.git
+contrib
+Documentation
+git-gui
+gitk-git
+gitweb
+t/t4013
+t/t5100
+t/t5515
--- /dev/null
+#!/bin/sh
+
+die() {
+ echo >&2 "$@"
+ exit 1
+}
+
+xmkdir() {
+ while [ -n "$1" ]; do
+ [ -d "$1" ] || mkdir "$1" || die "Unable to mkdir $1"
+ shift
+ done
+}
+
+R="$1"
+
+[ -n "$R" ] || die "Usage: prepare-chroot.sh <root>"
+[ -x git ] || die "This script needs to be executed at git source code's top directory"
+[ -x /bin/busybox ] || die "You need busybox"
+
+xmkdir "$R" "$R/bin" "$R/etc" "$R/lib" "$R/dev"
+[ -c "$R/dev/null" ] || die "/dev/null is missing. Do mknod $R/dev/null c 1 3 && chmod 666 $R/dev/null"
+echo "root:x:0:0:root:/:/bin/sh" > "$R/etc/passwd"
+echo "$(id -nu):x:$(id -u):$(id -g)::$(pwd)/t:/bin/sh" >> "$R/etc/passwd"
+echo "root::0:root" > "$R/etc/group"
+echo "$(id -ng)::$(id -g):$(id -nu)" >> "$R/etc/group"
+
+[ -x "$R/bin/busybox" ] || cp /bin/busybox "$R/bin/busybox"
+[ -x "$R/bin/sh" ] || ln -s /bin/busybox "$R/bin/sh"
+[ -x "$R/bin/su" ] || ln -s /bin/busybox "$R/bin/su"
+
+mkdir -p "$R$(pwd)"
+rsync --exclude-from t/t1509/excludes -Ha . "$R$(pwd)"
+ldd git | grep '/' | sed 's,.*\s\(/[^ ]*\).*,\1,' | while read i; do
+ mkdir -p "$R$(dirname $i)"
+ cp "$i" "$R/$i"
+done
+echo "Execute this in root: 'chroot $R /bin/su - $(id -nu)'"
'
+test_expect_success '"add -u non-existent" should fail' '
+ test_must_fail git add -u non-existent &&
+ ! (git ls-files | grep "non-existent")
+'
+
test_done
git add track-this
'
+test_expect_success '"add non-existent" should fail' '
+ test_must_fail git add non-existent &&
+ ! (git ls-files | grep "non-existent")
+'
+
test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2010 Stefan-W. Hahn
+#
+
+test_description='git-am mbox with dos line ending.
+
+'
+. ./test-lib.sh
+
+# Three patches which will be added as files with dos line ending.
+
+cat >file1 <<\EOF
+line 1
+EOF
+
+cat >file1a <<\EOF
+line 1
+line 4
+EOF
+
+cat >file2 <<\EOF
+line 1
+line 2
+EOF
+
+cat >file3 <<\EOF
+line 1
+line 2
+line 3
+EOF
+
+test_expect_success 'setup repository with dos files' '
+ append_cr <file1 >file &&
+ git add file &&
+ git commit -m Initial &&
+ git tag initial &&
+ append_cr <file2 >file &&
+ git commit -a -m Second &&
+ append_cr <file3 >file &&
+ git commit -a -m Third
+'
+
+test_expect_success 'am with dos files without --keep-cr' '
+ git checkout -b dosfiles initial &&
+ git format-patch -k initial..master &&
+ test_must_fail git am -k -3 000*.patch &&
+ git am --abort &&
+ rm -rf .git/rebase-apply 000*.patch
+'
+
+test_expect_success 'am with dos files with --keep-cr' '
+ git checkout -b dosfiles-keep-cr initial &&
+ git format-patch -k --stdout initial..master | git am --keep-cr -k -3 &&
+ git diff --exit-code master
+'
+
+test_expect_success 'am with dos files config am.keepcr' '
+ git config am.keepcr 1 &&
+ git checkout -b dosfiles-conf-keepcr initial &&
+ git format-patch -k --stdout initial..master | git am -k -3 &&
+ git diff --exit-code master
+'
+
+test_expect_success 'am with dos files config am.keepcr overriden by --no-keep-cr' '
+ git config am.keepcr 1 &&
+ git checkout -b dosfiles-conf-keepcr-override initial &&
+ git format-patch -k initial..master &&
+ test_must_fail git am -k -3 --no-keep-cr 000*.patch &&
+ git am --abort &&
+ rm -rf .git/rebase-apply 000*.patch
+'
+
+test_expect_success 'am with dos files with --keep-cr continue' '
+ git checkout -b dosfiles-keep-cr-continue initial &&
+ git format-patch -k initial..master &&
+ append_cr <file1a >file &&
+ git commit -m "different patch" file &&
+ test_must_fail git am --keep-cr -k -3 000*.patch &&
+ append_cr <file2 >file &&
+ git add file &&
+ git am -3 --resolved &&
+ git diff --exit-code master
+'
+
+test_expect_success 'am with unix files config am.keepcr overriden by --no-keep-cr' '
+ git config am.keepcr 1 &&
+ git checkout -b unixfiles-conf-keepcr-override initial &&
+ cp -f file1 file &&
+ git commit -m "line ending to unix" file &&
+ git format-patch -k initial..master &&
+ git am -k -3 --no-keep-cr 000*.patch &&
+ git diff --exit-code -w master
+'
+
+test_done
'
+test_expect_success 'fetch --dry-run' '
+
+ rm -f .git/FETCH_HEAD &&
+ git fetch --dry-run . &&
+ ! test -f .git/FETCH_HEAD
+'
+
test_expect_success "should be able to fetch with duplicate refspecs" '
mkdir dups &&
cd dups &&
test_expect_success "merge without conflict" \
"git merge-file test.txt orig.txt new2.txt"
+cp new1.txt test.txt
+test_expect_success "merge without conflict (--quiet)" \
+ "git merge-file --quiet 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"
test_cmp expected actual
'
+test_expect_success 'outside of git repository' '
+ rm -fr non &&
+ mkdir -p non/git/sub &&
+ echo hello >non/git/file1 &&
+ echo world >non/git/sub/file2 &&
+ echo ".*o*" >non/git/.gitignore &&
+ {
+ echo file1:hello &&
+ echo sub/file2:world
+ } >non/expect.full &&
+ echo file2:world >non/expect.sub
+ (
+ GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
+ export GIT_CEILING_DIRECTORIES &&
+ cd non/git &&
+ test_must_fail git grep o &&
+ git grep --no-index o >../actual.full &&
+ test_cmp ../expect.full ../actual.full
+ cd sub &&
+ test_must_fail git grep o &&
+ git grep --no-index o >../../actual.sub &&
+ test_cmp ../../expect.sub ../../actual.sub
+ )
+'
+
+test_expect_success 'inside git repository but with --no-index' '
+ rm -fr is &&
+ mkdir -p is/git/sub &&
+ echo hello >is/git/file1 &&
+ echo world >is/git/sub/file2 &&
+ echo ".*o*" >is/git/.gitignore &&
+ {
+ echo file1:hello &&
+ echo sub/file2:world
+ } >is/expect.full &&
+ : >is/expect.empty &&
+ echo file2:world >is/expect.sub
+ (
+ cd is/git &&
+ git init &&
+ test_must_fail git grep o >../actual.full &&
+ test_cmp ../expect.empty ../actual.full &&
+ git grep --no-index o >../actual.full &&
+ test_cmp ../expect.full ../actual.full &&
+ cd sub &&
+ test_must_fail git grep o >../../actual.sub &&
+ test_cmp ../../expect.empty ../../actual.sub &&
+ git grep --no-index o >../../actual.sub &&
+ test_cmp ../../expect.sub ../../actual.sub
+ )
+'
+
test_expect_success 'setup double-dash tests' '
cat >double-dash <<EOF &&
--
test_must_fail git submodule summary --files --cached
"
+test_expect_success 'should not fail in an empty repo' "
+ git init xyzzy &&
+ cd xyzzy &&
+ git submodule summary >output 2>&1 &&
+ test_cmp output /dev/null
+"
+
test_done
. ./lib-git-svn.sh
# Tested with: svn, version 1.4.4 (r25188)
+# Tested with: svn, version 1.6.[12345689]
v=`svn_cmd --version | sed -n -e 's/^svn, version \(1\.[0-9]*\.[0-9]*\).*$/\1/p'`
case $v in
-1.[45].*)
+1.[456].*)
;;
*)
say "skipping svn-info test (SVN version: $v not supported)"
svnadmin load -q '$rawsvnrepo' \
< '$TEST_DIRECTORY/t9150/svk-merge.dump' &&
git svn init --minimize-url -R svkmerge \
+ --rewrite-root=http://svn.example.org \
-T trunk -b branches '$svnrepo' &&
git svn fetch --all
"
svnadmin load -q '$rawsvnrepo' \
< '$TEST_DIRECTORY/t9151/svn-mergeinfo.dump' &&
git svn init --minimize-url -R svnmerge \
+ --rewrite-root=http://svn.example.org \
-T trunk -b branches '$svnrepo' &&
git svn fetch --all
"
[ -z "$bad_non_merges" ]
'
+test_expect_success 'commit made to merged branch is reachable from the merge' '
+ before_commit=$(git rev-list --all --grep="trunk commit before merging trunk to b2")
+ merge_commit=$(git rev-list --all --grep="Merge trunk to b2")
+ not_reachable=$(git rev-list -1 $before_commit --not $merge_commit)
+ [ -z "$not_reachable" ]
+ '
+
+test_expect_success 'merging two branches in one commit is detected correctly' '
+ f1_commit=$(git rev-list --all --grep="make f1 branch from trunk")
+ f2_commit=$(git rev-list --all --grep="make f2 branch from trunk")
+ merge_commit=$(git rev-list --all --grep="Merge f1 and f2 to trunk")
+ not_reachable=$(git rev-list -1 $f1_commit $f2_commit --not $merge_commit)
+ [ -z "$not_reachable" ]
+ '
+
test_expect_failure 'everything got merged in the end' '
unmerged=$(git rev-list --all --not master)
[ -z "$unmerged" ]
i=$(commit $i "non-merge right to trunk 2")
cd ..
+say "Branching b1 from trunk"
+svn update
+svn cp trunk branches/b1
+i=$(commit $i "make b1 branch from trunk")
+
+say "Branching b2 from trunk"
+svn update
+svn cp trunk branches/b2
+i=$(commit $i "make b2 branch from trunk")
+
+say "Make a commit to b2"
+svn update
+cd branches/b2
+echo "b2" > b2file
+svn add b2file
+i=$(commit $i "b2 update 1")
+cd ../..
+
+say "Make a commit to b1"
+svn update
+cd branches/b1
+echo "b1" > b1file
+svn add b1file
+i=$(commit $i "b1 update 1")
+cd ../..
+
+say "Merge b1 to trunk"
+svn update
+cd trunk
+svn merge ../branches/b1/ --accept postpone
+i=$(commit $i "Merge b1 to trunk")
+cd ..
+
+say "Make a commit to trunk before merging trunk to b2"
+svn update
+cd trunk
+echo "trunk" > trunkfile
+svn add trunkfile
+i=$(commit $i "trunk commit before merging trunk to b2")
+cd ..
+
+say "Merge trunk to b2"
+svn update
+cd branches/b2
+svn merge ../../trunk/ --accept postpone
+i=$(commit $i "Merge trunk to b2")
+cd ../..
+
+say "Merge b2 to trunk"
+svn update
+cd trunk
+svn merge ../branches/b2/ --accept postpone
+svn resolved b1file
+svn resolved trunkfile
+i=$(commit $i "Merge b2 to trunk")
+cd ..
+
+say "Creating f1 from trunk with a new file"
+svn update
+svn cp trunk branches/f1
+cd branches/f1
+echo "f1" > f1file
+svn add f1file
+cd ../..
+i=$(commit $i "make f1 branch from trunk with a new file")
+
+say "Creating f2 from trunk with a new file"
+svn update
+svn cp trunk branches/f2
+cd branches/f2
+echo "f2" > f2file
+svn add f2file
+cd ../..
+i=$(commit $i "make f2 branch from trunk with a new file")
+
+say "Merge f1 and f2 to trunk in one go"
+svn update
+cd trunk
+svn merge ../branches/f1/ --accept postpone
+svn merge ../branches/f2/ --accept postpone
+i=$(commit $i "Merge f1 and f2 to trunk")
+cd ..
+
say "Adding subdirectory to LEFT"
svn update
cd branches/left
say "Make PARTIAL branch"
svn update
-i=$(commit $i "make partial branch")
svn cp trunk/subdir branches/partial
+i=$(commit $i "make partial branch")
say "Make a commit to PARTIAL"
svn update
say "Tagging trunk"
svn update
-i=$(commit $i "tagging v1.0")
svn cp trunk tags/v1.0
+i=$(commit $i "tagging v1.0")
say "Branching BUGFIX from v1.0"
svn update
-i=$(commit $i "make bugfix branch from tag")
svn cp tags/v1.0 branches/bugfix
+i=$(commit $i "make bugfix branch from tag")
say "Make a commit to BUGFIX"
svn update
Revision-number: 25
+Prop-content-length: 129
+Content-length: 129
+
+K 7
+svn:log
+V 31
+(r25) make b1 branch from trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:18:56.084589Z
+PROPS-END
+
+Node-path: branches/b1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 24
+Node-copyfrom-path: trunk
+
+
+Revision-number: 26
+Prop-content-length: 129
+Content-length: 129
+
+K 7
+svn:log
+V 31
+(r26) make b2 branch from trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:18:59.076940Z
+PROPS-END
+
+Node-path: branches/b2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 25
+Node-copyfrom-path: trunk
+
+
+Revision-number: 27
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 17
+(r27) b2 update 1
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:01.095762Z
+PROPS-END
+
+Node-path: branches/b2/b2file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: 5edbdd57cba621eb3c6e601bf563b4dc
+Text-content-sha1: 9d4b38049776bd0a2074d67cad23f8eaed35a3b3
+Content-length: 13
+
+PROPS-END
+b2
+
+
+Revision-number: 28
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 17
+(r28) b1 update 1
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:03.097465Z
+PROPS-END
+
+Node-path: branches/b1/b1file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: 08778dfd9ac4f603231896aba7aad523
+Text-content-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f
+Content-length: 13
+
+PROPS-END
+b1
+
+
+Revision-number: 29
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 23
+(r29) Merge b1 to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:06.073175Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 118
+Content-length: 118
+
+K 13
+svn:mergeinfo
+V 83
+/branches/b1:25-28
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-22
+PROPS-END
+
+
+Node-path: trunk/b1file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 28
+Node-copyfrom-path: branches/b1/b1file
+Text-copy-source-md5: 08778dfd9ac4f603231896aba7aad523
+Text-copy-source-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f
+
+
+Revision-number: 30
+Prop-content-length: 143
+Content-length: 143
+
+K 7
+svn:log
+V 45
+(r30) trunk commit before merging trunk to b2
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:08.096353Z
+PROPS-END
+
+Node-path: trunk/trunkfile
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: edf45fe5c98c5367733b39bbb2bb20d9
+Text-content-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239
+Content-length: 16
+
+PROPS-END
+trunk
+
+
+Revision-number: 31
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 23
+(r31) Merge trunk to b2
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:11.081541Z
+PROPS-END
+
+Node-path: branches/b2
+Node-kind: dir
+Node-action: change
+Prop-content-length: 131
+Content-length: 131
+
+K 13
+svn:mergeinfo
+V 96
+/branches/b1:25-28
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-22
+/trunk:26-30
+PROPS-END
+
+
+Node-path: branches/b2/b1file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 30
+Node-copyfrom-path: trunk/b1file
+Text-copy-source-md5: 08778dfd9ac4f603231896aba7aad523
+Text-copy-source-sha1: b551771aa4ad5b14123fc3bd98d89db2bc0edd4f
+
+
+Node-path: branches/b2/trunkfile
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 30
+Node-copyfrom-path: trunk/trunkfile
+Text-copy-source-md5: edf45fe5c98c5367733b39bbb2bb20d9
+Text-copy-source-sha1: 7361d1685e5c86dfc523620cfaf598f196f86239
+
+
+Revision-number: 32
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 23
+(r32) Merge b2 to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:14.117939Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 138
+Content-length: 138
+
+K 13
+svn:mergeinfo
+V 102
+/branches/b1:25-28
+/branches/b2:26-31
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-22
+PROPS-END
+
+
+Node-path: trunk/b2file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 31
+Node-copyfrom-path: branches/b2/b2file
+Text-copy-source-md5: 5edbdd57cba621eb3c6e601bf563b4dc
+Text-copy-source-sha1: 9d4b38049776bd0a2074d67cad23f8eaed35a3b3
+
+
+Revision-number: 33
+Prop-content-length: 145
+Content-length: 145
+
+K 7
+svn:log
+V 47
+(r33) make f1 branch from trunk with a new file
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:17.105832Z
+PROPS-END
+
+Node-path: branches/f1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 32
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/f1/f1file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: 2b1abc6b6c5c0018851f9f8e6475563b
+Text-content-sha1: aece6dfba588900e00d95601d22b4408d49580af
+Content-length: 13
+
+PROPS-END
+f1
+
+
+Revision-number: 34
+Prop-content-length: 145
+Content-length: 145
+
+K 7
+svn:log
+V 47
+(r34) make f2 branch from trunk with a new file
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:20.110057Z
+PROPS-END
+
+Node-path: branches/f2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 33
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/f2/f2file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: 575c5638d60271457e54ab7d07309502
+Text-content-sha1: 1c49a440c352f3473efa9512255033b94dc7def0
+Content-length: 13
+
+PROPS-END
+f2
+
+
+Revision-number: 35
+Prop-content-length: 128
+Content-length: 128
+
+K 7
+svn:log
+V 30
+(r35) Merge f1 and f2 to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:24.081490Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 173
+Content-length: 173
+
+K 13
+svn:mergeinfo
+V 137
+/branches/b1:25-28
+/branches/b2:26-31
+/branches/f1:33-34
+/branches/f2:34
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-22
+PROPS-END
+
+
+Node-path: trunk/f1file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 34
+Node-copyfrom-path: branches/f1/f1file
+Text-copy-source-md5: 2b1abc6b6c5c0018851f9f8e6475563b
+Text-copy-source-sha1: aece6dfba588900e00d95601d22b4408d49580af
+
+
+Node-path: trunk/f2file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 34
+Node-copyfrom-path: branches/f2/f2file
+Text-copy-source-md5: 575c5638d60271457e54ab7d07309502
+Text-copy-source-sha1: 1c49a440c352f3473efa9512255033b94dc7def0
+
+
+Revision-number: 36
Prop-content-length: 135
Content-length: 135
K 7
svn:log
V 37
-(r25) add subdirectory to left branch
+(r36) add subdirectory to left branch
K 10
svn:author
V 3
K 8
svn:date
V 27
-2010-01-19T04:14:46.052649Z
+2010-02-22T06:19:26.113516Z
PROPS-END
Node-path: branches/left/subdir
Yeehaw
-Revision-number: 26
+Revision-number: 37
Prop-content-length: 123
Content-length: 123
K 7
svn:log
V 25
-(r26) merge left to trunk
+(r37) merge left to trunk
K 10
svn:author
V 3
K 8
svn:date
V 27
-2010-01-19T04:14:49.040783Z
+2010-02-22T06:19:29.073699Z
PROPS-END
Node-path: trunk
Node-kind: dir
Node-action: change
-Prop-content-length: 99
-Content-length: 99
+Prop-content-length: 173
+Content-length: 173
K 13
svn:mergeinfo
-V 64
-/branches/left:2-25
+V 137
+/branches/b1:25-28
+/branches/b2:26-31
+/branches/f1:33-34
+/branches/f2:34
+/branches/left:2-36
/branches/left-sub:4-19
/branches/right:2-22
PROPS-END
Node-path: trunk/subdir
Node-kind: dir
Node-action: add
-Node-copyfrom-rev: 25
+Node-copyfrom-rev: 36
Node-copyfrom-path: branches/left/subdir
-Revision-number: 27
-Prop-content-length: 118
-Content-length: 118
+Revision-number: 38
+Prop-content-length: 123
+Content-length: 123
K 7
svn:log
-V 20
-(r28) partial update
+V 25
+(r38) make partial branch
K 10
svn:author
V 3
K 8
svn:date
V 27
-2010-01-19T04:14:53.049037Z
+2010-02-22T06:19:32.072243Z
PROPS-END
Node-path: branches/partial
Node-kind: dir
Node-action: add
-Node-copyfrom-rev: 26
+Node-copyfrom-rev: 37
Node-copyfrom-path: trunk/subdir
+Revision-number: 39
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 20
+(r39) partial update
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:34.097961Z
+PROPS-END
+
Node-path: branches/partial/palindromes
Node-kind: file
Node-action: add
racecar
-Revision-number: 28
+Revision-number: 40
Prop-content-length: 126
Content-length: 126
K 7
svn:log
V 28
-(r29) merge partial to trunk
+(r40) merge partial to trunk
K 10
svn:author
V 3
K 8
svn:date
V 27
-2010-01-19T04:14:56.041526Z
+2010-02-22T06:19:37.080211Z
PROPS-END
Node-path: trunk/subdir
Node-kind: dir
Node-action: change
-Prop-content-length: 142
-Content-length: 142
+Prop-content-length: 246
+Content-length: 246
K 13
svn:mergeinfo
-V 106
-/branches/left/subdir:2-25
+V 210
+/branches/b1/subdir:25-28
+/branches/b2/subdir:26-31
+/branches/f1/subdir:33-34
+/branches/f2/subdir:34
+/branches/left/subdir:2-36
/branches/left-sub/subdir:4-19
-/branches/partial:27
+/branches/partial:38-39
/branches/right/subdir:2-22
PROPS-END
Node-path: trunk/subdir/palindromes
Node-kind: file
Node-action: add
-Node-copyfrom-rev: 27
+Node-copyfrom-rev: 39
Node-copyfrom-path: branches/partial/palindromes
Text-copy-source-md5: 5d1c2024fb5efc4eef812856df1b080c
Text-copy-source-sha1: 5f8509ddd14c91a52864dd1447344e706f9bbc69
-Revision-number: 29
-Prop-content-length: 131
-Content-length: 131
+Revision-number: 41
+Prop-content-length: 116
+Content-length: 116
K 7
svn:log
-V 33
-(r31) make bugfix branch from tag
+V 18
+(r41) tagging v1.0
K 10
svn:author
V 3
K 8
svn:date
V 27
-2010-01-19T04:15:00.039761Z
+2010-02-22T06:19:40.083460Z
PROPS-END
Node-path: tags/v1.0
Node-kind: dir
Node-action: add
-Node-copyfrom-rev: 28
+Node-copyfrom-rev: 40
Node-copyfrom-path: trunk
-Revision-number: 30
-Prop-content-length: 120
-Content-length: 120
+Revision-number: 42
+Prop-content-length: 131
+Content-length: 131
K 7
svn:log
-V 22
-(r32) commit to bugfix
+V 33
+(r42) make bugfix branch from tag
K 10
svn:author
V 3
K 8
svn:date
V 27
-2010-01-19T04:15:03.043218Z
+2010-02-22T06:19:43.118075Z
PROPS-END
Node-path: branches/bugfix
Node-kind: dir
Node-action: add
-Node-copyfrom-rev: 29
+Node-copyfrom-rev: 41
Node-copyfrom-path: tags/v1.0
+Revision-number: 43
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 22
+(r43) commit to bugfix
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-02-22T06:19:45.079536Z
+PROPS-END
+
Node-path: branches/bugfix/subdir/palindromes
Node-kind: file
Node-action: change
kayak
-Revision-number: 31
+Revision-number: 44
Prop-content-length: 125
Content-length: 125
K 7
svn:log
V 27
-(r33) Merge BUGFIX to TRUNK
+(r44) Merge BUGFIX to TRUNK
K 10
svn:author
V 3
K 8
svn:date
V 27
-2010-01-19T04:15:06.043723Z
+2010-02-22T06:19:48.078914Z
PROPS-END
Node-path: trunk
Node-kind: dir
Node-action: change
-Prop-content-length: 133
-Content-length: 133
+Prop-content-length: 210
+Content-length: 210
K 13
svn:mergeinfo
-V 98
-/branches/bugfix:30
-/branches/left:2-25
+V 174
+/branches/b1:25-28
+/branches/b2:26-31
+/branches/bugfix:42-43
+/branches/f1:33-34
+/branches/f2:34
+/branches/left:2-36
/branches/left-sub:4-19
/branches/right:2-22
-/tags/v1.0:29
+/tags/v1.0:41
PROPS-END
Node-path: trunk/subdir
Node-kind: dir
Node-action: change
-Prop-content-length: 190
-Content-length: 190
+Prop-content-length: 297
+Content-length: 297
K 13
svn:mergeinfo
-V 154
-/branches/bugfix/subdir:30
-/branches/left/subdir:2-25
+V 261
+/branches/b1/subdir:25-28
+/branches/b2/subdir:26-31
+/branches/bugfix/subdir:42-43
+/branches/f1/subdir:33-34
+/branches/f2/subdir:34
+/branches/left/subdir:2-36
/branches/left-sub/subdir:4-19
-/branches/partial:27
+/branches/partial:38-39
/branches/right/subdir:2-22
-/tags/v1.0/subdir:29
+/tags/v1.0/subdir:41
PROPS-END
test_expect_success 'import a trivial module' '
- git cvsimport -a -z 0 -C module-git module &&
+ git cvsimport -a -R -z 0 -C module-git module &&
test_cmp module-cvs/o_fortuna module-git/o_fortuna
'
test_expect_success 'pack refs' 'cd module-git && git gc && cd ..'
+test_expect_success 'initial import has correct .git/cvs-revisions' '
+
+ (cd module-git &&
+ git log --format="o_fortuna 1.1 %H" -1) > expected &&
+ test_cmp expected module-git/.git/cvs-revisions
+'
+
test_expect_success 'update cvs module' '
cd module-cvs &&
test_expect_success 'update git module' '
cd module-git &&
- git cvsimport -a -z 0 module &&
+ git cvsimport -a -R -z 0 module &&
git merge origin &&
cd .. &&
test_cmp module-cvs/o_fortuna module-git/o_fortuna
'
+test_expect_success 'update has correct .git/cvs-revisions' '
+
+ (cd module-git &&
+ git log --format="o_fortuna 1.1 %H" -1 HEAD^ &&
+ git log --format="o_fortuna 1.2 %H" -1 HEAD) > expected &&
+ test_cmp expected module-git/.git/cvs-revisions
+'
+
test_expect_success 'update cvs module' '
cd module-cvs &&
cd module-git &&
git config cvsimport.module module &&
- git cvsimport -a -z0 &&
+ git cvsimport -a -R -z0 &&
git merge origin &&
cd .. &&
test_cmp module-cvs/tick module-git/tick
'
+test_expect_success 'second update has correct .git/cvs-revisions' '
+
+ (cd module-git &&
+ git log --format="o_fortuna 1.1 %H" -1 HEAD^^ &&
+ git log --format="o_fortuna 1.2 %H" -1 HEAD^
+ git log --format="tick 1.1 %H" -1 HEAD) > expected &&
+ test_cmp expected module-git/.git/cvs-revisions
+'
+
test_expect_success 'import from a CVS working tree' '
$CVS co -d import-from-wt module &&
'
+test_expect_success 'no .git/cvs-revisions created by default' '
+
+ ! test -e import-from-wt/.git/cvs-revisions
+
+'
+
test_expect_success 'test entire HEAD' 'test_cmp_branch_tree master'
test_done
return 0;
}
-static int refs_pushed(struct ref *ref)
+int transport_refs_pushed(struct ref *ref)
{
for (; ref; ref = ref->next) {
switch(ref->status) {
return 0;
}
-static void update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
+void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
{
struct refspec rs;
}
}
-#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
-
static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg, int porcelain)
{
if (porcelain) {
else
fprintf(stdout, "%s\n", summary);
} else {
- fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
+ fprintf(stderr, " %c %-*s ", flag, TRANSPORT_SUMMARY_WIDTH, summary);
if (from)
fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
else
return 1;
}
-static void print_push_status(const char *dest, struct ref *refs,
- int verbose, int porcelain, int * nonfastforward)
+void transport_print_push_status(const char *dest, struct ref *refs,
+ int verbose, int porcelain, int *nonfastforward)
{
struct ref *ref;
int n = 0;
}
}
-static void verify_remote_names(int nr_heads, const char **heads)
+void transport_verify_remote_names(int nr_heads, const char **heads)
{
int i;
return S_ISREG(buf.st_mode);
}
+static int isurlschemechar(int first_flag, int ch)
+{
+ /*
+ * The set of valid URL schemes, as per STD66 (RFC3986) is
+ * '[A-Za-z][A-Za-z0-9+.-]*'. But use sightly looser check
+ * of '[A-Za-z0-9][A-Za-z0-9+.-]*' because earlier version
+ * of check used '[A-Za-z0-9]+' so not to break any remote
+ * helpers.
+ */
+ int alphanumeric, special;
+ alphanumeric = ch > 0 && isalnum(ch);
+ special = ch == '+' || ch == '-' || ch == '.';
+ return alphanumeric || (!first_flag && special);
+}
+
static int is_url(const char *url)
{
const char *url2, *first_slash;
*/
url2 = url;
while (url2 < first_slash - 1) {
- if (!isalnum((unsigned char)*url2))
+ if (!isurlschemechar(url2 == url, (unsigned char)*url2))
return 0;
url2++;
}
if (url) {
const char *p = url;
- while (isalnum(*p))
+ while (isurlschemechar(p == url, *p))
p++;
if (!prefixcmp(p, "::"))
helper = xstrndup(url, p - url);
int *nonfastforward)
{
*nonfastforward = 0;
- verify_remote_names(refspec_nr, refspec);
+ transport_verify_remote_names(refspec_nr, refspec);
if (transport->push) {
/* Maybe FIXME. But no important transport uses this case. */
ret |= err;
if (!quiet || err)
- print_push_status(transport->url, remote_refs,
+ transport_print_push_status(transport->url, remote_refs,
verbose | porcelain, porcelain,
nonfastforward);
if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
struct ref *ref;
for (ref = remote_refs; ref; ref = ref->next)
- update_tracking_ref(transport->remote, ref, verbose);
+ transport_update_tracking_ref(transport->remote, ref, verbose);
}
- if (!quiet && !ret && !refs_pushed(remote_refs))
+ if (!quiet && !ret && !transport_refs_pushed(remote_refs))
fprintf(stderr, "Everything up-to-date\n");
return ret;
}
#define TRANSPORT_PUSH_PORCELAIN 32
#define TRANSPORT_PUSH_QUIET 64
#define TRANSPORT_PUSH_SET_UPSTREAM 128
+#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
/* Returns a transport suitable for the url */
struct transport *transport_get(struct remote *, const char *);
/* Transport methods defined outside transport.c */
int transport_helper_init(struct transport *transport, const char *name);
+/* common methods used by transport.c and builtin-send-pack.c */
+void transport_verify_remote_names(int nr_heads, const char **heads);
+
+void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose);
+
+int transport_refs_pushed(struct ref *ref);
+
+void transport_print_push_status(const char *dest, struct ref *refs,
+ int verbose, int porcelain, int *nonfastforward);
+
#endif
# @@BUILD_DIR@@ and @@PROG@@.
GIT_EXEC_PATH='@@BUILD_DIR@@'
-GIT_TEMPLATE_DIR='@@BUILD_DIR@@/templates/blt'
+if test -n "$NO_SET_GIT_TEMPLATE_DIR"
+then
+ unset GIT_TEMPLATE_DIR
+else
+ GIT_TEMPLATE_DIR='@@BUILD_DIR@@/templates/blt'
+ export GIT_TEMPLATE_DIR
+fi
GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'
PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
-export GIT_EXEC_PATH GIT_TEMPLATE_DIR GITPERLLIB PATH
+export GIT_EXEC_PATH GITPERLLIB PATH
exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"
return 0;
}
+void read_mmblob(mmfile_t *ptr, const unsigned char *sha1)
+{
+ unsigned long size;
+ enum object_type type;
+
+ if (!hashcmp(sha1, null_sha1)) {
+ ptr->ptr = xstrdup("");
+ ptr->size = 0;
+ return;
+ }
+
+ ptr->ptr = read_sha1_file(sha1, &type, &size);
+ if (!ptr->ptr || type != OBJ_BLOB)
+ die("unable to read blob object %s", sha1_to_hex(sha1));
+ ptr->size = size;
+}
+
#define FIRST_FEW_BYTES 8000
int buffer_is_binary(const char *ptr, unsigned long size)
{
int *ob, int *on,
int *nb, int *nn);
int read_mmfile(mmfile_t *ptr, const char *filename);
+void read_mmblob(mmfile_t *ptr, const unsigned char *sha1);
int buffer_is_binary(const char *ptr, unsigned long size);
extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);