Code

Merge branch 'jc/diff-no-no-index'
authorJunio C Hamano <gitster@pobox.com>
Tue, 27 May 2008 05:38:19 +0000 (22:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 27 May 2008 05:38:19 +0000 (22:38 -0700)
* jc/diff-no-no-index:
  git diff --no-index: default to page like other diff frontends
  git-diff: allow  --no-index semantics a bit more
  "git diff": do not ignore index without --no-index
  diff-files: do not play --no-index games
  tests: do not use implicit "git diff --no-index"

152 files changed:
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/RelNotes-1.5.5.2.txt [new file with mode: 0644]
Documentation/RelNotes-1.5.6.txt
Documentation/config.txt
Documentation/core-tutorial.txt
Documentation/cvs-migration.txt [deleted file]
Documentation/diff-options.txt
Documentation/git-cat-file.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-cvsserver.txt
Documentation/git-hash-object.txt
Documentation/git-rev-parse.txt
Documentation/git-svn.txt
Documentation/git-update-index.txt
Documentation/git.txt
Documentation/gitcvs-migration.txt [new file with mode: 0644]
Documentation/gittutorial-2.txt [new file with mode: 0644]
Documentation/gittutorial.txt [new file with mode: 0644]
Documentation/technical/api-history-graph.txt
Documentation/tutorial-2.txt [deleted file]
Documentation/tutorial.txt [deleted file]
Documentation/user-manual.txt
Makefile
alias.c
archive-tar.c
builtin-add.c
builtin-apply.c
builtin-blame.c
builtin-branch.c
builtin-cat-file.c
builtin-checkout-index.c
builtin-checkout.c
builtin-clean.c
builtin-clone.c [new file with mode: 0644]
builtin-commit-tree.c
builtin-commit.c
builtin-config.c
builtin-diff-files.c
builtin-diff-index.c
builtin-diff-tree.c
builtin-diff.c
builtin-fast-export.c
builtin-fetch-pack.c
builtin-fetch.c
builtin-fmt-merge-msg.c
builtin-gc.c
builtin-http-fetch.c
builtin-init-db.c
builtin-log.c
builtin-ls-files.c
builtin-ls-tree.c
builtin-mailinfo.c
builtin-mailsplit.c
builtin-merge-base.c
builtin-merge-recursive.c
builtin-mv.c
builtin-name-rev.c
builtin-pack-objects.c
builtin-read-tree.c
builtin-reflog.c
builtin-remote.c
builtin-rerere.c
builtin-reset.c
builtin-rev-list.c
builtin-rev-parse.c
builtin-revert.c
builtin-rm.c
builtin-show-branch.c
builtin-symbolic-ref.c
builtin-tag.c
builtin-unpack-objects.c
builtin-update-index.c
builtin-update-ref.c
builtin-verify-pack.c
builtin-verify-tag.c
builtin-write-tree.c
builtin.h
cache.h
color.c
color.h
config.c
connect.c
contrib/examples/git-clone.sh [new file with mode: 0755]
contrib/hg-to-git/hg-to-git.py
contrib/hooks/update-paranoid
convert.c
daemon.c
diff.c
diff.h
environment.c
fast-import.c
git-bisect.sh
git-clone.sh [deleted file]
git-cvsexportcommit.perl
git-cvsimport.perl
git-cvsserver.perl
git-gui/git-gui.sh
git-gui/lib/choose_repository.tcl
git-rebase--interactive.sh
git-rebase.sh
git-stash.sh
git-svn.perl
git.c
graph.c
graph.h
hash-object.c
help.c
http-push.c
http.c
imap-send.c
index-pack.c
ll-merge.c
lockfile.c
log-tree.c
pager.c
perl/Git.pm
pretty.c
read-cache.c
receive-pack.c
refs.c
refs.h
remote.c
remote.h
revision.c
setup.c
sha1_file.c
t/t1006-cat-file.sh [new file with mode: 0755]
t/t1007-hash-object.sh [new file with mode: 0755]
t/t2200-add-update.sh
t/t4126-apply-empty.sh [new file with mode: 0755]
t/t5100-mailinfo.sh
t/t5100/nul [new file with mode: 0644]
t/t5100/nul-b64.expect [new file with mode: 0644]
t/t5100/nul-b64.in [new file with mode: 0644]
t/t5303-hash-object.sh [deleted file]
t/t5601-clone.sh
t/t5700-clone-reference.sh
t/t6030-bisect-porcelain.sh
t/t6031-merge-recursive.sh
t/t7402-submodule-rebase.sh [new file with mode: 0755]
t/t9122-git-svn-author.sh [new file with mode: 0755]
t/t9200-git-cvsexportcommit.sh
t/t9401-git-cvsserver-crlf.sh [new file with mode: 0755]
transport.c
transport.h
unpack-file.c
unpack-trees.c
unpack-trees.h
var.c
wt-status.c
wt-status.h

index 994eb9159a2b0e8a10f4f9510165d420004203bf..d2a0a76e6cfb275080f3b66309591888f5302850 100644 (file)
@@ -89,6 +89,8 @@ For C programs:
    of "else if" statements, it can make sense to add braces to
    single line blocks.
 
+ - We try to avoid assignments inside if().
+
  - Try to make your code understandable.  You may put comments
    in, but comments invariably tend to stale out when the code
    they were describing changes.  Often splitting a function
index 4144d1e086d5e8443da41a0939e5bead4efcafba..9750334b9764dc5901069501fd6b6fe196d22ca5 100644 (file)
@@ -3,7 +3,8 @@ MAN1_TXT= \
                $(wildcard git-*.txt)) \
        gitk.txt
 MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt
-MAN7_TXT=git.txt gitcli.txt
+MAN7_TXT=git.txt gitcli.txt gittutorial.txt gittutorial-2.txt \
+       gitcvs-migration.txt
 
 MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
 MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
@@ -11,10 +12,7 @@ MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT))
 
 DOC_HTML=$(MAN_HTML)
 
-ARTICLES = tutorial
-ARTICLES += tutorial-2
-ARTICLES += core-tutorial
-ARTICLES += cvs-migration
+ARTICLES = core-tutorial
 ARTICLES += diffcore
 ARTICLES += howto-index
 ARTICLES += repository-layout
diff --git a/Documentation/RelNotes-1.5.5.2.txt b/Documentation/RelNotes-1.5.5.2.txt
new file mode 100644 (file)
index 0000000..391a7b0
--- /dev/null
@@ -0,0 +1,27 @@
+GIT v1.5.5.2 Release Notes
+==========================
+
+Fixes since v1.5.5.1
+--------------------
+
+ * "git repack -n" was mistakenly made no-op earlier.
+
+ * "git imap-send" wanted to always have imap.host even when use of
+   imap.tunnel made it unnecessary.
+
+ * reflog syntax that uses time e.g. "HEAD@{10 seconds ago}:path" did not
+   stop parsing at the closing "}".
+
+ * "git rev-parse --symbolic-full-name ^master^2" printed solitary "^",
+   but it should print nothing.
+
+ * "git commit" did not detect when it failed to write tree objects.
+
+ * "git fetch" sometimes transferred too many objects unnecessarily.
+
+ * a path specification "a/b" in .gitattributes file should not match
+   "sub/a/b".
+
+ * various gitweb fixes.
+
+Also comes with various documentation updates.
index f3256fb82c851b18d45d6efe59bcb09578fef770..32af18b5723a445b7532d1321bbb79df8b530bf6 100644 (file)
@@ -6,16 +6,30 @@ Updates since v1.5.5
 
 (subsystems)
 
+* Comes with updated gitk and git-gui.
 
 (portability)
 
+* git will build on AIX better than before now.
+
+* core.ignorecase configuration variable can be used to work better on
+  filesystems that are not case sensitive.
+
+* "git init" now autodetects the case sensitivity of the filesystem and
+  sets core.ignorecase accordingly.
 
 (performance)
 
+* "git clone" was rewritten in C.  This will hopefully help cloning a
+  repository with insane number of refs.
+
 * "git rebase --onto $there $from $branch" used to switch to the tip of
   $branch only to immediately reset back to $from, smudging work tree
   files unnecessarily.  This has been optimized.
 
+* Object creation codepath in "git-svn" has been optimized by enhancing
+  plumbing commands git-cat-file and git-hash-object.
+
 (usability, bells and whistles)
 
 * "git add -p" (and the "patch" subcommand of "git add -i") can choose to
@@ -23,20 +37,53 @@ Updates since v1.5.5
 
 * "git bisect help" gives longer and more helpful usage information.
 
+* "git bisect" does not use a special branch "bisect" anymore; instead, it
+  does its work on a detached HEAD.
+
+* "git branch" (and "git checkout -b") can be told to set up
+  branch.<name>.rebase automatically, so that later you can say "git pull"
+  and magically cause "git pull --rebase" to happen.
+
+* "git branch --merged" and "git branch --no-merged" can be used to list
+  branches that have already been merged (or not yet merged) to the
+  current branch.
+
+* "git cherry-pick" and "git revert" can add a sign-off.
+
+* "git commit" mentions the author identity when you are committing
+  somebody else's changes.
+
 * "git diff/log --dirstat" output is consistent between binary and textual
   changes.
 
+* "git filter-branch" rewrites signed tags by demoting them to annotated.
+
+* "git format-patch --no-binary" can produce a patch that lack binary
+  changes (i.e. cannot be used to propagate the whole changes) meant only
+  for reviewing.
+
 * "git gc --auto" honors a new pre-aut-gc hook to temporarily disable it.
 
 * "git log --pretty=tformat:<custom format>" gives a LF after each entry,
   instead of giving a LF between each pair of entries which is how
   "git log --pretty=format:<custom format>" works.
 
+* "git log" and friends learned the "--graph" option to show the ancestry
+  graph at the left margin of the output.
+
+* "git log" and friends can be told to use date format that is different
+  from the default via 'log.date' configuration variable.
+
 * "git send-email" now can send out messages outside a git repository.
 
+* "git send-email --compose" was made aware of rfc2047 quoting.
+
 * "git status" can optionally include output from "git submodule
   summary".
 
+* "git svn" learned --add-author-from option to propagate the authorship
+  by munging the commit log message.
+
 * "gitweb" can read from a system-wide configuration file.
 
 (internal)
@@ -54,6 +101,6 @@ this release, unless otherwise noted.
 
 --
 exec >/var/tmp/1
-O=v1.5.5-56-g5f0734f
+O=v1.5.6-rc0
 echo O=`git describe refs/heads/master`
 git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
index 002a06689343df9201d8c08beff8147c8a55e8b7..c298dc21c5a4ba28eaf35c4d12ae9f0030487e83 100644 (file)
@@ -662,11 +662,24 @@ gitcvs.logfile::
        Path to a log file where the CVS server interface well... logs
        various stuff. See linkgit:git-cvsserver[1].
 
+gitcvs.usecrlfattr
+       If true, the server will look up the `crlf` attribute for
+       files to determine the '-k' modes to use. If `crlf` is set,
+       the '-k' mode will be left blank, so cvs clients will
+       treat it as text. If `crlf` is explicitly unset, the file
+       will be set with '-kb' mode, which supresses any newline munging
+       the client might otherwise do. If `crlf` is not specified,
+       then 'gitcvs.allbinary' is used. See linkgit:gitattribute[5].
+
 gitcvs.allbinary::
-       If true, all files are sent to the client in mode '-kb'. This
-       causes the client to treat all files as binary files which suppresses
-       any newline munging it otherwise might do. A work-around for the
-       fact that there is no way yet to set single files to mode '-kb'.
+       This is used if 'gitcvs.usecrlfattr' does not resolve
+       the correct '-kb' mode to use. If true, all
+       unresolved files are sent to the client in
+       mode '-kb'. This causes the client to treat them
+       as binary files, which suppresses any newline munging it
+       otherwise might do. Alternatively, if it is set to "guess",
+       then the contents of the file are examined to decide if
+       it is binary, similar to 'core.autocrlf'.
 
 gitcvs.dbname::
        Database used by git-cvsserver to cache revision information
@@ -697,8 +710,9 @@ gitcvs.dbTableNamePrefix::
        linkgit:git-cvsserver[1] for details).  Any non-alphabetic
        characters will be replaced with underscores.
 
-All gitcvs variables except for 'gitcvs.allbinary' can also be
-specified as 'gitcvs.<access_method>.<varname>' (where 'access_method'
+All gitcvs variables except for 'gitcvs.usecrlfattr' and
+'gitcvs.allbinary' can also be specified as
+'gitcvs.<access_method>.<varname>' (where 'access_method'
 is one of "ext" and "pserver") to make them apply only for the given
 access method.
 
index 5a5531222d8f514b27ba63d9fc9ecc17335cdc3c..b50b5dd487fc7d7fef32408683373e9dade0f3c1 100644 (file)
@@ -8,7 +8,7 @@ This tutorial explains how to use the "core" git programs to set up and
 work with a git repository.
 
 If you just need to use git as a revision control system you may prefer
-to start with link:tutorial.html[a tutorial introduction to git] or
+to start with linkgit:gittutorial[7][a tutorial introduction to git] or
 link:user-manual.html[the git user manual].
 
 However, an understanding of these low-level tools can be helpful if
@@ -1581,7 +1581,7 @@ suggested in the previous section may be new to you. You do not
 have to worry. git supports "shared public repository" style of
 cooperation you are probably more familiar with as well.
 
-See link:cvs-migration.html[git for CVS users] for the details.
+See linkgit:gitcvs-migration[7][git for CVS users] for the details.
 
 Bundling your work together
 ---------------------------
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt
deleted file mode 100644 (file)
index 374bc87..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-git for CVS users
-=================
-
-Git differs from CVS in that every working tree contains a repository with
-a full copy of the project history, and no repository is inherently more
-important than any other.  However, you can emulate the CVS model by
-designating a single shared repository which people can synchronize with;
-this document explains how to do that.
-
-Some basic familiarity with git is required.  This
-link:tutorial.html[tutorial introduction to git] and the
-link:glossary.html[git glossary] should be sufficient.
-
-Developing against a shared repository
---------------------------------------
-
-Suppose a shared repository is set up in /pub/repo.git on the host
-foo.com.  Then as an individual committer you can clone the shared
-repository over ssh with:
-
-------------------------------------------------
-$ git clone foo.com:/pub/repo.git/ my-project
-$ cd my-project
-------------------------------------------------
-
-and hack away.  The equivalent of `cvs update` is
-
-------------------------------------------------
-$ git pull origin
-------------------------------------------------
-
-which merges in any work that others might have done since the clone
-operation.  If there are uncommitted changes in your working tree, commit
-them first before running git pull.
-
-[NOTE]
-================================
-The `pull` command knows where to get updates from because of certain
-configuration variables that were set by the first `git clone`
-command; see `git config -l` and the linkgit:git-config[1] man
-page for details.
-================================
-
-You can update the shared repository with your changes by first committing
-your changes, and then using the linkgit:git-push[1] command:
-
-------------------------------------------------
-$ git push origin master
-------------------------------------------------
-
-to "push" those commits to the shared repository.  If someone else has
-updated the repository more recently, `git push`, like `cvs commit`, will
-complain, in which case you must pull any changes before attempting the
-push again.
-
-In the `git push` command above we specify the name of the remote branch
-to update (`master`).  If we leave that out, `git push` tries to update
-any branches in the remote repository that have the same name as a branch
-in the local repository.  So the last `push` can be done with either of:
-
-------------
-$ git push origin
-$ git push foo.com:/pub/project.git/
-------------
-
-as long as the shared repository does not have any branches
-other than `master`.
-
-Setting Up a Shared Repository
-------------------------------
-
-We assume you have already created a git repository for your project,
-possibly created from scratch or from a tarball (see the
-link:tutorial.html[tutorial]), or imported from an already existing CVS
-repository (see the next section).
-
-Assume your existing repo is at /home/alice/myproject.  Create a new "bare"
-repository (a repository without a working tree) and fetch your project into
-it:
-
-------------------------------------------------
-$ mkdir /pub/my-repo.git
-$ cd /pub/my-repo.git
-$ git --bare init --shared
-$ git --bare fetch /home/alice/myproject master:master
-------------------------------------------------
-
-Next, give every team member read/write access to this repository.  One
-easy way to do this is to give all the team members ssh access to the
-machine where the repository is hosted.  If you don't want to give them a
-full shell on the machine, there is a restricted shell which only allows
-users to do git pushes and pulls; see linkgit:git-shell[1].
-
-Put all the committers in the same group, and make the repository
-writable by that group:
-
-------------------------------------------------
-$ chgrp -R $group /pub/my-repo.git
-------------------------------------------------
-
-Make sure committers have a umask of at most 027, so that the directories
-they create are writable and searchable by other group members.
-
-Importing a CVS archive
------------------------
-
-First, install version 2.1 or higher of cvsps from
-link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
-sure it is in your path.  Then cd to a checked out CVS working directory
-of the project you are interested in and run linkgit:git-cvsimport[1]:
-
--------------------------------------------
-$ git cvsimport -C <destination> <module>
--------------------------------------------
-
-This puts a git archive of the named CVS module in the directory
-<destination>, which will be created if necessary.
-
-The import checks out from CVS every revision of every file.  Reportedly
-cvsimport can average some twenty revisions per second, so for a
-medium-sized project this should not take more than a couple of minutes.
-Larger projects or remote repositories may take longer.
-
-The main trunk is stored in the git branch named `origin`, and additional
-CVS branches are stored in git branches with the same names.  The most
-recent version of the main trunk is also left checked out on the `master`
-branch, so you can start adding your own changes right away.
-
-The import is incremental, so if you call it again next month it will
-fetch any CVS updates that have been made in the meantime.  For this to
-work, you must not modify the imported branches; instead, create new
-branches for your own changes, and merge in the imported branches as
-necessary.
-
-Advanced Shared Repository Management
--------------------------------------
-
-Git allows you to specify scripts called "hooks" to be run at certain
-points.  You can use these, for example, to send all commits to the shared
-repository to a mailing list.  See linkgit:githooks[5][Hooks used by git].
-
-You can enforce finer grained permissions using update hooks.  See
-link:howto/update-hook-example.txt[Controlling access to branches using
-update hooks].
-
-Providing CVS Access to a git Repository
-----------------------------------------
-
-It is also possible to provide true CVS access to a git repository, so
-that developers can still use CVS; see linkgit:git-cvsserver[1] for
-details.
-
-Alternative Development Models
-------------------------------
-
-CVS users are accustomed to giving a group of developers commit access to
-a common repository.  As we've seen, this is also possible with git.
-However, the distributed nature of git allows other development models,
-and you may want to first consider whether one of them might be a better
-fit for your project.
-
-For example, you can choose a single person to maintain the project's
-primary public repository.  Other developers then clone this repository
-and each work in their own clone.  When they have a series of changes that
-they're happy with, they ask the maintainer to pull from the branch
-containing the changes.  The maintainer reviews their changes and pulls
-them into the primary repository, which other developers pull from as
-necessary to stay coordinated.  The Linux kernel and other projects use
-variants of this model.
-
-With a small group, developers may just pull changes from each other's
-repositories without the need for a central maintainer.
index 13234fa280b279bfccad0a9aec6989727957567a..859d67990a62049c2f328dfd676cf21da8ff9a27 100644 (file)
@@ -228,6 +228,9 @@ endif::git-format-patch[]
 --no-ext-diff::
        Disallow external diff drivers.
 
+--ignore-submodules::
+       Ignore changes to submodules in the diff generation.
+
 --src-prefix=<prefix>::
        Show the given source prefix instead of "a/".
 
index df42cb10f24825623ffe32579f0cfbcda6f0337b..f6c394c48240314298dd67716c6d96e6d9704c11 100644 (file)
@@ -9,12 +9,16 @@ git-cat-file - Provide content or type/size information for repository objects
 SYNOPSIS
 --------
 'git-cat-file' [-t | -s | -e | -p | <type>] <object>
+'git-cat-file' [--batch | --batch-check] < <list-of-objects>
 
 DESCRIPTION
 -----------
-Provides content or type of objects in the repository. The type
-is required unless '-t' or '-p' is used to find the object type,
-or '-s' is used to find the object size.
+In the first form, provides content or type of objects in the repository. The
+type is required unless '-t' or '-p' is used to find the object type, or '-s'
+is used to find the object size.
+
+In the second form, a list of object (separated by LFs) is provided on stdin,
+and the SHA1, type, and size of each object is printed on stdout.
 
 OPTIONS
 -------
@@ -46,6 +50,14 @@ OPTIONS
        or to ask for a "blob" with <object> being a tag object that
        points at it.
 
+--batch::
+       Print the SHA1, type, size, and contents of each object provided on
+       stdin. May not be combined with any other options or arguments.
+
+--batch-check::
+       Print the SHA1, type, and size of each object provided on stdin. May not be
+       combined with any other options or arguments.
+
 OUTPUT
 ------
 If '-t' is specified, one of the <type>.
@@ -56,9 +68,30 @@ If '-e' is specified, no output.
 
 If '-p' is specified, the contents of <object> are pretty-printed.
 
-Otherwise the raw (though uncompressed) contents of the <object> will
-be returned.
+If <type> is specified, the raw (though uncompressed) contents of the <object>
+will be returned.
+
+If '--batch' is specified, output of the following form is printed for each
+object specified on stdin:
+
+------------
+<sha1> SP <type> SP <size> LF
+<contents> LF
+------------
+
+If '--batch-check' is specified, output of the following form is printed for
+each object specified fon stdin:
+
+------------
+<sha1> SP <type> SP <size> LF
+------------
+
+For both '--batch' and '--batch-check', output of the following form is printed
+for each object specified on stdin that does not exist in the repository:
 
+------------
+<object> SP missing LF
+------------
 
 Author
 ------
index 363c36d694231c5876527ff0d062fbd385d66630..f75afaaadc435653822d7851a5bec5a774ff10a3 100644 (file)
@@ -8,7 +8,7 @@ git-cvsexportcommit - Export a single commit to a CVS checkout
 
 SYNOPSIS
 --------
-'git-cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-w cvsworkdir] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-w cvsworkdir] [-W] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
 
 
 DESCRIPTION
@@ -68,6 +68,11 @@ OPTIONS
        current directory is within a git repository.  The default is the
        value of 'cvsexportcommit.cvsdir'.
 
+-W::
+       Tell cvsexportcommit that the current working directory is not only
+       a Git checkout, but also the CVS checkout.  Therefore, Git will
+       reset the working directory to the parent commit before proceeding.
+
 -v::
        Verbose.
 
index b1106714b2f7100124826819315e78f7f28325fb..a33382ec2d0baf89c57d65f246fcbe50630b676c 100644 (file)
@@ -301,11 +301,33 @@ checkout, diff, status, update, log, add, remove, commit.
 Legacy monitoring operations are not supported (edit, watch and related).
 Exports and tagging (tags and branches) are not supported at this stage.
 
-The server should set the '-k' mode to binary when relevant, however,
-this is not really implemented yet. For now, you can force the server
-to set '-kb' for all files by setting the `gitcvs.allbinary` config
-variable. In proper GIT tradition, the contents of the files are
-always respected. No keyword expansion or newline munging is supported.
+CRLF Line Ending Conversions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default the server leaves the '-k' mode blank for all files,
+which causes the cvs client to treat them as a text files, subject
+to crlf conversion on some platforms.
+
+You can make the server use `crlf` attributes to set the '-k' modes
+for files by setting the `gitcvs.usecrlfattr` config variable.
+In this case, if `crlf` is explicitly unset ('-crlf'), then the
+server will set '-kb' mode for binary files. If `crlf` is set,
+then the '-k' mode will explicitly be left blank.  See
+also linkgit:gitattributes[5] for more information about the `crlf`
+attribute.
+
+Alternatively, if `gitcvs.usecrlfattr` config is not enabled
+or if the `crlf` attribute is unspecified for a filename, then
+the server uses the `gitcvs.allbinary` config for the default setting.
+If `gitcvs.allbinary` is set, then file not otherwise
+specified will default to '-kb' mode. Otherwise the '-k' mode
+is left blank. But if `gitcvs.allbinary` is set to "guess", then
+the correct '-k' mode will be guessed based on the contents of
+the file.
+
+For best consistency with cvs, it is probably best to override the
+defaults by setting `gitcvs.usecrlfattr` to true,
+and `gitcvs.allbinary` to "guess".
 
 Dependencies
 ------------
index 33030c022f1b86a8dba281ff04537c1a40cb6782..99a21434b53a40a1b317e9d3da1728ca22d76b53 100644 (file)
@@ -8,7 +8,7 @@ git-hash-object - Compute object ID and optionally creates a blob from a file
 
 SYNOPSIS
 --------
-'git-hash-object' [-t <type>] [-w] [--stdin] [--] <file>...
+'git-hash-object' [-t <type>] [-w] [--stdin | --stdin-paths] [--] <file>...
 
 DESCRIPTION
 -----------
@@ -32,6 +32,9 @@ OPTIONS
 --stdin::
        Read the object from standard input instead of from a file.
 
+--stdin-paths::
+       Read file names from stdin instead of from the command-line.
+
 Author
 ------
 Written by Junio C Hamano <junkio@cox.net>
index 69599ffb679b17ebbe6ba85c772cd10a3511d89e..5641d995180b7aed563872b2403dbd29870a2185 100644 (file)
@@ -243,16 +243,18 @@ Here is an illustration, by Jon Loeliger.  Both commit nodes B
 and C are parents of commit node A.  Parent commits are ordered
 left-to-right.
 
-    G   H   I   J
-     \ /     \ /
-      D   E   F
-       \  |  / \ 
-        \ | /   |
-         \|/    |
-          B     C
-           \   /
-            \ /
-             A
+........................................
+G   H   I   J
+ \ /     \ /
+  D   E   F
+   \  |  / \
+    \ | /   |
+     \|/    |
+      B     C
+       \   /
+        \ /
+         A
+........................................
 
     A =      = A^0
     B = A^   = A^1     = A~1
index 3eae1ebb7dc104fd51c5e25d163ec33ab7b61ef1..c9e4efe7f4678ecc0beb87b387c71a7ebd0efc71 100644 (file)
@@ -61,6 +61,16 @@ COMMANDS
        Set the 'useSvnsyncProps' option in the [svn-remote] config.
 --rewrite-root=<URL>;;
        Set the 'rewriteRoot' option in the [svn-remote] config.
+--use-log-author;;
+       When retrieving svn commits into git (as part of fetch, rebase, or
+       dcommit operations), look for the first From: or Signed-off-by: line
+       in the log message and use that as the author string.
+--add-author-from;;
+       When committing to svn from git (as part of commit or dcommit
+       operations), if the existing log message doesn't already have a
+       From: or Signed-off-by: line, append a From: line based on the
+       git commit's author string.  If you use this, then --use-log-author
+       will retrieve a valid author string for all commits.
 --username=<USER>;;
        For transports that SVN handles authentication for (http,
        https, and plain svn), specify the username.  For other
index 66be18ef36696c7422acd46510b16d756962fb8d..06640603c4b2cbd3f1825f33706544db8cbfe492 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
             [--cacheinfo <mode> <object> <file>]\*
             [--chmod=(+|-)x]
             [--assume-unchanged | --no-assume-unchanged]
+            [--ignore-submodules]
             [--really-refresh] [--unresolve] [--again | -g]
             [--info-only] [--index-info]
             [-z] [--stdin]
@@ -54,6 +55,10 @@ OPTIONS
         default behavior is to error out.  This option makes
         git-update-index continue anyway.
 
+--ignore-submodules:
+       Do not try to update submodules.  This option is only respected
+       when passed before --refresh.
+
 --unmerged::
         If --refresh finds unmerged changes in the index, the default
         behavior is to error out.  This option makes git-update-index
index adcd3e00b201af6182bbc441aa0b288a5d2db8a5..1f68dec541826598caa057f6e2d73b000d4a9dfe 100644 (file)
@@ -20,10 +20,10 @@ Git is a fast, scalable, distributed revision control system with an
 unusually rich command set that provides both high-level operations
 and full access to internals.
 
-See this link:tutorial.html[tutorial] to get started, then see
+See this linkgit:gittutorial[7][tutorial] to get started, then see
 link:everyday.html[Everyday Git] for a useful minimum set of commands, and
 "man git-commandname" for documentation of each command.  CVS users may
-also want to read link:cvs-migration.html[CVS migration].  See
+also want to read linkgit:gitcvs-migration[7][CVS migration].  See
 link:user-manual.html[Git User's Manual] for a more in-depth
 introduction.
 
@@ -46,10 +46,11 @@ Documentation for older releases are available here:
 * link:v1.5.5/git.html[documentation for release 1.5.5]
 
 * release notes for
+  link:RelNotes-1.5.5.2.txt[1.5.5.2],
   link:RelNotes-1.5.5.1.txt[1.5.5.1],
   link:RelNotes-1.5.5.txt[1.5.5].
 
-* link:v1.5.5.1/git.html[documentation for release 1.5.5.1]
+* link:v1.5.5.2/git.html[documentation for release 1.5.5.2]
 
 * link:v1.5.4.5/git.html[documentation for release 1.5.4.5]
 
diff --git a/Documentation/gitcvs-migration.txt b/Documentation/gitcvs-migration.txt
new file mode 100644 (file)
index 0000000..c410805
--- /dev/null
@@ -0,0 +1,193 @@
+gitcvs-migration(7)
+===================
+
+NAME
+----
+gitcvs-migration - git for CVS users
+
+SYNOPSIS
+--------
+git cvsimport *
+
+DESCRIPTION
+-----------
+
+Git differs from CVS in that every working tree contains a repository with
+a full copy of the project history, and no repository is inherently more
+important than any other.  However, you can emulate the CVS model by
+designating a single shared repository which people can synchronize with;
+this document explains how to do that.
+
+Some basic familiarity with git is required.  This
+linkgit:gittutorial[7][tutorial introduction to git] and the
+link:glossary.html[git glossary] should be sufficient.
+
+Developing against a shared repository
+--------------------------------------
+
+Suppose a shared repository is set up in /pub/repo.git on the host
+foo.com.  Then as an individual committer you can clone the shared
+repository over ssh with:
+
+------------------------------------------------
+$ git clone foo.com:/pub/repo.git/ my-project
+$ cd my-project
+------------------------------------------------
+
+and hack away.  The equivalent of `cvs update` is
+
+------------------------------------------------
+$ git pull origin
+------------------------------------------------
+
+which merges in any work that others might have done since the clone
+operation.  If there are uncommitted changes in your working tree, commit
+them first before running git pull.
+
+[NOTE]
+================================
+The `pull` command knows where to get updates from because of certain
+configuration variables that were set by the first `git clone`
+command; see `git config -l` and the linkgit:git-config[1] man
+page for details.
+================================
+
+You can update the shared repository with your changes by first committing
+your changes, and then using the linkgit:git-push[1] command:
+
+------------------------------------------------
+$ git push origin master
+------------------------------------------------
+
+to "push" those commits to the shared repository.  If someone else has
+updated the repository more recently, `git push`, like `cvs commit`, will
+complain, in which case you must pull any changes before attempting the
+push again.
+
+In the `git push` command above we specify the name of the remote branch
+to update (`master`).  If we leave that out, `git push` tries to update
+any branches in the remote repository that have the same name as a branch
+in the local repository.  So the last `push` can be done with either of:
+
+------------
+$ git push origin
+$ git push foo.com:/pub/project.git/
+------------
+
+as long as the shared repository does not have any branches
+other than `master`.
+
+Setting Up a Shared Repository
+------------------------------
+
+We assume you have already created a git repository for your project,
+possibly created from scratch or from a tarball (see the
+linkgit:gittutorial[7][tutorial]), or imported from an already existing CVS
+repository (see the next section).
+
+Assume your existing repo is at /home/alice/myproject.  Create a new "bare"
+repository (a repository without a working tree) and fetch your project into
+it:
+
+------------------------------------------------
+$ mkdir /pub/my-repo.git
+$ cd /pub/my-repo.git
+$ git --bare init --shared
+$ git --bare fetch /home/alice/myproject master:master
+------------------------------------------------
+
+Next, give every team member read/write access to this repository.  One
+easy way to do this is to give all the team members ssh access to the
+machine where the repository is hosted.  If you don't want to give them a
+full shell on the machine, there is a restricted shell which only allows
+users to do git pushes and pulls; see linkgit:git-shell[1].
+
+Put all the committers in the same group, and make the repository
+writable by that group:
+
+------------------------------------------------
+$ chgrp -R $group /pub/my-repo.git
+------------------------------------------------
+
+Make sure committers have a umask of at most 027, so that the directories
+they create are writable and searchable by other group members.
+
+Importing a CVS archive
+-----------------------
+
+First, install version 2.1 or higher of cvsps from
+link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
+sure it is in your path.  Then cd to a checked out CVS working directory
+of the project you are interested in and run linkgit:git-cvsimport[1]:
+
+-------------------------------------------
+$ git cvsimport -C <destination> <module>
+-------------------------------------------
+
+This puts a git archive of the named CVS module in the directory
+<destination>, which will be created if necessary.
+
+The import checks out from CVS every revision of every file.  Reportedly
+cvsimport can average some twenty revisions per second, so for a
+medium-sized project this should not take more than a couple of minutes.
+Larger projects or remote repositories may take longer.
+
+The main trunk is stored in the git branch named `origin`, and additional
+CVS branches are stored in git branches with the same names.  The most
+recent version of the main trunk is also left checked out on the `master`
+branch, so you can start adding your own changes right away.
+
+The import is incremental, so if you call it again next month it will
+fetch any CVS updates that have been made in the meantime.  For this to
+work, you must not modify the imported branches; instead, create new
+branches for your own changes, and merge in the imported branches as
+necessary.
+
+Advanced Shared Repository Management
+-------------------------------------
+
+Git allows you to specify scripts called "hooks" to be run at certain
+points.  You can use these, for example, to send all commits to the shared
+repository to a mailing list.  See linkgit:githooks[5][Hooks used by git].
+
+You can enforce finer grained permissions using update hooks.  See
+link:howto/update-hook-example.txt[Controlling access to branches using
+update hooks].
+
+Providing CVS Access to a git Repository
+----------------------------------------
+
+It is also possible to provide true CVS access to a git repository, so
+that developers can still use CVS; see linkgit:git-cvsserver[1] for
+details.
+
+Alternative Development Models
+------------------------------
+
+CVS users are accustomed to giving a group of developers commit access to
+a common repository.  As we've seen, this is also possible with git.
+However, the distributed nature of git allows other development models,
+and you may want to first consider whether one of them might be a better
+fit for your project.
+
+For example, you can choose a single person to maintain the project's
+primary public repository.  Other developers then clone this repository
+and each work in their own clone.  When they have a series of changes that
+they're happy with, they ask the maintainer to pull from the branch
+containing the changes.  The maintainer reviews their changes and pulls
+them into the primary repository, which other developers pull from as
+necessary to stay coordinated.  The Linux kernel and other projects use
+variants of this model.
+
+With a small group, developers may just pull changes from each other's
+repositories without the need for a central maintainer.
+
+SEE ALSO
+--------
+linkgit:gittutorial[7], linkgit:gittutorial-2[7],
+link:everyday.html[Everyday Git],
+link:user-manual.html[The Git User's Manual]
+
+GIT
+---
+Part of the linkgit:git[7] suite.
diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt
new file mode 100644 (file)
index 0000000..5bbbf43
--- /dev/null
@@ -0,0 +1,428 @@
+gittutorial-2(7)
+================
+
+NAME
+----
+gittutorial-2 - A tutorial introduction to git: part two
+
+SYNOPSIS
+--------
+git *
+
+DESCRIPTION
+-----------
+
+You should work through linkgit:gittutorial[7][A tutorial introduction to
+git] before reading this tutorial.
+
+The goal of this tutorial is to introduce two fundamental pieces of
+git's architecture--the object database and the index file--and to
+provide the reader with everything necessary to understand the rest
+of the git documentation.
+
+The git object database
+-----------------------
+
+Let's start a new project and create a small amount of history:
+
+------------------------------------------------
+$ mkdir test-project
+$ cd test-project
+$ git init
+Initialized empty Git repository in .git/
+$ echo 'hello world' > file.txt
+$ git add .
+$ git commit -a -m "initial commit"
+Created initial commit 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
+ create mode 100644 file.txt
+$ echo 'hello world!' >file.txt
+$ git commit -a -m "add emphasis"
+Created commit c4d59f390b9cfd4318117afde11d601c1085f241
+------------------------------------------------
+
+What are the 40 digits of hex that git responded to the commit with?
+
+We saw in part one of the tutorial that commits have names like this.
+It turns out that every object in the git history is stored under
+such a 40-digit hex name.  That name is the SHA1 hash of the object's
+contents; among other things, this ensures that git will never store
+the same data twice (since identical data is given an identical SHA1
+name), and that the contents of a git object will never change (since
+that would change the object's name as well).
+
+It is expected that the content of the commit object you created while
+following the example above generates a different SHA1 hash than
+the one shown above because the commit object records the time when
+it was created and the name of the person performing the commit.
+
+We can ask git about this particular object with the cat-file
+command. Don't copy the 40 hex digits from this example but use those
+from your own version. Note that you can shorten it to only a few
+characters to save yourself typing all 40 hex digits:
+
+------------------------------------------------
+$ git-cat-file -t 54196cc2
+commit
+$ git-cat-file commit 54196cc2
+tree 92b8b694ffb1675e5975148e1121810081dbdffe
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+
+initial commit
+------------------------------------------------
+
+A tree can refer to one or more "blob" objects, each corresponding to
+a file.  In addition, a tree can also refer to other tree objects,
+thus creating a directory hierarchy.  You can examine the contents of
+any tree using ls-tree (remember that a long enough initial portion
+of the SHA1 will also work):
+
+------------------------------------------------
+$ git ls-tree 92b8b694
+100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file.txt
+------------------------------------------------
+
+Thus we see that this tree has one file in it.  The SHA1 hash is a
+reference to that file's data:
+
+------------------------------------------------
+$ git cat-file -t 3b18e512
+blob
+------------------------------------------------
+
+A "blob" is just file data, which we can also examine with cat-file:
+
+------------------------------------------------
+$ git cat-file blob 3b18e512
+hello world
+------------------------------------------------
+
+Note that this is the old file data; so the object that git named in
+its response to the initial tree was a tree with a snapshot of the
+directory state that was recorded by the first commit.
+
+All of these objects are stored under their SHA1 names inside the git
+directory:
+
+------------------------------------------------
+$ find .git/objects/
+.git/objects/
+.git/objects/pack
+.git/objects/info
+.git/objects/3b
+.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
+.git/objects/92
+.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
+.git/objects/54
+.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
+.git/objects/a0
+.git/objects/a0/423896973644771497bdc03eb99d5281615b51
+.git/objects/d0
+.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
+.git/objects/c4
+.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241
+------------------------------------------------
+
+and the contents of these files is just the compressed data plus a
+header identifying their length and their type.  The type is either a
+blob, a tree, a commit, or a tag.
+
+The simplest commit to find is the HEAD commit, which we can find
+from .git/HEAD:
+
+------------------------------------------------
+$ cat .git/HEAD
+ref: refs/heads/master
+------------------------------------------------
+
+As you can see, this tells us which branch we're currently on, and it
+tells us this by naming a file under the .git directory, which itself
+contains a SHA1 name referring to a commit object, which we can
+examine with cat-file:
+
+------------------------------------------------
+$ cat .git/refs/heads/master
+c4d59f390b9cfd4318117afde11d601c1085f241
+$ git cat-file -t c4d59f39
+commit
+$ git cat-file commit c4d59f39
+tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
+parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
+
+add emphasis
+------------------------------------------------
+
+The "tree" object here refers to the new state of the tree:
+
+------------------------------------------------
+$ git ls-tree d0492b36
+100644 blob a0423896973644771497bdc03eb99d5281615b51    file.txt
+$ git cat-file blob a0423896
+hello world!
+------------------------------------------------
+
+and the "parent" object refers to the previous commit:
+
+------------------------------------------------
+$ git-cat-file commit 54196cc2
+tree 92b8b694ffb1675e5975148e1121810081dbdffe
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+
+initial commit
+------------------------------------------------
+
+The tree object is the tree we examined first, and this commit is
+unusual in that it lacks any parent.
+
+Most commits have only one parent, but it is also common for a commit
+to have multiple parents.   In that case the commit represents a
+merge, with the parent references pointing to the heads of the merged
+branches.
+
+Besides blobs, trees, and commits, the only remaining type of object
+is a "tag", which we won't discuss here; refer to linkgit:git-tag[1]
+for details.
+
+So now we know how git uses the object database to represent a
+project's history:
+
+  * "commit" objects refer to "tree" objects representing the
+    snapshot of a directory tree at a particular point in the
+    history, and refer to "parent" commits to show how they're
+    connected into the project history.
+  * "tree" objects represent the state of a single directory,
+    associating directory names to "blob" objects containing file
+    data and "tree" objects containing subdirectory information.
+  * "blob" objects contain file data without any other structure.
+  * References to commit objects at the head of each branch are
+    stored in files under .git/refs/heads/.
+  * The name of the current branch is stored in .git/HEAD.
+
+Note, by the way, that lots of commands take a tree as an argument.
+But as we can see above, a tree can be referred to in many different
+ways--by the SHA1 name for that tree, by the name of a commit that
+refers to the tree, by the name of a branch whose head refers to that
+tree, etc.--and most such commands can accept any of these names.
+
+In command synopses, the word "tree-ish" is sometimes used to
+designate such an argument.
+
+The index file
+--------------
+
+The primary tool we've been using to create commits is "git commit
+-a", which creates a commit including every change you've made to
+your working tree.  But what if you want to commit changes only to
+certain files?  Or only certain changes to certain files?
+
+If we look at the way commits are created under the cover, we'll see
+that there are more flexible ways creating commits.
+
+Continuing with our test-project, let's modify file.txt again:
+
+------------------------------------------------
+$ echo "hello world, again" >>file.txt
+------------------------------------------------
+
+but this time instead of immediately making the commit, let's take an
+intermediate step, and ask for diffs along the way to keep track of
+what's happening:
+
+------------------------------------------------
+$ git diff
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+$ git add file.txt
+$ git diff
+------------------------------------------------
+
+The last diff is empty, but no new commits have been made, and the
+head still doesn't contain the new line:
+
+------------------------------------------------
+$ git-diff HEAD
+diff --git a/file.txt b/file.txt
+index a042389..513feba 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+------------------------------------------------
+
+So "git diff" is comparing against something other than the head.
+The thing that it's comparing against is actually the index file,
+which is stored in .git/index in a binary format, but whose contents
+we can examine with ls-files:
+
+------------------------------------------------
+$ git ls-files --stage
+100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
+$ git cat-file -t 513feba2
+blob
+$ git cat-file blob 513feba2
+hello world!
+hello world, again
+------------------------------------------------
+
+So what our "git add" did was store a new blob and then put
+a reference to it in the index file.  If we modify the file again,
+we'll see that the new modifications are reflected in the "git-diff"
+output:
+
+------------------------------------------------
+$ echo 'again?' >>file.txt
+$ git diff
+index 513feba..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1,2 +1,3 @@
+ hello world!
+ hello world, again
++again?
+------------------------------------------------
+
+With the right arguments, git diff can also show us the difference
+between the working directory and the last commit, or between the
+index and the last commit:
+
+------------------------------------------------
+$ git diff HEAD
+diff --git a/file.txt b/file.txt
+index a042389..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,3 @@
+ hello world!
++hello world, again
++again?
+$ git diff --cached
+diff --git a/file.txt b/file.txt
+index a042389..513feba 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+------------------------------------------------
+
+At any time, we can create a new commit using "git commit" (without
+the -a option), and verify that the state committed only includes the
+changes stored in the index file, not the additional change that is
+still only in our working tree:
+
+------------------------------------------------
+$ git commit -m "repeat"
+$ git diff HEAD
+diff --git a/file.txt b/file.txt
+index 513feba..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1,2 +1,3 @@
+ hello world!
+ hello world, again
++again?
+------------------------------------------------
+
+So by default "git commit" uses the index to create the commit, not
+the working tree; the -a option to commit tells it to first update
+the index with all changes in the working tree.
+
+Finally, it's worth looking at the effect of "git add" on the index
+file:
+
+------------------------------------------------
+$ echo "goodbye, world" >closing.txt
+$ git add closing.txt
+------------------------------------------------
+
+The effect of the "git add" was to add one entry to the index file:
+
+------------------------------------------------
+$ git ls-files --stage
+100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0       closing.txt
+100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
+------------------------------------------------
+
+And, as you can see with cat-file, this new entry refers to the
+current contents of the file:
+
+------------------------------------------------
+$ git cat-file blob 8b9743b2
+goodbye, world
+------------------------------------------------
+
+The "status" command is a useful way to get a quick summary of the
+situation:
+
+------------------------------------------------
+$ git status
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#       new file: closing.txt
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#
+#       modified: file.txt
+#
+------------------------------------------------
+
+Since the current state of closing.txt is cached in the index file,
+it is listed as "Changes to be committed".  Since file.txt has
+changes in the working directory that aren't reflected in the index,
+it is marked "changed but not updated".  At this point, running "git
+commit" would create a commit that added closing.txt (with its new
+contents), but that didn't modify file.txt.
+
+Also, note that a bare "git diff" shows the changes to file.txt, but
+not the addition of closing.txt, because the version of closing.txt
+in the index file is identical to the one in the working directory.
+
+In addition to being the staging area for new commits, the index file
+is also populated from the object database when checking out a
+branch, and is used to hold the trees involved in a merge operation.
+See the link:core-tutorial.html[core tutorial] and the relevant man
+pages for details.
+
+What next?
+----------
+
+At this point you should know everything necessary to read the man
+pages for any of the git commands; one good place to start would be
+with the commands mentioned in link:everyday.html[Everyday git].  You
+should be able to find any unknown jargon in the
+link:glossary.html[Glossary].
+
+The link:user-manual.html[Git User's Manual] provides a more
+comprehensive introduction to git.
+
+The linkgit:gitcvs-migration[7][CVS migration] document explains how to
+import a CVS repository into git, and shows how to use git in a
+CVS-like way.
+
+For some interesting examples of git use, see the
+link:howto-index.html[howtos].
+
+For git developers, the link:core-tutorial.html[Core tutorial] goes
+into detail on the lower-level git mechanisms involved in, for
+example, creating a new commit.
+
+SEE ALSO
+--------
+linkgit:gittutorial[7],
+linkgit:gitcvs-migration[7],
+link:everyday.html[Everyday git],
+link:user-manual.html[The Git User's Manual]
+
+GIT
+---
+Part of the linkgit:git[7] suite.
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
new file mode 100644 (file)
index 0000000..898acdb
--- /dev/null
@@ -0,0 +1,606 @@
+gittutorial(7)
+==============
+
+NAME
+----
+gittutorial - A tutorial introduction to git (for version 1.5.1 or newer)
+
+SYNOPSIS
+--------
+git *
+
+DESCRIPTION
+-----------
+
+This tutorial explains how to import a new project into git, make
+changes to it, and share changes with other developers.
+
+If you are instead primarily interested in using git to fetch a project,
+for example, to test the latest version, you may prefer to start with
+the first two chapters of link:user-manual.html[The Git User's Manual].
+
+First, note that you can get documentation for a command such as "git
+diff" with:
+
+------------------------------------------------
+$ man git-diff
+------------------------------------------------
+
+It is a good idea to introduce yourself to git with your name and
+public email address before doing any operation.  The easiest
+way to do so is:
+
+------------------------------------------------
+$ git config --global user.name "Your Name Comes Here"
+$ git config --global user.email you@yourdomain.example.com
+------------------------------------------------
+
+
+Importing a new project
+-----------------------
+
+Assume you have a tarball project.tar.gz with your initial work.  You
+can place it under git revision control as follows.
+
+------------------------------------------------
+$ tar xzf project.tar.gz
+$ cd project
+$ git init
+------------------------------------------------
+
+Git will reply
+
+------------------------------------------------
+Initialized empty Git repository in .git/
+------------------------------------------------
+
+You've now initialized the working directory--you may notice a new
+directory created, named ".git".
+
+Next, tell git to take a snapshot of the contents of all files under the
+current directory (note the '.'), with linkgit:git-add[1]:
+
+------------------------------------------------
+$ git add .
+------------------------------------------------
+
+This snapshot is now stored in a temporary staging area which git calls
+the "index".  You can permanently store the contents of the index in the
+repository with linkgit:git-commit[1]:
+
+------------------------------------------------
+$ git commit
+------------------------------------------------
+
+This will prompt you for a commit message.  You've now stored the first
+version of your project in git.
+
+Making changes
+--------------
+
+Modify some files, then add their updated contents to the index:
+
+------------------------------------------------
+$ git add file1 file2 file3
+------------------------------------------------
+
+You are now ready to commit.  You can see what is about to be committed
+using linkgit:git-diff[1] with the --cached option:
+
+------------------------------------------------
+$ git diff --cached
+------------------------------------------------
+
+(Without --cached, linkgit:git-diff[1] will show you any changes that
+you've made but not yet added to the index.)  You can also get a brief
+summary of the situation with linkgit:git-status[1]:
+
+------------------------------------------------
+$ git status
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      modified:   file1
+#      modified:   file2
+#      modified:   file3
+#
+------------------------------------------------
+
+If you need to make any further adjustments, do so now, and then add any
+newly modified content to the index.  Finally, commit your changes with:
+
+------------------------------------------------
+$ git commit
+------------------------------------------------
+
+This will again prompt your for a message describing the change, and then
+record a new version of the project.
+
+Alternatively, instead of running `git add` beforehand, you can use
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+which will automatically notice any modified (but not new) files, add
+them to the index, and commit, all in one step.
+
+A note on commit messages: Though not required, it's a good idea to
+begin the commit message with a single short (less than 50 character)
+line summarizing the change, followed by a blank line and then a more
+thorough description.  Tools that turn commits into email, for
+example, use the first line on the Subject: line and the rest of the
+commit in the body.
+
+Git tracks content not files
+----------------------------
+
+Many revision control systems provide an "add" command that tells the
+system to start tracking changes to a new file.  Git's "add" command
+does something simpler and more powerful: `git add` is used both for new
+and newly modified files, and in both cases it takes a snapshot of the
+given files and stages that content in the index, ready for inclusion in
+the next commit.
+
+Viewing project history
+-----------------------
+
+At any point you can view the history of your changes using
+
+------------------------------------------------
+$ git log
+------------------------------------------------
+
+If you also want to see complete diffs at each step, use
+
+------------------------------------------------
+$ git log -p
+------------------------------------------------
+
+Often the overview of the change is useful to get a feel of
+each step
+
+------------------------------------------------
+$ git log --stat --summary
+------------------------------------------------
+
+Managing branches
+-----------------
+
+A single git repository can maintain multiple branches of
+development.  To create a new branch named "experimental", use
+
+------------------------------------------------
+$ git branch experimental
+------------------------------------------------
+
+If you now run
+
+------------------------------------------------
+$ git branch
+------------------------------------------------
+
+you'll get a list of all existing branches:
+
+------------------------------------------------
+  experimental
+* master
+------------------------------------------------
+
+The "experimental" branch is the one you just created, and the
+"master" branch is a default branch that was created for you
+automatically.  The asterisk marks the branch you are currently on;
+type
+
+------------------------------------------------
+$ git checkout experimental
+------------------------------------------------
+
+to switch to the experimental branch.  Now edit a file, commit the
+change, and switch back to the master branch:
+
+------------------------------------------------
+(edit file)
+$ git commit -a
+$ git checkout master
+------------------------------------------------
+
+Check that the change you made is no longer visible, since it was
+made on the experimental branch and you're back on the master branch.
+
+You can make a different change on the master branch:
+
+------------------------------------------------
+(edit file)
+$ git commit -a
+------------------------------------------------
+
+at this point the two branches have diverged, with different changes
+made in each.  To merge the changes made in experimental into master, run
+
+------------------------------------------------
+$ git merge experimental
+------------------------------------------------
+
+If the changes don't conflict, you're done.  If there are conflicts,
+markers will be left in the problematic files showing the conflict;
+
+------------------------------------------------
+$ git diff
+------------------------------------------------
+
+will show this.  Once you've edited the files to resolve the
+conflicts,
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+will commit the result of the merge. Finally,
+
+------------------------------------------------
+$ gitk
+------------------------------------------------
+
+will show a nice graphical representation of the resulting history.
+
+At this point you could delete the experimental branch with
+
+------------------------------------------------
+$ git branch -d experimental
+------------------------------------------------
+
+This command ensures that the changes in the experimental branch are
+already in the current branch.
+
+If you develop on a branch crazy-idea, then regret it, you can always
+delete the branch with
+
+-------------------------------------
+$ git branch -D crazy-idea
+-------------------------------------
+
+Branches are cheap and easy, so this is a good way to try something
+out.
+
+Using git for collaboration
+---------------------------
+
+Suppose that Alice has started a new project with a git repository in
+/home/alice/project, and that Bob, who has a home directory on the
+same machine, wants to contribute.
+
+Bob begins with:
+
+------------------------------------------------
+$ git clone /home/alice/project myrepo
+------------------------------------------------
+
+This creates a new directory "myrepo" containing a clone of Alice's
+repository.  The clone is on an equal footing with the original
+project, possessing its own copy of the original project's history.
+
+Bob then makes some changes and commits them:
+
+------------------------------------------------
+(edit files)
+$ git commit -a
+(repeat as necessary)
+------------------------------------------------
+
+When he's ready, he tells Alice to pull changes from the repository
+at /home/bob/myrepo.  She does this with:
+
+------------------------------------------------
+$ cd /home/alice/project
+$ git pull /home/bob/myrepo master
+------------------------------------------------
+
+This merges the changes from Bob's "master" branch into Alice's
+current branch.  If Alice has made her own changes in the meantime,
+then she may need to manually fix any conflicts.  (Note that the
+"master" argument in the above command is actually unnecessary, as it
+is the default.)
+
+The "pull" command thus performs two operations: it fetches changes
+from a remote branch, then merges them into the current branch.
+
+When you are working in a small closely knit group, it is not
+unusual to interact with the same repository over and over
+again.  By defining 'remote' repository shorthand, you can make
+it easier:
+
+------------------------------------------------
+$ git remote add bob /home/bob/myrepo
+------------------------------------------------
+
+With this, Alice can perform the first operation alone using the
+"git fetch" command without merging them with her own branch,
+using:
+
+-------------------------------------
+$ git fetch bob
+-------------------------------------
+
+Unlike the longhand form, when Alice fetches from Bob using a
+remote repository shorthand set up with `git remote`, what was
+fetched is stored in a remote tracking branch, in this case
+`bob/master`.  So after this:
+
+-------------------------------------
+$ git log -p master..bob/master
+-------------------------------------
+
+shows a list of all the changes that Bob made since he branched from
+Alice's master branch.
+
+After examining those changes, Alice
+could merge the changes into her master branch:
+
+-------------------------------------
+$ git merge bob/master
+-------------------------------------
+
+This `merge` can also be done by 'pulling from her own remote
+tracking branch', like this:
+
+-------------------------------------
+$ git pull . remotes/bob/master
+-------------------------------------
+
+Note that git pull always merges into the current branch,
+regardless of what else is given on the command line.
+
+Later, Bob can update his repo with Alice's latest changes using
+
+-------------------------------------
+$ git pull
+-------------------------------------
+
+Note that he doesn't need to give the path to Alice's repository;
+when Bob cloned Alice's repository, git stored the location of her
+repository in the repository configuration, and that location is
+used for pulls:
+
+-------------------------------------
+$ git config --get remote.origin.url
+/home/alice/project
+-------------------------------------
+
+(The complete configuration created by git-clone is visible using
+"git config -l", and the linkgit:git-config[1] man page
+explains the meaning of each option.)
+
+Git also keeps a pristine copy of Alice's master branch under the
+name "origin/master":
+
+-------------------------------------
+$ git branch -r
+  origin/master
+-------------------------------------
+
+If Bob later decides to work from a different host, he can still
+perform clones and pulls using the ssh protocol:
+
+-------------------------------------
+$ git clone alice.org:/home/alice/project myrepo
+-------------------------------------
+
+Alternatively, git has a native protocol, or can use rsync or http;
+see linkgit:git-pull[1] for details.
+
+Git can also be used in a CVS-like mode, with a central repository
+that various users push changes to; see linkgit:git-push[1] and
+linkgit:gitcvs-migration[7][git for CVS users].
+
+Exploring history
+-----------------
+
+Git history is represented as a series of interrelated commits.  We
+have already seen that the git log command can list those commits.
+Note that first line of each git log entry also gives a name for the
+commit:
+
+-------------------------------------
+$ git log
+commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
+Author: Junio C Hamano <junkio@cox.net>
+Date:   Tue May 16 17:18:22 2006 -0700
+
+    merge-base: Clarify the comments on post processing.
+-------------------------------------
+
+We can give this name to git show to see the details about this
+commit.
+
+-------------------------------------
+$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
+-------------------------------------
+
+But there are other ways to refer to commits.  You can use any initial
+part of the name that is long enough to uniquely identify the commit:
+
+-------------------------------------
+$ git show c82a22c39c  # the first few characters of the name are
+                       # usually enough
+$ git show HEAD                # the tip of the current branch
+$ git show experimental        # the tip of the "experimental" branch
+-------------------------------------
+
+Every commit usually has one "parent" commit
+which points to the previous state of the project:
+
+-------------------------------------
+$ git show HEAD^  # to see the parent of HEAD
+$ git show HEAD^^ # to see the grandparent of HEAD
+$ git show HEAD~4 # to see the great-great grandparent of HEAD
+-------------------------------------
+
+Note that merge commits may have more than one parent:
+
+-------------------------------------
+$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)
+$ git show HEAD^2 # show the second parent of HEAD
+-------------------------------------
+
+You can also give commits names of your own; after running
+
+-------------------------------------
+$ git-tag v2.5 1b2e1d63ff
+-------------------------------------
+
+you can refer to 1b2e1d63ff by the name "v2.5".  If you intend to
+share this name with other people (for example, to identify a release
+version), you should create a "tag" object, and perhaps sign it; see
+linkgit:git-tag[1] for details.
+
+Any git command that needs to know a commit can take any of these
+names.  For example:
+
+-------------------------------------
+$ git diff v2.5 HEAD    # compare the current HEAD to v2.5
+$ git branch stable v2.5 # start a new branch named "stable" based
+                        # at v2.5
+$ git reset --hard HEAD^ # reset your current branch and working
+                        # directory to its state at HEAD^
+-------------------------------------
+
+Be careful with that last command: in addition to losing any changes
+in the working directory, it will also remove all later commits from
+this branch.  If this branch is the only branch containing those
+commits, they will be lost.  Also, don't use "git reset" on a
+publicly-visible branch that other developers pull from, as it will
+force needless merges on other developers to clean up the history.
+If you need to undo changes that you have pushed, use linkgit:git-revert[1]
+instead.
+
+The git grep command can search for strings in any version of your
+project, so
+
+-------------------------------------
+$ git grep "hello" v2.5
+-------------------------------------
+
+searches for all occurrences of "hello" in v2.5.
+
+If you leave out the commit name, git grep will search any of the
+files it manages in your current directory.  So
+
+-------------------------------------
+$ git grep "hello"
+-------------------------------------
+
+is a quick way to search just the files that are tracked by git.
+
+Many git commands also take sets of commits, which can be specified
+in a number of ways.  Here are some examples with git log:
+
+-------------------------------------
+$ git log v2.5..v2.6            # commits between v2.5 and v2.6
+$ git log v2.5..                # commits since v2.5
+$ git log --since="2 weeks ago" # commits from the last 2 weeks
+$ git log v2.5.. Makefile       # commits since v2.5 which modify
+                               # Makefile
+-------------------------------------
+
+You can also give git log a "range" of commits where the first is not
+necessarily an ancestor of the second; for example, if the tips of
+the branches "stable-release" and "master" diverged from a common
+commit some time ago, then
+
+-------------------------------------
+$ git log stable..experimental
+-------------------------------------
+
+will list commits made in the experimental branch but not in the
+stable branch, while
+
+-------------------------------------
+$ git log experimental..stable
+-------------------------------------
+
+will show the list of commits made on the stable branch but not
+the experimental branch.
+
+The "git log" command has a weakness: it must present commits in a
+list.  When the history has lines of development that diverged and
+then merged back together, the order in which "git log" presents
+those commits is meaningless.
+
+Most projects with multiple contributors (such as the linux kernel,
+or git itself) have frequent merges, and gitk does a better job of
+visualizing their history.  For example,
+
+-------------------------------------
+$ gitk --since="2 weeks ago" drivers/
+-------------------------------------
+
+allows you to browse any commits from the last 2 weeks of commits
+that modified files under the "drivers" directory.  (Note: you can
+adjust gitk's fonts by holding down the control key while pressing
+"-" or "+".)
+
+Finally, most commands that take filenames will optionally allow you
+to precede any filename by a commit, to specify a particular version
+of the file:
+
+-------------------------------------
+$ git diff v2.5:Makefile HEAD:Makefile.in
+-------------------------------------
+
+You can also use "git show" to see any such file:
+
+-------------------------------------
+$ git show v2.5:Makefile
+-------------------------------------
+
+Next Steps
+----------
+
+This tutorial should be enough to perform basic distributed revision
+control for your projects.  However, to fully understand the depth
+and power of git you need to understand two simple ideas on which it
+is based:
+
+  * The object database is the rather elegant system used to
+    store the history of your project--files, directories, and
+    commits.
+
+  * The index file is a cache of the state of a directory tree,
+    used to create commits, check out working directories, and
+    hold the various trees involved in a merge.
+
+linkgit:gittutorial-2[7][Part two of this tutorial] explains the object
+database, the index file, and a few other odds and ends that you'll
+need to make the most of git.
+
+If you don't want to continue with that right away, a few other
+digressions that may be interesting at this point are:
+
+  * linkgit:git-format-patch[1], linkgit:git-am[1]: These convert
+    series of git commits into emailed patches, and vice versa,
+    useful for projects such as the linux kernel which rely heavily
+    on emailed patches.
+
+  * linkgit:git-bisect[1]: When there is a regression in your
+    project, one way to track down the bug is by searching through
+    the history to find the exact commit that's to blame.  Git bisect
+    can help you perform a binary search for that commit.  It is
+    smart enough to perform a close-to-optimal search even in the
+    case of complex non-linear history with lots of merged branches.
+
+  * link:everyday.html[Everyday GIT with 20 Commands Or So]
+
+  * linkgit:gitcvs-migration[7][git for CVS users].
+
+SEE ALSO
+--------
+linkgit:gittutorial-2[7],
+linkgit:gitcvs-migration[7],
+link:everyday.html[Everyday git],
+link:user-manual.html[The Git User's Manual]
+
+GIT
+---
+Part of the linkgit:git[7] suite.
index ce1c08ee862b5eeb04a03b0e04caf68b0bdf9520..e9559790a32185b1d4ac8ae72881f4f63f082538 100644 (file)
@@ -115,7 +115,7 @@ Sample usage
 
 ------------
 struct commit *commit;
-struct git_graph *graph = graph_init();
+struct git_graph *graph = graph_init(opts);
 
 while ((commit = get_revision(opts)) != NULL) {
        graph_update(graph, commit);
diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt
deleted file mode 100644 (file)
index 7fac47d..0000000
+++ /dev/null
@@ -1,406 +0,0 @@
-A tutorial introduction to git: part two
-========================================
-
-You should work through link:tutorial.html[A tutorial introduction to
-git] before reading this tutorial.
-
-The goal of this tutorial is to introduce two fundamental pieces of
-git's architecture--the object database and the index file--and to
-provide the reader with everything necessary to understand the rest
-of the git documentation.
-
-The git object database
------------------------
-
-Let's start a new project and create a small amount of history:
-
-------------------------------------------------
-$ mkdir test-project
-$ cd test-project
-$ git init
-Initialized empty Git repository in .git/
-$ echo 'hello world' > file.txt
-$ git add .
-$ git commit -a -m "initial commit"
-Created initial commit 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
- create mode 100644 file.txt
-$ echo 'hello world!' >file.txt
-$ git commit -a -m "add emphasis"
-Created commit c4d59f390b9cfd4318117afde11d601c1085f241
-------------------------------------------------
-
-What are the 40 digits of hex that git responded to the commit with?
-
-We saw in part one of the tutorial that commits have names like this.
-It turns out that every object in the git history is stored under
-such a 40-digit hex name.  That name is the SHA1 hash of the object's
-contents; among other things, this ensures that git will never store
-the same data twice (since identical data is given an identical SHA1
-name), and that the contents of a git object will never change (since
-that would change the object's name as well).
-
-It is expected that the content of the commit object you created while
-following the example above generates a different SHA1 hash than
-the one shown above because the commit object records the time when
-it was created and the name of the person performing the commit.
-
-We can ask git about this particular object with the cat-file
-command. Don't copy the 40 hex digits from this example but use those
-from your own version. Note that you can shorten it to only a few
-characters to save yourself typing all 40 hex digits:
-
-------------------------------------------------
-$ git-cat-file -t 54196cc2
-commit
-$ git-cat-file commit 54196cc2
-tree 92b8b694ffb1675e5975148e1121810081dbdffe
-author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-
-initial commit
-------------------------------------------------
-
-A tree can refer to one or more "blob" objects, each corresponding to
-a file.  In addition, a tree can also refer to other tree objects,
-thus creating a directory hierarchy.  You can examine the contents of
-any tree using ls-tree (remember that a long enough initial portion
-of the SHA1 will also work):
-
-------------------------------------------------
-$ git ls-tree 92b8b694
-100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file.txt
-------------------------------------------------
-
-Thus we see that this tree has one file in it.  The SHA1 hash is a
-reference to that file's data:
-
-------------------------------------------------
-$ git cat-file -t 3b18e512
-blob
-------------------------------------------------
-
-A "blob" is just file data, which we can also examine with cat-file:
-
-------------------------------------------------
-$ git cat-file blob 3b18e512
-hello world
-------------------------------------------------
-
-Note that this is the old file data; so the object that git named in
-its response to the initial tree was a tree with a snapshot of the
-directory state that was recorded by the first commit.
-
-All of these objects are stored under their SHA1 names inside the git
-directory:
-
-------------------------------------------------
-$ find .git/objects/
-.git/objects/
-.git/objects/pack
-.git/objects/info
-.git/objects/3b
-.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
-.git/objects/92
-.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
-.git/objects/54
-.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
-.git/objects/a0
-.git/objects/a0/423896973644771497bdc03eb99d5281615b51
-.git/objects/d0
-.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
-.git/objects/c4
-.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241
-------------------------------------------------
-
-and the contents of these files is just the compressed data plus a
-header identifying their length and their type.  The type is either a
-blob, a tree, a commit, or a tag.
-
-The simplest commit to find is the HEAD commit, which we can find
-from .git/HEAD:
-
-------------------------------------------------
-$ cat .git/HEAD
-ref: refs/heads/master
-------------------------------------------------
-
-As you can see, this tells us which branch we're currently on, and it
-tells us this by naming a file under the .git directory, which itself
-contains a SHA1 name referring to a commit object, which we can
-examine with cat-file:
-
-------------------------------------------------
-$ cat .git/refs/heads/master
-c4d59f390b9cfd4318117afde11d601c1085f241
-$ git cat-file -t c4d59f39
-commit
-$ git cat-file commit c4d59f39
-tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
-parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
-author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
-committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
-
-add emphasis
-------------------------------------------------
-
-The "tree" object here refers to the new state of the tree:
-
-------------------------------------------------
-$ git ls-tree d0492b36
-100644 blob a0423896973644771497bdc03eb99d5281615b51    file.txt
-$ git cat-file blob a0423896
-hello world!
-------------------------------------------------
-
-and the "parent" object refers to the previous commit:
-
-------------------------------------------------
-$ git-cat-file commit 54196cc2
-tree 92b8b694ffb1675e5975148e1121810081dbdffe
-author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-
-initial commit
-------------------------------------------------
-
-The tree object is the tree we examined first, and this commit is
-unusual in that it lacks any parent.
-
-Most commits have only one parent, but it is also common for a commit
-to have multiple parents.   In that case the commit represents a
-merge, with the parent references pointing to the heads of the merged
-branches.
-
-Besides blobs, trees, and commits, the only remaining type of object
-is a "tag", which we won't discuss here; refer to linkgit:git-tag[1]
-for details.
-
-So now we know how git uses the object database to represent a
-project's history:
-
-  * "commit" objects refer to "tree" objects representing the
-    snapshot of a directory tree at a particular point in the
-    history, and refer to "parent" commits to show how they're
-    connected into the project history.
-  * "tree" objects represent the state of a single directory,
-    associating directory names to "blob" objects containing file
-    data and "tree" objects containing subdirectory information.
-  * "blob" objects contain file data without any other structure.
-  * References to commit objects at the head of each branch are
-    stored in files under .git/refs/heads/.
-  * The name of the current branch is stored in .git/HEAD.
-
-Note, by the way, that lots of commands take a tree as an argument.
-But as we can see above, a tree can be referred to in many different
-ways--by the SHA1 name for that tree, by the name of a commit that
-refers to the tree, by the name of a branch whose head refers to that
-tree, etc.--and most such commands can accept any of these names.
-
-In command synopses, the word "tree-ish" is sometimes used to
-designate such an argument.
-
-The index file
---------------
-
-The primary tool we've been using to create commits is "git commit
--a", which creates a commit including every change you've made to
-your working tree.  But what if you want to commit changes only to
-certain files?  Or only certain changes to certain files?
-
-If we look at the way commits are created under the cover, we'll see
-that there are more flexible ways creating commits.
-
-Continuing with our test-project, let's modify file.txt again:
-
-------------------------------------------------
-$ echo "hello world, again" >>file.txt
-------------------------------------------------
-
-but this time instead of immediately making the commit, let's take an
-intermediate step, and ask for diffs along the way to keep track of
-what's happening:
-
-------------------------------------------------
-$ git diff
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,2 @@
- hello world!
-+hello world, again
-$ git add file.txt
-$ git diff
-------------------------------------------------
-
-The last diff is empty, but no new commits have been made, and the
-head still doesn't contain the new line:
-
-------------------------------------------------
-$ git-diff HEAD
-diff --git a/file.txt b/file.txt
-index a042389..513feba 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,2 @@
- hello world!
-+hello world, again
-------------------------------------------------
-
-So "git diff" is comparing against something other than the head.
-The thing that it's comparing against is actually the index file,
-which is stored in .git/index in a binary format, but whose contents
-we can examine with ls-files:
-
-------------------------------------------------
-$ git ls-files --stage
-100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
-$ git cat-file -t 513feba2
-blob
-$ git cat-file blob 513feba2
-hello world!
-hello world, again
-------------------------------------------------
-
-So what our "git add" did was store a new blob and then put
-a reference to it in the index file.  If we modify the file again,
-we'll see that the new modifications are reflected in the "git-diff"
-output:
-
-------------------------------------------------
-$ echo 'again?' >>file.txt
-$ git diff
-index 513feba..ba3da7b 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1,2 +1,3 @@
- hello world!
- hello world, again
-+again?
-------------------------------------------------
-
-With the right arguments, git diff can also show us the difference
-between the working directory and the last commit, or between the
-index and the last commit:
-
-------------------------------------------------
-$ git diff HEAD
-diff --git a/file.txt b/file.txt
-index a042389..ba3da7b 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,3 @@
- hello world!
-+hello world, again
-+again?
-$ git diff --cached
-diff --git a/file.txt b/file.txt
-index a042389..513feba 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,2 @@
- hello world!
-+hello world, again
-------------------------------------------------
-
-At any time, we can create a new commit using "git commit" (without
-the -a option), and verify that the state committed only includes the
-changes stored in the index file, not the additional change that is
-still only in our working tree:
-
-------------------------------------------------
-$ git commit -m "repeat"
-$ git diff HEAD
-diff --git a/file.txt b/file.txt
-index 513feba..ba3da7b 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1,2 +1,3 @@
- hello world!
- hello world, again
-+again?
-------------------------------------------------
-
-So by default "git commit" uses the index to create the commit, not
-the working tree; the -a option to commit tells it to first update
-the index with all changes in the working tree.
-
-Finally, it's worth looking at the effect of "git add" on the index
-file:
-
-------------------------------------------------
-$ echo "goodbye, world" >closing.txt
-$ git add closing.txt
-------------------------------------------------
-
-The effect of the "git add" was to add one entry to the index file:
-
-------------------------------------------------
-$ git ls-files --stage
-100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0       closing.txt
-100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
-------------------------------------------------
-
-And, as you can see with cat-file, this new entry refers to the
-current contents of the file:
-
-------------------------------------------------
-$ git cat-file blob 8b9743b2
-goodbye, world
-------------------------------------------------
-
-The "status" command is a useful way to get a quick summary of the
-situation:
-
-------------------------------------------------
-$ git status
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#       new file: closing.txt
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#
-#       modified: file.txt
-#
-------------------------------------------------
-
-Since the current state of closing.txt is cached in the index file,
-it is listed as "Changes to be committed".  Since file.txt has
-changes in the working directory that aren't reflected in the index,
-it is marked "changed but not updated".  At this point, running "git
-commit" would create a commit that added closing.txt (with its new
-contents), but that didn't modify file.txt.
-
-Also, note that a bare "git diff" shows the changes to file.txt, but
-not the addition of closing.txt, because the version of closing.txt
-in the index file is identical to the one in the working directory.
-
-In addition to being the staging area for new commits, the index file
-is also populated from the object database when checking out a
-branch, and is used to hold the trees involved in a merge operation.
-See the link:core-tutorial.html[core tutorial] and the relevant man
-pages for details.
-
-What next?
-----------
-
-At this point you should know everything necessary to read the man
-pages for any of the git commands; one good place to start would be
-with the commands mentioned in link:everyday.html[Everyday git].  You
-should be able to find any unknown jargon in the
-link:glossary.html[Glossary].
-
-The link:user-manual.html[Git User's Manual] provides a more
-comprehensive introduction to git.
-
-The link:cvs-migration.html[CVS migration] document explains how to
-import a CVS repository into git, and shows how to use git in a
-CVS-like way.
-
-For some interesting examples of git use, see the
-link:howto-index.html[howtos].
-
-For git developers, the link:core-tutorial.html[Core tutorial] goes
-into detail on the lower-level git mechanisms involved in, for
-example, creating a new commit.
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
deleted file mode 100644 (file)
index e2bbda5..0000000
+++ /dev/null
@@ -1,584 +0,0 @@
-A tutorial introduction to git (for version 1.5.1 or newer)
-===========================================================
-
-This tutorial explains how to import a new project into git, make
-changes to it, and share changes with other developers.
-
-If you are instead primarily interested in using git to fetch a project,
-for example, to test the latest version, you may prefer to start with
-the first two chapters of link:user-manual.html[The Git User's Manual].
-
-First, note that you can get documentation for a command such as "git
-diff" with:
-
-------------------------------------------------
-$ man git-diff
-------------------------------------------------
-
-It is a good idea to introduce yourself to git with your name and
-public email address before doing any operation.  The easiest
-way to do so is:
-
-------------------------------------------------
-$ git config --global user.name "Your Name Comes Here"
-$ git config --global user.email you@yourdomain.example.com
-------------------------------------------------
-
-
-Importing a new project
------------------------
-
-Assume you have a tarball project.tar.gz with your initial work.  You
-can place it under git revision control as follows.
-
-------------------------------------------------
-$ tar xzf project.tar.gz
-$ cd project
-$ git init
-------------------------------------------------
-
-Git will reply
-
-------------------------------------------------
-Initialized empty Git repository in .git/
-------------------------------------------------
-
-You've now initialized the working directory--you may notice a new
-directory created, named ".git".
-
-Next, tell git to take a snapshot of the contents of all files under the
-current directory (note the '.'), with linkgit:git-add[1]:
-
-------------------------------------------------
-$ git add .
-------------------------------------------------
-
-This snapshot is now stored in a temporary staging area which git calls
-the "index".  You can permanently store the contents of the index in the
-repository with linkgit:git-commit[1]:
-
-------------------------------------------------
-$ git commit
-------------------------------------------------
-
-This will prompt you for a commit message.  You've now stored the first
-version of your project in git.
-
-Making changes
---------------
-
-Modify some files, then add their updated contents to the index:
-
-------------------------------------------------
-$ git add file1 file2 file3
-------------------------------------------------
-
-You are now ready to commit.  You can see what is about to be committed
-using linkgit:git-diff[1] with the --cached option:
-
-------------------------------------------------
-$ git diff --cached
-------------------------------------------------
-
-(Without --cached, linkgit:git-diff[1] will show you any changes that
-you've made but not yet added to the index.)  You can also get a brief
-summary of the situation with linkgit:git-status[1]:
-
-------------------------------------------------
-$ git status
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      modified:   file1
-#      modified:   file2
-#      modified:   file3
-#
-------------------------------------------------
-
-If you need to make any further adjustments, do so now, and then add any
-newly modified content to the index.  Finally, commit your changes with:
-
-------------------------------------------------
-$ git commit
-------------------------------------------------
-
-This will again prompt your for a message describing the change, and then
-record a new version of the project.
-
-Alternatively, instead of running `git add` beforehand, you can use
-
-------------------------------------------------
-$ git commit -a
-------------------------------------------------
-
-which will automatically notice any modified (but not new) files, add
-them to the index, and commit, all in one step.
-
-A note on commit messages: Though not required, it's a good idea to
-begin the commit message with a single short (less than 50 character)
-line summarizing the change, followed by a blank line and then a more
-thorough description.  Tools that turn commits into email, for
-example, use the first line on the Subject: line and the rest of the
-commit in the body.
-
-Git tracks content not files
-----------------------------
-
-Many revision control systems provide an "add" command that tells the
-system to start tracking changes to a new file.  Git's "add" command
-does something simpler and more powerful: `git add` is used both for new
-and newly modified files, and in both cases it takes a snapshot of the
-given files and stages that content in the index, ready for inclusion in
-the next commit.
-
-Viewing project history
------------------------
-
-At any point you can view the history of your changes using
-
-------------------------------------------------
-$ git log
-------------------------------------------------
-
-If you also want to see complete diffs at each step, use
-
-------------------------------------------------
-$ git log -p
-------------------------------------------------
-
-Often the overview of the change is useful to get a feel of
-each step
-
-------------------------------------------------
-$ git log --stat --summary
-------------------------------------------------
-
-Managing branches
------------------
-
-A single git repository can maintain multiple branches of
-development.  To create a new branch named "experimental", use
-
-------------------------------------------------
-$ git branch experimental
-------------------------------------------------
-
-If you now run
-
-------------------------------------------------
-$ git branch
-------------------------------------------------
-
-you'll get a list of all existing branches:
-
-------------------------------------------------
-  experimental
-* master
-------------------------------------------------
-
-The "experimental" branch is the one you just created, and the
-"master" branch is a default branch that was created for you
-automatically.  The asterisk marks the branch you are currently on;
-type
-
-------------------------------------------------
-$ git checkout experimental
-------------------------------------------------
-
-to switch to the experimental branch.  Now edit a file, commit the
-change, and switch back to the master branch:
-
-------------------------------------------------
-(edit file)
-$ git commit -a
-$ git checkout master
-------------------------------------------------
-
-Check that the change you made is no longer visible, since it was
-made on the experimental branch and you're back on the master branch.
-
-You can make a different change on the master branch:
-
-------------------------------------------------
-(edit file)
-$ git commit -a
-------------------------------------------------
-
-at this point the two branches have diverged, with different changes
-made in each.  To merge the changes made in experimental into master, run
-
-------------------------------------------------
-$ git merge experimental
-------------------------------------------------
-
-If the changes don't conflict, you're done.  If there are conflicts,
-markers will be left in the problematic files showing the conflict;
-
-------------------------------------------------
-$ git diff
-------------------------------------------------
-
-will show this.  Once you've edited the files to resolve the
-conflicts,
-
-------------------------------------------------
-$ git commit -a
-------------------------------------------------
-
-will commit the result of the merge. Finally,
-
-------------------------------------------------
-$ gitk
-------------------------------------------------
-
-will show a nice graphical representation of the resulting history.
-
-At this point you could delete the experimental branch with
-
-------------------------------------------------
-$ git branch -d experimental
-------------------------------------------------
-
-This command ensures that the changes in the experimental branch are
-already in the current branch.
-
-If you develop on a branch crazy-idea, then regret it, you can always
-delete the branch with
-
--------------------------------------
-$ git branch -D crazy-idea
--------------------------------------
-
-Branches are cheap and easy, so this is a good way to try something
-out.
-
-Using git for collaboration
----------------------------
-
-Suppose that Alice has started a new project with a git repository in
-/home/alice/project, and that Bob, who has a home directory on the
-same machine, wants to contribute.
-
-Bob begins with:
-
-------------------------------------------------
-$ git clone /home/alice/project myrepo
-------------------------------------------------
-
-This creates a new directory "myrepo" containing a clone of Alice's
-repository.  The clone is on an equal footing with the original
-project, possessing its own copy of the original project's history.
-
-Bob then makes some changes and commits them:
-
-------------------------------------------------
-(edit files)
-$ git commit -a
-(repeat as necessary)
-------------------------------------------------
-
-When he's ready, he tells Alice to pull changes from the repository
-at /home/bob/myrepo.  She does this with:
-
-------------------------------------------------
-$ cd /home/alice/project
-$ git pull /home/bob/myrepo master
-------------------------------------------------
-
-This merges the changes from Bob's "master" branch into Alice's
-current branch.  If Alice has made her own changes in the meantime,
-then she may need to manually fix any conflicts.  (Note that the
-"master" argument in the above command is actually unnecessary, as it
-is the default.)
-
-The "pull" command thus performs two operations: it fetches changes
-from a remote branch, then merges them into the current branch.
-
-When you are working in a small closely knit group, it is not
-unusual to interact with the same repository over and over
-again.  By defining 'remote' repository shorthand, you can make
-it easier:
-
-------------------------------------------------
-$ git remote add bob /home/bob/myrepo
-------------------------------------------------
-
-With this, Alice can perform the first operation alone using the
-"git fetch" command without merging them with her own branch,
-using:
-
--------------------------------------
-$ git fetch bob
--------------------------------------
-
-Unlike the longhand form, when Alice fetches from Bob using a
-remote repository shorthand set up with `git remote`, what was
-fetched is stored in a remote tracking branch, in this case
-`bob/master`.  So after this:
-
--------------------------------------
-$ git log -p master..bob/master
--------------------------------------
-
-shows a list of all the changes that Bob made since he branched from
-Alice's master branch.
-
-After examining those changes, Alice
-could merge the changes into her master branch:
-
--------------------------------------
-$ git merge bob/master
--------------------------------------
-
-This `merge` can also be done by 'pulling from her own remote
-tracking branch', like this:
-
--------------------------------------
-$ git pull . remotes/bob/master
--------------------------------------
-
-Note that git pull always merges into the current branch,
-regardless of what else is given on the command line.
-
-Later, Bob can update his repo with Alice's latest changes using
-
--------------------------------------
-$ git pull
--------------------------------------
-
-Note that he doesn't need to give the path to Alice's repository;
-when Bob cloned Alice's repository, git stored the location of her
-repository in the repository configuration, and that location is
-used for pulls:
-
--------------------------------------
-$ git config --get remote.origin.url
-/home/alice/project
--------------------------------------
-
-(The complete configuration created by git-clone is visible using
-"git config -l", and the linkgit:git-config[1] man page
-explains the meaning of each option.)
-
-Git also keeps a pristine copy of Alice's master branch under the
-name "origin/master":
-
--------------------------------------
-$ git branch -r
-  origin/master
--------------------------------------
-
-If Bob later decides to work from a different host, he can still
-perform clones and pulls using the ssh protocol:
-
--------------------------------------
-$ git clone alice.org:/home/alice/project myrepo
--------------------------------------
-
-Alternatively, git has a native protocol, or can use rsync or http;
-see linkgit:git-pull[1] for details.
-
-Git can also be used in a CVS-like mode, with a central repository
-that various users push changes to; see linkgit:git-push[1] and
-link:cvs-migration.html[git for CVS users].
-
-Exploring history
------------------
-
-Git history is represented as a series of interrelated commits.  We
-have already seen that the git log command can list those commits.
-Note that first line of each git log entry also gives a name for the
-commit:
-
--------------------------------------
-$ git log
-commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
-Author: Junio C Hamano <junkio@cox.net>
-Date:   Tue May 16 17:18:22 2006 -0700
-
-    merge-base: Clarify the comments on post processing.
--------------------------------------
-
-We can give this name to git show to see the details about this
-commit.
-
--------------------------------------
-$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
--------------------------------------
-
-But there are other ways to refer to commits.  You can use any initial
-part of the name that is long enough to uniquely identify the commit:
-
--------------------------------------
-$ git show c82a22c39c  # the first few characters of the name are
-                       # usually enough
-$ git show HEAD                # the tip of the current branch
-$ git show experimental        # the tip of the "experimental" branch
--------------------------------------
-
-Every commit usually has one "parent" commit
-which points to the previous state of the project:
-
--------------------------------------
-$ git show HEAD^  # to see the parent of HEAD
-$ git show HEAD^^ # to see the grandparent of HEAD
-$ git show HEAD~4 # to see the great-great grandparent of HEAD
--------------------------------------
-
-Note that merge commits may have more than one parent:
-
--------------------------------------
-$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)
-$ git show HEAD^2 # show the second parent of HEAD
--------------------------------------
-
-You can also give commits names of your own; after running
-
--------------------------------------
-$ git-tag v2.5 1b2e1d63ff
--------------------------------------
-
-you can refer to 1b2e1d63ff by the name "v2.5".  If you intend to
-share this name with other people (for example, to identify a release
-version), you should create a "tag" object, and perhaps sign it; see
-linkgit:git-tag[1] for details.
-
-Any git command that needs to know a commit can take any of these
-names.  For example:
-
--------------------------------------
-$ git diff v2.5 HEAD    # compare the current HEAD to v2.5
-$ git branch stable v2.5 # start a new branch named "stable" based
-                        # at v2.5
-$ git reset --hard HEAD^ # reset your current branch and working
-                        # directory to its state at HEAD^
--------------------------------------
-
-Be careful with that last command: in addition to losing any changes
-in the working directory, it will also remove all later commits from
-this branch.  If this branch is the only branch containing those
-commits, they will be lost.  Also, don't use "git reset" on a
-publicly-visible branch that other developers pull from, as it will
-force needless merges on other developers to clean up the history.
-If you need to undo changes that you have pushed, use linkgit:git-revert[1]
-instead.
-
-The git grep command can search for strings in any version of your
-project, so
-
--------------------------------------
-$ git grep "hello" v2.5
--------------------------------------
-
-searches for all occurrences of "hello" in v2.5.
-
-If you leave out the commit name, git grep will search any of the
-files it manages in your current directory.  So
-
--------------------------------------
-$ git grep "hello"
--------------------------------------
-
-is a quick way to search just the files that are tracked by git.
-
-Many git commands also take sets of commits, which can be specified
-in a number of ways.  Here are some examples with git log:
-
--------------------------------------
-$ git log v2.5..v2.6            # commits between v2.5 and v2.6
-$ git log v2.5..                # commits since v2.5
-$ git log --since="2 weeks ago" # commits from the last 2 weeks
-$ git log v2.5.. Makefile       # commits since v2.5 which modify
-                               # Makefile
--------------------------------------
-
-You can also give git log a "range" of commits where the first is not
-necessarily an ancestor of the second; for example, if the tips of
-the branches "stable-release" and "master" diverged from a common
-commit some time ago, then
-
--------------------------------------
-$ git log stable..experimental
--------------------------------------
-
-will list commits made in the experimental branch but not in the
-stable branch, while
-
--------------------------------------
-$ git log experimental..stable
--------------------------------------
-
-will show the list of commits made on the stable branch but not
-the experimental branch.
-
-The "git log" command has a weakness: it must present commits in a
-list.  When the history has lines of development that diverged and
-then merged back together, the order in which "git log" presents
-those commits is meaningless.
-
-Most projects with multiple contributors (such as the linux kernel,
-or git itself) have frequent merges, and gitk does a better job of
-visualizing their history.  For example,
-
--------------------------------------
-$ gitk --since="2 weeks ago" drivers/
--------------------------------------
-
-allows you to browse any commits from the last 2 weeks of commits
-that modified files under the "drivers" directory.  (Note: you can
-adjust gitk's fonts by holding down the control key while pressing
-"-" or "+".)
-
-Finally, most commands that take filenames will optionally allow you
-to precede any filename by a commit, to specify a particular version
-of the file:
-
--------------------------------------
-$ git diff v2.5:Makefile HEAD:Makefile.in
--------------------------------------
-
-You can also use "git show" to see any such file:
-
--------------------------------------
-$ git show v2.5:Makefile
--------------------------------------
-
-Next Steps
-----------
-
-This tutorial should be enough to perform basic distributed revision
-control for your projects.  However, to fully understand the depth
-and power of git you need to understand two simple ideas on which it
-is based:
-
-  * The object database is the rather elegant system used to
-    store the history of your project--files, directories, and
-    commits.
-
-  * The index file is a cache of the state of a directory tree,
-    used to create commits, check out working directories, and
-    hold the various trees involved in a merge.
-
-link:tutorial-2.html[Part two of this tutorial] explains the object
-database, the index file, and a few other odds and ends that you'll
-need to make the most of git.
-
-If you don't want to continue with that right away, a few other
-digressions that may be interesting at this point are:
-
-  * linkgit:git-format-patch[1], linkgit:git-am[1]: These convert
-    series of git commits into emailed patches, and vice versa,
-    useful for projects such as the linux kernel which rely heavily
-    on emailed patches.
-
-  * linkgit:git-bisect[1]: When there is a regression in your
-    project, one way to track down the bug is by searching through
-    the history to find the exact commit that's to blame.  Git bisect
-    can help you perform a binary search for that commit.  It is
-    smart enough to perform a close-to-optimal search even in the
-    case of complex non-linear history with lots of merged branches.
-
-  * link:everyday.html[Everyday GIT with 20 Commands Or So]
-
-  * link:cvs-migration.html[git for CVS users].
index e2db850150a683087491016159a0ff0a75eb879a..fd8cdb625afbfc8d8921a5e085abe32e05c2da9a 100644 (file)
@@ -1993,7 +1993,7 @@ the right to push to the same repository.  In that case, the correct
 solution is to retry the push after first updating your work by either a
 pull or a fetch followed by a rebase; see the
 <<setting-up-a-shared-repository,next section>> and
-link:cvs-migration.html[git for CVS users] for more.
+linkgit:gitcvs-migration[7][git for CVS users] for more.
 
 [[setting-up-a-shared-repository]]
 Setting up a shared repository
@@ -2002,7 +2002,7 @@ Setting up a shared repository
 Another way to collaborate is by using a model similar to that
 commonly used in CVS, where several developers with special rights
 all push to and pull from a single shared repository.  See
-link:cvs-migration.html[git for CVS users] for instructions on how to
+linkgit:gitcvs-migration[7][git for CVS users] for instructions on how to
 set this up.
 
 However, while there is nothing wrong with git's support for shared
index ad09e6c8ba576bb5408d480199d3ea092f6ff564..cce5a6e1bf9ad8f5ebf8046a5053588d18e90bc6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -235,7 +235,6 @@ BASIC_LDFLAGS =
 
 SCRIPT_SH += git-am.sh
 SCRIPT_SH += git-bisect.sh
-SCRIPT_SH += git-clone.sh
 SCRIPT_SH += git-filter-branch.sh
 SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
@@ -376,6 +375,7 @@ LIB_H += tree.h
 LIB_H += tree-walk.h
 LIB_H += unpack-trees.h
 LIB_H += utf8.h
+LIB_H += wt-status.h
 
 LIB_OBJS += alias.o
 LIB_OBJS += alloc.o
@@ -485,6 +485,7 @@ 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
diff --git a/alias.c b/alias.c
index 116cac87c37eea27b9fc7aa41c5889fbf66057a5..995f3e6a0ac84fa71c3196de6673225d4a5d8231 100644 (file)
--- a/alias.c
+++ b/alias.c
@@ -2,7 +2,8 @@
 
 static const char *alias_key;
 static char *alias_val;
-static int alias_lookup_cb(const char *k, const char *v)
+
+static int alias_lookup_cb(const char *k, const char *v, void *cb)
 {
        if (!prefixcmp(k, "alias.") && !strcmp(k+6, alias_key)) {
                if (!v)
@@ -17,6 +18,6 @@ char *alias_lookup(const char *alias)
 {
        alias_key = alias;
        alias_val = NULL;
-       git_config(alias_lookup_cb);
+       git_config(alias_lookup_cb, NULL);
        return alias_val;
 }
index 4add80284e4570d1da44861e9025fa75eeb775d6..d7598f907d9f7fb40c24c8cef815f8fc33a8b19b 100644 (file)
@@ -220,7 +220,7 @@ static void write_global_extended_header(const unsigned char *sha1)
        strbuf_release(&ext_header);
 }
 
-static int git_tar_config(const char *var, const char *value)
+static int git_tar_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "tar.umask")) {
                if (value && !strcmp(value, "user")) {
@@ -231,7 +231,7 @@ static int git_tar_config(const char *var, const char *value)
                }
                return 0;
        }
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static int write_tar_entry(const unsigned char *sha1,
@@ -268,7 +268,7 @@ int write_tar_archive(struct archiver_args *args)
 {
        int plen = args->base ? strlen(args->base) : 0;
 
-       git_config(git_tar_config);
+       git_config(git_tar_config, NULL);
 
        archive_time = args->time;
        verbose = args->verbose;
index 73235ed08a9d60f8134235d9b68632303f359ca9..1da22eec915539f06a4dfc1e4bb1d18482de0ede 100644 (file)
@@ -100,15 +100,16 @@ static void update_callback(struct diff_queue_struct *q,
                case DIFF_STATUS_UNMERGED:
                case DIFF_STATUS_MODIFIED:
                case DIFF_STATUS_TYPE_CHANGED:
-                       if (add_file_to_cache(path, data->flags & ADD_FILES_VERBOSE)) {
-                               if (!(data->flags & ADD_FILES_IGNORE_ERRORS))
+                       if (add_file_to_cache(path, data->flags)) {
+                               if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
                                        die("updating files failed");
                                data->add_errors++;
                        }
                        break;
                case DIFF_STATUS_DELETED:
-                       remove_file_from_cache(path);
-                       if (data->flags & ADD_FILES_VERBOSE)
+                       if (!(data->flags & ADD_CACHE_PRETEND))
+                               remove_file_from_cache(path);
+                       if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
                                printf("remove '%s'\n", path);
                        break;
                }
@@ -206,13 +207,13 @@ static struct option builtin_add_options[] = {
        OPT_END(),
 };
 
-static int add_config(const char *var, const char *value)
+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);
+       return git_default_config(var, value, cb);
 }
 
 int cmd_add(int argc, const char **argv, const char *prefix)
@@ -221,6 +222,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        int i, newfd;
        const char **pathspec;
        struct dir_struct dir;
+       int flags;
 
        argc = parse_options(argc, argv, builtin_add_options,
                          builtin_add_usage, 0);
@@ -229,22 +231,19 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        if (add_interactive)
                exit(interactive_add(argc, argv, prefix));
 
-       git_config(add_config);
+       git_config(add_config, NULL);
 
        newfd = hold_locked_index(&lock_file, 1);
 
+       flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
+                (show_only ? ADD_CACHE_PRETEND : 0) |
+                (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0));
+
        if (take_worktree_changes) {
-               int flags = 0;
                const char **pathspec;
                if (read_cache() < 0)
                        die("index file corrupt");
                pathspec = get_pathspec(prefix, argv);
-
-               if (verbose)
-                       flags |= ADD_FILES_VERBOSE;
-               if (ignore_add_errors)
-                       flags |= ADD_FILES_IGNORE_ERRORS;
-
                exit_status = add_files_to_cache(prefix, pathspec, flags);
                goto finish;
        }
@@ -263,17 +262,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
        fill_directory(&dir, pathspec, ignored_too);
 
-       if (show_only) {
-               const char *sep = "", *eof = "";
-               for (i = 0; i < dir.nr; i++) {
-                       printf("%s%s", sep, dir.entries[i]->name);
-                       sep = " ";
-                       eof = "\n";
-               }
-               fputs(eof, stdout);
-               return 0;
-       }
-
        if (read_cache() < 0)
                die("index file corrupt");
 
@@ -287,7 +275,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        }
 
        for (i = 0; i < dir.nr; i++)
-               if (add_file_to_cache(dir.entries[i]->name, verbose)) {
+               if (add_file_to_cache(dir.entries[i]->name, flags)) {
                        if (!ignore_add_errors)
                                die("adding files failed");
                        exit_status = 1;
index 1103625a4a923d92ca8d335d1a872236630541e5..c4978893122bbcfd80201fe937eb8433b29e1aa0 100644 (file)
@@ -418,7 +418,7 @@ static int guess_p_value(const char *nameline)
 }
 
 /*
- * Get the name etc info from the --/+++ lines of a traditional patch header
+ * 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
@@ -1143,21 +1143,6 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
        if (patch->is_delete < 0 &&
            (newlines || (patch->fragments && patch->fragments->next)))
                patch->is_delete = 0;
-       if (!unidiff_zero || context) {
-               /* If the user says the patch is not generated with
-                * --unified=0, or if we have seen context lines,
-                * then not having oldlines means the patch is creation,
-                * and not having newlines means the patch is deletion.
-                */
-               if (patch->is_new < 0 && !oldlines) {
-                       patch->is_new = 1;
-                       patch->old_name = NULL;
-               }
-               if (patch->is_delete < 0 && !newlines) {
-                       patch->is_delete = 1;
-                       patch->new_name = NULL;
-               }
-       }
 
        if (0 < patch->is_new && oldlines)
                die("new file %s depends on old contents", patch->new_name);
@@ -2267,16 +2252,11 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st)
        return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID);
 }
 
-static int check_patch(struct patch *patch, struct patch *prev_patch)
+static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
 {
-       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;
-       int ok_if_exists;
-
-       patch->rejected = 1; /* we will drop this after we succeed */
+       int stat_ret = 0;
+       unsigned st_mode = 0;
 
        /*
         * Make sure that we do not have local modifications from the
@@ -2284,58 +2264,84 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
         * 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) {
-               int stat_ret = 0;
-               unsigned st_mode = 0;
-
-               if (!cached)
-                       stat_ret = lstat(old_name, &st);
-               if (check_index) {
-                       int pos = cache_name_pos(old_name, strlen(old_name));
-                       if (pos < 0)
-                               return error("%s: does not exist in index",
-                                            old_name);
-                       ce = active_cache[pos];
-                       if (stat_ret < 0) {
-                               struct checkout costate;
-                               if (errno != ENOENT)
-                                       return error("%s: %s", old_name,
-                                                    strerror(errno));
-                               /* 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)
-                       return error("%s: %s", old_name, strerror(errno));
-
-               if (!cached)
-                       st_mode = ce_mode_from_stat(ce, st.st_mode);
+       if (!old_name)
+               return 0;
 
+       assert(patch->is_new <= 0);
+       if (!cached) {
+               stat_ret = lstat(old_name, st);
+               if (stat_ret && errno != ENOENT)
+                       return error("%s: %s", old_name, strerror(errno));
+       }
+       if (check_index) {
+               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)
-                       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)
-                       fprintf(stderr, "warning: %s has type %o, expected %o\n",
-                               old_name, st_mode, patch->old_mode);
+                       goto is_new;
+               return error("%s: %s", old_name, strerror(errno));
        }
 
+       if (!cached)
+               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)
+               fprintf(stderr, "warning: %s has type %o, expected %o\n",
+                       old_name, st_mode, patch->old_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 patch *prev_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;
+       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 (new_name && prev_patch && 0 < prev_patch->is_delete &&
            !strcmp(prev_patch->old_name, new_name))
                /*
@@ -2979,11 +2985,11 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
        return 0;
 }
 
-static int git_apply_config(const char *var, const char *value)
+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);
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 
@@ -2999,7 +3005,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
 
        prefix = setup_git_directory_gently(&is_not_gitdir);
        prefix_length = prefix ? strlen(prefix) : 0;
-       git_config(git_apply_config);
+       git_config(git_apply_config, NULL);
        if (apply_default_whitespace)
                parse_whitespace_option(apply_default_whitespace);
 
index bfd562d7d2b897ad4ab3d35dda33701e315b7d5d..b451f6c64dde8ce6358bb5c8dfccc8bad181a6bb 100644 (file)
@@ -1993,7 +1993,7 @@ static void prepare_blame_range(struct scoreboard *sb,
                usage(blame_usage);
 }
 
-static int git_blame_config(const char *var, const char *value)
+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);
@@ -2003,7 +2003,7 @@ static int git_blame_config(const char *var, const char *value)
                blank_boundary = git_config_bool(var, value);
                return 0;
        }
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
@@ -2141,7 +2141,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        cmd_is_annotate = !strcmp(argv[0], "annotate");
 
-       git_config(git_blame_config);
+       git_config(git_blame_config, NULL);
        save_commit_buffer = 0;
 
        opt = 0;
index 19c508a60827c9c7c744da995bf5737733864a76..d279702ba9a90faa020fa2f102d595cec082be21 100644 (file)
@@ -63,7 +63,7 @@ static int parse_branch_color_slot(const char *var, int ofs)
        die("bad config variable '%s'", var);
 }
 
-static int git_branch_config(const char *var, const char *value)
+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);
@@ -76,7 +76,7 @@ static int git_branch_config(const char *var, const char *value)
                color_parse(value, var, branch_colors[slot]);
                return 0;
        }
-       return git_color_default_config(var, value);
+       return git_color_default_config(var, value, cb);
 }
 
 static const char *branch_get_color(enum color_branch ix)
@@ -461,7 +461,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_END(),
        };
 
-       git_config(git_branch_config);
+       git_config(git_branch_config, NULL);
 
        if (branch_use_color == -1)
                branch_use_color = git_use_color_default;
index f132d583d3e2a2ac0fe696b66723c846902d0a19..200345e7fbd54d86fb1e4db228244b7a7a221562 100644 (file)
@@ -8,6 +8,10 @@
 #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)
 {
@@ -76,31 +80,16 @@ static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long
                write_or_die(1, cp, endp - cp);
 }
 
-int cmd_cat_file(int argc, const char **argv, const char *prefix)
+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;
-       int opt;
-       const char *exp_type, *obj_name;
-
-       git_config(git_default_config);
-       if (argc != 3)
-               usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
-       exp_type = argv[1];
-       obj_name = argv[2];
 
        if (get_sha1(obj_name, sha1))
                die("Not a valid object name %s", obj_name);
 
-       opt = 0;
-       if ( exp_type[0] == '-' ) {
-               opt = exp_type[1];
-               if ( !opt || exp_type[2] )
-                       opt = -1; /* Not a single character option */
-       }
-
        buf = NULL;
        switch (opt) {
        case 't':
@@ -157,3 +146,108 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
        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;
+       unsigned long size;
+       void *contents = contents;
+
+       if (!obj_name)
+          return 1;
+
+       if (get_sha1(obj_name, sha1)) {
+               printf("%s missing\n", obj_name);
+               return 0;
+       }
+
+       if (print_contents == BATCH)
+               contents = read_sha1_file(sha1, &type, &size);
+       else
+               type = sha1_object_info(sha1, &size);
+
+       if (type <= 0)
+               return 1;
+
+       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);
+       }
+
+       return 0;
+}
+
+static int batch_objects(int print_contents)
+{
+       struct strbuf buf;
+
+       strbuf_init(&buf, 0);
+       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>] <sha1>",
+       "git-cat-file [--batch|--batch-check] < <list_of_sha1s>",
+       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 feeded on stdin", BATCH),
+               OPT_SET_INT(0, "batch-check", &batch,
+                           "show info about objects feeded on stdin",
+                           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, 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);
+}
index 7e42024c67a2c0fda72f94935b7d5a723c73d131..eb1fc9aa6f7f61a817a198feaf94ca9d983eb14b 100644 (file)
@@ -166,7 +166,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
        int read_from_stdin = 0;
        int prefix_length;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        state.base_dir = "";
        prefix_length = prefix ? strlen(prefix) : 0;
 
index 05c06421b6c3a6d75b598e3c58a40c8461febd75..1ea017f5f70f2255fb937866e9913a06399ce517 100644 (file)
@@ -236,6 +236,8 @@ static int merge_working_tree(struct checkout_opts *opts,
                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()) {
@@ -514,7 +516,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        memset(&opts, 0, sizeof(opts));
        memset(&new, 0, sizeof(new));
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        opts.track = git_branch_track;
 
index 6778a03ae4c901d216e4b91cd41f2073e0e98589..80a7ff9ae45035b44bbac44a9436152645c4fb38 100644 (file)
@@ -19,11 +19,11 @@ static const char *const builtin_clean_usage[] = {
        NULL
 };
 
-static int git_clean_config(const char *var, const char *value)
+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);
+       return git_default_config(var, value, cb);
 }
 
 int cmd_clean(int argc, const char **argv, const char *prefix)
@@ -50,7 +50,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
-       git_config(git_clean_config);
+       git_config(git_clean_config, NULL);
        if (force < 0)
                force = 0;
        else
diff --git a/builtin-clone.c b/builtin-clone.c
new file mode 100644 (file)
index 0000000..4740b13
--- /dev/null
@@ -0,0 +1,550 @@
+/*
+ * 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"
+
+/*
+ * 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;
+static int option_local, option_no_hardlinks, option_shared;
+static char *option_template, *option_reference, *option_depth;
+static char *option_origin = NULL;
+static char *option_upload_pack = "git-upload-pack";
+
+static struct option builtin_clone_options[] = {
+       OPT__QUIET(&option_quiet),
+       OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
+                   "don't create a checkout"),
+       OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
+       OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"),
+       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_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 or 'origin' to track upstream"),
+       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 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 (!stat(path, &st) && S_ISDIR(st.st_mode)) {
+                       *is_bundle = 0;
+                       return xstrdup(make_absolute_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_absolute_path(path));
+               }
+       }
+
+       return NULL;
+}
+
+static char *guess_dir_name(const char *repo, int is_bundle)
+{
+       const char *p, *start, *end, *limit;
+       int after_slash_or_colon;
+
+       /* Guess dir name from repository: strip trailing '/',
+        * strip trailing '[:/]*.{git,bundle}', strip leading '.*[/:]'. */
+
+       after_slash_or_colon = 1;
+       limit = repo + strlen(repo);
+       start = repo;
+       end = limit;
+       for (p = repo; p < limit; p++) {
+               const char *prefix = is_bundle ? ".bundle" : ".git";
+               if (!prefixcmp(p, prefix)) {
+                       if (!after_slash_or_colon)
+                               end = p;
+                       p += strlen(prefix) - 1;
+               } else if (!prefixcmp(p, ".bundle")) {
+                       if (!after_slash_or_colon)
+                               end = p;
+                       p += 7;
+               } else if (*p == '/' || *p == ':') {
+                       if (end == limit)
+                               end = p;
+                       after_slash_or_colon = 1;
+               } else if (after_slash_or_colon) {
+                       start = p;
+                       end = limit;
+                       after_slash_or_colon = 0;
+               }
+       }
+
+       return xstrndup(start, end - start);
+}
+
+static int is_directory(const char *path)
+{
+       struct stat buf;
+
+       return !stat(path, &buf) && S_ISDIR(buf.st_mode);
+}
+
+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(char *src, char *dest)
+{
+       struct dirent *de;
+       struct stat buf;
+       int src_len, dest_len;
+       DIR *dir;
+
+       dir = opendir(src);
+       if (!dir)
+               die("failed to open %s\n", src);
+
+       if (mkdir(dest, 0777)) {
+               if (errno != EEXIST)
+                       die("failed to create directory %s\n", dest);
+               else if (stat(dest, &buf))
+                       die("failed to stat %s\n", dest);
+               else if (!S_ISDIR(buf.st_mode))
+                       die("%s exists and is not a directory\n", dest);
+       }
+
+       src_len = strlen(src);
+       src[src_len] = '/';
+       dest_len = strlen(dest);
+       dest[dest_len] = '/';
+
+       while ((de = readdir(dir)) != NULL) {
+               strcpy(src + src_len + 1, de->d_name);
+               strcpy(dest + dest_len + 1, de->d_name);
+               if (stat(src, &buf)) {
+                       warning ("failed to stat %s\n", src);
+                       continue;
+               }
+               if (S_ISDIR(buf.st_mode)) {
+                       if (de->d_name[0] != '.')
+                               copy_or_link_directory(src, dest);
+                       continue;
+               }
+
+               if (unlink(dest) && errno != ENOENT)
+                       die("failed to unlink %s\n", dest);
+               if (!option_no_hardlinks) {
+                       if (!link(src, dest))
+                               continue;
+                       if (option_local)
+                               die("failed to create link %s\n", dest);
+                       option_no_hardlinks = 1;
+               }
+               if (copy_file(dest, src, 0666))
+                       die("failed to copy file to %s\n", dest);
+       }
+       closedir(dir);
+}
+
+static const struct ref *clone_local(const char *src_repo,
+                                    const char *dest_repo)
+{
+       const struct ref *ret;
+       char src[PATH_MAX];
+       char dest[PATH_MAX];
+       struct remote *remote;
+       struct transport *transport;
+
+       if (option_shared)
+               add_to_alternates_file(src_repo);
+       else {
+               snprintf(src, PATH_MAX, "%s/objects", src_repo);
+               snprintf(dest, PATH_MAX, "%s/objects", dest_repo);
+               copy_or_link_directory(src, 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;
+pid_t junk_pid;
+
+static void remove_junk(void)
+{
+       struct strbuf sb;
+       if (getpid() != junk_pid)
+               return;
+       strbuf_init(&sb, 0);
+       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();
+       signal(SIGINT, SIG_DFL);
+       raise(signo);
+}
+
+static const struct ref *locate_head(const struct ref *refs,
+                                    const struct ref *mapped_refs,
+                                    const struct ref **remote_head_p)
+{
+       const struct ref *remote_head = NULL;
+       const struct ref *remote_master = NULL;
+       const struct ref *r;
+       for (r = refs; r; r = r->next)
+               if (!strcmp(r->name, "HEAD"))
+                       remote_head = r;
+
+       for (r = mapped_refs; r; r = r->next)
+               if (!strcmp(r->name, "refs/heads/master"))
+                       remote_master = r;
+
+       if (remote_head_p)
+               *remote_head_p = remote_head;
+
+       /* If there's no HEAD value at all, never mind. */
+       if (!remote_head)
+               return NULL;
+
+       /* If refs/heads/master could be right, it is. */
+       if (remote_master && !hashcmp(remote_master->old_sha1,
+                                     remote_head->old_sha1))
+               return remote_master;
+
+       /* Look for another ref that points there */
+       for (r = mapped_refs; r; r = r->next)
+               if (r != remote_head &&
+                   !hashcmp(r->old_sha1, remote_head->old_sha1))
+                       return r;
+
+       /* Nothing is the same */
+       return NULL;
+}
+
+static struct ref *write_remote_refs(const struct ref *refs,
+               struct refspec *refspec, const char *reflog)
+{
+       struct ref *local_refs = NULL;
+       struct ref **tail = &local_refs;
+       struct ref *r;
+
+       get_fetch_map(refs, refspec, &tail, 0);
+       get_fetch_map(refs, tag_refspec, &tail, 0);
+
+       for (r = local_refs; r; r = r->next)
+               update_ref(reflog,
+                          r->peer_ref->name, r->old_sha1, NULL, 0, DIE_ON_ERR);
+       return local_refs;
+}
+
+int cmd_clone(int argc, const char **argv, const char *prefix)
+{
+       int use_local_hardlinks = 1;
+       int use_separate_remote = 1;
+       int is_bundle = 0;
+       struct stat buf;
+       const char *repo_name, *repo, *work_tree, *git_dir;
+       char *path, *dir;
+       const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
+       char branch_top[256], key[256], value[256];
+       struct strbuf reflog_msg;
+
+       struct refspec refspec;
+
+       junk_pid = getpid();
+
+       argc = parse_options(argc, argv, builtin_clone_options,
+                            builtin_clone_usage, 0);
+
+       if (argc == 0)
+               die("You must specify a repository to clone.");
+
+       if (option_no_hardlinks)
+               use_local_hardlinks = 0;
+
+       if (option_bare) {
+               if (option_origin)
+                       die("--bare and --origin %s options are incompatible.",
+                           option_origin);
+               option_no_checkout = 1;
+               use_separate_remote = 0;
+       }
+
+       if (!option_origin)
+               option_origin = "origin";
+
+       repo_name = argv[0];
+
+       path = get_repo_path(repo_name, &is_bundle);
+       if (path)
+               repo = path;
+       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);
+
+       if (!stat(dir, &buf))
+               die("destination directory '%s' already exists.", dir);
+
+       strbuf_init(&reflog_msg, 0);
+       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 (mkdir(work_tree, 0755))
+                       die("could not create work tree dir '%s'.", work_tree);
+               set_git_work_tree(work_tree);
+       }
+       junk_git_dir = git_dir;
+       atexit(remove_junk);
+       signal(SIGINT, remove_junk_on_signal);
+
+       setenv(CONFIG_ENVIRONMENT, xstrdup(mkpath("%s/config", git_dir)), 1);
+
+       set_git_dir(make_absolute_path(git_dir));
+
+       fprintf(stderr, "Initialize %s\n", git_dir);
+       init_db(option_template, option_quiet ? INIT_DB_QUIET : 0);
+
+       if (option_reference)
+               setup_reference(git_dir);
+
+       git_config(git_default_config, NULL);
+
+       if (option_bare) {
+               strcpy(branch_top, "refs/heads/");
+
+               git_config_set("core.bare", "true");
+       } else {
+               snprintf(branch_top, sizeof(branch_top),
+                        "refs/remotes/%s/", option_origin);
+
+               /* Configure the remote */
+               snprintf(key, sizeof(key), "remote.%s.url", option_origin);
+               git_config_set(key, repo);
+
+               snprintf(key, sizeof(key), "remote.%s.fetch", option_origin);
+               snprintf(value, sizeof(value),
+                               "+refs/heads/*:%s*", branch_top);
+               git_config_set_multivar(key, value, "^$", 0);
+       }
+
+       refspec.force = 0;
+       refspec.pattern = 1;
+       refspec.src = "refs/heads/";
+       refspec.dst = branch_top;
+
+       if (path && !is_bundle)
+               refs = clone_local(path, git_dir);
+       else {
+               struct remote *remote = remote_get(argv[0]);
+               struct transport *transport = transport_get(remote, argv[0]);
+
+               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;
+
+               refs = transport_get_remote_refs(transport);
+               transport_fetch_refs(transport, refs);
+       }
+
+       clear_extra_refs();
+
+       mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);
+
+       head_points_at = locate_head(refs, mapped_refs, &remote_head);
+
+       if (head_points_at) {
+               /* Local default branch link */
+               create_symref("HEAD", head_points_at->name, NULL);
+
+               if (!option_bare) {
+                       struct strbuf head_ref;
+                       const char *head = head_points_at->name;
+
+                       if (!prefixcmp(head, "refs/heads/"))
+                               head += 11;
+
+                       /* Set up the initial local branch */
+
+                       /* Local branch initial value */
+                       update_ref(reflog_msg.buf, "HEAD",
+                                  head_points_at->old_sha1,
+                                  NULL, 0, DIE_ON_ERR);
+
+                       strbuf_init(&head_ref, 0);
+                       strbuf_addstr(&head_ref, branch_top);
+                       strbuf_addstr(&head_ref, "HEAD");
+
+                       /* Remote branch link */
+                       create_symref(head_ref.buf,
+                                     head_points_at->peer_ref->name,
+                                     reflog_msg.buf);
+
+                       snprintf(key, sizeof(key), "branch.%s.remote", head);
+                       git_config_set(key, option_origin);
+                       snprintf(key, sizeof(key), "branch.%s.merge", head);
+                       git_config_set(key, 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);
+       } 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 (!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(remote_head->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");
+       }
+
+       strbuf_release(&reflog_msg);
+       junk_pid = 0;
+       return 0;
+}
index 6610d18358bae81ac1f162ca0c71062d36664c2a..e5e4bdbe862b23aafe932da411f597b0f3b5c997 100644 (file)
@@ -60,7 +60,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
        struct strbuf buffer;
        int encoding_is_utf8;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        if (argc < 2)
                usage(commit_tree_usage);
index d75224381b7ad88d6bbb67db3a85bbb881cea941..07872c8ea71064c1bcf5813dd66b4a7e5feb9bec 100644 (file)
@@ -806,7 +806,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        const char *index_file;
        int commitable;
 
-       git_config(git_status_config);
+       git_config(git_status_config, NULL);
 
        if (wt_status_use_color == -1)
                wt_status_use_color = git_use_color_default;
@@ -860,7 +860,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
        }
 }
 
-int git_commit_config(const char *k, const char *v)
+int git_commit_config(const char *k, const char *v, void *cb)
 {
        if (!strcmp(k, "commit.template")) {
                if (!v)
@@ -869,7 +869,7 @@ int git_commit_config(const char *k, const char *v)
                return 0;
        }
 
-       return git_status_config(k, v);
+       return git_status_config(k, v, cb);
 }
 
 static const char commit_utf8_warn[] =
@@ -897,7 +897,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        unsigned char commit_sha1[20];
        struct ref_lock *ref_lock;
 
-       git_config(git_commit_config);
+       git_config(git_commit_config, NULL);
 
        argc = parse_and_validate_options(argc, argv, builtin_commit_usage);
 
index 8ee01bdecde1ede4062a630761ce56f3366f3a14..3a441ef64868b7c477ee9c49546ef1cb39e5acad 100644 (file)
@@ -18,7 +18,7 @@ static char key_delim = ' ';
 static char term = '\n';
 static enum { T_RAW, T_INT, T_BOOL, T_BOOL_OR_INT } type = T_RAW;
 
-static int show_all_config(const char *key_, const char *value_)
+static int show_all_config(const char *key_, const char *value_, void *cb)
 {
        if (value_)
                printf("%s%c%s%c", key_, delim, value_, term);
@@ -27,7 +27,7 @@ static int show_all_config(const char *key_, const char *value_)
        return 0;
 }
 
-static int show_config(const char* key_, const char* value_)
+static int show_config(const char* key_, const char* value_, void *cb)
 {
        char value[256];
        const char *vptr = value;
@@ -121,14 +121,14 @@ static int get_value(const char* key_, const char* regex_)
        }
 
        if (do_all && system_wide)
-               git_config_from_file(show_config, system_wide);
+               git_config_from_file(show_config, system_wide, NULL);
        if (do_all && global)
-               git_config_from_file(show_config, global);
-       git_config_from_file(show_config, local);
+               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);
+               git_config_from_file(show_config, global, NULL);
        if (!do_all && !seen && system_wide)
-               git_config_from_file(show_config, system_wide);
+               git_config_from_file(show_config, system_wide, NULL);
 
        free(key);
        if (regexp) {
@@ -182,7 +182,7 @@ static int get_color_found;
 static const char *get_color_slot;
 static char parsed_color[COLOR_MAXLEN];
 
-static int git_get_color_config(const char *var, const char *value)
+static int git_get_color_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, get_color_slot)) {
                if (!value)
@@ -218,7 +218,7 @@ static int get_color(int argc, const char **argv)
 
        get_color_found = 0;
        parsed_color[0] = '\0';
-       git_config(git_get_color_config);
+       git_config(git_get_color_config, NULL);
 
        if (!get_color_found && def_color)
                color_parse(def_color, "command line", parsed_color);
@@ -230,7 +230,8 @@ static int get_color(int argc, const char **argv)
 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)
+static int git_get_colorbool_config(const char *var, const char *value,
+               void *cb)
 {
        if (!strcmp(var, get_color_slot)) {
                get_colorbool_found =
@@ -265,7 +266,7 @@ static int get_colorbool(int argc, const char **argv)
        get_colorbool_found = -1;
        get_diff_color_found = -1;
        get_color_slot = argv[0];
-       git_config(git_get_colorbool_config);
+       git_config(git_get_colorbool_config, NULL);
 
        if (get_colorbool_found < 0) {
                if (!strcmp(get_color_slot, "color.diff"))
@@ -298,7 +299,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) {
                        if (argc != 2)
                                usage(git_config_set_usage);
-                       if (git_config(show_all_config) < 0 && file && errno)
+                       if (git_config(show_all_config, NULL) < 0 &&
+                                       file && errno)
                                die("unable to read config file %s: %s", file,
                                    strerror(errno));
                        return 0;
index 3aa031f24f83f6f1877d1aaf24bd834183713563..384d871263383e89c6f8f52b16e9e7e147426d1d 100644 (file)
@@ -20,7 +20,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
        unsigned options = 0;
 
        init_revisions(&rev, prefix);
-       git_config(git_diff_basic_config); /* no "diff" UI options */
+       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
        rev.abbrev = 0;
 
        argc = setup_revisions(argc, argv, &rev, NULL);
index 2b955deb912a38ccabb115f8d3a6c7feb815eb45..2f44ebfcdd86cde2347258dbeb1e5c4b9cab0622 100644 (file)
@@ -17,7 +17,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
        int result;
 
        init_revisions(&rev, prefix);
-       git_config(git_diff_basic_config); /* no "diff" UI options */
+       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
        rev.abbrev = 0;
 
        argc = setup_revisions(argc, argv, &rev, NULL);
index 832797ff3bb45751758f0d629abb2cf8fa4507ad..9d2a48fd6833038fee1b608175f71a867bb9f3e6 100644 (file)
@@ -68,7 +68,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
        int read_stdin = 0;
 
        init_revisions(opt, prefix);
-       git_config(git_diff_basic_config); /* no "diff" UI options */
+       git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
        nr_sha1 = 0;
        opt->abbrev = 0;
        opt->diff = 1;
index 8b3b51eae0f928d217c7c91f294f1498d05ad1cf..4c289e798a25ee3893a483abd8465740999dcbef 100644 (file)
@@ -268,7 +268,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
         */
 
        prefix = setup_git_directory_gently(&nongit);
-       git_config(git_diff_ui_config);
+       git_config(git_diff_ui_config, NULL);
 
        if (diff_use_color_default == -1)
                diff_use_color_default = git_use_color_default;
index e1c56303e5cb7882bc79980c4f8e6c6791ad323b..1dfc01e8f009968ef5d6118bffed7818e736576c 100755 (executable)
@@ -204,14 +204,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
                        continue;
                if (i == 0)
                        printf("from :%d\n", mark);
-               else if (i == 1)
-                       printf("merge :%d", mark);
                else
-                       printf(" :%d", mark);
+                       printf("merge :%d\n", mark);
                i++;
        }
-       if (i > 1)
-               printf("\n");
 
        log_tree_diff_flush(rev);
        rev->diffopt.output_format = saved_output_format;
@@ -372,7 +368,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
        };
 
        /* we handle encodings */
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        init_revisions(&revs, prefix);
        argc = setup_revisions(argc, argv, &revs, NULL);
index c97a42739d23ac7ed90ae207e5a3770c388e66a7..de1e8d1365769c7c841749901f705c0b6a78eab3 100644 (file)
@@ -635,7 +635,7 @@ static int remove_duplicates(int nr_heads, char **heads)
        return dst;
 }
 
-static int fetch_pack_config(const char *var, const char *value)
+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);
@@ -647,7 +647,7 @@ static int fetch_pack_config(const char *var, const char *value)
                return 0;
        }
 
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static struct lock_file lock;
@@ -657,7 +657,7 @@ static void fetch_pack_setup(void)
        static int did_setup;
        if (did_setup)
                return;
-       git_config(fetch_pack_config);
+       git_config(fetch_pack_config, NULL);
        if (0 <= transfer_unpack_limit)
                unpack_limit = transfer_unpack_limit;
        else if (0 <= fetch_unpack_limit)
index f6584ecea165704208059b632bb1a1ac2b6a6d1f..bfe7711aa8b032c098eeb630e8cfec5bac364106 100644 (file)
@@ -127,14 +127,8 @@ static struct ref *get_ref_map(struct transport *transport,
                /* Merge everything on the command line, but not --tags */
                for (rm = ref_map; rm; rm = rm->next)
                        rm->merge = 1;
-               if (tags == TAGS_SET) {
-                       struct refspec refspec;
-                       refspec.src = "refs/tags/";
-                       refspec.dst = "refs/tags/";
-                       refspec.pattern = 1;
-                       refspec.force = 0;
-                       get_fetch_map(remote_refs, &refspec, &tail, 0);
-               }
+               if (tags == TAGS_SET)
+                       get_fetch_map(remote_refs, tag_refspec, &tail, 0);
        } else {
                /* Use the defaults */
                struct remote *remote = transport->remote;
index b72cb59e6a6aab33e7170826242d82785e4fa1e4..b892621ab50346ff29a0a1335de76e3f2fbbc723 100644 (file)
@@ -10,7 +10,7 @@ static const char *fmt_merge_msg_usage =
 
 static int merge_summary;
 
-static int fmt_merge_msg_config(const char *key, const char *value)
+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)) {
@@ -260,7 +260,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
        unsigned char head_sha1[20];
        const char *current_branch;
 
-       git_config(fmt_merge_msg_config);
+       git_config(fmt_merge_msg_config, NULL);
 
        while (argc > 1) {
                if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
index 48f7d95f9784d27f023b22343929f5ad188caa4c..f5625bb9fb090f5af4dfb782dc04005a4149a5c8 100644 (file)
@@ -35,7 +35,7 @@ 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)
+static int gc_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "gc.packrefs")) {
                if (value && !strcmp(value, "notbare"))
@@ -67,7 +67,7 @@ static int gc_config(const char *var, const char *value)
                prune_expire = xstrdup(value);
                return 0;
        }
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static void append_option(const char **cmd, const char *opt, int max_length)
@@ -226,7 +226,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
-       git_config(gc_config);
+       git_config(gc_config, NULL);
 
        if (pack_refs < 0)
                pack_refs = !is_bare_repository();
index b1f33891c36fe38d855ace535cf9218fe9603541..3a062487a7eacd01ed824b46a9124dd343cd2e60 100644 (file)
@@ -18,7 +18,7 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
        int get_verbosely = 0;
        int get_recover = 0;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't') {
index b061317275792ccb33eea80a5071b72ed6f8d6a7..d8bdf928b1e97864722352955a2e2e37b5c00806 100644 (file)
@@ -104,12 +104,14 @@ static void copy_templates_1(char *path, int baselen,
        }
 }
 
-static void copy_templates(const char *git_dir, int len, const char *template_dir)
+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);
@@ -142,7 +144,7 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
        strcpy(template_path + template_len, "config");
        repository_format_version = 0;
        git_config_from_file(check_repository_format_version,
-                            template_path);
+                            template_path, NULL);
        template_path[template_len] = 0;
 
        if (repository_format_version &&
@@ -156,6 +158,8 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
        }
 
        memcpy(path, git_dir, len);
+       if (len && path[len - 1] != '/')
+               path[len++] = '/';
        path[len] = 0;
        copy_templates_1(path, len,
                         template_path, template_len,
@@ -163,8 +167,9 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
        closedir(dir);
 }
 
-static int create_default_files(const char *git_dir, const char *template_path)
+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;
@@ -183,35 +188,27 @@ static int create_default_files(const char *git_dir, const char *template_path)
        /*
         * Create .git/refs/{heads,tags}
         */
-       strcpy(path + len, "refs");
-       safe_create_dir(path, 1);
-       strcpy(path + len, "refs/heads");
-       safe_create_dir(path, 1);
-       strcpy(path + len, "refs/tags");
-       safe_create_dir(path, 1);
+       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.
         */
-       path[len] = 0;
-       copy_templates(path, len, template_path);
+       copy_templates(template_path);
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        /*
         * We would have created the above under user's umask -- under
         * shared-repository settings, we would need to fix them up.
         */
        if (shared_repository) {
-               path[len] = 0;
-               adjust_shared_perm(path);
-               strcpy(path + len, "refs");
-               adjust_shared_perm(path);
-               strcpy(path + len, "refs/heads");
-               adjust_shared_perm(path);
-               strcpy(path + len, "refs/tags");
-               adjust_shared_perm(path);
+               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"));
        }
 
        /*
@@ -251,8 +248,10 @@ static int create_default_files(const char *git_dir, const char *template_path)
                /* allow template config file to override the default */
                if (log_all_ref_updates == -1)
                    git_config_set("core.logallrefupdates", "true");
-               if (work_tree != git_work_tree_cfg)
+               if (prefixcmp(git_dir, work_tree) ||
+                   strcmp(git_dir + strlen(work_tree), "/.git")) {
                        git_config_set("core.worktree", work_tree);
+               }
        }
 
        if (!reinit) {
@@ -278,42 +277,90 @@ static int create_default_files(const char *git_dir, const char *template_path)
        return reinit;
 }
 
-static void guess_repository_type(const char *git_dir)
+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);
+
+       /* 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 == PERM_GROUP)
+                       sprintf(buf, "%d", OLD_PERM_GROUP);
+               else if (shared_repository == PERM_EVERYBODY)
+                       sprintf(buf, "%d", OLD_PERM_EVERYBODY);
+               else
+                       sprintf(buf, "0%o", shared_repository);
+               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;
 
-       if (0 <= is_bare_repository_cfg)
-               return;
-       if (!git_dir)
-               return;
-
        /*
         * "GIT_DIR=. git init" is always bare.
         * "GIT_DIR=`pwd` git init" too.
         */
        if (!strcmp(".", git_dir))
-               goto force_bare;
+               return 1;
        if (!getcwd(cwd, sizeof(cwd)))
                die("cannot tell cwd");
        if (!strcmp(git_dir, cwd))
-               goto force_bare;
+               return 1;
        /*
         * "GIT_DIR=.git or GIT_DIR=something/.git is usually not.
         */
        if (!strcmp(git_dir, ".git"))
-               return;
+               return 0;
        slash = strrchr(git_dir, '/');
        if (slash && !strcmp(slash, "/.git"))
-               return;
+               return 0;
 
        /*
         * Otherwise it is often bare.  At this point
         * we are just guessing.
         */
- force_bare:
-       is_bare_repository_cfg = 1;
-       return;
+       return 1;
 }
 
 static const char init_db_usage[] =
@@ -328,11 +375,9 @@ static const char init_db_usage[] =
 int cmd_init_db(int argc, const char **argv, const char *prefix)
 {
        const char *git_dir;
-       const char *sha1_dir;
        const char *template_dir = NULL;
-       char *path;
-       int len, i, reinit;
-       int quiet = 0;
+       unsigned int flags = 0;
+       int i;
 
        for (i = 1; i < argc; i++, argv++) {
                const char *arg = argv[1];
@@ -343,7 +388,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                else if (!prefixcmp(arg, "--shared="))
                        shared_repository = git_config_perm("arg", arg+9);
                else if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet"))
-                       quiet = 1;
+                       flags |= INIT_DB_QUIET;
                else
                        usage(init_db_usage);
        }
@@ -360,71 +405,35 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                    GIT_WORK_TREE_ENVIRONMENT,
                    GIT_DIR_ENVIRONMENT);
 
-       guess_repository_type(git_dir);
-
-       if (is_bare_repository_cfg <= 0) {
-               git_work_tree_cfg = xcalloc(PATH_MAX, 1);
-               if (!getcwd(git_work_tree_cfg, PATH_MAX))
-                       die ("Cannot access current working directory.");
-               if (access(get_git_work_tree(), X_OK))
-                       die ("Cannot access work tree '%s'",
-                            get_git_work_tree());
-       }
-
        /*
         * Set up the default .git directory contents
         */
-       git_dir = getenv(GIT_DIR_ENVIRONMENT);
        if (!git_dir)
                git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
-       safe_create_dir(git_dir, 0);
-
-       /* 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(git_dir, template_dir);
-
-       /*
-        * And set up the object store.
-        */
-       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 == PERM_GROUP)
-                       sprintf(buf, "%d", OLD_PERM_GROUP);
-               else if (shared_repository == PERM_EVERYBODY)
-                       sprintf(buf, "%d", OLD_PERM_EVERYBODY);
-               else
-                       sprintf(buf, "0%o", shared_repository);
-               git_config_set("core.sharedrepository", buf);
-               git_config_set("receive.denyNonFastforwards", "true");
+       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 ("Cannot access current working directory.");
+               }
+               if (access(get_git_work_tree(), X_OK))
+                       die ("Cannot access work tree '%s'",
+                            get_git_work_tree());
        }
 
-       if (!quiet)
-               printf("%s%s Git repository in %s/\n",
-                      reinit ? "Reinitialized existing" : "Initialized empty",
-                      shared_repository ? " shared" : "",
-                      git_dir);
+       set_git_dir(make_absolute_path(git_dir));
 
-       return 0;
+       return init_db(template_dir, flags);
 }
index 543855b7ad11bd0355d88bc1502030509875e0c7..9817d6fbeb69393ed321e71b6211a8630b982fab 100644 (file)
@@ -230,7 +230,7 @@ static int cmd_log_walk(struct rev_info *rev)
        return 0;
 }
 
-static int git_log_config(const char *var, const char *value)
+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);
@@ -246,14 +246,14 @@ static int git_log_config(const char *var, const char *value)
                default_show_root = git_config_bool(var, value);
                return 0;
        }
-       return git_diff_ui_config(var, value);
+       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);
+       git_config(git_log_config, NULL);
 
        if (diff_use_color_default == -1)
                diff_use_color_default = git_use_color_default;
@@ -329,7 +329,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
        struct object_array_entry *objects;
        int i, count, ret = 0;
 
-       git_config(git_log_config);
+       git_config(git_log_config, NULL);
 
        if (diff_use_color_default == -1)
                diff_use_color_default = git_use_color_default;
@@ -393,7 +393,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
 
-       git_config(git_log_config);
+       git_config(git_log_config, NULL);
 
        if (diff_use_color_default == -1)
                diff_use_color_default = git_use_color_default;
@@ -426,7 +426,7 @@ int cmd_log(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
 
-       git_config(git_log_config);
+       git_config(git_log_config, NULL);
 
        if (diff_use_color_default == -1)
                diff_use_color_default = git_use_color_default;
@@ -481,7 +481,7 @@ static void add_header(const char *value)
        extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
 }
 
-static int git_format_config(const char *var, const char *value)
+static int git_format_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "format.headers")) {
                if (!value)
@@ -514,7 +514,7 @@ static int git_format_config(const char *var, const char *value)
                return 0;
        }
 
-       return git_log_config(var, value);
+       return git_log_config(var, value, cb);
 }
 
 
@@ -781,7 +781,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        char *add_signoff = NULL;
        struct strbuf buf;
 
-       git_config(git_format_config);
+       git_config(git_format_config, NULL);
        init_revisions(&rev, prefix);
        rev.commit_format = CMIT_FMT_EMAIL;
        rev.verbose_header = 1;
index dc7eab89b34fed32dbb198a9aa9a7503fc162216..75ba42246ee340634d317ec39948ed983171a42f 100644 (file)
@@ -437,7 +437,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
        memset(&dir, 0, sizeof(dir));
        if (prefix)
                prefix_offset = strlen(prefix);
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
index 7abe333ce99a3448373119c1a811341cb15f1d10..f4a75ddbc3fecb9c1690a94d39ce230e82fefd8d 100644 (file)
@@ -122,7 +122,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
        unsigned char sha1[20];
        struct tree *tree;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        ls_tree_prefix = prefix;
        if (prefix && *prefix)
                chomp_prefix = strlen(prefix);
index 11f154b31fcbd5c788299855e7d69f54ae8c7e70..97c1ff97440ec107e336df774f86aadc28f26b89 100644 (file)
@@ -434,6 +434,7 @@ static int read_one_header_line(char *line, int sz, FILE *in)
 
 static int decode_q_segment(char *in, char *ot, unsigned otsize, char *ep, int rfc2047)
 {
+       char *otbegin = ot;
        char *otend = ot + otsize;
        int c;
        while ((c = *in++) != 0 && (in <= ep)) {
@@ -453,13 +454,14 @@ static int decode_q_segment(char *in, char *ot, unsigned otsize, char *ep, int r
                *ot++ = c;
        }
        *ot = 0;
-       return 0;
+       return (ot - otbegin);
 }
 
 static int decode_b_segment(char *in, char *ot, unsigned otsize, char *ep)
 {
        /* Decode in..ep, possibly in-place to ot */
        int c, pos = 0, acc = 0;
+       char *otbegin = ot;
        char *otend = ot + otsize;
 
        while ((c = *in++) != 0 && (in <= ep)) {
@@ -505,7 +507,7 @@ static int decode_b_segment(char *in, char *ot, unsigned otsize, char *ep)
                }
        }
        *ot = 0;
-       return 0;
+       return (ot - otbegin);
 }
 
 /*
@@ -623,25 +625,24 @@ static void decode_header(char *it, unsigned itsize)
                convert_to_utf8(it, itsize, "");
 }
 
-static void decode_transfer_encoding(char *line, unsigned linesize)
+static int decode_transfer_encoding(char *line, unsigned linesize, int inputlen)
 {
        char *ep;
 
        switch (transfer_encoding) {
        case TE_QP:
-               ep = line + strlen(line);
-               decode_q_segment(line, line, linesize, ep, 0);
-               break;
+               ep = line + inputlen;
+               return decode_q_segment(line, line, linesize, ep, 0);
        case TE_BASE64:
-               ep = line + strlen(line);
-               decode_b_segment(line, line, linesize, ep);
-               break;
+               ep = line + inputlen;
+               return decode_b_segment(line, line, linesize, ep);
        case TE_DONTCARE:
-               break;
+       default:
+               return inputlen;
        }
 }
 
-static int handle_filter(char *line, unsigned linesize);
+static int handle_filter(char *line, unsigned linesize, int linelen);
 
 static int find_boundary(void)
 {
@@ -669,7 +670,7 @@ again:
                                        "can't recover\n");
                        exit(1);
                }
-               handle_filter(newline, sizeof(newline));
+               handle_filter(newline, sizeof(newline), strlen(newline));
 
                /* skip to the next boundary */
                if (!find_boundary())
@@ -759,14 +760,14 @@ static int handle_commit_msg(char *line, unsigned linesize)
        return 0;
 }
 
-static int handle_patch(char *line)
+static int handle_patch(char *line, int len)
 {
-       fputs(line, patchfile);
+       fwrite(line, 1, len, patchfile);
        patch_lines++;
        return 0;
 }
 
-static int handle_filter(char *line, unsigned linesize)
+static int handle_filter(char *line, unsigned linesize, int linelen)
 {
        static int filter = 0;
 
@@ -779,7 +780,7 @@ static int handle_filter(char *line, unsigned linesize)
                        break;
                filter++;
        case 1:
-               if (!handle_patch(line))
+               if (!handle_patch(line, linelen))
                        break;
                filter++;
        default:
@@ -794,6 +795,7 @@ static void handle_body(void)
        int rc = 0;
        static char newline[2000];
        static char *np = newline;
+       int len = strlen(line);
 
        /* Skip up to the first boundary */
        if (content_top->boundary) {
@@ -805,16 +807,19 @@ static void handle_body(void)
                /* process any boundary lines */
                if (content_top->boundary && is_multipart_boundary(line)) {
                        /* flush any leftover */
-                       if ((transfer_encoding == TE_BASE64)  &&
-                           (np != newline)) {
-                               handle_filter(newline, sizeof(newline));
-                       }
+                       if (np != newline)
+                               handle_filter(newline, sizeof(newline),
+                                             np - newline);
                        if (!handle_boundary())
                                return;
                }
 
                /* Unwrap transfer encoding */
-               decode_transfer_encoding(line, sizeof(line));
+               len = decode_transfer_encoding(line, sizeof(line), len);
+               if (len < 0) {
+                       error("Malformed input line");
+                       return;
+               }
 
                switch (transfer_encoding) {
                case TE_BASE64:
@@ -824,39 +829,40 @@ static void handle_body(void)
 
                        /* binary data most likely doesn't have newlines */
                        if (message_type != TYPE_TEXT) {
-                               rc = handle_filter(line, sizeof(newline));
+                               rc = handle_filter(line, sizeof(line), len);
                                break;
                        }
 
-                       /* this is a decoded line that may contain
+                       /*
+                        * This is a decoded line that may contain
                         * multiple new lines.  Pass only one chunk
                         * at a time to handle_filter()
                         */
-
                        do {
-                               while (*op != '\n' && *op != 0)
+                               while (op < line + len && *op != '\n')
                                        *np++ = *op++;
                                *np = *op;
                                if (*np != 0) {
                                        /* should be sitting on a new line */
                                        *(++np) = 0;
                                        op++;
-                                       rc = handle_filter(newline, sizeof(newline));
+                                       rc = handle_filter(newline, sizeof(newline), np - newline);
                                        np = newline;
                                }
-                       } while (*op != 0);
-                       /* the partial chunk is saved in newline and
-                        * will be appended by the next iteration of fgets
+                       } while (op < line + len);
+                       /*
+                        * The partial chunk is saved in newline and will be
+                        * appended by the next iteration of read_line_with_nul().
                         */
                        break;
                }
                default:
-                       rc = handle_filter(line, sizeof(newline));
+                       rc = handle_filter(line, sizeof(line), len);
                }
                if (rc)
                        /* nothing left to filter */
                        break;
-       } while (fgets(line, sizeof(line), fin));
+       } while ((len = read_line_with_nul(line, sizeof(line), fin)));
 
        return;
 }
@@ -962,7 +968,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
        /* NEEDSWORK: might want to do the optional .git/ directory
         * discovery
         */
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        def_charset = (git_commit_encoding ? git_commit_encoding : "utf-8");
        metainfo_charset = def_charset;
index 46b27cdaea71cba92974480da74ec5922fcf3a7a..ae2b4cb21bf4e691a044e2ace3cdd4861562ceca 100644 (file)
@@ -45,6 +45,24 @@ static int is_from_line(const char *line, int len)
 /* Could be as small as 64, enough to hold a Unix "From " line. */
 static char buf[4096];
 
+/* We cannot use fgets() because our lines can contain NULs */
+int read_line_with_nul(char *buf, int size, FILE *in)
+{
+       int len = 0, c;
+
+       for (;;) {
+               c = getc(in);
+               if (c == EOF)
+                       break;
+               buf[len++] = c;
+               if (c == '\n' || len + 1 >= size)
+                       break;
+       }
+       buf[len] = '\0';
+
+       return len;
+}
+
 /* Called with the first line (potentially partial)
  * already in buf[] -- normally that should begin with
  * the Unix "From " line.  Write it into the specified
@@ -70,19 +88,19 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)
         * "From " and having something that looks like a date format.
         */
        for (;;) {
-               int is_partial = (buf[len-1] != '\n');
+               int is_partial = len && buf[len-1] != '\n';
 
-               if (fputs(buf, output) == EOF)
+               if (fwrite(buf, 1, len, output) != len)
                        die("cannot write output");
 
-               if (fgets(buf, sizeof(buf), mbox) == NULL) {
+               len = read_line_with_nul(buf, sizeof(buf), mbox);
+               if (len == 0) {
                        if (feof(mbox)) {
                                status = 1;
                                break;
                        }
                        die("cannot read mbox");
                }
-               len = strlen(buf);
                if (!is_partial && !is_bare && is_from_line(buf, len))
                        break; /* done with one message */
        }
index 0108e22adee8b4de922a2b00b514dd4d4cf23c55..bcf9395aafb475edd22459eaf05cf5e180ca9b8f 100644 (file)
@@ -28,7 +28,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix)
        unsigned char rev1key[20], rev2key[20];
        int show_all = 0;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        while (1 < argc && argv[1][0] == '-') {
                const char *arg = argv[1];
index 46e636fdcf1cb854a486e425df05a2485d18d5d7..362c290028d68ebc2e589d424bc42cd5e8630a4e 100644 (file)
@@ -1340,7 +1340,7 @@ static struct commit *get_ref(const char *ref)
        return (struct commit *)object;
 }
 
-static int merge_config(const char *var, const char *value)
+static int merge_config(const char *var, const char *value, void *cb)
 {
        if (!strcasecmp(var, "merge.verbosity")) {
                verbosity = git_config_int(var, value);
@@ -1354,7 +1354,7 @@ static int merge_config(const char *var, const char *value)
                merge_rename_limit = git_config_int(var, value);
                return 0;
        }
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
@@ -1375,7 +1375,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
                        subtree_merge = 1;
        }
 
-       git_config(merge_config);
+       git_config(merge_config, NULL);
        if (getenv("GIT_MERGE_VERBOSITY"))
                verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
 
index fb8ffb41aaba5f6e2a8cc13207a2f2c407d6c91d..5530e11b89c2c05c95c8ab1e82999eec0fdcc4c5 100644 (file)
@@ -81,7 +81,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
        struct path_list deleted = {NULL, 0, 0, 0};
        struct path_list changed = {NULL, 0, 0, 0};
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        newfd = hold_locked_index(&lock_file, 1);
        if (read_cache() < 0)
@@ -256,7 +256,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
 
                for (i = 0; i < added.nr; i++) {
                        const char *path = added.items[i].path;
-                       if (add_file_to_cache(path, verbose))
+                       if (add_file_to_cache(path, verbose ? ADD_CACHE_VERBOSE : 0))
                                die("updating index entries failed");
                }
 
index 384da4db13d9ff82b9bd45e6ab26b5dbf925dc22..cde5de56fa43c1552722965343867b2baaebdaf4 100644 (file)
@@ -195,7 +195,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                OPT_END(),
        };
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, opts, name_rev_usage, 0);
        if (!!all + !!transform_stdin + !!argc > 1) {
                error("Specify either a list, or --all, not both!");
index f43eb67016ee719d34d2e2f4ae8d77e1ec74b396..70d2f5d4161a0bb62f4734be97d0a8dd2a5098dc 100644 (file)
@@ -1760,7 +1760,7 @@ static void prepare_pack(int window, int depth)
        free(delta_list);
 }
 
-static int git_pack_config(const char *k, const char *v)
+static int git_pack_config(const char *k, const char *v, void *cb)
 {
        if(!strcmp(k, "pack.window")) {
                window = git_config_int(k, v);
@@ -1813,7 +1813,7 @@ static int git_pack_config(const char *k, const char *v)
                pack_size_limit_cfg = git_config_ulong(k, v);
                return 0;
        }
-       return git_default_config(k, v);
+       return git_default_config(k, v, cb);
 }
 
 static void read_object_list_from_stdin(void)
@@ -2033,7 +2033,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
        rp_ac = 2;
 
-       git_config(git_pack_config);
+       git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
                pack_compression_level = core_compression_level;
 
index 7ac30883bc72c51b0227828bef5758751e524f65..5a09e17f1ad4fe19cf4e7cc29235d14d35405168 100644 (file)
@@ -104,12 +104,10 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        newfd = hold_locked_index(&lock_file, 1);
 
-       git_config(git_default_config);
-
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
index 280e24e1514f2989b571acc768d7067cbb223933..897d1dcac6b34e0c631c94b98c489db075e90bb2 100644 (file)
@@ -329,7 +329,7 @@ static int collect_reflog(const char *ref, const unsigned char *sha1, int unused
        return 0;
 }
 
-static int reflog_expire_config(const char *var, const char *value)
+static int reflog_expire_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "gc.reflogexpire")) {
                if (!value)
@@ -343,7 +343,7 @@ static int reflog_expire_config(const char *var, const char *value)
                default_reflog_expire_unreachable = approxidate(value);
                return 0;
        }
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
@@ -352,7 +352,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
        unsigned long now = time(NULL);
        int i, status, do_all;
 
-       git_config(reflog_expire_config);
+       git_config(reflog_expire_config, NULL);
 
        save_commit_buffer = 0;
        do_all = status = 0;
index 8b63619ef08a2ac3d96000908fe4986396ddd6a2..99a34dfe86a79df353d3c094cb2ebf71fdca1cc0 100644 (file)
@@ -153,7 +153,7 @@ struct branch_info {
 
 static struct path_list branch_list;
 
-static int config_read_branches(const char *key, const char *value)
+static int config_read_branches(const char *key, const char *value, void *cb)
 {
        if (!prefixcmp(key, "branch.")) {
                char *name;
@@ -200,7 +200,7 @@ static void read_branches(void)
 {
        if (branch_list.nr)
                return;
-       git_config(config_read_branches);
+       git_config(config_read_branches, NULL);
        sort_path_list(&branch_list);
 }
 
@@ -514,7 +514,7 @@ struct remote_group {
        struct path_list *list;
 } remote_group;
 
-static int get_remote_group(const char *key, const char *value)
+static int get_remote_group(const char *key, const char *value, void *cb)
 {
        if (!prefixcmp(key, "remotes.") &&
                        !strcmp(key + 8, remote_group.name)) {
@@ -546,7 +546,7 @@ static int update(int argc, const char **argv)
        remote_group.list = &list;
        for (i = 1; i < argc; i++) {
                remote_group.name = argv[i];
-               result = git_config(get_remote_group);
+               result = git_config(get_remote_group, NULL);
        }
 
        if (!result && !list.nr  && argc == 2 && !strcmp(argv[1], "default"))
index c607aade637423f0129167163fa4055b08546248..5c811423cc1234d933cff013f9923b45f19dc6fa 100644 (file)
@@ -339,7 +339,7 @@ tail_optimization:
        return write_rr(rr, fd);
 }
 
-static int git_rerere_config(const char *var, const char *value)
+static int git_rerere_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "gc.rerereresolved"))
                cutoff_resolve = git_config_int(var, value);
@@ -348,7 +348,7 @@ static int git_rerere_config(const char *var, const char *value)
        else if (!strcmp(var, "rerere.enabled"))
                rerere_enabled = git_config_bool(var, value);
        else
-               return git_default_config(var, value);
+               return git_default_config(var, value, cb);
        return 0;
 }
 
@@ -376,7 +376,7 @@ static int setup_rerere(struct path_list *merge_rr)
 {
        int fd;
 
-       git_config(git_rerere_config);
+       git_config(git_rerere_config, NULL);
        if (!is_rerere_enabled())
                return -1;
 
index 79424bb26efb4525b6bc2080e639b51d59eaabd9..e32ddd90ac9e6f1bdf755b268d1a7c8b034c9e63 100644 (file)
@@ -186,7 +186,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        argc = parse_options(argc, argv, options, git_reset_usage,
                                                PARSE_OPT_KEEP_DASHDASH);
index 54d55cc3a33e55d5c3021eae14b7a771b4e1c23f..83a7b1349e06dbf1a355888272d9b13a7d4c22c4 100644 (file)
@@ -65,15 +65,18 @@ static void show_commit(struct commit *commit)
                printf("%lu ", commit->date);
        if (header_prefix)
                fputs(header_prefix, stdout);
-       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.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),
@@ -588,7 +591,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        int bisect_find_all = 0;
        int quiet = 0;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        init_revisions(&revs, prefix);
        revs.abbrev = 0;
        revs.commit_format = CMIT_FMT_UNSPECIFIED;
index ab3e85054e174d2d328c625f1db744854b62f5f7..a7860ed75ac4a7b1895bfe088e34a39b24448f57 100644 (file)
@@ -387,7 +387,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                return cmd_parseopt(argc - 1, argv + 1, prefix);
 
        prefix = setup_git_directory();
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
index 2b57525d7234086bdf2022d13fa405e5007627fb..0270f9b85a8229d30ab3fab5ce73661f383b005e 100644 (file)
@@ -269,7 +269,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
        const char *message, *encoding;
        const char *defmsg = xstrdup(git_path("MERGE_MSG"));
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        me = action == REVERT ? "revert" : "cherry-pick";
        setenv(GIT_REFLOG_ACTION, me, 0);
        parse_args(argc, argv);
index c0a8bb6cf5675d671a2cca50bf72d5c60f155313..22c9bd1c6cf2d372e0262506cc1e21daebdf579a 100644 (file)
@@ -144,7 +144,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
        const char **pathspec;
        char *seen;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        newfd = hold_locked_index(&lock_file, 1);
 
index 019abd3527e7c573c69900a58313749e2ef2280a..93047f5117796fb7556fa23871397e68e9f5e4c2 100644 (file)
@@ -533,7 +533,7 @@ static void append_one_rev(const char *av)
        die("bad sha1 reference %s", av);
 }
 
-static int git_show_branch_config(const char *var, const char *value)
+static int git_show_branch_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "showbranch.default")) {
                if (!value)
@@ -547,7 +547,7 @@ static int git_show_branch_config(const char *var, const char *value)
                return 0;
        }
 
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
@@ -611,7 +611,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        int reflog = 0;
        const char *reflog_base = NULL;
 
-       git_config(git_show_branch_config);
+       git_config(git_show_branch_config, NULL);
 
        /* If nothing is specified, try the default first */
        if (ac == 1 && default_num) {
@@ -782,8 +782,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                                has_head++;
                }
                if (!has_head) {
-                       int pfxlen = strlen("refs/heads/");
-                       append_one_rev(head + pfxlen);
+                       int offset = !prefixcmp(head, "refs/heads/") ? 11 : 0;
+                       append_one_rev(head + offset);
                }
        }
 
index d33982b967e7665ae79fb186435d9ed9aabb907b..b49bdb6900f2c49b55880f9f220597bf3f156810 100644 (file)
@@ -35,7 +35,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
                OPT_END(),
        };
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, options, git_symbolic_ref_usage, 0);
        if (msg &&!*msg)
                die("Refusing to perform update with empty message");
index 129ff57f11d2cdfdacc6be9685736ada07caef7f..e675206de38f0709b4c07e1667d791bf9b6704cb 100644 (file)
@@ -256,7 +256,7 @@ static void set_signingkey(const char *value)
                die("signing key value too long (%.10s...)", value);
 }
 
-static int git_tag_config(const char *var, const char *value)
+static int git_tag_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "user.signingkey")) {
                if (!value)
@@ -265,7 +265,7 @@ static int git_tag_config(const char *var, const char *value)
                return 0;
        }
 
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static void write_tag_body(int fd, const unsigned char *sha1)
@@ -408,7 +408,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
-       git_config(git_tag_config);
+       git_config(git_tag_config, NULL);
 
        argc = parse_options(argc, argv, options, git_tag_usage, 0);
 
index fecf0be7796f34fbb06a6bbbe61e9e3d042f6780..85043d1fde917e67746fb1ee106ce2cf78f3d161 100644 (file)
@@ -493,7 +493,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
        int i;
        unsigned char sha1[20];
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        quiet = !isatty(2);
 
index a8795d3d5fea9f340dfe21a71190f120c63856cd..9e0d7ab11ee01c6a1def223820038a94113f348c 100644 (file)
@@ -567,7 +567,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
        int lock_error = 0;
        struct lock_file *lock_file;
 
-       git_config(git_default_config);
+       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));
@@ -593,6 +593,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                                refresh_flags |= REFRESH_QUIET;
                                continue;
                        }
+                       if (!strcmp(path, "--ignore-submodules")) {
+                               refresh_flags |= REFRESH_IGNORE_SUBMODULES;
+                               continue;
+                       }
                        if (!strcmp(path, "--add")) {
                                allow_add = 1;
                                continue;
index e90737c350402fec8937a9e343485e1c71411f55..93c127196d272d99d8af24a5fbc7ac89efdbb3d4 100644 (file)
@@ -22,7 +22,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                OPT_END(),
        };
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, options, git_update_ref_usage, 0);
        if (msg && !*msg)
                die("Refusing to perform update with empty message.");
index 4958bbbf11f5f796feedfa7480b827029f912d01..4c515a0570ec2b05e5dca8156d885a70b922251e 100644 (file)
@@ -55,7 +55,7 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix)
        int no_more_options = 0;
        int nothing_done = 1;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        while (1 < argc) {
                if (!no_more_options && argv[1][0] == '-') {
                        if (!strcmp("-v", argv[1]))
index db81496b464e253341a42e01eb72d6845e87199c..92eaa89a45eff2e76e531a914bdccca5fafe98b0 100644 (file)
@@ -90,7 +90,7 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
 {
        int i = 1, verbose = 0, had_error = 0;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        if (argc == 1)
                usage(builtin_verify_tag_usage);
index e838d01233eb1d6cfaa0885c6b37c8726079b0ac..c2187997447df64948577d8a9a78f4462239b369 100644 (file)
@@ -18,7 +18,7 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
        unsigned char sha1[20];
        const char *me = "git-write-tree";
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        while (1 < argc) {
                const char *arg = argv[1];
                if (!strcmp(arg, "--missing-ok"))
index 95126fd0c12149034372afd8eb37b944be684957..8bda1117e4c56b38ac799f60d39310ffc51b9b63 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -9,6 +9,7 @@ extern const char git_usage_string[];
 extern void list_common_cmds_help(void);
 extern void help_unknown_cmd(const char *cmd);
 extern void prune_packed_objects(int);
+extern int read_line_with_nul(char *buf, int size, FILE *file);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
 extern int cmd_annotate(int argc, const char **argv, const char *prefix);
@@ -24,6 +25,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
+extern int cmd_clone(int argc, const char **argv, const char *prefix);
 extern int cmd_clean(int argc, const char **argv, const char *prefix);
 extern int cmd_commit(int argc, const char **argv, const char *prefix);
 extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index 8db19cc253dda9b63401f7b55ffbc0d333e216a8..eab1a172fe4b2a9d735dc480879fd1034da7942a 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -261,8 +261,8 @@ static inline void remove_name_hash(struct cache_entry *ce)
 #define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option))
 #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
 #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
-#define add_to_cache(path, st, verbose) add_to_index(&the_index, (path), (st), (verbose))
-#define add_file_to_cache(path, verbose) add_file_to_index(&the_index, (path), (verbose))
+#define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags))
+#define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags))
 #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL)
 #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
 #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
@@ -317,6 +317,7 @@ extern char *get_graft_file(void);
 extern int set_git_dir(const char *path);
 extern const char *get_git_work_tree(void);
 extern const char *read_gitfile_gently(const char *path);
+extern void set_git_work_tree(const char *tree);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
@@ -329,6 +330,10 @@ extern const char *prefix_filename(const char *prefix, int len, const char *path
 extern void verify_filename(const char *prefix, const char *name);
 extern void verify_non_filename(const char *prefix, const char *name);
 
+#define INIT_DB_QUIET 0x0001
+
+extern int init_db(const char *template_dir, unsigned int flags);
+
 #define alloc_nr(x) (((x)+16)*3/2)
 
 /*
@@ -366,8 +371,11 @@ extern int add_index_entry(struct index_state *, struct cache_entry *ce, int opt
 extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 extern int remove_index_entry_at(struct index_state *, int pos);
 extern int remove_file_from_index(struct index_state *, const char *path);
-extern int add_to_index(struct index_state *, const char *path, struct stat *, int verbose);
-extern int add_file_to_index(struct index_state *, const char *path, int verbose);
+#define ADD_CACHE_VERBOSE 1
+#define ADD_CACHE_PRETEND 2
+#define ADD_CACHE_IGNORE_ERRORS        4
+extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
+extern int add_file_to_index(struct index_state *, const char *path, int flags);
 extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
 extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 
@@ -388,6 +396,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 #define REFRESH_UNMERGED       0x0002  /* allow unmerged */
 #define REFRESH_QUIET          0x0004  /* be quiet about it */
 #define REFRESH_IGNORE_MISSING 0x0008  /* ignore non-existent */
+#define REFRESH_IGNORE_SUBMODULES      0x0008  /* ignore submodules */
 extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen);
 
 struct lock_file {
@@ -398,6 +407,7 @@ struct lock_file {
        char filename[PATH_MAX];
 };
 extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
+extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
 extern int commit_lock_file(struct lock_file *);
 
 extern int hold_locked_index(struct lock_file *, int);
@@ -615,6 +625,7 @@ extern struct alternate_object_database {
        char base[FLEX_ARRAY]; /* more */
 } *alt_odb_list;
 extern void prepare_alt_odb(void);
+extern void add_to_alternates_file(const char *reference);
 
 struct pack_window {
        struct pack_window *next;
@@ -712,10 +723,10 @@ extern int matches_pack_name(struct packed_git *p, const char *name);
 /* Dumb servers support */
 extern int update_server_info(int);
 
-typedef int (*config_fn_t)(const char *, const char *);
-extern int git_default_config(const char *, const char *);
-extern int git_config_from_file(config_fn_t fn, const char *);
-extern int git_config(config_fn_t fn);
+typedef int (*config_fn_t)(const char *, const char *, void *);
+extern int git_default_config(const char *, const char *, void *);
+extern int git_config_from_file(config_fn_t fn, const char *, void *);
+extern int git_config(config_fn_t fn, void *);
 extern int git_parse_long(const char *, long *);
 extern int git_parse_ulong(const char *, unsigned long *);
 extern int git_config_int(const char *, const char *);
@@ -727,7 +738,7 @@ extern int git_config_set(const char *, const char *);
 extern int git_config_set_multivar(const char *, const char *, const char *, int);
 extern int git_config_rename_section(const char *, const char *);
 extern const char *git_etc_gitconfig(void);
-extern int check_repository_format_version(const char *var, const char *value);
+extern int check_repository_format_version(const char *var, const char *value, void *cb);
 extern int git_env_bool(const char *, int);
 extern int git_config_system(void);
 extern int git_config_global(void);
@@ -783,8 +794,6 @@ extern int convert_to_git(const char *path, const char *src, size_t len,
 extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
 
 /* add */
-#define ADD_FILES_VERBOSE      01
-#define ADD_FILES_IGNORE_ERRORS        02
 /*
  * return 0 if success, 1 - if addition of a file failed and
  * ADD_FILES_IGNORE_ERRORS was specified in flags
diff --git a/color.c b/color.c
index 12a6453f90eb4ce2b39f85762bf3acd1cff2b9f2..fc0b72ad59b13e4bd86372e5e81b4f400c99d26e 100644 (file)
--- a/color.c
+++ b/color.c
@@ -145,14 +145,14 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
        return 0;
 }
 
-int git_color_default_config(const char *var, const char *value)
+int git_color_default_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "color.ui")) {
                git_use_color_default = git_config_colorbool(var, value, -1);
                return 0;
        }
 
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static int color_vfprintf(FILE *fp, const char *color, const char *fmt,
diff --git a/color.h b/color.h
index ecda5569a2cccabaf16fcf0aad3bce57dfb3aca4..6cf5c88aaf8d0e38e2853e6fd212e3cdd6c180cb 100644 (file)
--- a/color.h
+++ b/color.h
@@ -13,7 +13,7 @@ extern int git_use_color_default;
 /*
  * Use this instead of git_default_config if you need the value of color.ui.
  */
-int git_color_default_config(const char *var, const char *value);
+int git_color_default_config(const char *var, const char *value, void *cb);
 
 int git_config_colorbool(const char *var, const char *value, int stdout_is_tty);
 void color_parse(const char *var, const char *value, char *dst);
index 5195de10c9289a25053756ce799e654d5e01483e..c2f2bbb000cd003002c1bd346e6be6c9d3ef5c2d 100644 (file)
--- a/config.c
+++ b/config.c
@@ -111,7 +111,7 @@ static inline int iskeychar(int c)
        return isalnum(c) || c == '-';
 }
 
-static int get_value(config_fn_t fn, char *name, unsigned int len)
+static int get_value(config_fn_t fn, void *data, char *name, unsigned int len)
 {
        int c;
        char *value;
@@ -139,7 +139,7 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
                if (!value)
                        return -1;
        }
-       return fn(name, value);
+       return fn(name, value, data);
 }
 
 static int get_extended_base_var(char *name, int baselen, int c)
@@ -197,7 +197,7 @@ static int get_base_var(char *name)
        }
 }
 
-static int git_parse_file(config_fn_t fn)
+static int git_parse_file(config_fn_t fn, void *data)
 {
        int comment = 0;
        int baselen = 0;
@@ -228,7 +228,7 @@ static int git_parse_file(config_fn_t fn)
                if (!isalpha(c))
                        break;
                var[baselen] = tolower(c);
-               if (get_value(fn, var, baselen+1) < 0)
+               if (get_value(fn, data, var, baselen+1) < 0)
                        break;
        }
        die("bad config file line %d in %s", config_linenr, config_file_name);
@@ -332,7 +332,7 @@ int git_config_string(const char **dest, const char *var, const char *value)
        return 0;
 }
 
-int git_default_config(const char *var, const char *value)
+int git_default_config(const char *var, const char *value, void *dummy)
 {
        /* This needs a better name */
        if (!strcmp(var, "core.filemode")) {
@@ -516,7 +516,7 @@ int git_default_config(const char *var, const char *value)
        return 0;
 }
 
-int git_config_from_file(config_fn_t fn, const char *filename)
+int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 {
        int ret;
        FILE *f = fopen(filename, "r");
@@ -527,7 +527,7 @@ int git_config_from_file(config_fn_t fn, const char *filename)
                config_file_name = filename;
                config_linenr = 1;
                config_file_eof = 0;
-               ret = git_parse_file(fn);
+               ret = git_parse_file(fn, data);
                fclose(f);
                config_file_name = NULL;
        }
@@ -565,7 +565,7 @@ int git_config_global(void)
        return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0);
 }
 
-int git_config(config_fn_t fn)
+int git_config(config_fn_t fn, void *data)
 {
        int ret = 0;
        char *repo_config = NULL;
@@ -578,7 +578,8 @@ int git_config(config_fn_t fn)
        filename = getenv(CONFIG_ENVIRONMENT);
        if (!filename) {
                if (git_config_system() && !access(git_etc_gitconfig(), R_OK))
-                       ret += git_config_from_file(fn, git_etc_gitconfig());
+                       ret += git_config_from_file(fn, git_etc_gitconfig(),
+                               data);
                home = getenv("HOME");
                filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!filename)
@@ -588,11 +589,11 @@ int git_config(config_fn_t fn)
        if (git_config_global() && home) {
                char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
                if (!access(user_config, R_OK))
-                       ret = git_config_from_file(fn, user_config);
+                       ret = git_config_from_file(fn, user_config, data);
                free(user_config);
        }
 
-       ret += git_config_from_file(fn, filename);
+       ret += git_config_from_file(fn, filename, data);
        free(repo_config);
        return ret;
 }
@@ -622,7 +623,7 @@ static int matches(const char* key, const char* value)
                  !regexec(store.value_regex, value, 0, NULL, 0)));
 }
 
-static int store_aux(const char* key, const char* value)
+static int store_aux(const char* key, const char* value, void *cb)
 {
        const char *ep;
        size_t section_len;
@@ -951,7 +952,7 @@ int git_config_set_multivar(const char* key, const char* value,
                 * As a side effect, we make sure to transform only a valid
                 * existing config file.
                 */
-               if (git_config_from_file(store_aux, config_filename)) {
+               if (git_config_from_file(store_aux, config_filename, NULL)) {
                        error("invalid config file %s", config_filename);
                        free(store.key);
                        if (store.value_regex != NULL) {
index d12b105970bca82017998cae52f5807629736bb9..e92af2973565197f2f001c45c6059f9a838fe289 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -360,7 +360,8 @@ static char *git_proxy_command;
 static const char *rhost_name;
 static int rhost_len;
 
-static int git_proxy_command_options(const char *var, const char *value)
+static int git_proxy_command_options(const char *var, const char *value,
+               void *cb)
 {
        if (!strcmp(var, "core.gitproxy")) {
                const char *for_pos;
@@ -404,7 +405,7 @@ static int git_proxy_command_options(const char *var, const char *value)
                return 0;
        }
 
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static int git_use_proxy(const char *host)
@@ -412,7 +413,7 @@ static int git_use_proxy(const char *host)
        rhost_name = host;
        rhost_len = strlen(host);
        git_proxy_command = getenv("GIT_PROXY_COMMAND");
-       git_config(git_proxy_command_options);
+       git_config(git_proxy_command_options, NULL);
        rhost_name = NULL;
        return (git_proxy_command && *git_proxy_command);
 }
diff --git a/contrib/examples/git-clone.sh b/contrib/examples/git-clone.sh
new file mode 100755 (executable)
index 0000000..547228e
--- /dev/null
@@ -0,0 +1,525 @@
+#!/bin/sh
+#
+# Copyright (c) 2005, Linus Torvalds
+# Copyright (c) 2005, Junio C Hamano
+#
+# Clone a repository into a different directory that does not yet exist.
+
+# See git-sh-setup why.
+unset CDPATH
+
+OPTIONS_SPEC="\
+git-clone [options] [--] <repo> [<dir>]
+--
+n,no-checkout        don't create a checkout
+bare                 create a bare repository
+naked                create a bare repository
+l,local              to clone from a local repository
+no-hardlinks         don't use local hardlinks, always copy
+s,shared             setup as a shared repository
+template=            path to the template directory
+q,quiet              be quiet
+reference=           reference repository
+o,origin=            use <name> instead of 'origin' to track upstream
+u,upload-pack=       path to git-upload-pack on the remote
+depth=               create a shallow clone of that depth
+
+use-separate-remote  compatibility, do not use
+no-separate-remote   compatibility, do not use"
+
+die() {
+       echo >&2 "$@"
+       exit 1
+}
+
+usage() {
+       exec "$0" -h
+}
+
+eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+
+get_repo_base() {
+       (
+               cd "`/bin/pwd`" &&
+               cd "$1" || cd "$1.git" &&
+               {
+                       cd .git
+                       pwd
+               }
+       ) 2>/dev/null
+}
+
+if [ -n "$GIT_SSL_NO_VERIFY" -o \
+       "`git config --bool http.sslVerify`" = false ]; then
+    curl_extra_args="-k"
+fi
+
+http_fetch () {
+       # $1 = Remote, $2 = Local
+       curl -nsfL $curl_extra_args "$1" >"$2"
+       curl_exit_status=$?
+       case $curl_exit_status in
+       126|127) exit ;;
+       *)       return $curl_exit_status ;;
+       esac
+}
+
+clone_dumb_http () {
+       # $1 - remote, $2 - local
+       cd "$2" &&
+       clone_tmp="$GIT_DIR/clone-tmp" &&
+       mkdir -p "$clone_tmp" || exit 1
+       if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+               "`git config --bool http.noEPSV`" = true ]; then
+               curl_extra_args="${curl_extra_args} --disable-epsv"
+       fi
+       http_fetch "$1/info/refs" "$clone_tmp/refs" ||
+               die "Cannot get remote repository information.
+Perhaps git-update-server-info needs to be run there?"
+       test "z$quiet" = z && v=-v || v=
+       while read sha1 refname
+       do
+               name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
+               case "$name" in
+               *^*)    continue;;
+               esac
+               case "$bare,$name" in
+               yes,* | ,heads/* | ,tags/*) ;;
+               *)      continue ;;
+               esac
+               if test -n "$use_separate_remote" &&
+                  branch_name=`expr "z$name" : 'zheads/\(.*\)'`
+               then
+                       tname="remotes/$origin/$branch_name"
+               else
+                       tname=$name
+               fi
+               git-http-fetch $v -a -w "$tname" "$sha1" "$1" || exit 1
+       done <"$clone_tmp/refs"
+       rm -fr "$clone_tmp"
+       http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
+       rm -f "$GIT_DIR/REMOTE_HEAD"
+       if test -f "$GIT_DIR/REMOTE_HEAD"; then
+               head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+               case "$head_sha1" in
+               'ref: refs/'*)
+                       ;;
+               *)
+                       git-http-fetch $v -a "$head_sha1" "$1" ||
+                       rm -f "$GIT_DIR/REMOTE_HEAD"
+                       ;;
+               esac
+       fi
+}
+
+quiet=
+local=no
+use_local_hardlink=yes
+local_shared=no
+unset template
+no_checkout=
+upload_pack=
+bare=
+reference=
+origin=
+origin_override=
+use_separate_remote=t
+depth=
+no_progress=
+local_explicitly_asked_for=
+test -t 1 || no_progress=--no-progress
+
+while test $# != 0
+do
+       case "$1" in
+       -n|--no-checkout)
+               no_checkout=yes ;;
+       --naked|--bare)
+               bare=yes ;;
+       -l|--local)
+               local_explicitly_asked_for=yes
+               use_local_hardlink=yes
+               ;;
+       --no-hardlinks)
+               use_local_hardlink=no ;;
+       -s|--shared)
+               local_shared=yes ;;
+       --template)
+               shift; template="--template=$1" ;;
+       -q|--quiet)
+               quiet=-q ;;
+       --use-separate-remote|--no-separate-remote)
+               die "clones are always made with separate-remote layout" ;;
+       --reference)
+               shift; reference="$1" ;;
+       -o|--origin)
+               shift;
+               case "$1" in
+               '')
+                   usage ;;
+               */*)
+                   die "'$1' is not suitable for an origin name"
+               esac
+               git check-ref-format "heads/$1" ||
+                   die "'$1' is not suitable for a branch name"
+               test -z "$origin_override" ||
+                   die "Do not give more than one --origin options."
+               origin_override=yes
+               origin="$1"
+               ;;
+       -u|--upload-pack)
+               shift
+               upload_pack="--upload-pack=$1" ;;
+       --depth)
+               shift
+               depth="--depth=$1" ;;
+       --)
+               shift
+               break ;;
+       *)
+               usage ;;
+       esac
+       shift
+done
+
+repo="$1"
+test -n "$repo" ||
+    die 'you must specify a repository to clone.'
+
+# --bare implies --no-checkout and --no-separate-remote
+if test yes = "$bare"
+then
+       if test yes = "$origin_override"
+       then
+               die '--bare and --origin $origin options are incompatible.'
+       fi
+       no_checkout=yes
+       use_separate_remote=
+fi
+
+if test -z "$origin"
+then
+       origin=origin
+fi
+
+# Turn the source into an absolute path if
+# it is local
+if base=$(get_repo_base "$repo"); then
+       repo="$base"
+       if test -z "$depth"
+       then
+               local=yes
+       fi
+elif test -f "$repo"
+then
+       case "$repo" in /*) ;; *) repo="$PWD/$repo" ;; esac
+fi
+
+# Decide the directory name of the new repository
+if test -n "$2"
+then
+       dir="$2"
+       test $# = 2 || die "excess parameter to git-clone"
+else
+       # Derive one from the repository name
+       # Try using "humanish" part of source repo if user didn't specify one
+       if test -f "$repo"
+       then
+               # Cloning from a bundle
+               dir=$(echo "$repo" | sed -e 's|/*\.bundle$||' -e 's|.*/||g')
+       else
+               dir=$(echo "$repo" |
+                       sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
+       fi
+fi
+
+[ -e "$dir" ] && die "destination directory '$dir' already exists."
+[ yes = "$bare" ] && unset GIT_WORK_TREE
+[ -n "$GIT_WORK_TREE" ] && [ -e "$GIT_WORK_TREE" ] &&
+die "working tree '$GIT_WORK_TREE' already exists."
+D=
+W=
+cleanup() {
+       test -z "$D" && rm -rf "$dir"
+       test -z "$W" && test -n "$GIT_WORK_TREE" && rm -rf "$GIT_WORK_TREE"
+       cd ..
+       test -n "$D" && rm -rf "$D"
+       test -n "$W" && rm -rf "$W"
+       exit $err
+}
+trap 'err=$?; cleanup' 0
+mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage
+test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" &&
+W=$(cd "$GIT_WORK_TREE" && pwd) && GIT_WORK_TREE="$W" && export GIT_WORK_TREE
+if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then
+       GIT_DIR="$D"
+else
+       GIT_DIR="$D/.git"
+fi &&
+export GIT_DIR &&
+GIT_CONFIG="$GIT_DIR/config" git-init $quiet ${template+"$template"} || usage
+
+if test -n "$bare"
+then
+       GIT_CONFIG="$GIT_DIR/config" git config core.bare true
+fi
+
+if test -n "$reference"
+then
+       ref_git=
+       if test -d "$reference"
+       then
+               if test -d "$reference/.git/objects"
+               then
+                       ref_git="$reference/.git"
+               elif test -d "$reference/objects"
+               then
+                       ref_git="$reference"
+               fi
+       fi
+       if test -n "$ref_git"
+       then
+               ref_git=$(cd "$ref_git" && pwd)
+               echo "$ref_git/objects" >"$GIT_DIR/objects/info/alternates"
+               (
+                       GIT_DIR="$ref_git" git for-each-ref \
+                               --format='%(objectname) %(*objectname)'
+               ) |
+               while read a b
+               do
+                       test -z "$a" ||
+                       git update-ref "refs/reference-tmp/$a" "$a"
+                       test -z "$b" ||
+                       git update-ref "refs/reference-tmp/$b" "$b"
+               done
+       else
+               die "reference repository '$reference' is not a local directory."
+       fi
+fi
+
+rm -f "$GIT_DIR/CLONE_HEAD"
+
+# We do local magic only when the user tells us to.
+case "$local" in
+yes)
+       ( cd "$repo/objects" ) ||
+               die "cannot chdir to local '$repo/objects'."
+
+       if test "$local_shared" = yes
+       then
+               mkdir -p "$GIT_DIR/objects/info"
+               echo "$repo/objects" >>"$GIT_DIR/objects/info/alternates"
+       else
+               cpio_quiet_flag=""
+               cpio --help 2>&1 | grep -- --quiet >/dev/null && \
+                       cpio_quiet_flag=--quiet
+               l= &&
+               if test "$use_local_hardlink" = yes
+               then
+                       # See if we can hardlink and drop "l" if not.
+                       sample_file=$(cd "$repo" && \
+                                     find objects -type f -print | sed -e 1q)
+                       # objects directory should not be empty because
+                       # we are cloning!
+                       test -f "$repo/$sample_file" ||
+                               die "fatal: cannot clone empty repository"
+                       if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
+                       then
+                               rm -f "$GIT_DIR/objects/sample"
+                               l=l
+                       elif test -n "$local_explicitly_asked_for"
+                       then
+                               echo >&2 "Warning: -l asked but cannot hardlink to $repo"
+                       fi
+               fi &&
+               cd "$repo" &&
+               # Create dirs using umask and permissions and destination
+               find objects -type d -print | (cd "$GIT_DIR" && xargs mkdir -p) &&
+               # Copy existing 0444 permissions on content
+               find objects ! -type d -print | cpio $cpio_quiet_flag -pumd$l "$GIT_DIR/" || \
+                       exit 1
+       fi
+       git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
+       ;;
+*)
+       case "$repo" in
+       rsync://*)
+               case "$depth" in
+               "") ;;
+               *) die "shallow over rsync not supported" ;;
+               esac
+               rsync $quiet -av --ignore-existing  \
+                       --exclude info "$repo/objects/" "$GIT_DIR/objects/" ||
+               exit
+               # Look at objects/info/alternates for rsync -- http will
+               # support it natively and git native ones will do it on the
+               # remote end.  Not having that file is not a crime.
+               rsync -q "$repo/objects/info/alternates" \
+                       "$GIT_DIR/TMP_ALT" 2>/dev/null ||
+                       rm -f "$GIT_DIR/TMP_ALT"
+               if test -f "$GIT_DIR/TMP_ALT"
+               then
+                   ( cd "$D" &&
+                     . git-parse-remote &&
+                     resolve_alternates "$repo" <"$GIT_DIR/TMP_ALT" ) |
+                   while read alt
+                   do
+                       case "$alt" in 'bad alternate: '*) die "$alt";; esac
+                       case "$quiet" in
+                       '')     echo >&2 "Getting alternate: $alt" ;;
+                       esac
+                       rsync $quiet -av --ignore-existing  \
+                           --exclude info "$alt" "$GIT_DIR/objects" || exit
+                   done
+                   rm -f "$GIT_DIR/TMP_ALT"
+               fi
+               git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
+               ;;
+       https://*|http://*|ftp://*)
+               case "$depth" in
+               "") ;;
+               *) die "shallow over http or ftp not supported" ;;
+               esac
+               if test -z "@@NO_CURL@@"
+               then
+                       clone_dumb_http "$repo" "$D"
+               else
+                       die "http transport not supported, rebuild Git with curl support"
+               fi
+               ;;
+       *)
+               if [ -f "$repo" ] ; then
+                       git bundle unbundle "$repo" > "$GIT_DIR/CLONE_HEAD" ||
+                       die "unbundle from '$repo' failed."
+               else
+                       case "$upload_pack" in
+                       '') git-fetch-pack --all -k $quiet $depth $no_progress "$repo";;
+                       *) git-fetch-pack --all -k \
+                               $quiet "$upload_pack" $depth $no_progress "$repo" ;;
+                       esac >"$GIT_DIR/CLONE_HEAD" ||
+                       die "fetch-pack from '$repo' failed."
+               fi
+               ;;
+       esac
+       ;;
+esac
+test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp"
+
+if test -f "$GIT_DIR/CLONE_HEAD"
+then
+       # Read git-fetch-pack -k output and store the remote branches.
+       if [ -n "$use_separate_remote" ]
+       then
+               branch_top="remotes/$origin"
+       else
+               branch_top="heads"
+       fi
+       tag_top="tags"
+       while read sha1 name
+       do
+               case "$name" in
+               *'^{}')
+                       continue ;;
+               HEAD)
+                       destname="REMOTE_HEAD" ;;
+               refs/heads/*)
+                       destname="refs/$branch_top/${name#refs/heads/}" ;;
+               refs/tags/*)
+                       destname="refs/$tag_top/${name#refs/tags/}" ;;
+               *)
+                       continue ;;
+               esac
+               git update-ref -m "clone: from $repo" "$destname" "$sha1" ""
+       done < "$GIT_DIR/CLONE_HEAD"
+fi
+
+if test -n "$W"; then
+       cd "$W" || exit
+else
+       cd "$D" || exit
+fi
+
+if test -z "$bare"
+then
+       # a non-bare repository is always in separate-remote layout
+       remote_top="refs/remotes/$origin"
+       head_sha1=
+       test ! -r "$GIT_DIR/REMOTE_HEAD" || head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+       case "$head_sha1" in
+       'ref: refs/'*)
+               # Uh-oh, the remote told us (http transport done against
+               # new style repository with a symref HEAD).
+               # Ideally we should skip the guesswork but for now
+               # opt for minimum change.
+               head_sha1=`expr "z$head_sha1" : 'zref: refs/heads/\(.*\)'`
+               head_sha1=`cat "$GIT_DIR/$remote_top/$head_sha1"`
+               ;;
+       esac
+
+       # The name under $remote_top the remote HEAD seems to point at.
+       head_points_at=$(
+               (
+                       test -f "$GIT_DIR/$remote_top/master" && echo "master"
+                       cd "$GIT_DIR/$remote_top" &&
+                       find . -type f -print | sed -e 's/^\.\///'
+               ) | (
+               done=f
+               while read name
+               do
+                       test t = $done && continue
+                       branch_tip=`cat "$GIT_DIR/$remote_top/$name"`
+                       if test "$head_sha1" = "$branch_tip"
+                       then
+                               echo "$name"
+                               done=t
+                       fi
+               done
+               )
+       )
+
+       # Upstream URL
+       git config remote."$origin".url "$repo" &&
+
+       # Set up the mappings to track the remote branches.
+       git config remote."$origin".fetch \
+               "+refs/heads/*:$remote_top/*" '^$' &&
+
+       # Write out remote.$origin config, and update our "$head_points_at".
+       case "$head_points_at" in
+       ?*)
+               # Local default branch
+               git symbolic-ref HEAD "refs/heads/$head_points_at" &&
+
+               # Tracking branch for the primary branch at the remote.
+               git update-ref HEAD "$head_sha1" &&
+
+               rm -f "refs/remotes/$origin/HEAD"
+               git symbolic-ref "refs/remotes/$origin/HEAD" \
+                       "refs/remotes/$origin/$head_points_at" &&
+
+               git config branch."$head_points_at".remote "$origin" &&
+               git config branch."$head_points_at".merge "refs/heads/$head_points_at"
+               ;;
+       '')
+               if test -z "$head_sha1"
+               then
+                       # Source had nonexistent ref in HEAD
+                       echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout."
+                       no_checkout=t
+               else
+                       # Source had detached HEAD pointing nowhere
+                       git update-ref --no-deref HEAD "$head_sha1" &&
+                       rm -f "refs/remotes/$origin/HEAD"
+               fi
+               ;;
+       esac
+
+       case "$no_checkout" in
+       '')
+               test "z$quiet" = z -a "z$no_progress" = z && v=-v || v=
+               git read-tree -m -u $v HEAD HEAD
+       esac
+fi
+rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
+
+trap - 0
index d72ffbb7773268514d8de29c5d5c33fdaf31f339..f68ef725d42cdb1a6082574f9ef3a45d2346c296 100755 (executable)
@@ -46,6 +46,7 @@ options:
                          for incrementals
     -n, --nrepack=INT:   number of changesets that will trigger
                          a repack (default=0, -1 to deactivate)
+    -v, --verbose:       be verbose
 
 required:
     hgprj:  name of the HG project to import (directory)
@@ -75,15 +76,18 @@ def getgitenv(user, date):
 
 state = ''
 opt_nrepack = 0
+verbose = False
 
 try:
-    opts, args = getopt.getopt(sys.argv[1:], 's:t:n:', ['gitstate=', 'tempdir=', 'nrepack='])
+    opts, args = getopt.getopt(sys.argv[1:], 's:t:n:v', ['gitstate=', 'tempdir=', 'nrepack=', 'verbose'])
     for o, a in opts:
         if o in ('-s', '--gitstate'):
             state = a
             state = os.path.abspath(state)
         if o in ('-n', '--nrepack'):
             opt_nrepack = int(a)
+        if o in ('-v', '--verbose'):
+            verbose = True
     if len(args) != 1:
         raise('params')
 except:
@@ -95,17 +99,20 @@ os.chdir(hgprj)
 
 if state:
     if os.path.exists(state):
-        print 'State does exist, reading'
+        if verbose:
+            print 'State does exist, reading'
         f = open(state, 'r')
         hgvers = pickle.load(f)
     else:
         print 'State does not exist, first run'
 
 tip = os.popen('hg tip --template "{rev}"').read()
-print 'tip is', tip
+if verbose:
+    print 'tip is', tip
 
 # Calculate the branches
-print 'analysing the branches...'
+if verbose:
+    print 'analysing the branches...'
 hgchildren["0"] = ()
 hgparents["0"] = (None, None)
 hgbranch["0"] = "master"
@@ -232,7 +239,8 @@ if hgnewcsets >= opt_nrepack and opt_nrepack != -1:
 
 # write the state for incrementals
 if state:
-    print 'Writing state'
+    if verbose:
+        print 'Writing state'
     f = open(state, 'w')
     pickle.dump(hgvers, f)
 
index 068fa3708331bbb7d451040715c8bba9623f8c7e..d18b317b2f018d1d1a5a9677a7bdaf8956d65186 100644 (file)
@@ -136,6 +136,7 @@ sub parse_config ($$$$) {
        local $ENV{GIT_DIR} = shift;
        my $br = shift;
        my $fn = shift;
+       return unless git_value('rev-list','--max-count=1',$br,'--',$fn);
        info "Loading $br:$fn";
        open(I,'-|','git','cat-file','blob',"$br:$fn");
        my $section = '';
@@ -225,14 +226,12 @@ sub load_diff ($) {
                local $/ = "\0";
                my %this_diff;
                if ($base =~ /^0{40}$/) {
-                       open(T,'-|','git','ls-tree',
-                               '-r','--name-only','-z',
-                               $new) or return undef;
-                       while (<T>) {
-                               chop;
-                               $this_diff{$_} = 'A';
-                       }
-                       close T or return undef;
+                       # Don't load the diff at all; we are making the
+                       # branch and have no base to compare to in this
+                       # case.  A file level ACL makes no sense in this
+                       # context.  Having an empty diff will allow the
+                       # branch creation.
+                       #
                } else {
                        open(T,'-|','git','diff-tree',
                                '-r','--name-status','-z',
@@ -260,6 +259,7 @@ deny "Refusing funny ref $ref" unless $ref =~ s,^refs/,,;
 deny "Bad old value $old" unless $old =~ /^[a-z0-9]{40}$/;
 deny "Bad new value $new" unless $new =~ /^[a-z0-9]{40}$/;
 deny "Cannot determine who you are." unless $this_user;
+grant "No change requested." if $old eq $new;
 
 $repository_name = File::Spec->rel2abs($git_dir);
 $repository_name =~ m,/([^/]+)(?:\.git|/\.git)$,;
index d8c94cb3edeceff7a79b2f5e6ba7cad250e6b301..1c66844783ec3baa1373b3f0c41ea080bc7354fe 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -323,7 +323,7 @@ static struct convert_driver {
        char *clean;
 } *user_convert, **user_convert_tail;
 
-static int read_convert_config(const char *var, const char *value)
+static int read_convert_config(const char *var, const char *value, void *cb)
 {
        const char *ep, *name;
        int namelen;
@@ -385,7 +385,7 @@ static void setup_convert_check(struct git_attr_check *check)
                attr_ident = git_attr("ident", 5);
                attr_filter = git_attr("filter", 6);
                user_convert_tail = &user_convert;
-               git_config(read_convert_config);
+               git_config(read_convert_config, NULL);
        }
        check[0].attr = attr_crlf;
        check[1].attr = attr_ident;
index 2b4a6f145cc0f37c95da31df23dfde3ba7fa63a7..63cd12cd9c1909edbaab49aea066ff6fe03c3542 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -306,7 +306,7 @@ struct daemon_service {
 static struct daemon_service *service_looking_at;
 static int service_enabled;
 
-static int git_daemon_config(const char *var, const char *value)
+static int git_daemon_config(const char *var, const char *value, void *cb)
 {
        if (!prefixcmp(var, "daemon.") &&
            !strcmp(var + 7, service_looking_at->config_name)) {
@@ -356,7 +356,7 @@ static int run_service(struct interp *itable, struct daemon_service *service)
        if (service->overridable) {
                service_looking_at = service;
                service_enabled = -1;
-               git_config(git_daemon_config);
+               git_config(git_daemon_config, NULL);
                if (0 <= service_enabled)
                        enabled = service_enabled;
        }
diff --git a/diff.c b/diff.c
index 439d4746cae343f08b298473d35d9792048abfa0..62fdc5492bbe5dacab6bc5b615a76ba5cd9de760 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -129,7 +129,7 @@ static int parse_funcname_pattern(const char *var, const char *ep, const char *v
  * never be affected by the setting of diff.renames
  * the user happens to have in the configuration file.
  */
-int git_diff_ui_config(const char *var, const char *value)
+int git_diff_ui_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "diff.renamelimit")) {
                diff_rename_limit_default = git_config_int(var, value);
@@ -166,10 +166,10 @@ int git_diff_ui_config(const char *var, const char *value)
                        return parse_lldiff_command(var, ep, value);
        }
 
-       return git_diff_basic_config(var, value);
+       return git_diff_basic_config(var, value, cb);
 }
 
-int git_diff_basic_config(const char *var, const char *value)
+int git_diff_basic_config(const char *var, const char *value, void *cb)
 {
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
@@ -190,7 +190,7 @@ int git_diff_basic_config(const char *var, const char *value)
                }
        }
 
-       return git_color_default_config(var, value);
+       return git_color_default_config(var, value, cb);
 }
 
 static char *quote_two(const char *one, const char *two)
@@ -2496,6 +2496,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_SET(options, ALLOW_EXTERNAL);
        else if (!strcmp(arg, "--no-ext-diff"))
                DIFF_OPT_CLR(options, ALLOW_EXTERNAL);
+       else if (!strcmp(arg, "--ignore-submodules"))
+               DIFF_OPT_SET(options, IGNORE_SUBMODULES);
 
        /* misc options */
        else if (!strcmp(arg, "-z"))
@@ -3355,6 +3357,9 @@ void diff_addremove(struct diff_options *options,
        char concatpath[PATH_MAX];
        struct diff_filespec *one, *two;
 
+       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
+               return;
+
        /* This may look odd, but it is a preparation for
         * feeding "there are unchanged files which should
         * not produce diffs, but when you are doing copy
@@ -3399,6 +3404,10 @@ void diff_change(struct diff_options *options,
        char concatpath[PATH_MAX];
        struct diff_filespec *one, *two;
 
+       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
+                       && S_ISGITLINK(new_mode))
+               return;
+
        if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
                unsigned tmp;
                const unsigned char *tmp_c;
diff --git a/diff.h b/diff.h
index e019730638bb84da5696db656e3749f1b9521b56..5dc0cb595b64868ca8b8e26e5311d2df102c89e0 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -63,6 +63,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_REVERSE_DIFF        (1 << 15)
 #define DIFF_OPT_CHECK_FAILED        (1 << 16)
 #define DIFF_OPT_RELATIVE_NAME       (1 << 17)
+#define DIFF_OPT_IGNORE_SUBMODULES   (1 << 18)
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
 #define DIFF_OPT_CLR(opts, flag)    ((opts)->flags &= ~DIFF_OPT_##flag)
@@ -181,8 +182,8 @@ extern void diff_unmerge(struct diff_options *,
 #define DIFF_SETUP_USE_CACHE           2
 #define DIFF_SETUP_USE_SIZE_CACHE      4
 
-extern int git_diff_basic_config(const char *var, const char *value);
-extern int git_diff_ui_config(const char *var, const char *value);
+extern int git_diff_basic_config(const char *var, const char *value, void *cb);
+extern int git_diff_ui_config(const char *var, const char *value, void *cb);
 extern int diff_use_color_default;
 extern void diff_setup(struct diff_options *);
 extern int diff_opt_parse(struct diff_options *, const char **, int);
index 4fcb471248fb5276a68b9f86a77a2f5cde7dd884..73feb2d03a917d1fbdc8d9397b42fd90e5916bca 100644 (file)
@@ -44,7 +44,7 @@ enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
 
 /* This is set by setup_git_dir_gently() and/or git_default_config() */
 char *git_work_tree_cfg;
-static const char *work_tree;
+static char *work_tree;
 
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
@@ -86,10 +86,26 @@ const char *get_git_dir(void)
        return git_dir;
 }
 
+static int git_work_tree_initialized;
+
+/*
+ * Note.  This works only before you used a work tree.  This was added
+ * primarily to support git-clone to work in a new repository it just
+ * created, and is not meant to flip between different work trees.
+ */
+void set_git_work_tree(const char *new_work_tree)
+{
+       if (is_bare_repository_cfg >= 0)
+               die("cannot set work tree after initialization");
+       git_work_tree_initialized = 1;
+       free(work_tree);
+       work_tree = xstrdup(make_absolute_path(new_work_tree));
+       is_bare_repository_cfg = 0;
+}
+
 const char *get_git_work_tree(void)
 {
-       static int initialized = 0;
-       if (!initialized) {
+       if (!git_work_tree_initialized) {
                work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
                /* core.bare = true overrides implicit and config work tree */
                if (!work_tree && is_bare_repository_cfg < 1) {
@@ -99,7 +115,7 @@ const char *get_git_work_tree(void)
                                work_tree = xstrdup(make_absolute_path(git_path(work_tree)));
                } else if (work_tree)
                        work_tree = xstrdup(make_absolute_path(work_tree));
-               initialized = 1;
+               git_work_tree_initialized = 1;
                if (work_tree)
                        is_bare_repository_cfg = 0;
        }
index caea684338031431dc3177716654a1a289faa9a3..93119bbd9484504d19f069a5c0038fb1e2f40a26 100644 (file)
@@ -2352,7 +2352,7 @@ static void import_marks(const char *input_file)
        fclose(f);
 }
 
-static int git_pack_config(const char *k, const char *v)
+static int git_pack_config(const char *k, const char *v, void *cb)
 {
        if (!strcmp(k, "pack.depth")) {
                max_depth = git_config_int(k, v);
@@ -2370,7 +2370,7 @@ static int git_pack_config(const char *k, const char *v)
                pack_compression_seen = 1;
                return 0;
        }
-       return git_default_config(k, v);
+       return git_default_config(k, v, cb);
 }
 
 static const char fast_import_usage[] =
@@ -2381,7 +2381,7 @@ int main(int argc, const char **argv)
        unsigned int i, show_stats = 1;
 
        setup_git_directory();
-       git_config(git_pack_config);
+       git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
                pack_compression_level = core_compression_level;
 
index 164e8ed81fc3ecb5d33dafb9af0992761bfe513d..4bcbaceb8bd6a5dc9021b2525f43ae5317a7d46a 100755 (executable)
@@ -63,40 +63,40 @@ bisect_autostart() {
 
 bisect_start() {
        #
-       # Verify HEAD. If we were bisecting before this, reset to the
-       # top-of-line master first!
+       # Verify HEAD.
        #
        head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
        head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
        die "Bad HEAD - I need a HEAD"
+
        #
-       # Check that we either already have BISECT_START, or that the
-       # branches bisect, new-bisect don't exist, to not override them.
+       # Check if we are bisecting.
        #
-       test -s "$GIT_DIR/BISECT_START" ||
-               if git show-ref --verify -q refs/heads/bisect ||
-                   git show-ref --verify -q refs/heads/new-bisect; then
-                       die 'The branches "bisect" and "new-bisect" must not exist.'
-               fi
        start_head=''
-       case "$head" in
-       refs/heads/bisect)
-               branch=`cat "$GIT_DIR/BISECT_START"`
-               git checkout $branch || exit
-               ;;
-       refs/heads/*|$_x40)
-               # This error message should only be triggered by cogito usage,
-               # and cogito users should understand it relates to cg-seek.
-               [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
-               start_head="${head#refs/heads/}"
-               ;;
-       *)
-               die "Bad HEAD - strange symbolic ref"
-               ;;
-       esac
+       if test -s "$GIT_DIR/BISECT_START"
+       then
+               # Reset to the rev from where we started.
+               start_head=$(cat "$GIT_DIR/BISECT_START")
+               git checkout "$start_head" || exit
+       else
+               # Get rev from where we start.
+               case "$head" in
+               refs/heads/*|$_x40)
+                       # This error message should only be triggered by
+                       # cogito usage, and cogito users should understand
+                       # it relates to cg-seek.
+                       [ -s "$GIT_DIR/head-name" ] &&
+                               die "won't bisect on seeked tree"
+                       start_head="${head#refs/heads/}"
+                       ;;
+               *)
+                       die "Bad HEAD - strange symbolic ref"
+                       ;;
+               esac
+       fi
 
        #
-       # Get rid of any old bisect state
+       # Get rid of any old bisect state.
        #
        bisect_clean_state
 
@@ -118,7 +118,7 @@ bisect_start() {
                break
                ;;
            *)
-               rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
+               rev=$(git rev-parse -q --verify "$arg^{commit}") || {
                    test $has_double_dash -eq 1 &&
                        die "'$arg' does not appear to be a valid revision"
                    break
@@ -133,11 +133,29 @@ bisect_start() {
            esac
        done
 
-       sq "$@" >"$GIT_DIR/BISECT_NAMES"
-       test -n "$start_head" && echo "$start_head" >"$GIT_DIR/BISECT_START"
-       eval "$eval"
-       echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG"
+       #
+       # Change state.
+       # In case of mistaken revs or checkout error, or signals received,
+       # "bisect_auto_next" below may exit or misbehave.
+       # We have to trap this to be able to clean up using
+       # "bisect_clean_state".
+       #
+       trap 'bisect_clean_state' 0
+       trap 'exit 255' 1 2 3 15
+
+       #
+       # Write new start state.
+       #
+       sq "$@" >"$GIT_DIR/BISECT_NAMES" &&
+       echo "$start_head" >"$GIT_DIR/BISECT_START" &&
+       eval "$eval" &&
+       echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
+       #
+       # Check if we can proceed to the next bisect state.
+       #
        bisect_auto_next
+
+       trap '-' 0
 }
 
 bisect_write() {
@@ -149,9 +167,9 @@ bisect_write() {
                good|skip)      tag="$state"-"$rev" ;;
                *)              die "Bad bisect_write argument: $state" ;;
        esac
-       git update-ref "refs/bisect/$tag" "$rev"
+       git update-ref "refs/bisect/$tag" "$rev" || exit
        echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
-       test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
+       test -n "$nolog" || echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
 }
 
 bisect_state() {
@@ -348,9 +366,7 @@ bisect_next() {
        exit_if_skipped_commits "$bisect_rev"
 
        echo "Bisecting: $bisect_nr revisions left to test after this"
-       git branch -D new-bisect 2> /dev/null
-       git checkout -q -b new-bisect "$bisect_rev" || exit
-       git branch -M new-bisect bisect
+       git checkout -q "$bisect_rev" || exit
        git show-branch "$bisect_rev"
 }
 
@@ -392,24 +408,22 @@ bisect_reset() {
        *)
            usage ;;
        esac
-       if git checkout "$branch"; then
-               # Cleanup head-name if it got left by an old version of git-bisect
-               rm -f "$GIT_DIR/head-name"
-               rm -f "$GIT_DIR/BISECT_START"
-               bisect_clean_state
-       fi
+       git checkout "$branch" && bisect_clean_state
 }
 
 bisect_clean_state() {
        # There may be some refs packed during bisection.
-       git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* refs/heads/bisect |
+       git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
        while read ref hash
        do
                git update-ref -d $ref $hash
        done
+       rm -f "$GIT_DIR/BISECT_START"
        rm -f "$GIT_DIR/BISECT_LOG"
        rm -f "$GIT_DIR/BISECT_NAMES"
        rm -f "$GIT_DIR/BISECT_RUN"
+       # Cleanup head-name if it got left by an old version of git-bisect
+       rm -f "$GIT_DIR/head-name"
 }
 
 bisect_replay () {
diff --git a/git-clone.sh b/git-clone.sh
deleted file mode 100755 (executable)
index 547228e..0000000
+++ /dev/null
@@ -1,525 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005, Linus Torvalds
-# Copyright (c) 2005, Junio C Hamano
-#
-# Clone a repository into a different directory that does not yet exist.
-
-# See git-sh-setup why.
-unset CDPATH
-
-OPTIONS_SPEC="\
-git-clone [options] [--] <repo> [<dir>]
---
-n,no-checkout        don't create a checkout
-bare                 create a bare repository
-naked                create a bare repository
-l,local              to clone from a local repository
-no-hardlinks         don't use local hardlinks, always copy
-s,shared             setup as a shared repository
-template=            path to the template directory
-q,quiet              be quiet
-reference=           reference repository
-o,origin=            use <name> instead of 'origin' to track upstream
-u,upload-pack=       path to git-upload-pack on the remote
-depth=               create a shallow clone of that depth
-
-use-separate-remote  compatibility, do not use
-no-separate-remote   compatibility, do not use"
-
-die() {
-       echo >&2 "$@"
-       exit 1
-}
-
-usage() {
-       exec "$0" -h
-}
-
-eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
-
-get_repo_base() {
-       (
-               cd "`/bin/pwd`" &&
-               cd "$1" || cd "$1.git" &&
-               {
-                       cd .git
-                       pwd
-               }
-       ) 2>/dev/null
-}
-
-if [ -n "$GIT_SSL_NO_VERIFY" -o \
-       "`git config --bool http.sslVerify`" = false ]; then
-    curl_extra_args="-k"
-fi
-
-http_fetch () {
-       # $1 = Remote, $2 = Local
-       curl -nsfL $curl_extra_args "$1" >"$2"
-       curl_exit_status=$?
-       case $curl_exit_status in
-       126|127) exit ;;
-       *)       return $curl_exit_status ;;
-       esac
-}
-
-clone_dumb_http () {
-       # $1 - remote, $2 - local
-       cd "$2" &&
-       clone_tmp="$GIT_DIR/clone-tmp" &&
-       mkdir -p "$clone_tmp" || exit 1
-       if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
-               "`git config --bool http.noEPSV`" = true ]; then
-               curl_extra_args="${curl_extra_args} --disable-epsv"
-       fi
-       http_fetch "$1/info/refs" "$clone_tmp/refs" ||
-               die "Cannot get remote repository information.
-Perhaps git-update-server-info needs to be run there?"
-       test "z$quiet" = z && v=-v || v=
-       while read sha1 refname
-       do
-               name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
-               case "$name" in
-               *^*)    continue;;
-               esac
-               case "$bare,$name" in
-               yes,* | ,heads/* | ,tags/*) ;;
-               *)      continue ;;
-               esac
-               if test -n "$use_separate_remote" &&
-                  branch_name=`expr "z$name" : 'zheads/\(.*\)'`
-               then
-                       tname="remotes/$origin/$branch_name"
-               else
-                       tname=$name
-               fi
-               git-http-fetch $v -a -w "$tname" "$sha1" "$1" || exit 1
-       done <"$clone_tmp/refs"
-       rm -fr "$clone_tmp"
-       http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
-       rm -f "$GIT_DIR/REMOTE_HEAD"
-       if test -f "$GIT_DIR/REMOTE_HEAD"; then
-               head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
-               case "$head_sha1" in
-               'ref: refs/'*)
-                       ;;
-               *)
-                       git-http-fetch $v -a "$head_sha1" "$1" ||
-                       rm -f "$GIT_DIR/REMOTE_HEAD"
-                       ;;
-               esac
-       fi
-}
-
-quiet=
-local=no
-use_local_hardlink=yes
-local_shared=no
-unset template
-no_checkout=
-upload_pack=
-bare=
-reference=
-origin=
-origin_override=
-use_separate_remote=t
-depth=
-no_progress=
-local_explicitly_asked_for=
-test -t 1 || no_progress=--no-progress
-
-while test $# != 0
-do
-       case "$1" in
-       -n|--no-checkout)
-               no_checkout=yes ;;
-       --naked|--bare)
-               bare=yes ;;
-       -l|--local)
-               local_explicitly_asked_for=yes
-               use_local_hardlink=yes
-               ;;
-       --no-hardlinks)
-               use_local_hardlink=no ;;
-       -s|--shared)
-               local_shared=yes ;;
-       --template)
-               shift; template="--template=$1" ;;
-       -q|--quiet)
-               quiet=-q ;;
-       --use-separate-remote|--no-separate-remote)
-               die "clones are always made with separate-remote layout" ;;
-       --reference)
-               shift; reference="$1" ;;
-       -o|--origin)
-               shift;
-               case "$1" in
-               '')
-                   usage ;;
-               */*)
-                   die "'$1' is not suitable for an origin name"
-               esac
-               git check-ref-format "heads/$1" ||
-                   die "'$1' is not suitable for a branch name"
-               test -z "$origin_override" ||
-                   die "Do not give more than one --origin options."
-               origin_override=yes
-               origin="$1"
-               ;;
-       -u|--upload-pack)
-               shift
-               upload_pack="--upload-pack=$1" ;;
-       --depth)
-               shift
-               depth="--depth=$1" ;;
-       --)
-               shift
-               break ;;
-       *)
-               usage ;;
-       esac
-       shift
-done
-
-repo="$1"
-test -n "$repo" ||
-    die 'you must specify a repository to clone.'
-
-# --bare implies --no-checkout and --no-separate-remote
-if test yes = "$bare"
-then
-       if test yes = "$origin_override"
-       then
-               die '--bare and --origin $origin options are incompatible.'
-       fi
-       no_checkout=yes
-       use_separate_remote=
-fi
-
-if test -z "$origin"
-then
-       origin=origin
-fi
-
-# Turn the source into an absolute path if
-# it is local
-if base=$(get_repo_base "$repo"); then
-       repo="$base"
-       if test -z "$depth"
-       then
-               local=yes
-       fi
-elif test -f "$repo"
-then
-       case "$repo" in /*) ;; *) repo="$PWD/$repo" ;; esac
-fi
-
-# Decide the directory name of the new repository
-if test -n "$2"
-then
-       dir="$2"
-       test $# = 2 || die "excess parameter to git-clone"
-else
-       # Derive one from the repository name
-       # Try using "humanish" part of source repo if user didn't specify one
-       if test -f "$repo"
-       then
-               # Cloning from a bundle
-               dir=$(echo "$repo" | sed -e 's|/*\.bundle$||' -e 's|.*/||g')
-       else
-               dir=$(echo "$repo" |
-                       sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
-       fi
-fi
-
-[ -e "$dir" ] && die "destination directory '$dir' already exists."
-[ yes = "$bare" ] && unset GIT_WORK_TREE
-[ -n "$GIT_WORK_TREE" ] && [ -e "$GIT_WORK_TREE" ] &&
-die "working tree '$GIT_WORK_TREE' already exists."
-D=
-W=
-cleanup() {
-       test -z "$D" && rm -rf "$dir"
-       test -z "$W" && test -n "$GIT_WORK_TREE" && rm -rf "$GIT_WORK_TREE"
-       cd ..
-       test -n "$D" && rm -rf "$D"
-       test -n "$W" && rm -rf "$W"
-       exit $err
-}
-trap 'err=$?; cleanup' 0
-mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage
-test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" &&
-W=$(cd "$GIT_WORK_TREE" && pwd) && GIT_WORK_TREE="$W" && export GIT_WORK_TREE
-if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then
-       GIT_DIR="$D"
-else
-       GIT_DIR="$D/.git"
-fi &&
-export GIT_DIR &&
-GIT_CONFIG="$GIT_DIR/config" git-init $quiet ${template+"$template"} || usage
-
-if test -n "$bare"
-then
-       GIT_CONFIG="$GIT_DIR/config" git config core.bare true
-fi
-
-if test -n "$reference"
-then
-       ref_git=
-       if test -d "$reference"
-       then
-               if test -d "$reference/.git/objects"
-               then
-                       ref_git="$reference/.git"
-               elif test -d "$reference/objects"
-               then
-                       ref_git="$reference"
-               fi
-       fi
-       if test -n "$ref_git"
-       then
-               ref_git=$(cd "$ref_git" && pwd)
-               echo "$ref_git/objects" >"$GIT_DIR/objects/info/alternates"
-               (
-                       GIT_DIR="$ref_git" git for-each-ref \
-                               --format='%(objectname) %(*objectname)'
-               ) |
-               while read a b
-               do
-                       test -z "$a" ||
-                       git update-ref "refs/reference-tmp/$a" "$a"
-                       test -z "$b" ||
-                       git update-ref "refs/reference-tmp/$b" "$b"
-               done
-       else
-               die "reference repository '$reference' is not a local directory."
-       fi
-fi
-
-rm -f "$GIT_DIR/CLONE_HEAD"
-
-# We do local magic only when the user tells us to.
-case "$local" in
-yes)
-       ( cd "$repo/objects" ) ||
-               die "cannot chdir to local '$repo/objects'."
-
-       if test "$local_shared" = yes
-       then
-               mkdir -p "$GIT_DIR/objects/info"
-               echo "$repo/objects" >>"$GIT_DIR/objects/info/alternates"
-       else
-               cpio_quiet_flag=""
-               cpio --help 2>&1 | grep -- --quiet >/dev/null && \
-                       cpio_quiet_flag=--quiet
-               l= &&
-               if test "$use_local_hardlink" = yes
-               then
-                       # See if we can hardlink and drop "l" if not.
-                       sample_file=$(cd "$repo" && \
-                                     find objects -type f -print | sed -e 1q)
-                       # objects directory should not be empty because
-                       # we are cloning!
-                       test -f "$repo/$sample_file" ||
-                               die "fatal: cannot clone empty repository"
-                       if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
-                       then
-                               rm -f "$GIT_DIR/objects/sample"
-                               l=l
-                       elif test -n "$local_explicitly_asked_for"
-                       then
-                               echo >&2 "Warning: -l asked but cannot hardlink to $repo"
-                       fi
-               fi &&
-               cd "$repo" &&
-               # Create dirs using umask and permissions and destination
-               find objects -type d -print | (cd "$GIT_DIR" && xargs mkdir -p) &&
-               # Copy existing 0444 permissions on content
-               find objects ! -type d -print | cpio $cpio_quiet_flag -pumd$l "$GIT_DIR/" || \
-                       exit 1
-       fi
-       git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
-       ;;
-*)
-       case "$repo" in
-       rsync://*)
-               case "$depth" in
-               "") ;;
-               *) die "shallow over rsync not supported" ;;
-               esac
-               rsync $quiet -av --ignore-existing  \
-                       --exclude info "$repo/objects/" "$GIT_DIR/objects/" ||
-               exit
-               # Look at objects/info/alternates for rsync -- http will
-               # support it natively and git native ones will do it on the
-               # remote end.  Not having that file is not a crime.
-               rsync -q "$repo/objects/info/alternates" \
-                       "$GIT_DIR/TMP_ALT" 2>/dev/null ||
-                       rm -f "$GIT_DIR/TMP_ALT"
-               if test -f "$GIT_DIR/TMP_ALT"
-               then
-                   ( cd "$D" &&
-                     . git-parse-remote &&
-                     resolve_alternates "$repo" <"$GIT_DIR/TMP_ALT" ) |
-                   while read alt
-                   do
-                       case "$alt" in 'bad alternate: '*) die "$alt";; esac
-                       case "$quiet" in
-                       '')     echo >&2 "Getting alternate: $alt" ;;
-                       esac
-                       rsync $quiet -av --ignore-existing  \
-                           --exclude info "$alt" "$GIT_DIR/objects" || exit
-                   done
-                   rm -f "$GIT_DIR/TMP_ALT"
-               fi
-               git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
-               ;;
-       https://*|http://*|ftp://*)
-               case "$depth" in
-               "") ;;
-               *) die "shallow over http or ftp not supported" ;;
-               esac
-               if test -z "@@NO_CURL@@"
-               then
-                       clone_dumb_http "$repo" "$D"
-               else
-                       die "http transport not supported, rebuild Git with curl support"
-               fi
-               ;;
-       *)
-               if [ -f "$repo" ] ; then
-                       git bundle unbundle "$repo" > "$GIT_DIR/CLONE_HEAD" ||
-                       die "unbundle from '$repo' failed."
-               else
-                       case "$upload_pack" in
-                       '') git-fetch-pack --all -k $quiet $depth $no_progress "$repo";;
-                       *) git-fetch-pack --all -k \
-                               $quiet "$upload_pack" $depth $no_progress "$repo" ;;
-                       esac >"$GIT_DIR/CLONE_HEAD" ||
-                       die "fetch-pack from '$repo' failed."
-               fi
-               ;;
-       esac
-       ;;
-esac
-test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp"
-
-if test -f "$GIT_DIR/CLONE_HEAD"
-then
-       # Read git-fetch-pack -k output and store the remote branches.
-       if [ -n "$use_separate_remote" ]
-       then
-               branch_top="remotes/$origin"
-       else
-               branch_top="heads"
-       fi
-       tag_top="tags"
-       while read sha1 name
-       do
-               case "$name" in
-               *'^{}')
-                       continue ;;
-               HEAD)
-                       destname="REMOTE_HEAD" ;;
-               refs/heads/*)
-                       destname="refs/$branch_top/${name#refs/heads/}" ;;
-               refs/tags/*)
-                       destname="refs/$tag_top/${name#refs/tags/}" ;;
-               *)
-                       continue ;;
-               esac
-               git update-ref -m "clone: from $repo" "$destname" "$sha1" ""
-       done < "$GIT_DIR/CLONE_HEAD"
-fi
-
-if test -n "$W"; then
-       cd "$W" || exit
-else
-       cd "$D" || exit
-fi
-
-if test -z "$bare"
-then
-       # a non-bare repository is always in separate-remote layout
-       remote_top="refs/remotes/$origin"
-       head_sha1=
-       test ! -r "$GIT_DIR/REMOTE_HEAD" || head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
-       case "$head_sha1" in
-       'ref: refs/'*)
-               # Uh-oh, the remote told us (http transport done against
-               # new style repository with a symref HEAD).
-               # Ideally we should skip the guesswork but for now
-               # opt for minimum change.
-               head_sha1=`expr "z$head_sha1" : 'zref: refs/heads/\(.*\)'`
-               head_sha1=`cat "$GIT_DIR/$remote_top/$head_sha1"`
-               ;;
-       esac
-
-       # The name under $remote_top the remote HEAD seems to point at.
-       head_points_at=$(
-               (
-                       test -f "$GIT_DIR/$remote_top/master" && echo "master"
-                       cd "$GIT_DIR/$remote_top" &&
-                       find . -type f -print | sed -e 's/^\.\///'
-               ) | (
-               done=f
-               while read name
-               do
-                       test t = $done && continue
-                       branch_tip=`cat "$GIT_DIR/$remote_top/$name"`
-                       if test "$head_sha1" = "$branch_tip"
-                       then
-                               echo "$name"
-                               done=t
-                       fi
-               done
-               )
-       )
-
-       # Upstream URL
-       git config remote."$origin".url "$repo" &&
-
-       # Set up the mappings to track the remote branches.
-       git config remote."$origin".fetch \
-               "+refs/heads/*:$remote_top/*" '^$' &&
-
-       # Write out remote.$origin config, and update our "$head_points_at".
-       case "$head_points_at" in
-       ?*)
-               # Local default branch
-               git symbolic-ref HEAD "refs/heads/$head_points_at" &&
-
-               # Tracking branch for the primary branch at the remote.
-               git update-ref HEAD "$head_sha1" &&
-
-               rm -f "refs/remotes/$origin/HEAD"
-               git symbolic-ref "refs/remotes/$origin/HEAD" \
-                       "refs/remotes/$origin/$head_points_at" &&
-
-               git config branch."$head_points_at".remote "$origin" &&
-               git config branch."$head_points_at".merge "refs/heads/$head_points_at"
-               ;;
-       '')
-               if test -z "$head_sha1"
-               then
-                       # Source had nonexistent ref in HEAD
-                       echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout."
-                       no_checkout=t
-               else
-                       # Source had detached HEAD pointing nowhere
-                       git update-ref --no-deref HEAD "$head_sha1" &&
-                       rm -f "refs/remotes/$origin/HEAD"
-               fi
-               ;;
-       esac
-
-       case "$no_checkout" in
-       '')
-               test "z$quiet" = z -a "z$no_progress" = z && v=-v || v=
-               git read-tree -m -u $v HEAD HEAD
-       esac
-fi
-rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
-
-trap - 0
index c93bd9c9b553f18b29e72a08176588d889e7acde..c6c70e9eba37d14b353e43444c17d815403e66fb 100755 (executable)
@@ -8,9 +8,9 @@ use File::Basename qw(basename dirname);
 use File::Spec;
 use Git;
 
-our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w);
+our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w, $opt_W);
 
-getopts('uhPpvcfam:d:w:');
+getopts('uhPpvcfam:d:w:W');
 
 $opt_h && usage();
 
@@ -20,7 +20,7 @@ die "Need at least one commit identifier!" unless @ARGV;
 my $repo = Git->repository();
 $opt_w = $repo->config('cvsexportcommit.cvsdir') unless defined $opt_w;
 
-if ($opt_w) {
+if ($opt_w || $opt_W) {
        # Remember where GIT_DIR is before changing to CVS checkout
        unless ($ENV{GIT_DIR}) {
                # No GIT_DIR set. Figure it out for ourselves
@@ -30,7 +30,9 @@ if ($opt_w) {
        }
        # Make sure GIT_DIR is absolute
        $ENV{GIT_DIR} = File::Spec->rel2abs($ENV{GIT_DIR});
+}
 
+if ($opt_w) {
        if (! -d $opt_w."/CVS" ) {
                die "$opt_w is not a CVS checkout";
        }
@@ -121,6 +123,15 @@ if ($parent) {
     }
 }
 
+my $go_back_to = 0;
+
+if ($opt_W) {
+    $opt_v && print "Resetting to $parent\n";
+    $go_back_to = `git symbolic-ref HEAD 2> /dev/null ||
+       git rev-parse HEAD` || die "Could not determine current branch";
+    system("git checkout -q $parent^0") && die "Could not check out $parent^0";
+}
+
 $opt_v && print "Applying to CVS commit $commit from parent $parent\n";
 
 # grab the commit message
@@ -215,7 +226,8 @@ if (@canstatusfiles) {
        my $basename = basename($name);
 
        $basename = "no file " . $basename if (exists($added{$basename}));
-       chomp($basename);
+       $basename =~ s/^\s+//;
+       $basename =~ s/\s+$//;
 
        if (!exists($fullname{$basename})) {
          $fullname{$basename} = $name;
@@ -264,7 +276,11 @@ if ($dirty) {
 }
 
 print "Applying\n";
-`GIT_DIR= git-apply $context --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch";
+if ($opt_W) {
+    system("git checkout -q $commit^0") && die "cannot patch";
+} else {
+    `GIT_DIR= git-apply $context --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch";
+}
 
 print "Patch applied successfully. Adding new files and directories to CVS\n";
 my $dirtypatch = 0;
@@ -317,7 +333,9 @@ if ($dirtypatch) {
     print "using a patch program. After applying the patch and resolving the\n";
     print "problems you may commit using:";
     print "\n    cd \"$opt_w\"" if $opt_w;
-    print "\n    $cmd\n\n";
+    print "\n    $cmd\n";
+    print "\n    git checkout $go_back_to\n" if $go_back_to;
+    print "\n";
     exit(1);
 }
 
@@ -337,6 +355,14 @@ if ($opt_c) {
 # clean up
 unlink(".cvsexportcommit.diff");
 
+if ($opt_W) {
+    system("git checkout $go_back_to") && die "cannot move back to $go_back_to";
+    if (!($go_back_to =~ /^[0-9a-fA-F]{40}$/)) {
+       system("git symbolic-ref HEAD $go_back_to") &&
+           die "cannot move back to $go_back_to";
+    }
+}
+
 # CVS version 1.11.x and 1.12.x sleeps the wrong way to ensure the timestamp
 # used by CVS and the one set by subsequence file modifications are different.
 # If they are not different CVS will not detect changes.
index bdac5d51b6dfb721bb648e455cb8f3c5078217e4..5a0255052c9360fdc4cbbdd79008a206ab3e4623 100755 (executable)
@@ -780,6 +780,7 @@ sub commit {
                $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
                $xtag =~ tr/_/\./ if ( $opt_u );
                $xtag =~ s/[\/]/$opt_s/g;
+               $xtag =~ s/\[//g;
 
                system('git-tag', '-f', $xtag, $cid) == 0
                        or die "Cannot create tag $xtag: $!\n";
index 29dbfc940b6c8ec10c9e0eb12d08a70581fe870c..920bbe15a3bbc7768d47a2adbc84aa81264e2226 100755 (executable)
@@ -21,6 +21,7 @@ use bytes;
 
 use Fcntl;
 use File::Temp qw/tempdir tempfile/;
+use File::Path qw/rmtree/;
 use File::Basename;
 use Getopt::Long qw(:config require_order no_ignore_case);
 
@@ -86,6 +87,17 @@ my $methods = {
 # $state holds all the bits of information the clients sends us that could
 # potentially be useful when it comes to actually _doing_ something.
 my $state = { prependdir => '' };
+
+# Work is for managing temporary working directory
+my $work =
+    {
+        state => undef,  # undef, 1 (empty), 2 (with stuff)
+        workDir => undef,
+        index => undef,
+        emptyDir => undef,
+        tmpDir => undef
+    };
+
 $log->info("--------------- STARTING -----------------");
 
 my $usage =
@@ -189,6 +201,9 @@ while (<STDIN>)
 $log->debug("Processing time : user=" . (times)[0] . " system=" . (times)[1]);
 $log->info("--------------- FINISH -----------------");
 
+chdir '/';
+exit 0;
+
 # Magic catchall method.
 #    This is the method that will handle all commands we haven't yet
 #    implemented. It simply sends a warning to the log file indicating a
@@ -487,7 +502,7 @@ sub req_add
                 print $state->{CVSROOT} . "/$state->{module}/$filename\n";
 
                 # this is an "entries" line
-                my $kopts = kopts_from_path($filepart);
+                my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash});
                 $log->debug("/$filepart/1.$meta->{revision}//$kopts/");
                 print "/$filepart/1.$meta->{revision}//$kopts/\n";
                 # permissions
@@ -518,9 +533,26 @@ sub req_add
 
         print "Checked-in $dirpart\n";
         print "$filename\n";
-        my $kopts = kopts_from_path($filepart);
+        my $kopts = kopts_from_path($filename,"file",
+                        $state->{entries}{$filename}{modified_filename});
         print "/$filepart/0//$kopts/\n";
 
+        my $requestedKopts = $state->{opt}{k};
+        if(defined($requestedKopts))
+        {
+            $requestedKopts = "-k$requestedKopts";
+        }
+        else
+        {
+            $requestedKopts = "";
+        }
+        if( $kopts ne $requestedKopts )
+        {
+            $log->warn("Ignoring requested -k='$requestedKopts'"
+                        . " for '$filename'; detected -k='$kopts' instead");
+            #TODO: Also have option to send warning to user?
+        }
+
         $addcount++;
     }
 
@@ -600,7 +632,7 @@ sub req_remove
 
         print "Checked-in $dirpart\n";
         print "$filename\n";
-        my $kopts = kopts_from_path($filepart);
+        my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash});
         print "/$filepart/-1.$wrev//$kopts/\n";
 
         $rmcount++;
@@ -770,6 +802,7 @@ sub req_co
     argsplit("co");
 
     my $module = $state->{args}[0];
+    $state->{module} = $module;
     my $checkout_path = $module;
 
     # use the user specified directory if we're given it
@@ -847,6 +880,7 @@ sub req_co
         # Don't want to check out deleted files
         next if ( $git->{filehash} eq "deleted" );
 
+        my $fullName = $git->{name};
         ( $git->{name}, $git->{dir} ) = filenamesplit($git->{name});
 
        if (length($git->{dir}) && $git->{dir} ne './'
@@ -877,7 +911,7 @@ sub req_co
        print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n";
 
         # this is an "entries" line
-        my $kopts = kopts_from_path($git->{name});
+        my $kopts = kopts_from_path($fullName,"sha1",$git->{filehash});
         print "/$git->{name}/1.$git->{revision}//$kopts/\n";
         # permissions
         print "u=$git->{mode},g=$git->{mode},o=$git->{mode}\n";
@@ -1086,7 +1120,7 @@ sub req_update
                print $state->{CVSROOT} . "/$state->{module}/$filename\n";
 
                # this is an "entries" line
-               my $kopts = kopts_from_path($filepart);
+               my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash});
                $log->debug("/$filepart/1.$meta->{revision}//$kopts/");
                print "/$filepart/1.$meta->{revision}//$kopts/\n";
 
@@ -1101,10 +1135,10 @@ sub req_update
             $log->info("Updating '$filename'");
             my ( $filepart, $dirpart ) = filenamesplit($meta->{name},1);
 
-            my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/";
+            my $mergeDir = setupTmpDir();
 
-            chdir $dir;
             my $file_local = $filepart . ".mine";
+            my $mergedFile = "$mergeDir/$file_local";
             system("ln","-s",$state->{entries}{$filename}{modified_filename}, $file_local);
             my $file_old = $filepart . "." . $oldmeta->{revision};
             transmitfile($oldmeta->{filehash}, { targetfile => $file_old });
@@ -1115,11 +1149,13 @@ sub req_update
             $log->info("Merging $file_local, $file_old, $file_new");
             print "M Merging differences between 1.$oldmeta->{revision} and 1.$meta->{revision} into $filename\n";
 
-            $log->debug("Temporary directory for merge is $dir");
+            $log->debug("Temporary directory for merge is $mergeDir");
 
             my $return = system("git", "merge-file", $file_local, $file_old, $file_new);
             $return >>= 8;
 
+            cleanupTmpDir();
+
             if ( $return == 0 )
             {
                 $log->info("Merged successfully");
@@ -1132,7 +1168,8 @@ sub req_update
                     print "Merged $dirpart\n";
                     $log->debug($state->{CVSROOT} . "/$state->{module}/$filename");
                     print $state->{CVSROOT} . "/$state->{module}/$filename\n";
-                    my $kopts = kopts_from_path($filepart);
+                    my $kopts = kopts_from_path("$dirpart/$filepart",
+                                                "file",$mergedFile);
                     $log->debug("/$filepart/1.$meta->{revision}//$kopts/");
                     print "/$filepart/1.$meta->{revision}//$kopts/\n";
                 }
@@ -1148,7 +1185,8 @@ sub req_update
                 {
                     print "Merged $dirpart\n";
                     print $state->{CVSROOT} . "/$state->{module}/$filename\n";
-                    my $kopts = kopts_from_path($filepart);
+                    my $kopts = kopts_from_path("$dirpart/$filepart",
+                                                "file",$mergedFile);
                     print "/$filepart/1.$meta->{revision}/+/$kopts/\n";
                 }
             }
@@ -1168,13 +1206,11 @@ sub req_update
                 # transmit file, format is single integer on a line by itself (file
                 # size) followed by the file contents
                 # TODO : we should copy files in blocks
-                my $data = `cat $file_local`;
+                my $data = `cat $mergedFile`;
                 $log->debug("File size : " . length($data));
                 print length($data) . "\n";
                 print $data;
             }
-
-            chdir "/";
         }
 
     }
@@ -1195,6 +1231,7 @@ sub req_ci
     if ( $state->{method} eq 'pserver')
     {
         print "error 1 pserver access cannot commit\n";
+        cleanupWorkTree();
         exit;
     }
 
@@ -1202,6 +1239,7 @@ sub req_ci
     {
         $log->warn("file 'index' already exists in the git repository");
         print "error 1 Index already exists in git repo\n";
+        cleanupWorkTree();
         exit;
     }
 
@@ -1209,31 +1247,20 @@ sub req_ci
     my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
     $updater->update();
 
-    my $tmpdir = tempdir ( DIR => $TEMP_DIR );
-    my ( undef, $file_index ) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
-    $log->info("Lockless commit start, basing commit on '$tmpdir', index file is '$file_index'");
-
-    $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
-    $ENV{GIT_WORK_TREE} = ".";
-    $ENV{GIT_INDEX_FILE} = $file_index;
-
     # Remember where the head was at the beginning.
     my $parenthash = `git show-ref -s refs/heads/$state->{module}`;
     chomp $parenthash;
     if ($parenthash !~ /^[0-9a-f]{40}$/) {
            print "error 1 pserver cannot find the current HEAD of module";
+           cleanupWorkTree();
            exit;
     }
 
-    chdir $tmpdir;
+    setupWorkTree($parenthash);
 
-    # populate the temporary index
-    system("git-read-tree", $parenthash);
-    unless ($? == 0)
-    {
-       die "Error running git-read-tree $state->{module} $file_index $!";
-    }
-    $log->info("Created index '$file_index' for head $state->{module} - exit status $?");
+    $log->info("Lockless commit start, basing commit on '$work->{workDir}', index file is '$work->{index}'");
+
+    $log->info("Created index '$work->{index}' for head $state->{module} - exit status $?");
 
     my @committedfiles = ();
     my %oldmeta;
@@ -1271,7 +1298,7 @@ sub req_ci
         {
             # fail everything if an up to date check fails
             print "error 1 Up to date check failed for $filename\n";
-            chdir "/";
+            cleanupWorkTree();
             exit;
         }
 
@@ -1313,7 +1340,7 @@ sub req_ci
     {
         print "E No files to commit\n";
         print "ok\n";
-        chdir "/";
+        cleanupWorkTree();
         return;
     }
 
@@ -1336,7 +1363,7 @@ sub req_ci
     {
         $log->warn("Commit failed (Invalid commit hash)");
         print "error 1 Commit failed (unknown reason)\n";
-        chdir "/";
+        cleanupWorkTree();
         exit;
     }
 
@@ -1348,7 +1375,7 @@ sub req_ci
                {
                        $log->warn("Commit failed (update hook declined to update ref)");
                        print "error 1 Commit failed (update hook declined)\n";
-                       chdir "/";
+                       cleanupWorkTree();
                        exit;
                }
        }
@@ -1358,6 +1385,7 @@ sub req_ci
                        "refs/heads/$state->{module}", $commithash, $parenthash)) {
                $log->warn("update-ref for $state->{module} failed.");
                print "error 1 Cannot commit -- update first\n";
+               cleanupWorkTree();
                exit;
        }
 
@@ -1409,12 +1437,12 @@ sub req_ci
             }
             print "Checked-in $dirpart\n";
             print "$filename\n";
-            my $kopts = kopts_from_path($filepart);
+            my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash});
             print "/$filepart/1.$meta->{revision}//$kopts/\n";
         }
     }
 
-    chdir "/";
+    cleanupWorkTree();
     print "ok\n";
 }
 
@@ -1757,15 +1785,9 @@ sub req_annotate
     argsfromdir($updater);
 
     # we'll need a temporary checkout dir
-    my $tmpdir = tempdir ( DIR => $TEMP_DIR );
-    my ( undef, $file_index ) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
-    $log->info("Temp checkoutdir creation successful, basing annotate session work on '$tmpdir', index file is '$file_index'");
-
-    $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
-    $ENV{GIT_WORK_TREE} = ".";
-    $ENV{GIT_INDEX_FILE} = $file_index;
+    setupWorkTree();
 
-    chdir $tmpdir;
+    $log->info("Temp checkoutdir creation successful, basing annotate session work on '$work->{workDir}', index file is '$ENV{GIT_INDEX_FILE}'");
 
     # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
@@ -1789,10 +1811,10 @@ sub req_annotate
        system("git-read-tree", $lastseenin);
        unless ($? == 0)
        {
-           print "E error running git-read-tree $lastseenin $file_index $!\n";
+           print "E error running git-read-tree $lastseenin $ENV{GIT_INDEX_FILE} $!\n";
            return;
        }
-       $log->info("Created index '$file_index' with commit $lastseenin - exit status $?");
+       $log->info("Created index '$ENV{GIT_INDEX_FILE}' with commit $lastseenin - exit status $?");
 
         # do a checkout of the file
         system('git-checkout-index', '-f', '-u', $filename);
@@ -1808,7 +1830,7 @@ sub req_annotate
         # git-jsannotate telling us about commits we are hiding
         # from the client.
 
-        my $a_hints = "$tmpdir/.annotate_hints";
+        my $a_hints = "$work->{workDir}/.annotate_hints";
         if (!open(ANNOTATEHINTS, '>', $a_hints)) {
             print "E failed to open '$a_hints' for writing: $!\n";
             return;
@@ -1862,7 +1884,7 @@ sub req_annotate
     }
 
     # done; get out of the tempdir
-    chdir "/";
+    cleanupWorkDir();
 
     print "ok\n";
 
@@ -2115,26 +2137,388 @@ sub filecleanup
     return $filename;
 }
 
+sub validateGitDir
+{
+    if( !defined($state->{CVSROOT}) )
+    {
+        print "error 1 CVSROOT not specified\n";
+        cleanupWorkTree();
+        exit;
+    }
+    if( $ENV{GIT_DIR} ne ($state->{CVSROOT} . '/') )
+    {
+        print "error 1 Internally inconsistent CVSROOT\n";
+        cleanupWorkTree();
+        exit;
+    }
+}
+
+# Setup working directory in a work tree with the requested version
+# loaded in the index.
+sub setupWorkTree
+{
+    my ($ver) = @_;
+
+    validateGitDir();
+
+    if( ( defined($work->{state}) && $work->{state} != 1 ) ||
+        defined($work->{tmpDir}) )
+    {
+        $log->warn("Bad work tree state management");
+        print "error 1 Internal setup multiple work trees without cleanup\n";
+        cleanupWorkTree();
+        exit;
+    }
+
+    $work->{workDir} = tempdir ( DIR => $TEMP_DIR );
+
+    if( !defined($work->{index}) )
+    {
+        (undef, $work->{index}) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
+    }
+
+    chdir $work->{workDir} or
+        die "Unable to chdir to $work->{workDir}\n";
+
+    $log->info("Setting up GIT_WORK_TREE as '.' in '$work->{workDir}', index file is '$work->{index}'");
+
+    $ENV{GIT_WORK_TREE} = ".";
+    $ENV{GIT_INDEX_FILE} = $work->{index};
+    $work->{state} = 2;
+
+    if($ver)
+    {
+        system("git","read-tree",$ver);
+        unless ($? == 0)
+        {
+            $log->warn("Error running git-read-tree");
+            die "Error running git-read-tree $ver in $work->{workDir} $!\n";
+        }
+    }
+    # else # req_annotate reads tree for each file
+}
+
+# Ensure current directory is in some kind of working directory,
+# with a recent version loaded in the index.
+sub ensureWorkTree
+{
+    if( defined($work->{tmpDir}) )
+    {
+        $log->warn("Bad work tree state management [ensureWorkTree()]");
+        print "error 1 Internal setup multiple dirs without cleanup\n";
+        cleanupWorkTree();
+        exit;
+    }
+    if( $work->{state} )
+    {
+        return;
+    }
+
+    validateGitDir();
+
+    if( !defined($work->{emptyDir}) )
+    {
+        $work->{emptyDir} = tempdir ( DIR => $TEMP_DIR, OPEN => 0);
+    }
+    chdir $work->{emptyDir} or
+        die "Unable to chdir to $work->{emptyDir}\n";
+
+    my $ver = `git show-ref -s refs/heads/$state->{module}`;
+    chomp $ver;
+    if ($ver !~ /^[0-9a-f]{40}$/)
+    {
+        $log->warn("Error from git show-ref -s refs/head$state->{module}");
+        print "error 1 cannot find the current HEAD of module";
+        cleanupWorkTree();
+        exit;
+    }
+
+    if( !defined($work->{index}) )
+    {
+        (undef, $work->{index}) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
+    }
+
+    $ENV{GIT_WORK_TREE} = ".";
+    $ENV{GIT_INDEX_FILE} = $work->{index};
+    $work->{state} = 1;
+
+    system("git","read-tree",$ver);
+    unless ($? == 0)
+    {
+        die "Error running git-read-tree $ver $!\n";
+    }
+}
+
+# Cleanup working directory that is not needed any longer.
+sub cleanupWorkTree
+{
+    if( ! $work->{state} )
+    {
+        return;
+    }
+
+    chdir "/" or die "Unable to chdir '/'\n";
+
+    if( defined($work->{workDir}) )
+    {
+        rmtree( $work->{workDir} );
+        undef $work->{workDir};
+    }
+    undef $work->{state};
+}
+
+# Setup a temporary directory (not a working tree), typically for
+# merging dirty state as in req_update.
+sub setupTmpDir
+{
+    $work->{tmpDir} = tempdir ( DIR => $TEMP_DIR );
+    chdir $work->{tmpDir} or die "Unable to chdir $work->{tmpDir}\n";
+
+    return $work->{tmpDir};
+}
+
+# Clean up a previously setupTmpDir.  Restore previous work tree if
+# appropriate.
+sub cleanupTmpDir
+{
+    if ( !defined($work->{tmpDir}) )
+    {
+        $log->warn("cleanup tmpdir that has not been setup");
+        die "Cleanup tmpDir that has not been setup\n";
+    }
+    if( defined($work->{state}) )
+    {
+        if( $work->{state} == 1 )
+        {
+            chdir $work->{emptyDir} or
+                die "Unable to chdir to $work->{emptyDir}\n";
+        }
+        elsif( $work->{state} == 2 )
+        {
+            chdir $work->{workDir} or
+                die "Unable to chdir to $work->{emptyDir}\n";
+        }
+        else
+        {
+            $log->warn("Inconsistent work dir state");
+            die "Inconsistent work dir state\n";
+        }
+    }
+    else
+    {
+        chdir "/" or die "Unable to chdir '/'\n";
+    }
+}
+
 # Given a path, this function returns a string containing the kopts
 # that should go into that path's Entries line.  For example, a binary
 # file should get -kb.
 sub kopts_from_path
 {
-       my ($path) = @_;
+    my ($path, $srcType, $name) = @_;
 
-       # Once it exists, the git attributes system should be used to look up
-       # what attributes apply to this path.
+    if ( defined ( $cfg->{gitcvs}{usecrlfattr} ) and
+         $cfg->{gitcvs}{usecrlfattr} =~ /\s*(1|true|yes)\s*$/i )
+    {
+        my ($val) = check_attr( "crlf", $path );
+        if ( $val eq "set" )
+        {
+            return "";
+        }
+        elsif ( $val eq "unset" )
+        {
+            return "-kb"
+        }
+        else
+        {
+            $log->info("Unrecognized check_attr crlf $path : $val");
+        }
+    }
 
-       # Until then, take the setting from the config file
-    unless ( defined ( $cfg->{gitcvs}{allbinary} ) and $cfg->{gitcvs}{allbinary} =~ /^\s*(1|true|yes)\s*$/i )
+    if ( defined ( $cfg->{gitcvs}{allbinary} ) )
     {
-               # Return "" to give no special treatment to any path
-               return "";
-    } else {
-               # Alternatively, to have all files treated as if they are binary (which
-               # is more like git itself), always return the "-kb" option
-               return "-kb";
+        if( ($cfg->{gitcvs}{allbinary} =~ /^\s*(1|true|yes)\s*$/i) )
+        {
+            return "-kb";
+        }
+        elsif( ($cfg->{gitcvs}{allbinary} =~ /^\s*guess\s*$/i) )
+        {
+            if( $srcType eq "sha1Or-k" &&
+                !defined($name) )
+            {
+                my ($ret)=$state->{entries}{$path}{options};
+                if( !defined($ret) )
+                {
+                    $ret=$state->{opt}{k};
+                    if(defined($ret))
+                    {
+                        $ret="-k$ret";
+                    }
+                    else
+                    {
+                        $ret="";
+                    }
+                }
+                if( ! ($ret=~/^(|-kb|-kkv|-kkvl|-kk|-ko|-kv)$/) )
+                {
+                    print "E Bad -k option\n";
+                    $log->warn("Bad -k option: $ret");
+                    die "Error: Bad -k option: $ret\n";
+                }
+
+                return $ret;
+            }
+            else
+            {
+                if( is_binary($srcType,$name) )
+                {
+                    $log->debug("... as binary");
+                    return "-kb";
+                }
+                else
+                {
+                    $log->debug("... as text");
+                }
+            }
+        }
+    }
+    # Return "" to give no special treatment to any path
+    return "";
+}
+
+sub check_attr
+{
+    my ($attr,$path) = @_;
+    ensureWorkTree();
+    if ( open my $fh, '-|', "git", "check-attr", $attr, "--", $path )
+    {
+        my $val = <$fh>;
+        close $fh;
+        $val =~ s/.*: ([^:\r\n]*)\s*$/$1/;
+        return $val;
+    }
+    else
+    {
+        return undef;
+    }
+}
+
+# This should have the same heuristics as convert.c:is_binary() and related.
+# Note that the bare CR test is done by callers in convert.c.
+sub is_binary
+{
+    my ($srcType,$name) = @_;
+    $log->debug("is_binary($srcType,$name)");
+
+    # Minimize amount of interpreted code run in the inner per-character
+    # loop for large files, by totalling each character value and
+    # then analyzing the totals.
+    my @counts;
+    my $i;
+    for($i=0;$i<256;$i++)
+    {
+        $counts[$i]=0;
+    }
+
+    my $fh = open_blob_or_die($srcType,$name);
+    my $line;
+    while( defined($line=<$fh>) )
+    {
+        # Any '\0' and bare CR are considered binary.
+        if( $line =~ /\0|(\r[^\n])/ )
+        {
+            close($fh);
+            return 1;
+        }
+
+        # Count up each character in the line:
+        my $len=length($line);
+        for($i=0;$i<$len;$i++)
+        {
+            $counts[ord(substr($line,$i,1))]++;
+        }
+    }
+    close $fh;
+
+    # Don't count CR and LF as either printable/nonprintable
+    $counts[ord("\n")]=0;
+    $counts[ord("\r")]=0;
+
+    # Categorize individual character count into printable and nonprintable:
+    my $printable=0;
+    my $nonprintable=0;
+    for($i=0;$i<256;$i++)
+    {
+        if( $i < 32 &&
+            $i != ord("\b") &&
+            $i != ord("\t") &&
+            $i != 033 &&       # ESC
+            $i != 014 )        # FF
+        {
+            $nonprintable+=$counts[$i];
+        }
+        elsif( $i==127 )  # DEL
+        {
+            $nonprintable+=$counts[$i];
+        }
+        else
+        {
+            $printable+=$counts[$i];
+        }
+    }
+
+    return ($printable >> 7) < $nonprintable;
+}
+
+# Returns open file handle.  Possible invocations:
+#  - open_blob_or_die("file",$filename);
+#  - open_blob_or_die("sha1",$filehash);
+sub open_blob_or_die
+{
+    my ($srcType,$name) = @_;
+    my ($fh);
+    if( $srcType eq "file" )
+    {
+        if( !open $fh,"<",$name )
+        {
+            $log->warn("Unable to open file $name: $!");
+            die "Unable to open file $name: $!\n";
+        }
+    }
+    elsif( $srcType eq "sha1" || $srcType eq "sha1Or-k" )
+    {
+        unless ( defined ( $name ) and $name =~ /^[a-zA-Z0-9]{40}$/ )
+        {
+            $log->warn("Need filehash");
+            die "Need filehash\n";
+        }
+
+        my $type = `git cat-file -t $name`;
+        chomp $type;
+
+        unless ( defined ( $type ) and $type eq "blob" )
+        {
+            $log->warn("Invalid type '$type' for '$name'");
+            die ( "Invalid type '$type' (expected 'blob')" )
+        }
+
+        my $size = `git cat-file -s $name`;
+        chomp $size;
+
+        $log->debug("open_blob_or_die($name) size=$size, type=$type");
+
+        unless( open $fh, '-|', "git", "cat-file", "blob", $name )
+        {
+            $log->warn("Unable to open sha1 $name");
+            die "Unable to open sha1 $name\n";
+        }
+    }
+    else
+    {
+        $log->warn("Unknown type of blob source: $srcType");
+        die "Unknown type of blob source: $srcType\n";
     }
+    return $fh;
 }
 
 # Generate a CVS author name from Git author information, by taking
index 9df49710e1b378af1f49c5ce3c8c8c48731d7d38..e6e88902f1dd1f678eaa5d1dc2d62f8dcacef484 100755 (executable)
@@ -122,6 +122,14 @@ set _reponame {}
 set _iscygwin {}
 set _search_path {}
 
+set _trace [lsearch -exact $argv --trace]
+if {$_trace >= 0} {
+       set argv [lreplace $argv $_trace $_trace]
+       set _trace 1
+} else {
+       set _trace 0
+}
+
 proc appname {} {
        global _appname
        return $_appname
@@ -245,6 +253,21 @@ proc get_config {name} {
 ##
 ## handy utils
 
+proc _trace_exec {cmd} {
+       if {!$::_trace} return
+       set d {}
+       foreach v $cmd {
+               if {$d ne {}} {
+                       append d { }
+               }
+               if {[regexp {[ \t\r\n'"$?*]} $v]} {
+                       set v [sq $v]
+               }
+               append d $v
+       }
+       puts stderr $d
+}
+
 proc _git_cmd {name} {
        global _git_cmd_path
 
@@ -339,7 +362,7 @@ proc _lappend_nice {cmd_var} {
 }
 
 proc git {args} {
-       set opt [list exec]
+       set opt [list]
 
        while {1} {
                switch -- [lindex $args 0] {
@@ -359,12 +382,18 @@ proc git {args} {
        set cmdp [_git_cmd [lindex $args 0]]
        set args [lrange $args 1 end]
 
-       return [eval $opt $cmdp $args]
+       _trace_exec [concat $opt $cmdp $args]
+       set result [eval exec $opt $cmdp $args]
+       if {$::_trace} {
+               puts stderr "< $result"
+       }
+       return $result
 }
 
 proc _open_stdout_stderr {cmd} {
+       _trace_exec $cmd
        if {[catch {
-                       set fd [open $cmd r]
+                       set fd [open [concat [list | ] $cmd] r]
                } err]} {
                if {   [lindex $cmd end] eq {2>@1}
                    && $err eq {can not find channel named "1"}
@@ -375,6 +404,7 @@ proc _open_stdout_stderr {cmd} {
                        # to try to start it a second time.
                        #
                        set fd [open [concat \
+                               [list | ] \
                                [lrange $cmd 0 end-1] \
                                [list |& cat] \
                                ] r]
@@ -387,7 +417,7 @@ proc _open_stdout_stderr {cmd} {
 }
 
 proc git_read {args} {
-       set opt [list |]
+       set opt [list]
 
        while {1} {
                switch -- [lindex $args 0] {
@@ -415,7 +445,7 @@ proc git_read {args} {
 }
 
 proc git_write {args} {
-       set opt [list |]
+       set opt [list]
 
        while {1} {
                switch -- [lindex $args 0] {
@@ -435,7 +465,8 @@ proc git_write {args} {
        set cmdp [_git_cmd [lindex $args 0]]
        set args [lrange $args 1 end]
 
-       return [open [concat $opt $cmdp $args] w]
+       _trace_exec [concat $opt $cmdp $args]
+       return [open [concat [list | ] $opt $cmdp $args] w]
 }
 
 proc githook_read {hook_name args} {
@@ -455,12 +486,12 @@ proc githook_read {hook_name args} {
                }
 
                set scr {if test -x "$1";then exec "$@";fi}
-               set sh_c [list $interp -c $scr $interp $pchook]
+               set sh_c [list $interp -c $scr $interp $pchook]
                return [_open_stdout_stderr [concat $sh_c $args]]
        }
 
        if {[file executable $pchook]} {
-               return [_open_stdout_stderr [concat [list $pchook] $args]]
+               return [_open_stdout_stderr [concat [list $pchook] $args]]
        }
 
        return {}
@@ -1096,27 +1127,18 @@ proc rescan {after {honor_trustmtime 1}} {
 }
 
 if {[is_Cygwin]} {
-       set is_git_info_link {}
        set is_git_info_exclude {}
        proc have_info_exclude {} {
-               global is_git_info_link is_git_info_exclude
-
-               if {$is_git_info_link eq {}} {
-                       set is_git_info_link [file isfile [gitdir info.lnk]]
-               }
+               global is_git_info_exclude
 
-               if {$is_git_info_link} {
-                       if {$is_git_info_exclude eq {}} {
-                               if {[catch {exec test -f [gitdir info exclude]}]} {
-                                       set is_git_info_exclude 0
-                               } else {
-                                       set is_git_info_exclude 1
-                               }
+               if {$is_git_info_exclude eq {}} {
+                       if {[catch {exec test -f [gitdir info exclude]}]} {
+                               set is_git_info_exclude 0
+                       } else {
+                               set is_git_info_exclude 1
                        }
-                       return $is_git_info_exclude
-               } else {
-                       return [file readable [gitdir info exclude]]
                }
+               return $is_git_info_exclude
        }
 } else {
        proc have_info_exclude {} {
index ae4a4cd0a883df272f405d4998da8ab3044109d4..318078615862adab052d0d0f637f82176a0a785a 100644 (file)
@@ -388,9 +388,7 @@ method _do_new {} {
                -command [cb _new_local_path]
        set w_localpath $w_body.where.t
 
-       pack $w_body.where.b -side right
-       pack $w_body.where.l -side left
-       pack $w_body.where.t -fill x
+       grid $w_body.where.l $w_body.where.t $w_body.where.b -sticky ew
        pack $w_body.where -fill x
 
        trace add variable @local_path write [cb _write_local_path]
@@ -987,9 +985,7 @@ method _do_open {} {
                -text [mc "Browse"] \
                -command [cb _open_local_path]
 
-       pack $w_body.where.b -side right
-       pack $w_body.where.l -side left
-       pack $w_body.where.t -fill x
+       grid $w_body.where.l $w_body.where.t $w_body.where.b -sticky ew
        pack $w_body.where -fill x
 
        trace add variable @local_path write [cb _write_local_path]
index 8aa73712ca11f4527f4b8d42bf3a14de2a16c39c..8ee08ff2fd4e414337f79a7e52d29ccda72377e6 100755 (executable)
@@ -56,9 +56,9 @@ output () {
 require_clean_work_tree () {
        # test if working tree is dirty
        git rev-parse --verify HEAD > /dev/null &&
-       git update-index --refresh &&
-       git diff-files --quiet &&
-       git diff-index --cached --quiet HEAD -- ||
+       git update-index --ignore-submodules --refresh &&
+       git diff-files --quiet --ignore-submodules &&
+       git diff-index --cached --quiet HEAD --ignore-submodules -- ||
        die "Working tree is dirty"
 }
 
@@ -377,11 +377,12 @@ do
                # Sanity check
                git rev-parse --verify HEAD >/dev/null ||
                        die "Cannot read HEAD"
-               git update-index --refresh && git diff-files --quiet ||
+               git update-index --ignore-submodules --refresh &&
+                       git diff-files --quiet --ignore-submodules ||
                        die "Working tree is dirty"
 
                # do we have anything to commit?
-               if git diff-index --cached --quiet HEAD --
+               if git diff-index --cached --quiet --ignore-submodules HEAD --
                then
                        : Nothing to commit -- skip this
                else
index 68855c18ae62ef6f4a3fdacb6f8de9c57511aa79..dd7dfe123c15a4b281c4a25a77887150b4a5dbb5 100755 (executable)
@@ -60,7 +60,7 @@ continue_merge () {
        fi
 
        cmt=`cat "$dotest/current"`
-       if ! git diff-index --quiet HEAD --
+       if ! git diff-index --quiet --ignore-submodules HEAD --
        then
                if ! git commit --no-verify -C "$cmt"
                then
@@ -150,7 +150,7 @@ while test $# != 0
 do
        case "$1" in
        --continue)
-               git diff-files --quiet || {
+               git diff-files --quiet --ignore-submodules || {
                        echo "You must edit all merge conflicts and then"
                        echo "mark them as resolved using git add"
                        exit 1
@@ -282,8 +282,8 @@ else
 fi
 
 # The tree must be really really clean.
-git update-index --refresh || exit
-diff=$(git diff-index --cached --name-status -r HEAD --)
+git update-index --ignore-submodules --refresh || exit
+diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)
 case "$diff" in
 ?*)    echo "cannot rebase: your index is not up-to-date"
        echo "$diff"
index c2b68205a2b69eb91b2403e11238093fc65c04ef..4938ade589f2f7d1c69bd53f7bc7bc1bd33f488c 100755 (executable)
@@ -15,8 +15,8 @@ trap 'rm -f "$TMP-*"' 0
 ref_stash=refs/stash
 
 no_changes () {
-       git diff-index --quiet --cached HEAD -- &&
-       git diff-files --quiet
+       git diff-index --quiet --cached HEAD --ignore-submodules -- &&
+       git diff-files --quiet --ignore-submodules
 }
 
 clear_stash () {
@@ -130,7 +130,7 @@ show_stash () {
 }
 
 apply_stash () {
-       git diff-files --quiet ||
+       git diff-files --quiet --ignore-submodules ||
                die 'Cannot restore on top of a dirty state'
 
        unstash_index=
index 2c53f39aefa0131cb15ab679e6b2633622c000a1..37976f25057ac5d3c91a13ab8564a4cb40f59f79 100755 (executable)
@@ -4,7 +4,7 @@
 use warnings;
 use strict;
 use vars qw/   $AUTHOR $VERSION
-               $sha1 $sha1_short $_revision
+               $sha1 $sha1_short $_revision $_repository
                $_q $_authors %users/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '@@GIT_VERSION@@';
@@ -83,6 +83,7 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
                'repack-flags|repack-args|repack-opts=s' =>
                   \$Git::SVN::_repack_flags,
                'use-log-author' => \$Git::SVN::_use_log_author,
+               'add-author-from' => \$Git::SVN::_add_author_from,
                %remote_opts );
 
 my ($_trunk, $_tags, $_branches, $_stdlayout);
@@ -221,6 +222,7 @@ unless ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) {
                }
                $ENV{GIT_DIR} = $git_dir;
        }
+       $_repository = Git->repository(Repository => $ENV{GIT_DIR});
 }
 
 my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
@@ -302,6 +304,7 @@ sub do_git_init_db {
                        }
                }
                command_noisy(@init_db);
+               $_repository = Git->repository(Repository => ".git");
        }
        my $set;
        my $pfx = "svn-remote.$Git::SVN::default_repo_id";
@@ -318,6 +321,7 @@ sub init_subdir {
        mkpath([$repo_path]) unless -d $repo_path;
        chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n";
        $ENV{GIT_DIR} = '.git';
+       $_repository = Git->repository(Repository => $ENV{GIT_DIR});
 }
 
 sub cmd_clone {
@@ -1012,17 +1016,28 @@ sub get_commit_entry {
                my ($msg_fh, $ctx) = command_output_pipe('cat-file',
                                                         $type, $treeish);
                my $in_msg = 0;
+               my $author;
+               my $saw_from = 0;
                while (<$msg_fh>) {
                        if (!$in_msg) {
                                $in_msg = 1 if (/^\s*$/);
+                               $author = $1 if (/^author (.*>)/);
                        } elsif (/^git-svn-id: /) {
                                # skip this for now, we regenerate the
                                # correct one on re-fetch anyways
                                # TODO: set *:merge properties or like...
                        } else {
+                               if (/^From:/ || /^Signed-off-by:/) {
+                                       $saw_from = 1;
+                               }
                                print $log_fh $_ or croak $!;
                        }
                }
+               if ($Git::SVN::_add_author_from && defined($author)
+                   && !$saw_from) {
+                       print $log_fh "\nFrom: $author\n"
+                             or croak $!;
+               }
                command_close_pipe($msg_fh, $ctx);
        }
        close $log_fh or croak $!;
@@ -1249,7 +1264,7 @@ use constant rev_map_fmt => 'NH40';
 use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent
             $_repack $_repack_flags $_use_svm_props $_head
             $_use_svnsync_props $no_reuse_existing $_minimize_url
-           $_use_log_author/;
+           $_use_log_author $_add_author_from/;
 use Carp qw/croak/;
 use File::Path qw/mkpath/;
 use File::Copy qw/copy/;
@@ -3018,6 +3033,7 @@ use vars qw/@ISA/;
 use strict;
 use warnings;
 use Carp qw/croak/;
+use File::Temp qw/tempfile/;
 use IO::File qw//;
 
 # file baton members: path, mode_a, mode_b, pool, fh, blob, base
@@ -3173,14 +3189,9 @@ sub apply_textdelta {
        my $base = IO::File->new_tmpfile;
        $base->autoflush(1);
        if ($fb->{blob}) {
-               defined (my $pid = fork) or croak $!;
-               if (!$pid) {
-                       open STDOUT, '>&', $base or croak $!;
-                       print STDOUT 'link ' if ($fb->{mode_a} == 120000);
-                       exec qw/git-cat-file blob/, $fb->{blob} or croak $!;
-               }
-               waitpid $pid, 0;
-               croak $? if $?;
+               print $base 'link ' if ($fb->{mode_a} == 120000);
+               my $size = $::_repository->cat_blob($fb->{blob}, $base);
+               die "Failed to read object $fb->{blob}" unless $size;
 
                if (defined $exp) {
                        seek $base, 0, 0 or croak $!;
@@ -3221,14 +3232,18 @@ sub close_file {
                                sysseek($fh, 0, 0) or croak $!;
                        }
                }
-               defined(my $pid = open my $out,'-|') or die "Can't fork: $!\n";
-               if (!$pid) {
-                       open STDIN, '<&', $fh or croak $!;
-                       exec qw/git-hash-object -w --stdin/ or croak $!;
+
+               my ($tmp_fh, $tmp_filename) = File::Temp::tempfile(UNLINK => 1);
+               my $result;
+               while ($result = sysread($fh, my $string, 1024)) {
+                       syswrite($tmp_fh, $string, $result);
                }
-               chomp($hash = do { local $/; <$out> });
-               close $out or croak $!;
+               defined $result or croak $!;
+               close $tmp_fh or croak $!;
+
                close $fh or croak $!;
+
+               $hash = $::_repository->hash_and_insert_object($tmp_filename);
                $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
                close $fb->{base} or croak $!;
        } else {
@@ -3554,13 +3569,8 @@ sub chg_file {
        } elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
                $self->change_file_prop($fbat,'svn:special',undef);
        }
-       defined(my $pid = fork) or croak $!;
-       if (!$pid) {
-               open STDOUT, '>&', $fh or croak $!;
-               exec qw/git-cat-file blob/, $m->{sha1_b} or croak $!;
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
+       my $size = $::_repository->cat_blob($m->{sha1_b}, $fh);
+       croak "Failed to read object $m->{sha1_b}" unless $size;
        $fh->flush == 0 or croak $!;
        seek $fh, 0, 0 or croak $!;
 
diff --git a/git.c b/git.c
index 4b79380b7266d5bac0545c85178ca44d4451648e..272bf03da3b3aee7dbdb088ddc1042c18567492a 100644 (file)
--- a/git.c
+++ b/git.c
@@ -286,6 +286,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
                { "cherry", cmd_cherry, RUN_SETUP },
                { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
+               { "clone", cmd_clone },
                { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
                { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
diff --git a/graph.c b/graph.c
index 9d6ed30b0bef22bd8b56da9a12688c760b661776..26b8c5209e280697cc35ffd5313fe3e79681fc43 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -54,10 +54,14 @@ struct git_graph {
         * The commit currently being processed
         */
        struct commit *commit;
+       /* The rev-info used for the current traversal */
+       struct rev_info *revs;
        /*
-        * The number of parents this commit has.
-        * (Stored so we don't have to walk over them each time we need
-        * this number)
+        * The number of interesting parents that this commit has.
+        *
+        * Note that this is not the same as the actual number of parents.
+        * This count excludes parents that won't be printed in the graph
+        * output, as determined by graph_is_interesting().
         */
        int num_parents;
        /*
@@ -125,10 +129,11 @@ struct git_graph {
        int *new_mapping;
 };
 
-struct git_graph *graph_init(void)
+struct git_graph *graph_init(struct rev_info *opt)
 {
        struct git_graph *graph = xmalloc(sizeof(struct git_graph));
        graph->commit = NULL;
+       graph->revs = opt;
        graph->num_parents = 0;
        graph->expansion_row = 0;
        graph->state = GRAPH_PADDING;
@@ -180,6 +185,28 @@ static void graph_ensure_capacity(struct git_graph *graph, int num_columns)
                                      sizeof(int) * 2 * graph->column_capacity);
 }
 
+/*
+ * Returns 1 if the commit will be printed in the graph output,
+ * and 0 otherwise.
+ */
+static int graph_is_interesting(struct git_graph *graph, struct commit *commit)
+{
+       /*
+        * If revs->boundary is set, commits whose children have
+        * been shown are always interesting, even if they have the
+        * UNINTERESTING or TREESAME flags set.
+        */
+       if (graph->revs && graph->revs->boundary) {
+               if (commit->object.flags & CHILD_SHOWN)
+                       return 1;
+       }
+
+       /*
+        * Uninteresting and pruned commits won't be printed
+        */
+       return (commit->object.flags & (UNINTERESTING | TREESAME)) ? 0 : 1;
+}
+
 static void graph_insert_into_new_columns(struct git_graph *graph,
                                          struct commit *commit,
                                          int *mapping_index)
@@ -187,9 +214,9 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
        int i;
 
        /*
-        * Ignore uinteresting and pruned commits
+        * Ignore uinteresting commits
         */
-       if (commit->object.flags & (UNINTERESTING | TREESAME))
+       if (!graph_is_interesting(graph, commit))
                return;
 
        /*
@@ -228,8 +255,8 @@ static void graph_update_width(struct git_graph *graph,
        int max_cols = graph->num_columns + graph->num_parents;
 
        /*
-        * Even if the current commit has no parents, it still takes up a
-        * column for itself.
+        * Even if the current commit has no parents to be printed, it
+        * still takes up a column for itself.
         */
        if (graph->num_parents < 1)
                max_cols++;
@@ -313,6 +340,7 @@ static void graph_update_columns(struct git_graph *graph)
                }
 
                if (col_commit == graph->commit) {
+                       int old_mapping_idx = mapping_idx;
                        seen_this = 1;
                        for (parent = graph->commit->parents;
                             parent;
@@ -321,6 +349,14 @@ static void graph_update_columns(struct git_graph *graph)
                                                              parent->item,
                                                              &mapping_idx);
                        }
+                       /*
+                        * We always need to increment mapping_idx by at
+                        * least 2, even if it has no interesting parents.
+                        * The current commit always takes up at least 2
+                        * spaces.
+                        */
+                       if (mapping_idx == old_mapping_idx)
+                               mapping_idx += 2;
                } else {
                        graph_insert_into_new_columns(graph, col_commit,
                                                      &mapping_idx);
@@ -350,11 +386,13 @@ void graph_update(struct git_graph *graph, struct commit *commit)
        graph->commit = commit;
 
        /*
-        * Count how many parents this commit has
+        * Count how many interesting parents this commit has
         */
        graph->num_parents = 0;
-       for (parent = commit->parents; parent; parent = parent->next)
-               graph->num_parents++;
+       for (parent = commit->parents; parent; parent = parent->next) {
+               if (graph_is_interesting(graph, parent->item))
+                       graph->num_parents++;
+       }
 
        /*
         * Call graph_update_columns() to update
@@ -515,6 +553,51 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
                graph->state = GRAPH_COMMIT;
 }
 
+static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)
+{
+       /*
+        * For boundary commits, print 'o'
+        * (We should only see boundary commits when revs->boundary is set.)
+        */
+       if (graph->commit->object.flags & BOUNDARY) {
+               assert(graph->revs->boundary);
+               strbuf_addch(sb, 'o');
+               return;
+       }
+
+       /*
+        * If revs->left_right is set, print '<' for commits that
+        * come from the left side, and '>' for commits from the right
+        * side.
+        */
+       if (graph->revs && graph->revs->left_right) {
+               if (graph->commit->object.flags & SYMMETRIC_LEFT)
+                       strbuf_addch(sb, '<');
+               else
+                       strbuf_addch(sb, '>');
+               return;
+       }
+
+       /*
+        * Print 'M' for merge commits
+        *
+        * Note that we don't check graph->num_parents to determine if the
+        * commit is a merge, since that only tracks the number of
+        * "interesting" parents.  We want to print 'M' for merge commits
+        * even if they have less than 2 interesting parents.
+        */
+       if (graph->commit->parents != NULL &&
+           graph->commit->parents->next != NULL) {
+               strbuf_addch(sb, 'M');
+               return;
+       }
+
+       /*
+        * Print '*' in all other cases
+        */
+       strbuf_addch(sb, '*');
+}
+
 void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
 {
        int seen_this = 0;
@@ -540,10 +623,7 @@ void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
 
                if (col_commit == graph->commit) {
                        seen_this = 1;
-                       if (graph->num_parents > 1)
-                               strbuf_addch(sb, 'M');
-                       else
-                               strbuf_addch(sb, '*');
+                       graph_output_commit_char(graph, sb);
 
                        if (graph->num_parents < 2)
                                strbuf_addch(sb, ' ');
diff --git a/graph.h b/graph.h
index a7748a5b229b11f0d068ecc2843c1b3a02cac07f..eab4e3daba9812293d4e005c3ebe28f9a97744ce 100644 (file)
--- a/graph.h
+++ b/graph.h
@@ -8,7 +8,7 @@ struct git_graph;
  * Create a new struct git_graph.
  * The graph should be freed with graph_release() when no longer needed.
  */
-struct git_graph *graph_init();
+struct git_graph *graph_init(struct rev_info *opt);
 
 /*
  * Destroy a struct git_graph and free associated memory.
index 61e7160b361f60e6d673ab64aa3d22c1a783d057..48d522368454d2663d5cc075045d87f120058ced 100644 (file)
@@ -6,6 +6,7 @@
  */
 #include "cache.h"
 #include "blob.h"
+#include "quote.h"
 
 static void hash_object(const char *path, enum object_type type, int write_object)
 {
@@ -20,6 +21,7 @@ static void hash_object(const char *path, enum object_type type, int write_objec
                    ? "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_stdin(const char *type, int write_object)
@@ -30,8 +32,27 @@ static void hash_stdin(const char *type, int write_object)
        printf("%s\n", sha1_to_hex(sha1));
 }
 
+static void hash_stdin_paths(const char *type, int write_objects)
+{
+       struct strbuf buf, nbuf;
+
+       strbuf_init(&buf, 0);
+       strbuf_init(&nbuf, 0);
+       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_from_string(type), write_objects);
+       }
+       strbuf_release(&buf);
+       strbuf_release(&nbuf);
+}
+
 static const char hash_object_usage[] =
-"git-hash-object [-t <type>] [-w] [--stdin] <file>...";
+"git-hash-object [ [-t <type>] [-w] [--stdin] <file>... | --stdin-paths < <list-of-paths> ]";
 
 int main(int argc, char **argv)
 {
@@ -42,8 +63,9 @@ int main(int argc, char **argv)
        int prefix_length = -1;
        int no_more_flags = 0;
        int hashstdin = 0;
+       int stdin_paths = 0;
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        for (i = 1 ; i < argc; i++) {
                if (!no_more_flags && argv[i][0] == '-') {
@@ -65,7 +87,19 @@ int main(int argc, char **argv)
                        }
                        else if (!strcmp(argv[i], "--help"))
                                usage(hash_object_usage);
+                       else if (!strcmp(argv[i], "--stdin-paths")) {
+                               if (hashstdin) {
+                                       error("Can't use --stdin-paths with --stdin");
+                                       usage(hash_object_usage);
+                               }
+                               stdin_paths = 1;
+
+                       }
                        else if (!strcmp(argv[i], "--stdin")) {
+                               if (stdin_paths) {
+                                       error("Can't use %s with --stdin-paths", argv[i]);
+                                       usage(hash_object_usage);
+                               }
                                if (hashstdin)
                                        die("Multiple --stdin arguments are not supported");
                                hashstdin = 1;
@@ -76,6 +110,11 @@ int main(int argc, char **argv)
                else {
                        const char *arg = argv[i];
 
+                       if (stdin_paths) {
+                               error("Can't specify files (such as \"%s\") with --stdin-paths", arg);
+                               usage(hash_object_usage);
+                       }
+
                        if (hashstdin) {
                                hash_stdin(type, write_object);
                                hashstdin = 0;
@@ -87,6 +126,10 @@ int main(int argc, char **argv)
                        no_more_flags = 1;
                }
        }
+
+       if (stdin_paths)
+               hash_stdin_paths(type, write_object);
+
        if (hashstdin)
                hash_stdin(type, write_object);
        return 0;
diff --git a/help.c b/help.c
index af80979fcb177faace150a7cfa7cb66daeb59f49..d89d43796f3ee9d3689131d48861967048f5c5d7 100644 (file)
--- a/help.c
+++ b/help.c
@@ -252,7 +252,7 @@ static int add_man_viewer_info(const char *var, const char *value)
        return 0;
 }
 
-static int git_help_config(const char *var, const char *value)
+static int git_help_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "help.format")) {
                if (!value)
@@ -269,7 +269,7 @@ static int git_help_config(const char *var, const char *value)
        if (!prefixcmp(var, "man."))
                return add_man_viewer_info(var, value);
 
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 /* most GUI terminals set COLUMNS (although some don't export it) */
@@ -641,7 +641,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
        const char *alias;
 
        setup_git_directory_gently(&nongit);
-       git_config(git_help_config);
+       git_config(git_help_config, NULL);
 
        argc = parse_options(argc, argv, builtin_help_options,
                        builtin_help_usage, 0);
index 42727c8a45c7b50e8e6b2b04a0799c9ba85a0114..f173dcd64f73bddc891808c96c43407533aac1f5 100644 (file)
@@ -1349,6 +1349,24 @@ static int unlock_remote(struct remote_lock *lock)
        return rc;
 }
 
+static void remove_locks(void)
+{
+       struct remote_lock *lock = remote->locks;
+
+       fprintf(stderr, "Removing remote locks...\n");
+       while (lock) {
+               unlock_remote(lock);
+               lock = lock->next;
+       }
+}
+
+static void remove_locks_on_signal(int signo)
+{
+       remove_locks();
+       signal(signo, SIG_DFL);
+       raise(signo);
+}
+
 static void remote_ls(const char *path, int flags,
                      void (*userFunc)(struct remote_ls_ctx *ls),
                      void *userData);
@@ -2256,6 +2274,10 @@ int main(int argc, char **argv)
                goto cleanup;
        }
 
+       signal(SIGINT, remove_locks_on_signal);
+       signal(SIGHUP, remove_locks_on_signal);
+       signal(SIGQUIT, remove_locks_on_signal);
+
        /* Check whether the remote has server info files */
        remote->can_update_info_refs = 0;
        remote->has_info_refs = remote_exists("info/refs");
diff --git a/http.c b/http.c
index acf746a12da0f0b5e3fe3f097a6626e17da8852c..2a21ccbb76351d108301d2b9c334e2c5a5feacd9 100644 (file)
--- a/http.c
+++ b/http.c
@@ -90,7 +90,7 @@ static void process_curl_messages(void)
 }
 #endif
 
-static int http_options(const char *var, const char *value)
+static int http_options(const char *var, const char *value, void *cb)
 {
        if (!strcmp("http.sslverify", var)) {
                if (curl_ssl_verify == -1) {
@@ -169,7 +169,7 @@ static int http_options(const char *var, const char *value)
        }
 
        /* Fall back on the default ones */
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static CURL* get_curl_handle(void)
@@ -263,7 +263,7 @@ void http_init(struct remote *remote)
        if (low_speed_time != NULL)
                curl_low_speed_time = strtol(low_speed_time, NULL, 10);
 
-       git_config(http_options);
+       git_config(http_options, NULL);
 
        if (curl_ssl_verify == -1)
                curl_ssl_verify = 1;
index db6559725e6867131800b6682348ebbf896ddbf5..1ec131092109aa3fbed3cd20f10b56a864584a94 100644 (file)
@@ -1247,7 +1247,7 @@ static imap_server_conf_t server =
 static char *imap_folder;
 
 static int
-git_imap_config(const char *key, const char *val)
+git_imap_config(const char *key, const char *val, void *cb)
 {
        char imap_key[] = "imap.";
 
@@ -1296,7 +1296,7 @@ main(int argc, char **argv)
        /* init the random number generator */
        arc4_init();
 
-       git_config( git_imap_config );
+       git_config(git_imap_config, NULL);
 
        if (!imap_folder) {
                fprintf( stderr, "no imap store specified\n" );
index 9c0c27813fd6736218d1caaed6248e4ae4d388b5..aaba9443ccbab1315e67c0e6bedaa82afe5464fc 100644 (file)
@@ -765,7 +765,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
        }
 }
 
-static int git_index_pack_config(const char *k, const char *v)
+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);
@@ -773,7 +773,7 @@ static int git_index_pack_config(const char *k, const char *v)
                        die("bad pack.indexversion=%d", pack_idx_default_version);
                return 0;
        }
-       return git_default_config(k, v);
+       return git_default_config(k, v, cb);
 }
 
 int main(int argc, char **argv)
@@ -786,7 +786,7 @@ int main(int argc, char **argv)
        struct pack_idx_entry **idx_objects;
        unsigned char sha1[20];
 
-       git_config(git_index_pack_config);
+       git_config(git_index_pack_config, NULL);
 
        for (i = 1; i < argc; i++) {
                char *arg = argv[i];
index 5ae74331bc2c55638bc5bf1bbfdc7e340199cb01..9837c842a3f215ebee7cbe9690e42e216fb5c76c 100644 (file)
@@ -225,7 +225,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
 static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
 static const char *default_ll_merge;
 
-static int read_merge_config(const char *var, const char *value)
+static int read_merge_config(const char *var, const char *value, void *cb)
 {
        struct ll_merge_driver *fn;
        const char *ep, *name;
@@ -309,7 +309,7 @@ static void initialize_ll_merge(void)
        if (ll_user_merge_tail)
                return;
        ll_user_merge_tail = &ll_user_merge;
-       git_config(read_merge_config);
+       git_config(read_merge_config, NULL);
 }
 
 static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
index 663f18f9c40145e5ab772021e0c700e9123a4b01..cfc7335347c4875537d1c45c909921908f3d4f10 100644 (file)
@@ -24,7 +24,7 @@ static void remove_lock_file(void)
 static void remove_lock_file_on_signal(int signo)
 {
        remove_lock_file();
-       signal(SIGINT, SIG_DFL);
+       signal(signo, SIG_DFL);
        raise(signo);
 }
 
@@ -160,6 +160,34 @@ int hold_lock_file_for_update(struct lock_file *lk, const char *path, int die_on
        return fd;
 }
 
+int hold_lock_file_for_append(struct lock_file *lk, const char *path, int die_on_error)
+{
+       int fd, orig_fd;
+
+       fd = lock_file(lk, path);
+       if (fd < 0) {
+               if (die_on_error)
+                       die("unable to create '%s.lock': %s", path, strerror(errno));
+               return fd;
+       }
+
+       orig_fd = open(path, O_RDONLY);
+       if (orig_fd < 0) {
+               if (errno != ENOENT) {
+                       if (die_on_error)
+                               die("cannot open '%s' for copying", path);
+                       close(fd);
+                       return error("cannot open '%s' for copying", path);
+               }
+       } else if (copy_fd(orig_fd, fd)) {
+               if (die_on_error)
+                       exit(128);
+               close(fd);
+               return -1;
+       }
+       return fd;
+}
+
 int close_lock_file(struct lock_file *lk)
 {
        int fd = lk->fd;
index 1474d1f5d96c9b04b0d5c190f88b2fdefd2ef679..5505606ed6a292cadf1a04ea0b1abc2ca93e3c09 100644 (file)
@@ -228,15 +228,17 @@ void show_log(struct rev_info *opt)
        if (!opt->verbose_header) {
                graph_show_commit(opt->graph);
 
-               if (commit->object.flags & BOUNDARY)
-                       putchar('-');
-               else if (commit->object.flags & UNINTERESTING)
-                       putchar('^');
-               else if (opt->left_right) {
-                       if (commit->object.flags & SYMMETRIC_LEFT)
-                               putchar('<');
-                       else
-                               putchar('>');
+               if (!opt->graph) {
+                       if (commit->object.flags & BOUNDARY)
+                               putchar('-');
+                       else if (commit->object.flags & UNINTERESTING)
+                               putchar('^');
+                       else if (opt->left_right) {
+                               if (commit->object.flags & SYMMETRIC_LEFT)
+                                       putchar('<');
+                               else
+                                       putchar('>');
+                       }
                }
                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
                if (opt->print_parents)
@@ -293,15 +295,18 @@ void show_log(struct rev_info *opt)
                fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
                if (opt->commit_format != CMIT_FMT_ONELINE)
                        fputs("commit ", stdout);
-               if (commit->object.flags & BOUNDARY)
-                       putchar('-');
-               else if (commit->object.flags & UNINTERESTING)
-                       putchar('^');
-               else if (opt->left_right) {
-                       if (commit->object.flags & SYMMETRIC_LEFT)
-                               putchar('<');
-                       else
-                               putchar('>');
+
+               if (!opt->graph) {
+                       if (commit->object.flags & BOUNDARY)
+                               putchar('-');
+                       else if (commit->object.flags & UNINTERESTING)
+                               putchar('^');
+                       else if (opt->left_right) {
+                               if (commit->object.flags & SYMMETRIC_LEFT)
+                                       putchar('<');
+                               else
+                                       putchar('>');
+                       }
                }
                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
                      stdout);
diff --git a/pager.c b/pager.c
index ca002f9f791b6cdb8257836be9e13fe648afa1cc..dbd941421bf90bb1c4b6ad26ba46869f57d15cd8 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -33,7 +33,7 @@ void setup_pager(void)
                return;
        if (!pager) {
                if (!pager_program)
-                       git_config(git_default_config);
+                       git_config(git_default_config, NULL);
                pager = pager_program;
        }
        if (!pager)
index 2e7f896baec00d644903af5d967b6c781ee3503a..6ba8ee5c0d209704fdf1c9d58f63579d3a7e0836 100644 (file)
@@ -39,6 +39,10 @@ $VERSION = '0.01';
   my $lastrev = $repo->command_oneline( [ 'rev-list', '--all' ],
                                         STDERR => 0 );
 
+  my $sha1 = $repo->hash_and_insert_object('file.txt');
+  my $tempfile = tempfile();
+  my $size = $repo->cat_blob($sha1, $tempfile);
+
 =cut
 
 
@@ -51,6 +55,7 @@ require Exporter;
 # Methods which can be called as standalone functions as well:
 @EXPORT_OK = qw(command command_oneline command_noisy
                 command_output_pipe command_input_pipe command_close_pipe
+                command_bidi_pipe command_close_bidi_pipe
                 version exec_path hash_object git_cmd_try);
 
 
@@ -92,6 +97,7 @@ increate nonwithstanding).
 use Carp qw(carp croak); # but croak is bad - throw instead
 use Error qw(:try);
 use Cwd qw(abs_path);
+use IPC::Open2 qw(open2);
 
 }
 
@@ -216,7 +222,6 @@ sub repository {
        bless $self, $class;
 }
 
-
 =back
 
 =head1 METHODS
@@ -375,6 +380,60 @@ sub command_close_pipe {
        _cmd_close($fh, $ctx);
 }
 
+=item command_bidi_pipe ( COMMAND [, ARGUMENTS... ] )
+
+Execute the given C<COMMAND> in the same way as command_output_pipe()
+does but return both an input pipe filehandle and an output pipe filehandle.
+
+The function will return return C<($pid, $pipe_in, $pipe_out, $ctx)>.
+See C<command_close_bidi_pipe()> for details.
+
+=cut
+
+sub command_bidi_pipe {
+       my ($pid, $in, $out);
+       $pid = open2($in, $out, 'git', @_);
+       return ($pid, $in, $out, join(' ', @_));
+}
+
+=item command_close_bidi_pipe ( PID, PIPE_IN, PIPE_OUT [, CTX] )
+
+Close the C<PIPE_IN> and C<PIPE_OUT> as returned from C<command_bidi_pipe()>,
+checking whether the command finished successfully. The optional C<CTX>
+argument is required if you want to see the command name in the error message,
+and it is the fourth value returned by C<command_bidi_pipe()>.  The call idiom
+is:
+
+       my ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check');
+       print "000000000\n" $out;
+       while (<$in>) { ... }
+       $r->command_close_bidi_pipe($pid, $in, $out, $ctx);
+
+Note that you should not rely on whatever actually is in C<CTX>;
+currently it is simply the command name but in future the context might
+have more complicated structure.
+
+=cut
+
+sub command_close_bidi_pipe {
+       my ($pid, $in, $out, $ctx) = @_;
+       foreach my $fh ($in, $out) {
+               unless (close $fh) {
+                       if ($!) {
+                               carp "error closing pipe: $!";
+                       } elsif ($? >> 8) {
+                               throw Git::Error::Command($ctx, $? >>8);
+                       }
+               }
+       }
+
+       waitpid $pid, 0;
+
+       if ($? >> 8) {
+               throw Git::Error::Command($ctx, $? >>8);
+       }
+}
+
 
 =item command_noisy ( COMMAND [, ARGUMENTS... ] )
 
@@ -678,6 +737,147 @@ sub hash_object {
 }
 
 
+=item hash_and_insert_object ( FILENAME )
+
+Compute the SHA1 object id of the given C<FILENAME> and add the object to the
+object database.
+
+The function returns the SHA1 hash.
+
+=cut
+
+# TODO: Support for passing FILEHANDLE instead of FILENAME
+sub hash_and_insert_object {
+       my ($self, $filename) = @_;
+
+       carp "Bad filename \"$filename\"" if $filename =~ /[\r\n]/;
+
+       $self->_open_hash_and_insert_object_if_needed();
+       my ($in, $out) = ($self->{hash_object_in}, $self->{hash_object_out});
+
+       unless (print $out $filename, "\n") {
+               $self->_close_hash_and_insert_object();
+               throw Error::Simple("out pipe went bad");
+       }
+
+       chomp(my $hash = <$in>);
+       unless (defined($hash)) {
+               $self->_close_hash_and_insert_object();
+               throw Error::Simple("in pipe went bad");
+       }
+
+       return $hash;
+}
+
+sub _open_hash_and_insert_object_if_needed {
+       my ($self) = @_;
+
+       return if defined($self->{hash_object_pid});
+
+       ($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));
+}
+
+sub _close_hash_and_insert_object {
+       my ($self) = @_;
+
+       return unless defined($self->{hash_object_pid});
+
+       my @vars = map { 'hash_object_' . $_ } qw(pid in out ctx);
+
+       command_close_bidi_pipe($self->{@vars});
+       delete $self->{@vars};
+}
+
+=item cat_blob ( SHA1, FILEHANDLE )
+
+Prints the contents of the blob identified by C<SHA1> to C<FILEHANDLE> and
+returns the number of bytes printed.
+
+=cut
+
+sub cat_blob {
+       my ($self, $sha1, $fh) = @_;
+
+       $self->_open_cat_blob_if_needed();
+       my ($in, $out) = ($self->{cat_blob_in}, $self->{cat_blob_out});
+
+       unless (print $out $sha1, "\n") {
+               $self->_close_cat_blob();
+               throw Error::Simple("out pipe went bad");
+       }
+
+       my $description = <$in>;
+       if ($description =~ / missing$/) {
+               carp "$sha1 doesn't exist in the repository";
+               return 0;
+       }
+
+       if ($description !~ /^[0-9a-fA-F]{40} \S+ (\d+)$/) {
+               carp "Unexpected result returned from git cat-file";
+               return 0;
+       }
+
+       my $size = $1;
+
+       my $blob;
+       my $bytesRead = 0;
+
+       while (1) {
+               my $bytesLeft = $size - $bytesRead;
+               last unless $bytesLeft;
+
+               my $bytesToRead = $bytesLeft < 1024 ? $bytesLeft : 1024;
+               my $read = read($in, $blob, $bytesToRead, $bytesRead);
+               unless (defined($read)) {
+                       $self->_close_cat_blob();
+                       throw Error::Simple("in pipe went bad");
+               }
+
+               $bytesRead += $read;
+       }
+
+       # Skip past the trailing newline.
+       my $newline;
+       my $read = read($in, $newline, 1);
+       unless (defined($read)) {
+               $self->_close_cat_blob();
+               throw Error::Simple("in pipe went bad");
+       }
+       unless ($read == 1 && $newline eq "\n") {
+               $self->_close_cat_blob();
+               throw Error::Simple("didn't find newline after blob");
+       }
+
+       unless (print $fh $blob) {
+               $self->_close_cat_blob();
+               throw Error::Simple("couldn't write to passed in filehandle");
+       }
+
+       return $size;
+}
+
+sub _open_cat_blob_if_needed {
+       my ($self) = @_;
+
+       return if defined($self->{cat_blob_pid});
+
+       ($self->{cat_blob_pid}, $self->{cat_blob_in},
+        $self->{cat_blob_out}, $self->{cat_blob_ctx}) =
+               command_bidi_pipe(qw(cat-file --batch));
+}
+
+sub _close_cat_blob {
+       my ($self) = @_;
+
+       return unless defined($self->{cat_blob_pid});
+
+       my @vars = map { 'cat_blob_' . $_ } qw(pid in out ctx);
+
+       command_close_bidi_pipe($self->{@vars});
+       delete $self->{@vars};
+}
 
 =back
 
@@ -895,7 +1095,11 @@ sub _cmd_close {
 }
 
 
-sub DESTROY { }
+sub DESTROY {
+       my ($self) = @_;
+       $self->_close_hash_and_insert_object();
+       $self->_close_cat_blob();
+}
 
 
 # Pipe implementation for ActiveState Perl.
index 687293224c3bca20da413d132e17a436e2c79990..8eb39e915e730ff5039247695adc1e25c14773f4 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -28,8 +28,6 @@ void get_commit_format(const char *arg, struct rev_info *rev)
                rev->commit_format = CMIT_FMT_DEFAULT;
                return;
        }
-       if (*arg == '=')
-               arg++;
        if (!prefixcmp(arg, "format:") || !prefixcmp(arg, "tformat:")) {
                const char *cp = strchr(arg, ':') + 1;
                free(user_format);
index 8b467f8f415b68db6e6ea83720ce6ae63dedb9e4..ac9a8e7e323bb7b0728b2b199ab4f4a9bc5ce22f 100644 (file)
@@ -462,12 +462,14 @@ static struct cache_entry *create_alias_ce(struct cache_entry *ce, struct cache_
        return new;
 }
 
-int add_to_index(struct index_state *istate, const char *path, struct stat *st, int verbose)
+int add_to_index(struct index_state *istate, const char *path, struct stat *st, int flags)
 {
-       int size, namelen;
+       int size, namelen, was_same;
        mode_t st_mode = st->st_mode;
        struct cache_entry *ce, *alias;
        unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
+       int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND);
+       int pretend = flags & ADD_CACHE_PRETEND;
 
        if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode))
                return error("%s: can only add regular files, symbolic links or git-directories", path);
@@ -509,19 +511,28 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        if (ignore_case && alias && different_name(ce, alias))
                ce = create_alias_ce(ce, alias);
        ce->ce_flags |= CE_ADDED;
-       if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
+
+       /* It was suspected to be recily clean, but it turns out to be Ok */
+       was_same = (alias &&
+                   !ce_stage(alias) &&
+                   !hashcmp(alias->sha1, ce->sha1) &&
+                   ce->ce_mode == alias->ce_mode);
+
+       if (pretend)
+               ;
+       else if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
                return error("unable to add %s to index",path);
-       if (verbose)
+       if (verbose && !was_same)
                printf("add '%s'\n", path);
        return 0;
 }
 
-int add_file_to_index(struct index_state *istate, const char *path, int verbose)
+int add_file_to_index(struct index_state *istate, const char *path, int flags)
 {
        struct stat st;
        if (lstat(path, &st))
                die("%s: unable to stat (%s)", path, strerror(errno));
-       return add_to_index(istate, path, &st, verbose);
+       return add_to_index(istate, path, &st, flags);
 }
 
 struct cache_entry *make_cache_entry(unsigned int mode,
@@ -942,6 +953,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
        int allow_unmerged = (flags & REFRESH_UNMERGED) != 0;
        int quiet = (flags & REFRESH_QUIET) != 0;
        int not_new = (flags & REFRESH_IGNORE_MISSING) != 0;
+       int ignore_submodules = (flags & REFRESH_IGNORE_SUBMODULES) != 0;
        unsigned int options = really ? CE_MATCH_IGNORE_VALID : 0;
 
        for (i = 0; i < istate->cache_nr; i++) {
@@ -949,6 +961,9 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                int cache_errno = 0;
 
                ce = istate->cache[i];
+               if (ignore_submodules && S_ISGITLINK(ce->ce_mode))
+                       continue;
+
                if (ce_stage(ce)) {
                        while ((i < istate->cache_nr) &&
                               ! strcmp(istate->cache[i]->name, ce->name))
index 828d49001d6c8494ede35e4ebbcea4fe482802dd..b26f2e3a41c870d1b73690e389dbd74a8b8a40a0 100644 (file)
@@ -19,7 +19,7 @@ static int report_status;
 static char capabilities[] = " report-status delete-refs ";
 static int capabilities_sent;
 
-static int receive_pack_config(const char *var, const char *value)
+static int receive_pack_config(const char *var, const char *value, void *cb)
 {
        if (strcmp(var, "receive.denynonfastforwards") == 0) {
                deny_non_fast_forwards = git_config_bool(var, value);
@@ -41,7 +41,7 @@ static int receive_pack_config(const char *var, const char *value)
                return 0;
        }
 
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
 static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
@@ -489,7 +489,7 @@ int main(int argc, char **argv)
        if (is_repository_shallow())
                die("attempt to push into a shallow repository");
 
-       git_config(receive_pack_config);
+       git_config(receive_pack_config, NULL);
 
        if (0 <= transfer_unpack_limit)
                unpack_limit = transfer_unpack_limit;
diff --git a/refs.c b/refs.c
index 9b495eb16ef03b14791aa44e1098cd918859f0cc..9e8e8581ba981ca88366b614f1e8749cfe8a6ae5 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -159,6 +159,8 @@ static struct cached_refs {
 } cached_refs;
 static struct ref_list *current_ref;
 
+static struct ref_list *extra_refs;
+
 static void free_ref_list(struct ref_list *list)
 {
        struct ref_list *next;
@@ -215,6 +217,17 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
        cached_refs->packed = sort_ref_list(list);
 }
 
+void add_extra_ref(const char *name, const unsigned char *sha1, int flag)
+{
+       extra_refs = add_ref(name, sha1, flag, extra_refs, NULL);
+}
+
+void clear_extra_refs(void)
+{
+       free_ref_list(extra_refs);
+       extra_refs = NULL;
+}
+
 static struct ref_list *get_packed_refs(void)
 {
        if (!cached_refs.did_packed) {
@@ -547,6 +560,11 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
        struct ref_list *packed = get_packed_refs();
        struct ref_list *loose = get_loose_refs();
 
+       struct ref_list *extra;
+
+       for (extra = extra_refs; extra; extra = extra->next)
+               retval = do_one_ref(base, fn, trim, cb_data, extra);
+
        while (packed && loose) {
                struct ref_list *entry;
                int cmp = strcmp(packed->name, loose->name);
diff --git a/refs.h b/refs.h
index 06abee15266cc3b234ec64cd27362c482874e54b..06ad26055661a9b9e475d0f8a7bd6d1cfb42e792 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -24,6 +24,15 @@ extern int for_each_tag_ref(each_ref_fn, void *);
 extern int for_each_branch_ref(each_ref_fn, void *);
 extern int for_each_remote_ref(each_ref_fn, void *);
 
+/*
+ * Extra refs will be listed by for_each_ref() before any actual refs
+ * for the duration of this process or until clear_extra_refs() is
+ * called. Only extra refs added before for_each_ref() is called will
+ * be listed on a given call of for_each_ref().
+ */
+extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags);
+extern void clear_extra_refs(void);
+
 extern int peel_ref(const char *, unsigned char *);
 
 /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
index 9e4f2b84d90cb97a6cb19779325f9ea443a76e43..91e3b112bb5ffa151a8ede9391d3bd88ea4d3500 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -2,6 +2,16 @@
 #include "remote.h"
 #include "refs.h"
 
+static struct refspec s_tag_refspec = {
+       0,
+       1,
+       0,
+       "refs/tags/",
+       "refs/tags/"
+};
+
+const struct refspec *tag_refspec = &s_tag_refspec;
+
 struct counted_string {
        size_t len;
        const char *s;
@@ -288,7 +298,7 @@ static void read_branches_file(struct remote *remote)
        remote->fetch_tags = 1; /* always auto-follow */
 }
 
-static int handle_config(const char *key, const char *value)
+static int handle_config(const char *key, const char *value, void *cb)
 {
        const char *name;
        const char *subkey;
@@ -410,7 +420,7 @@ static void read_config(void)
                current_branch =
                        make_branch(head_ref + strlen("refs/heads/"), 0);
        }
-       git_config(handle_config);
+       git_config(handle_config, NULL);
        alias_all_urls();
 }
 
index c2f557357fd4a0eb247f0e6f688efd10f946a08a..8eed87ba5ab78eb4635632c21843590467d0d864 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -53,6 +53,8 @@ struct refspec {
        char *dst;
 };
 
+extern const struct refspec *tag_refspec;
+
 struct ref *alloc_ref(unsigned namelen);
 
 struct ref *alloc_ref_from_str(const char* str);
index 7142cf96cde81377522e49de5f102a25eaec2d54..fc667552592daa3c894d0df4e97469d1809dbf27 100644 (file)
@@ -1197,15 +1197,20 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->verbose_header = 1;
                                continue;
                        }
-                       if (!prefixcmp(arg, "--pretty")) {
+                       if (!strcmp(arg, "--pretty")) {
                                revs->verbose_header = 1;
                                get_commit_format(arg+8, revs);
                                continue;
                        }
-                       if (!prefixcmp(arg, "--graph")) {
+                       if (!prefixcmp(arg, "--pretty=")) {
+                               revs->verbose_header = 1;
+                               get_commit_format(arg+9, revs);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--graph")) {
                                revs->topo_order = 1;
                                revs->rewrite_parents = 1;
-                               revs->graph = graph_init();
+                               revs->graph = graph_init(revs);
                                continue;
                        }
                        if (!strcmp(arg, "--root")) {
@@ -1612,28 +1617,62 @@ static void gc_boundary(struct object_array *array)
        }
 }
 
+static void create_boundary_commit_list(struct rev_info *revs)
+{
+       unsigned i;
+       struct commit *c;
+       struct object_array *array = &revs->boundary_commits;
+       struct object_array_entry *objects = array->objects;
+
+       /*
+        * If revs->commits is non-NULL at this point, an error occurred in
+        * get_revision_1().  Ignore the error and continue printing the
+        * boundary commits anyway.  (This is what the code has always
+        * done.)
+        */
+       if (revs->commits) {
+               free_commit_list(revs->commits);
+               revs->commits = NULL;
+       }
+
+       /*
+        * Put all of the actual boundary commits from revs->boundary_commits
+        * into revs->commits
+        */
+       for (i = 0; i < array->nr; i++) {
+               c = (struct commit *)(objects[i].item);
+               if (!c)
+                       continue;
+               if (!(c->object.flags & CHILD_SHOWN))
+                       continue;
+               if (c->object.flags & (SHOWN | BOUNDARY))
+                       continue;
+               c->object.flags |= BOUNDARY;
+               commit_list_insert(c, &revs->commits);
+       }
+
+       /*
+        * If revs->topo_order is set, sort the boundary commits
+        * in topological order
+        */
+       sort_in_topological_order(&revs->commits, revs->lifo);
+}
+
 static struct commit *get_revision_internal(struct rev_info *revs)
 {
        struct commit *c = NULL;
        struct commit_list *l;
 
        if (revs->boundary == 2) {
-               unsigned i;
-               struct object_array *array = &revs->boundary_commits;
-               struct object_array_entry *objects = array->objects;
-               for (i = 0; i < array->nr; i++) {
-                       c = (struct commit *)(objects[i].item);
-                       if (!c)
-                               continue;
-                       if (!(c->object.flags & CHILD_SHOWN))
-                               continue;
-                       if (!(c->object.flags & SHOWN))
-                               break;
-               }
-               if (array->nr <= i)
-                       return NULL;
-
-               c->object.flags |= SHOWN | BOUNDARY;
+               /*
+                * All of the normal commits have already been returned,
+                * and we are now returning boundary commits.
+                * create_boundary_commit_list() has populated
+                * revs->commits with the remaining commits to return.
+                */
+               c = pop_commit(&revs->commits);
+               if (c)
+                       c->object.flags |= SHOWN;
                return c;
        }
 
@@ -1697,7 +1736,14 @@ static struct commit *get_revision_internal(struct rev_info *revs)
                 * switch to boundary commits output mode.
                 */
                revs->boundary = 2;
-               return get_revision(revs);
+
+               /*
+                * Update revs->commits to contain the list of
+                * boundary commits.
+                */
+               create_boundary_commit_list(revs);
+
+               return get_revision_internal(revs);
        }
 
        /*
diff --git a/setup.c b/setup.c
index b8fd476395db782bf942a548b87242b3a9f990f4..d630e374e7c03b934de14adceb93c9c327796e6d 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -300,7 +300,7 @@ void setup_work_tree(void)
 
 static int check_repository_format_gently(int *nongit_ok)
 {
-       git_config(check_repository_format_version);
+       git_config(check_repository_format_version, NULL);
        if (GIT_REPO_VERSION < repository_format_version) {
                if (!nongit_ok)
                        die ("Expected git repo version <= %d, found %d",
@@ -524,7 +524,7 @@ int git_config_perm(const char *var, const char *value)
        return i & 0666;
 }
 
-int check_repository_format_version(const char *var, const char *value)
+int check_repository_format_version(const char *var, const char *value, void *cb)
 {
        if (strcmp(var, "core.repositoryformatversion") == 0)
                repository_format_version = git_config_int(var, value);
index 141f5a713a2e542c0bcccbfa57b0dccc3142f362..9679040d62189304392554d0d3cfd561ed5a7e45 100644 (file)
@@ -176,21 +176,22 @@ char *sha1_file_name(const unsigned char *sha1)
        return base;
 }
 
-char *sha1_pack_name(const unsigned char *sha1)
+static char *sha1_get_pack_name(const unsigned char *sha1,
+                               char **name, char **base)
 {
        static const char hex[] = "0123456789abcdef";
-       static char *name, *base, *buf;
+       char *buf;
        int i;
 
-       if (!base) {
+       if (!*base) {
                const char *sha1_file_directory = get_object_directory();
                int len = strlen(sha1_file_directory);
-               base = xmalloc(len + 60);
-               sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory);
-               name = base + len + 11;
+               *base = xmalloc(len + 60);
+               sprintf(*base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory);
+               *name = *base + len + 11;
        }
 
-       buf = name;
+       buf = *name;
 
        for (i = 0; i < 20; i++) {
                unsigned int val = *sha1++;
@@ -198,32 +199,21 @@ char *sha1_pack_name(const unsigned char *sha1)
                *buf++ = hex[val & 0xf];
        }
 
-       return base;
+       return *base;
 }
 
-char *sha1_pack_index_name(const unsigned char *sha1)
+char *sha1_pack_name(const unsigned char *sha1)
 {
-       static const char hex[] = "0123456789abcdef";
-       static char *name, *base, *buf;
-       int i;
-
-       if (!base) {
-               const char *sha1_file_directory = get_object_directory();
-               int len = strlen(sha1_file_directory);
-               base = xmalloc(len + 60);
-               sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.idx", sha1_file_directory);
-               name = base + len + 11;
-       }
+       static char *name, *base;
 
-       buf = name;
+       return sha1_get_pack_name(sha1, &name, &base);
+}
 
-       for (i = 0; i < 20; i++) {
-               unsigned int val = *sha1++;
-               *buf++ = hex[val >> 4];
-               *buf++ = hex[val & 0xf];
-       }
+char *sha1_pack_index_name(const unsigned char *sha1)
+{
+       static char *name, *base;
 
-       return base;
+       return sha1_get_pack_name(sha1, &name, &base);
 }
 
 struct alternate_object_database *alt_odb_list;
@@ -380,6 +370,18 @@ static void read_info_alternates(const char * relative_base, int depth)
        munmap(map, mapsz);
 }
 
+void add_to_alternates_file(const char *reference)
+{
+       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+       int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), 1);
+       char *alt = mkpath("%s/objects\n", reference);
+       write_or_die(fd, alt, strlen(alt));
+       if (commit_lock_file(lock))
+               die("could not close alternates file");
+       if (alt_odb_tail)
+               link_alt_odb_entries(alt, alt + strlen(alt), '\n', NULL, 0);
+}
+
 void prepare_alt_odb(void)
 {
        const char *alt;
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
new file mode 100755 (executable)
index 0000000..cb1fbe5
--- /dev/null
@@ -0,0 +1,226 @@
+#!/bin/sh
+
+test_description='git cat-file'
+
+. ./test-lib.sh
+
+echo_without_newline () {
+    printf '%s' "$*"
+}
+
+strlen () {
+    echo_without_newline "$1" | wc -c | sed -e 's/^ *//'
+}
+
+maybe_remove_timestamp () {
+    if test -z "$2"; then
+        echo_without_newline "$1"
+    else
+       echo_without_newline "$(printf '%s\n' "$1" | sed -e 's/ [0-9][0-9]* [-+][0-9][0-9][0-9][0-9]$//')"
+    fi
+}
+
+run_tests () {
+    type=$1
+    sha1=$2
+    size=$3
+    content=$4
+    pretty_content=$5
+    no_ts=$6
+
+    batch_output="$sha1 $type $size
+$content"
+
+    test_expect_success "$type exists" '
+       git cat-file -e $sha1
+    '
+
+    test_expect_success "Type of $type is correct" '
+        test $type = "$(git cat-file -t $sha1)"
+    '
+
+    test_expect_success "Size of $type is correct" '
+        test $size = "$(git cat-file -s $sha1)"
+    '
+
+    test -z "$content" ||
+    test_expect_success "Content of $type is correct" '
+       expect="$(maybe_remove_timestamp "$content" $no_ts)"
+       actual="$(maybe_remove_timestamp "$(git cat-file $type $sha1)" $no_ts)"
+
+        if test "z$expect" = "z$actual"
+       then
+               : happy
+       else
+               echo "Oops: expected $expect"
+               echo "but got $actual"
+               false
+        fi
+    '
+
+    test_expect_success "Pretty content of $type is correct" '
+       expect="$(maybe_remove_timestamp "$pretty_content" $no_ts)"
+       actual="$(maybe_remove_timestamp "$(git cat-file -p $sha1)" $no_ts)"
+        if test "z$expect" = "z$actual"
+       then
+               : happy
+       else
+               echo "Oops: expected $expect"
+               echo "but got $actual"
+               false
+        fi
+    '
+
+    test -z "$content" ||
+    test_expect_success "--batch output of $type is correct" '
+       expect="$(maybe_remove_timestamp "$batch_output" $no_ts)"
+       actual="$(maybe_remove_timestamp "$(echo $sha1 | git cat-file --batch)" no_ts)"
+        if test "z$expect" = "z$actual"
+       then
+               : happy
+       else
+               echo "Oops: expected $expect"
+               echo "but got $actual"
+               false
+        fi
+    '
+
+    test_expect_success "--batch-check output of $type is correct" '
+       expect="$sha1 $type $size"
+       actual="$(echo_without_newline $sha1 | git cat-file --batch-check)"
+        if test "z$expect" = "z$actual"
+       then
+               : happy
+       else
+               echo "Oops: expected $expect"
+               echo "but got $actual"
+               false
+        fi
+    '
+}
+
+hello_content="Hello World"
+hello_size=$(strlen "$hello_content")
+hello_sha1=$(echo_without_newline "$hello_content" | git hash-object --stdin)
+
+test_expect_success "setup" '
+       echo_without_newline "$hello_content" > hello &&
+       git update-index --add hello
+'
+
+run_tests 'blob' $hello_sha1 $hello_size "$hello_content" "$hello_content"
+
+tree_sha1=$(git write-tree)
+tree_size=33
+tree_pretty_content="100644 blob $hello_sha1   hello"
+
+run_tests 'tree' $tree_sha1 $tree_size "" "$tree_pretty_content"
+
+commit_message="Intial commit"
+commit_sha1=$(echo_without_newline "$commit_message" | git commit-tree $tree_sha1)
+commit_size=176
+commit_content="tree $tree_sha1
+author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0000000000 +0000
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0000000000 +0000
+
+$commit_message"
+
+run_tests 'commit' $commit_sha1 $commit_size "$commit_content" "$commit_content" 1
+
+tag_header_without_timestamp="object $hello_sha1
+type blob
+tag hellotag
+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+tag_description="This is a tag"
+tag_content="$tag_header_without_timestamp 0000000000 +0000
+
+$tag_description"
+tag_pretty_content="$tag_header_without_timestamp Thu Jan 1 00:00:00 1970 +0000
+
+$tag_description"
+
+tag_sha1=$(echo_without_newline "$tag_content" | git mktag)
+tag_size=$(strlen "$tag_content")
+
+run_tests 'tag' $tag_sha1 $tag_size "$tag_content" "$tag_pretty_content" 1
+
+test_expect_success \
+    "Reach a blob from a tag pointing to it" \
+    "test '$hello_content' = \"\$(git cat-file blob $tag_sha1)\""
+
+for batch in batch batch-check
+do
+    for opt in t s e p
+    do
+       test_expect_success "Passing -$opt with --$batch fails" '
+           test_must_fail git cat-file --$batch -$opt $hello_sha1
+       '
+
+       test_expect_success "Passing --$batch with -$opt fails" '
+           test_must_fail git cat-file -$opt --$batch $hello_sha1
+       '
+    done
+
+    test_expect_success "Passing <type> with --$batch fails" '
+       test_must_fail git cat-file --$batch blob $hello_sha1
+    '
+
+    test_expect_success "Passing --$batch with <type> fails" '
+       test_must_fail git cat-file blob --$batch $hello_sha1
+    '
+
+    test_expect_success "Passing sha1 with --$batch fails" '
+       test_must_fail git cat-file --$batch $hello_sha1
+    '
+done
+
+test_expect_success "--batch-check for a non-existent object" '
+    test "deadbeef missing" = \
+    "$(echo_without_newline deadbeef | git cat-file --batch-check)"
+'
+
+test_expect_success "--batch-check for an emtpy line" '
+    test " missing" = "$(echo | git cat-file --batch-check)"
+'
+
+batch_input="$hello_sha1
+$commit_sha1
+$tag_sha1
+deadbeef
+
+"
+
+batch_output="$hello_sha1 blob $hello_size
+$hello_content
+$commit_sha1 commit $commit_size
+$commit_content
+$tag_sha1 tag $tag_size
+$tag_content
+deadbeef missing
+ missing"
+
+test_expect_success '--batch with multiple sha1s gives correct format' '
+       test "$(maybe_remove_timestamp "$batch_output" 1)" = "$(maybe_remove_timestamp "$(echo_without_newline "$batch_input" | git cat-file --batch)" 1)"
+'
+
+batch_check_input="$hello_sha1
+$tree_sha1
+$commit_sha1
+$tag_sha1
+deadbeef
+
+"
+
+batch_check_output="$hello_sha1 blob $hello_size
+$tree_sha1 tree $tree_size
+$commit_sha1 commit $commit_size
+$tag_sha1 tag $tag_size
+deadbeef missing
+ missing"
+
+test_expect_success "--batch-check with multiple sha1s gives correct format" '
+    test "$batch_check_output" = \
+    "$(echo_without_newline "$batch_check_input" | git cat-file --batch-check)"
+'
+
+test_done
diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
new file mode 100755 (executable)
index 0000000..0526295
--- /dev/null
@@ -0,0 +1,133 @@
+#!/bin/sh
+
+test_description=git-hash-object
+
+. ./test-lib.sh
+
+echo_without_newline() {
+       printf '%s' "$*"
+}
+
+test_blob_does_not_exist() {
+       test_expect_success 'blob does not exist in database' "
+               test_must_fail git cat-file blob $1
+       "
+}
+
+test_blob_exists() {
+       test_expect_success 'blob exists in database' "
+               git cat-file blob $1
+       "
+}
+
+hello_content="Hello World"
+hello_sha1=5e1c309dae7f45e0f39b1bf3ac3cd9db12e7d689
+
+example_content="This is an example"
+example_sha1=ddd3f836d3e3fbb7ae289aa9ae83536f76956399
+
+setup_repo() {
+       echo_without_newline "$hello_content" > hello
+       echo_without_newline "$example_content" > example
+}
+
+test_repo=test
+push_repo() {
+       test_create_repo $test_repo
+       cd $test_repo
+
+       setup_repo
+}
+
+pop_repo() {
+       cd ..
+       rm -rf $test_repo
+}
+
+setup_repo
+
+# Argument checking
+
+test_expect_success "multiple '--stdin's are rejected" '
+       test_must_fail git hash-object --stdin --stdin < example
+'
+
+test_expect_success "Can't use --stdin and --stdin-paths together" '
+       test_must_fail git hash-object --stdin --stdin-paths &&
+       test_must_fail git hash-object --stdin-paths --stdin
+'
+
+test_expect_success "Can't pass filenames as arguments with --stdin-paths" '
+       test_must_fail git hash-object --stdin-paths hello < example
+'
+
+# Behavior
+
+push_repo
+
+test_expect_success 'hash a file' '
+       test $hello_sha1 = $(git hash-object hello)
+'
+
+test_blob_does_not_exist $hello_sha1
+
+test_expect_success 'hash from stdin' '
+       test $example_sha1 = $(git hash-object --stdin < example)
+'
+
+test_blob_does_not_exist $example_sha1
+
+test_expect_success 'hash a file and write to database' '
+       test $hello_sha1 = $(git hash-object -w hello)
+'
+
+test_blob_exists $hello_sha1
+
+test_expect_success 'git hash-object --stdin file1 <file0 first operates on file0, then file1' '
+       echo foo > file1 &&
+       obname0=$(echo bar | git hash-object --stdin) &&
+       obname1=$(git hash-object file1) &&
+       obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&
+       obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&
+       test "$obname0" = "$obname0new" &&
+       test "$obname1" = "$obname1new"
+'
+
+pop_repo
+
+for args in "-w --stdin" "--stdin -w"; do
+       push_repo
+
+       test_expect_success "hash from stdin and write to database ($args)" '
+               test $example_sha1 = $(git hash-object $args < example)
+       '
+
+       test_blob_exists $example_sha1
+
+       pop_repo
+done
+
+filenames="hello
+example"
+
+sha1s="$hello_sha1
+$example_sha1"
+
+test_expect_success "hash two files with names on stdin" '
+       test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object --stdin-paths)"
+'
+
+for args in "-w --stdin-paths" "--stdin-paths -w"; do
+       push_repo
+
+       test_expect_success "hash two files with names on stdin and write to database ($args)" '
+               test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object $args)"
+       '
+
+       test_blob_exists $hello_sha1
+       test_blob_exists $example_sha1
+
+       pop_repo
+done
+
+test_done
index b664341926071a3286ba949dc09b5e62b280e79a..f57a6e077c3b85dcdedc3f4813150feebc8e647d 100755 (executable)
@@ -111,4 +111,21 @@ test_expect_success 'touch and then add explicitly' '
 
 '
 
+test_expect_success 'add -n -u should not add but just report' '
+
+       (
+               echo "add '\''check'\''" &&
+               echo "remove '\''top'\''"
+       ) >expect &&
+       before=$(git ls-files -s check top) &&
+       echo changed >>check &&
+       rm -f top &&
+       git add -n -u >actual &&
+       after=$(git ls-files -s check top) &&
+
+       test "$before" = "$after" &&
+       test_cmp expect actual
+
+'
+
 test_done
diff --git a/t/t4126-apply-empty.sh b/t/t4126-apply-empty.sh
new file mode 100755 (executable)
index 0000000..0cfd47c
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+test_description='apply empty'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       >empty &&
+       git add empty &&
+       test_tick &&
+       git commit -m initial &&
+       for i in a b c d e
+       do
+               echo $i
+       done >empty &&
+       cat empty >expect &&
+       git diff |
+       sed -e "/^diff --git/d" \
+           -e "/^index /d" \
+           -e "s|a/empty|empty.orig|" \
+           -e "s|b/empty|empty|" >patch0 &&
+       sed -e "s|empty|missing|" patch0 >patch1 &&
+       >empty &&
+       git update-index --refresh
+'
+
+test_expect_success 'apply empty' '
+       git reset --hard &&
+       >empty &&
+       rm -f missing &&
+       git apply patch0 &&
+       test_cmp expect empty
+'
+
+test_expect_success 'apply --index empty' '
+       git reset --hard &&
+       >empty &&
+       rm -f missing &&
+       git apply --index patch0 &&
+       test_cmp expect empty &&
+       git diff --exit-code
+'
+
+test_expect_success 'apply create' '
+       git reset --hard &&
+       >empty &&
+       rm -f missing &&
+       git apply patch1 &&
+       test_cmp expect missing
+'
+
+test_expect_success 'apply --index create' '
+       git reset --hard &&
+       >empty &&
+       rm -f missing &&
+       git apply --index patch1 &&
+       test_cmp expect missing &&
+       git diff --exit-code
+'
+
+test_done
index d6c55c115779730fdef8d05fbdb039fe90e3fad7..a8b78ebf7d021cb626d4452545b5f4d1e0efe34f 100755 (executable)
@@ -25,4 +25,22 @@ do
                diff ../t5100/info$mail info$mail"
 done
 
+test_expect_success 'respect NULs' '
+
+       git mailsplit -d3 -o. ../t5100/nul &&
+       cmp ../t5100/nul 001 &&
+       (cat 001 | git mailinfo msg patch) &&
+       test 4 = $(wc -l < patch)
+
+'
+
+test_expect_success 'Preserve NULs out of MIME encoded message' '
+
+       git mailsplit -d5 -o. ../t5100/nul-b64.in &&
+       cmp ../t5100/nul-b64.in 00001 &&
+       git mailinfo msg patch <00001 &&
+       cmp ../t5100/nul-b64.expect patch
+
+'
+
 test_done
diff --git a/t/t5100/nul b/t/t5100/nul
new file mode 100644 (file)
index 0000000..3d40691
Binary files /dev/null and b/t/t5100/nul differ
diff --git a/t/t5100/nul-b64.expect b/t/t5100/nul-b64.expect
new file mode 100644 (file)
index 0000000..d7d680f
Binary files /dev/null and b/t/t5100/nul-b64.expect differ
diff --git a/t/t5100/nul-b64.in b/t/t5100/nul-b64.in
new file mode 100644 (file)
index 0000000..16540d9
--- /dev/null
@@ -0,0 +1,37 @@
+From 667d8940e719cddee1cfe237cbbe215e20270b09 Mon Sep 17 00:00:00 2001
+From: Junio C Hamano <gitster@pobox.com>
+Date: Sun, 25 May 2008 00:38:18 -0700
+Subject: [PATCH] second
+Content-Transfer-Encoding: base64
+
+LS0tCiBmaWxlIHwgIEJpbiAxMzU3IC0+IDEzNTcgYnl0ZXMKIDEgZmlsZXMgY2hhbmdlZCwg
+MCBpbnNlcnRpb25zKCspLCAwIGRlbGV0aW9ucygtKQoKZGlmZiAtLWdpdCBhL2ZpbGUgYi9m
+aWxlCmluZGV4IDc3MzYxZDguLjllMDJiZTYgMTAwNjQ0Ci0tLSBhL2ZpbGUKKysrIGIvZmls
+ZQpAQCAtMSwxMiArMSwxMiBAQAogTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl
+Y3RldHVlciBhZGlwaXNjaW5nIGVsaXQuIFN1c3BlbmRpc3NlCiBzaXQgYW1ldCB0dXJwaXMg
+ZWdldCBlc3QgY3Vyc3VzIGxhb3JlZXQuIEFsaXF1YW0gbWF1cmlzLiBQcmFlc2VudAotdm9s
+dXRwYXQuIFByb2luIGluIHB1cnVzLiBOdWxsYSB1cm5hIHNhcGllbiwgZGFwaWJ1cyBzaXQg
+YW1ldCwKK3ZvbHV0cGF0LiBQcm9pbiBpbiBwdXJ1cy4gTnVsbGEgdXJuYSBzYXBpZW4sIGRh
+cGkAdXMgc2l0IGFtZXQsCiBoZW5kcmVyaXQgbmVjLCB0ZW1wdXMgZXUsIG1pLiBVdCBwb3J0
+YSwgbGVvIGlkIHRpbmNpZHVudCB1bGxhbWNvcnBlciwKLXZlbGl0IGZlbGlzIHRyaXN0aXF1
+ZSBhbnRlLCBhdCBsb2JvcnRpcyBkaWFtIHBlZGUgdXQgZHVpLiBQcm9pbiBhYwordmVsaXQg
+ZmVsaXMgdHJpc3RpcXVlIGFudGUsIGF0IGxvAG9ydGlzIGRpYW0gcGVkZSB1dCBkdWkuIFBy
+b2luIGFjCiBsZWN0dXMuIERvbmVjIGF0IG1hc3NhIGFjIGlwc3VtIGhlbmRyZXJpdCBzb2xs
+aWNpdHVkaW4uIE5hbSBkaWN0dW0KIG5pc2kgc2VkIG1pLiBEdWlzIHNlZCBhbnRlLiBVdCB2
+aXRhZSBlc3QgdXQgZHVpIHVsdHJpY2llcyBkaWduaXNzaW0uCiAKLUluIHZlbCBvZGlvIGVn
+ZXQgbmlzbCBjb252YWxsaXMgdm9sdXRwYXQuIE1vcmJpIHZpdGFlIG5pYmguIE51bGxhbQor
+SW4gdmVsIG9kaW8gZWdldCBuaXNsIGNvbnZhbGxpcyB2b2x1dHBhdC4gTW9yAGkgdml0YWUg
+bmkAaC4gTnVsbGFtCiBhY2N1bXNhbiwgZG9sb3IgcXVpcyBhbGlxdWFtIHNjZWxlcmlzcXVl
+LCBlbGl0IGVuaW0gY29uZGltZW50dW0KIG1hdXJpcywgbm9uIHRyaXN0aXF1ZSBtYXVyaXMg
+dHVycGlzIGV0IG1hdXJpcy4gVXQgbm9uIG5pc2wuIE5hbSBkaWFtCiBtaSwgc2VtcGVyIHBv
+c3VlcmUsIGVsZWlmZW5kIHV0LCBhdWN0b3IgdmVsLCBlcmF0LiBTZWQgcG9zdWVyZQpAQCAt
+MTYsNyArMTYsNyBAQCBzZWQgZXN0LiBFdGlhbSBkaWFtIGZlbGlzLCBmZXJtZW50dW0gZWdl
+dCwgYWRpcGlzY2luZyBhdCwgcG9zdWVyZSBpbiwKIGR1aS4gRXRpYW0gbHVjdHVzLgogCiBO
+dWxsYSBpZCBhdWd1ZS4gTmFtIGlhY3VsaXMgYWNjdW1zYW4gbmlzaS4gU3VzcGVuZGlzc2Ug
+cG90ZW50aS4gTnVuYwotdmFyaXVzIGF1Z3VlIG5lYyBvcmNpLiBVdCBjb25kaW1lbnR1bSBk
+b2xvciBzYWdpdHRpcyBuaWJoLiBTdXNwZW5kaXNzZQordmFyaXVzIGF1Z3VlIG5lYyBvcmNp
+LiBVdCBjb25kaW1lbnR1bSBkb2xvciBzYWdpdHRpcyBuaQBoLiBTdXNwZW5kaXNzZQogdGVt
+cG9yIGxlY3R1cyBzZWQgbWFnbmEuIFN1c3BlbmRpc3NlIHBvdGVudGkuIE51bGxhbSB0ZW1w
+b3IgaXBzdW0uIFNlZAogbW9sZXN0aWUgdGVsbHVzLiBQaGFzZWxsdXMgbGlndWxhLiBJbiB2
+ZWhpY3VsYSB1bHRyaWNlcwogbmlzaS4gU3VzcGVuZGlzc2UgZmVsaXMgYXVndWUsIHBlbGxl
+bnRlc3F1ZSBhdCwgZGljdHVtIHZpdmVycmEsCi0tIAoxLjUuNS4xLjU0MC5nNTc3ODAKCg==
diff --git a/t/t5303-hash-object.sh b/t/t5303-hash-object.sh
deleted file mode 100755 (executable)
index 543c078..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/sh
-
-test_description=git-hash-object
-
-. ./test-lib.sh
-
-test_expect_success \
-    'git hash-object -w --stdin saves the object' \
-    'obname=$(echo foo | git hash-object -w --stdin) &&
-    obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
-    test -r .git/objects/"$obpath" &&
-    rm -f .git/objects/"$obpath"'
-    
-test_expect_success \
-    'git hash-object --stdin -w saves the object' \
-    'obname=$(echo foo | git hash-object --stdin -w) &&
-    obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
-    test -r .git/objects/"$obpath" &&
-    rm -f .git/objects/"$obpath"'    
-
-test_expect_success \
-    'git hash-object --stdin file1 <file0 first operates on file0, then file1' \
-    'echo foo > file1 &&
-    obname0=$(echo bar | git hash-object --stdin) &&
-    obname1=$(git hash-object file1) &&
-    obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&
-    obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&
-    test "$obname0" = "$obname0new" &&
-    test "$obname1" = "$obname1new"'
-
-test_expect_success \
-    'git hash-object refuses multiple --stdin arguments' \
-    '! git hash-object --stdin --stdin < file1'
-
-test_done
index dc9d63dbf9d21482c881606152f10594f2fe124f..593d1a38772843f1afd67814dcf8920bea3366bc 100755 (executable)
@@ -23,4 +23,11 @@ test_expect_success 'clone with excess parameters' '
 
 '
 
+test_expect_success 'clone checks out files' '
+
+       git clone src dst &&
+       test -f dst/file
+
+'
+
 test_done
index e5619a9f5c9aae8c9565dd6937c20ce6401d26d4..e1ca7303ac83a79eb4668c8f828e068c1220e72d 100755 (executable)
@@ -8,6 +8,8 @@ test_description='test clone --reference'
 
 base_dir=`pwd`
 
+U=$base_dir/UPLOAD_LOG
+
 test_expect_success 'preparing first repository' \
 'test_create_repo A && cd A &&
 echo first > file1 &&
@@ -50,8 +52,13 @@ diff expected current'
 
 cd "$base_dir"
 
+rm -f $U
+
 test_expect_success 'cloning with reference (no -l -s)' \
-'git clone --reference B "file://$(pwd)/A" D'
+'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>$U'
+
+test_expect_success 'fetched no objects' \
+'! grep "^want" $U'
 
 cd "$base_dir"
 
@@ -113,4 +120,30 @@ diff expected current'
 
 cd "$base_dir"
 
+test_expect_success 'preparing alternate repository #1' \
+'test_create_repo F && cd F &&
+echo first > file1 &&
+git add file1 &&
+git commit -m initial'
+
+cd "$base_dir"
+
+test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' \
+'git clone F G && cd F &&
+echo second > file2 &&
+git add file2 &&
+git commit -m addition'
+
+cd "$base_dir"
+
+test_expect_success 'cloning alternate repo #1, using #2 as reference' \
+'git clone --reference G F H'
+
+cd "$base_dir"
+
+test_expect_success 'cloning with reference being subset of source (-l -s)' \
+'git clone -l -s --reference A B E'
+
+cd "$base_dir"
+
 test_done
index 933f5679831b1df3baac8164094b204a09fd7f16..0626544823d6ff9d37ea86c752285d093788df3e 100755 (executable)
@@ -126,6 +126,47 @@ test_expect_success 'bisect reset removes packed refs' '
        test -z "$(git for-each-ref "refs/heads/bisect")"
 '
 
+test_expect_success 'bisect start: back in good branch' '
+       git branch > branch.output &&
+       grep "* other" branch.output > /dev/null &&
+       git bisect start $HASH4 $HASH1 -- &&
+       git bisect good &&
+       git bisect start $HASH4 $HASH1 -- &&
+       git bisect bad &&
+       git bisect reset &&
+       git branch > branch.output &&
+       grep "* other" branch.output > /dev/null
+'
+
+test_expect_success 'bisect start: no ".git/BISECT_START" if junk rev' '
+       git bisect start $HASH4 $HASH1 -- &&
+       git bisect good &&
+       test_must_fail git bisect start $HASH4 foo -- &&
+       git branch > branch.output &&
+       grep "* other" branch.output > /dev/null &&
+       test_must_fail test -e .git/BISECT_START
+'
+
+test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
+       git bisect start $HASH4 $HASH1 -- &&
+       git bisect good &&
+       test_must_fail git bisect start $HASH1 $HASH4 -- &&
+       git branch > branch.output &&
+       grep "* other" branch.output > /dev/null &&
+       test_must_fail test -e .git/BISECT_START
+'
+
+test_expect_success 'bisect start: no ".git/BISECT_START" if checkout error' '
+       echo "temp stuff" > hello &&
+       test_must_fail git bisect start $HASH4 $HASH1 -- &&
+       git branch &&
+       git branch > branch.output &&
+       grep "* other" branch.output > /dev/null &&
+       test_must_fail test -e .git/BISECT_START &&
+       test -z "$(git for-each-ref "refs/bisect/*")" &&
+       git checkout HEAD hello
+'
+
 # $HASH1 is good, $HASH4 is bad, we skip $HASH3
 # but $HASH2 is bad,
 # so we should find $HASH2 as the first bad commit
@@ -281,25 +322,6 @@ test_expect_success 'bisect starting with a detached HEAD' '
        test $HEAD = $(cat .git/BISECT_START) &&
        git bisect reset &&
        test $HEAD = $(git rev-parse --verify HEAD)
-
-'
-
-test_expect_success 'bisect refuses to start if branch bisect exists' '
-       git bisect reset &&
-       git branch bisect &&
-       test_must_fail git bisect start &&
-       git branch -d bisect &&
-       git checkout -b bisect &&
-       test_must_fail git bisect start &&
-       git checkout master &&
-       git branch -d bisect
-'
-
-test_expect_success 'bisect refuses to start if branch new-bisect exists' '
-       git bisect reset &&
-       git branch new-bisect &&
-       test_must_fail git bisect start &&
-       git branch -d new-bisect
 '
 
 test_expect_success 'bisect errors out if bad and good are mistaken' '
@@ -309,6 +331,25 @@ test_expect_success 'bisect errors out if bad and good are mistaken' '
        git bisect reset
 '
 
+test_expect_success 'bisect does not create a "bisect" branch' '
+       git bisect reset &&
+       git bisect start $HASH7 $HASH1 &&
+       git branch bisect &&
+       rev_hash4=$(git rev-parse --verify HEAD) &&
+       test "$rev_hash4" = "$HASH4" &&
+       git branch -D bisect &&
+       git bisect good &&
+       git branch bisect &&
+       rev_hash6=$(git rev-parse --verify HEAD) &&
+       test "$rev_hash6" = "$HASH6" &&
+       git bisect good > my_bisect_log.txt &&
+       grep "$HASH7 is first bad commit" my_bisect_log.txt &&
+       git bisect reset &&
+       rev_hash6=$(git rev-parse --verify bisect) &&
+       test "$rev_hash6" = "$HASH6" &&
+       git branch -D bisect
+'
+
 #
 #
 test_done
index c8310aee4f77e19281b2559c1f52804c7c98a81d..8073e0c3efb2ac01e4a81e722fc357bcab13f5d4 100755 (executable)
@@ -3,6 +3,9 @@
 test_description='merge-recursive: handle file mode'
 . ./test-lib.sh
 
+# Note that we follow "chmod +x F" with "update-index --chmod=+x F" to
+# help filesystems that do not have the executable bit.
+
 test_expect_success 'mode change in one branch: keep changed version' '
        : >file1 &&
        git add file1 &&
@@ -13,7 +16,7 @@ test_expect_success 'mode change in one branch: keep changed version' '
        git commit -m a &&
        git checkout -b b1 master &&
        chmod +x file1 &&
-       git add file1 &&
+       git update-index --chmod=+x file1 &&
        git commit -m b1 &&
        git checkout a1 &&
        git merge-recursive master -- a1 b1 &&
@@ -26,7 +29,7 @@ test_expect_success 'mode change in both branches: expect conflict' '
        : >file2 &&
        H=$(git hash-object file2) &&
        chmod +x file2 &&
-       git add file2 &&
+       git update-index --add --chmod=+x file2 &&
        git commit -m a2 &&
        git checkout -b b2 master &&
        : >file2 &&
diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh
new file mode 100755 (executable)
index 0000000..5becb3e
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Johannes Schindelin
+#
+
+test_description='Test rebasing and stashing with dirty submodules'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       echo file > file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+       git clone . submodule &&
+       git add submodule &&
+       test_tick &&
+       git commit -m submodule &&
+       echo second line >> file &&
+       (cd submodule && git pull) &&
+       test_tick &&
+       git commit -m file-and-submodule -a
+
+'
+
+test_expect_success 'rebase with a dirty submodule' '
+
+       (cd submodule &&
+        echo 3rd line >> file &&
+        test_tick &&
+        git commit -m fork -a) &&
+       echo unrelated >> file2 &&
+       git add file2 &&
+       test_tick &&
+       git commit -m unrelated file2 &&
+       echo other line >> file &&
+       test_tick &&
+       git commit -m update file &&
+       CURRENT=$(cd submodule && git rev-parse HEAD) &&
+       EXPECTED=$(git rev-parse HEAD~2:submodule) &&
+       GIT_TRACE=1 git rebase --onto HEAD~2 HEAD^ &&
+       STORED=$(git rev-parse HEAD:submodule) &&
+       test $EXPECTED = $STORED &&
+       test $CURRENT = $(cd submodule && git rev-parse HEAD)
+
+'
+
+cat > fake-editor.sh << \EOF
+#!/bin/sh
+echo $EDITOR_TEXT
+EOF
+chmod a+x fake-editor.sh
+
+test_expect_success 'interactive rebase with a dirty submodule' '
+
+       test submodule = $(git diff --name-only) &&
+       HEAD=$(git rev-parse HEAD) &&
+       GIT_EDITOR="\"$(pwd)/fake-editor.sh\"" EDITOR_TEXT="pick $HEAD" \
+               git rebase -i HEAD^ &&
+       test submodule = $(git diff --name-only)
+
+'
+
+test_expect_success 'rebase with dirty file and submodule fails' '
+
+       echo yet another line >> file &&
+       test_tick &&
+       git commit -m next file &&
+       echo rewrite > file &&
+       test_tick &&
+       git commit -m rewrite file &&
+       echo dirty > file &&
+       ! git rebase --onto HEAD~2 HEAD^
+
+'
+
+test_expect_success 'stash with a dirty submodule' '
+
+       echo new > file &&
+       CURRENT=$(cd submodule && git rev-parse HEAD) &&
+       git stash &&
+       test new != $(cat file) &&
+       test submodule = $(git diff --name-only) &&
+       test $CURRENT = $(cd submodule && git rev-parse HEAD) &&
+       git stash apply &&
+       test new = $(cat file) &&
+       test $CURRENT = $(cd submodule && git rev-parse HEAD)
+
+'
+
+test_done
diff --git a/t/t9122-git-svn-author.sh b/t/t9122-git-svn-author.sh
new file mode 100755 (executable)
index 0000000..8c58f0b
--- /dev/null
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+test_description='git svn authorship'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup svn repository' '
+       svn checkout "$svnrepo" work.svn &&
+       (
+               cd work.svn &&
+               echo >file
+               svn add file
+               svn commit -m "first commit" file
+       )
+'
+
+test_expect_success 'interact with it via git-svn' '
+       mkdir work.git &&
+       (
+               cd work.git &&
+               git svn init "$svnrepo"
+               git svn fetch &&
+
+               echo modification >file &&
+               test_tick &&
+               git commit -a -m second &&
+
+               test_tick &&
+               git svn dcommit &&
+
+               echo "further modification" >file &&
+               test_tick &&
+               git commit -a -m third &&
+
+               test_tick &&
+               git svn --add-author-from dcommit &&
+
+               echo "yet further modification" >file &&
+               test_tick &&
+               git commit -a -m fourth &&
+
+               test_tick &&
+               git svn --add-author-from --use-log-author dcommit &&
+
+               git log &&
+
+               git show -s HEAD^^ >../actual.2 &&
+               git show -s HEAD^  >../actual.3 &&
+               git show -s HEAD   >../actual.4
+
+       ) &&
+
+       # Make sure that --add-author-from without --use-log-author
+       # did not affect the authorship information
+       myself=$(grep "^Author: " actual.2) &&
+       unaffected=$(grep "^Author: " actual.3) &&
+       test "z$myself" = "z$unaffected" &&
+
+       # Make sure lack of --add-author-from did not add cruft
+       ! grep "^    From: A U Thor " actual.2 &&
+
+       # Make sure --add-author-from added cruft
+       grep "^    From: A U Thor " actual.3 &&
+       grep "^    From: A U Thor " actual.4 &&
+
+       # Make sure --add-author-from with --use-log-author affected
+       # the authorship information
+       grep "^Author: A U Thor " actual.4
+'
+
+test_done
index 42b144b1b3f38b406b09ae7bff455f1e436ee33c..b1dc32d056929ee0cafbd94f6df52669350935b2 100755 (executable)
@@ -297,4 +297,21 @@ test_expect_success 'commit a file with leading spaces in the name' '
 
 '
 
+test_expect_success 'use the same checkout for Git and CVS' '
+
+       (mkdir shared &&
+        cd shared &&
+        unset GIT_DIR &&
+        cvs co . &&
+        git init &&
+        git add " space" &&
+        git commit -m "fake initial commit" &&
+        echo Hello >> " space" &&
+        git commit -m "Another change" " space" &&
+        git cvsexportcommit -W -p -u -c HEAD &&
+        grep Hello " space" &&
+        git diff-files)
+
+'
+
 test_done
diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh
new file mode 100755 (executable)
index 0000000..e27a1c5
--- /dev/null
@@ -0,0 +1,337 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Matthew Ogilvie
+# Parts adapted from other tests.
+#
+
+test_description='git-cvsserver -kb modes
+
+tests -kb mode for binary files when accessing a git
+repository using cvs CLI client via git-cvsserver server'
+
+. ./test-lib.sh
+
+q_to_nul () {
+    perl -pe 'y/Q/\000/'
+}
+
+q_to_cr () {
+    tr Q '\015'
+}
+
+marked_as () {
+    foundEntry="$(grep "^/$2/" "$1/CVS/Entries")"
+    if [ x"$foundEntry" = x"" ] ; then
+       echo "NOT FOUND: $1 $2 1 $3" >> "${WORKDIR}/marked.log"
+       return 1
+    fi
+    test x"$(grep "^/$2/" "$1/CVS/Entries" | cut -d/ -f5)" = x"$3"
+    stat=$?
+    echo "$1 $2 $stat '$3'" >> "${WORKDIR}/marked.log"
+    return $stat
+}
+
+not_present() {
+    foundEntry="$(grep "^/$2/" "$1/CVS/Entries")"
+    if [ -r "$1/$2" ] ; then
+        echo "Error: File still exists: $1 $2" >> "${WORKDIR}/marked.log"
+        return 1;
+    fi
+    if [ x"$foundEntry" != x"" ] ; then
+        echo "Error: should not have found: $1 $2" >> "${WORKDIR}/marked.log"
+        return 1;
+    else
+        echo "Correctly not found: $1 $2" >> "${WORKDIR}/marked.log"
+        return 0;
+    fi
+}
+
+cvs >/dev/null 2>&1
+if test $? -ne 1
+then
+    test_expect_success 'skipping git-cvsserver tests, cvs not found' :
+    test_done
+    exit
+fi
+perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
+    test_expect_success 'skipping git-cvsserver tests, Perl SQLite interface unavailable' :
+    test_done
+    exit
+}
+
+unset GIT_DIR GIT_CONFIG
+WORKDIR=$(pwd)
+SERVERDIR=$(pwd)/gitcvs.git
+git_config="$SERVERDIR/config"
+CVSROOT=":fork:$SERVERDIR"
+CVSWORK="$(pwd)/cvswork"
+CVS_SERVER=git-cvsserver
+export CVSROOT CVS_SERVER
+
+rm -rf "$CVSWORK" "$SERVERDIR"
+test_expect_success 'setup' '
+    echo "Simple text file" >textfile.c &&
+    echo "File with embedded NUL: Q <- there" | q_to_nul > binfile.bin &&
+    mkdir subdir &&
+    echo "Another text file" > subdir/file.h &&
+    echo "Another binary: Q (this time CR)" | q_to_cr > subdir/withCr.bin &&
+    echo "Mixed up NUL, but marked text: Q <- there" | q_to_nul > mixedUp.c
+    echo "Unspecified" > subdir/unspecified.other &&
+    echo "/*.bin -crlf" > .gitattributes &&
+    echo "/*.c crlf" >> .gitattributes &&
+    echo "subdir/*.bin -crlf" >> .gitattributes &&
+    echo "subdir/*.c crlf" >> .gitattributes &&
+    echo "subdir/file.h crlf" >> .gitattributes &&
+    git add .gitattributes textfile.c binfile.bin mixedUp.c subdir/* &&
+    git commit -q -m "First Commit" &&
+    git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
+    GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
+    GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log"
+'
+
+test_expect_success 'cvs co (default crlf)' '
+    GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+    test x"$(grep '/-k' cvswork/CVS/Entries cvswork/subdir/CVS/Entries)" = x""
+'
+
+rm -rf cvswork
+test_expect_success 'cvs co (allbinary)' '
+    GIT_DIR="$SERVERDIR" git config --bool gitcvs.allbinary true &&
+    GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+    marked_as cvswork textfile.c -kb &&
+    marked_as cvswork binfile.bin -kb &&
+    marked_as cvswork .gitattributes -kb &&
+    marked_as cvswork mixedUp.c -kb &&
+    marked_as cvswork/subdir withCr.bin -kb &&
+    marked_as cvswork/subdir file.h -kb &&
+    marked_as cvswork/subdir unspecified.other -kb
+'
+
+rm -rf cvswork cvs.log
+test_expect_success 'cvs co (use attributes/allbinary)' '
+    GIT_DIR="$SERVERDIR" git config --bool gitcvs.usecrlfattr true &&
+    GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+    marked_as cvswork textfile.c "" &&
+    marked_as cvswork binfile.bin -kb &&
+    marked_as cvswork .gitattributes -kb &&
+    marked_as cvswork mixedUp.c "" &&
+    marked_as cvswork/subdir withCr.bin -kb &&
+    marked_as cvswork/subdir file.h "" &&
+    marked_as cvswork/subdir unspecified.other -kb
+'
+
+rm -rf cvswork
+test_expect_success 'cvs co (use attributes)' '
+    GIT_DIR="$SERVERDIR" git config --bool gitcvs.allbinary false &&
+    GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+    marked_as cvswork textfile.c "" &&
+    marked_as cvswork binfile.bin -kb &&
+    marked_as cvswork .gitattributes "" &&
+    marked_as cvswork mixedUp.c "" &&
+    marked_as cvswork/subdir withCr.bin -kb &&
+    marked_as cvswork/subdir file.h "" &&
+    marked_as cvswork/subdir unspecified.other ""
+'
+
+test_expect_success 'adding files' '
+    cd cvswork/subdir &&
+    echo "more text" > src.c &&
+    GIT_CONFIG="$git_config" cvs -Q add src.c >cvs.log 2>&1 &&
+    marked_as . src.c "" &&
+    echo "psuedo-binary" > temp.bin &&
+    cd .. &&
+    GIT_CONFIG="$git_config" cvs -Q add subdir/temp.bin >cvs.log 2>&1 &&
+    marked_as subdir temp.bin "-kb" &&
+    cd subdir &&
+    GIT_CONFIG="$git_config" cvs -Q ci -m "adding files" >cvs.log 2>&1 &&
+    marked_as . temp.bin "-kb" &&
+    marked_as . src.c ""
+'
+
+cd "$WORKDIR"
+test_expect_success 'updating' '
+    git pull gitcvs.git &&
+    echo 'hi' > subdir/newfile.bin &&
+    echo 'junk' > subdir/file.h &&
+    echo 'hi' > subdir/newfile.c &&
+    echo 'hello' >> binfile.bin &&
+    git add subdir/newfile.bin subdir/file.h subdir/newfile.c binfile.bin &&
+    git commit -q -m "Add and change some files" &&
+    git push gitcvs.git >/dev/null &&
+    cd cvswork &&
+    GIT_CONFIG="$git_config" cvs -Q update &&
+    cd .. &&
+    marked_as cvswork textfile.c "" &&
+    marked_as cvswork binfile.bin -kb &&
+    marked_as cvswork .gitattributes "" &&
+    marked_as cvswork mixedUp.c "" &&
+    marked_as cvswork/subdir withCr.bin -kb &&
+    marked_as cvswork/subdir file.h "" &&
+    marked_as cvswork/subdir unspecified.other "" &&
+    marked_as cvswork/subdir newfile.bin -kb &&
+    marked_as cvswork/subdir newfile.c "" &&
+    echo "File with embedded NUL: Q <- there" | q_to_nul > tmpExpect1 &&
+    echo "hello" >> tmpExpect1 &&
+    cmp cvswork/binfile.bin tmpExpect1
+'
+
+rm -rf cvswork
+test_expect_success 'cvs co (use attributes/guess)' '
+    GIT_DIR="$SERVERDIR" git config gitcvs.allbinary guess &&
+    GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+    marked_as cvswork textfile.c "" &&
+    marked_as cvswork binfile.bin -kb &&
+    marked_as cvswork .gitattributes "" &&
+    marked_as cvswork mixedUp.c "" &&
+    marked_as cvswork/subdir withCr.bin -kb &&
+    marked_as cvswork/subdir file.h "" &&
+    marked_as cvswork/subdir unspecified.other "" &&
+    marked_as cvswork/subdir newfile.bin -kb &&
+    marked_as cvswork/subdir newfile.c ""
+'
+
+test_expect_success 'setup multi-line files' '
+    ( echo "line 1" &&
+      echo "line 2" &&
+      echo "line 3" &&
+      echo "line 4 with NUL: Q <-" ) | q_to_nul > multiline.c &&
+    git add multiline.c &&
+    ( echo "line 1" &&
+      echo "line 2" &&
+      echo "line 3" &&
+      echo "line 4" ) | q_to_nul > multilineTxt.c &&
+    git add multilineTxt.c &&
+    git commit -q -m "multiline files" &&
+    git push gitcvs.git >/dev/null
+'
+
+rm -rf cvswork
+test_expect_success 'cvs co (guess)' '
+    GIT_DIR="$SERVERDIR" git config --bool gitcvs.usecrlfattr false &&
+    GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+    marked_as cvswork textfile.c "" &&
+    marked_as cvswork binfile.bin -kb &&
+    marked_as cvswork .gitattributes "" &&
+    marked_as cvswork mixedUp.c -kb &&
+    marked_as cvswork multiline.c -kb &&
+    marked_as cvswork multilineTxt.c "" &&
+    marked_as cvswork/subdir withCr.bin -kb &&
+    marked_as cvswork/subdir file.h "" &&
+    marked_as cvswork/subdir unspecified.other "" &&
+    marked_as cvswork/subdir newfile.bin "" &&
+    marked_as cvswork/subdir newfile.c ""
+'
+
+test_expect_success 'cvs co another copy (guess)' '
+    GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
+    marked_as cvswork2 textfile.c "" &&
+    marked_as cvswork2 binfile.bin -kb &&
+    marked_as cvswork2 .gitattributes "" &&
+    marked_as cvswork2 mixedUp.c -kb &&
+    marked_as cvswork2 multiline.c -kb &&
+    marked_as cvswork2 multilineTxt.c "" &&
+    marked_as cvswork2/subdir withCr.bin -kb &&
+    marked_as cvswork2/subdir file.h "" &&
+    marked_as cvswork2/subdir unspecified.other "" &&
+    marked_as cvswork2/subdir newfile.bin "" &&
+    marked_as cvswork2/subdir newfile.c ""
+'
+
+test_expect_success 'add text (guess)' '
+    cd cvswork &&
+    echo "simpleText" > simpleText.c &&
+    GIT_CONFIG="$git_config" cvs -Q add simpleText.c &&
+    cd .. &&
+    marked_as cvswork simpleText.c ""
+'
+
+test_expect_success 'add bin (guess)' '
+    cd cvswork &&
+    echo "simpleBin: NUL: Q <- there" | q_to_nul > simpleBin.bin &&
+    GIT_CONFIG="$git_config" cvs -Q add simpleBin.bin &&
+    cd .. &&
+    marked_as cvswork simpleBin.bin -kb
+'
+
+test_expect_success 'remove files (guess)' '
+    cd cvswork &&
+    GIT_CONFIG="$git_config" cvs -Q rm -f subdir/file.h &&
+    cd subdir &&
+    GIT_CONFIG="$git_config" cvs -Q rm -f withCr.bin &&
+    cd ../.. &&
+    marked_as cvswork/subdir withCr.bin -kb &&
+    marked_as cvswork/subdir file.h ""
+'
+
+test_expect_success 'cvs ci (guess)' '
+    cd cvswork &&
+    GIT_CONFIG="$git_config" cvs -Q ci -m "add/rm files" >cvs.log 2>&1 &&
+    cd .. &&
+    marked_as cvswork textfile.c "" &&
+    marked_as cvswork binfile.bin -kb &&
+    marked_as cvswork .gitattributes "" &&
+    marked_as cvswork mixedUp.c -kb &&
+    marked_as cvswork multiline.c -kb &&
+    marked_as cvswork multilineTxt.c "" &&
+    not_present cvswork/subdir withCr.bin &&
+    not_present cvswork/subdir file.h &&
+    marked_as cvswork/subdir unspecified.other "" &&
+    marked_as cvswork/subdir newfile.bin "" &&
+    marked_as cvswork/subdir newfile.c "" &&
+    marked_as cvswork simpleBin.bin -kb &&
+    marked_as cvswork simpleText.c ""
+'
+
+test_expect_success 'update subdir of other copy (guess)' '
+    cd cvswork2/subdir &&
+    GIT_CONFIG="$git_config" cvs -Q update &&
+    cd ../.. &&
+    marked_as cvswork2 textfile.c "" &&
+    marked_as cvswork2 binfile.bin -kb &&
+    marked_as cvswork2 .gitattributes "" &&
+    marked_as cvswork2 mixedUp.c -kb &&
+    marked_as cvswork2 multiline.c -kb &&
+    marked_as cvswork2 multilineTxt.c "" &&
+    not_present cvswork2/subdir withCr.bin &&
+    not_present cvswork2/subdir file.h &&
+    marked_as cvswork2/subdir unspecified.other "" &&
+    marked_as cvswork2/subdir newfile.bin "" &&
+    marked_as cvswork2/subdir newfile.c "" &&
+    not_present cvswork2 simpleBin.bin &&
+    not_present cvswork2 simpleText.c
+'
+
+echo "starting update/merge" >> "${WORKDIR}/marked.log"
+test_expect_success 'update/merge full other copy (guess)' '
+    git pull gitcvs.git master &&
+    sed "s/3/replaced_3/" < multilineTxt.c > ml.temp &&
+    mv ml.temp multilineTxt.c &&
+    git add multilineTxt.c &&
+    git commit -q -m "modify multiline file" >> "${WORKDIR}/marked.log" &&
+    git push gitcvs.git >/dev/null &&
+    cd cvswork2 &&
+    sed "s/1/replaced_1/" < multilineTxt.c > ml.temp &&
+    mv ml.temp multilineTxt.c &&
+    GIT_CONFIG="$git_config" cvs update > cvs.log 2>&1 &&
+    cd .. &&
+    marked_as cvswork2 textfile.c "" &&
+    marked_as cvswork2 binfile.bin -kb &&
+    marked_as cvswork2 .gitattributes "" &&
+    marked_as cvswork2 mixedUp.c -kb &&
+    marked_as cvswork2 multiline.c -kb &&
+    marked_as cvswork2 multilineTxt.c "" &&
+    not_present cvswork2/subdir withCr.bin &&
+    not_present cvswork2/subdir file.h &&
+    marked_as cvswork2/subdir unspecified.other "" &&
+    marked_as cvswork2/subdir newfile.bin "" &&
+    marked_as cvswork2/subdir newfile.c "" &&
+    marked_as cvswork2 simpleBin.bin -kb &&
+    marked_as cvswork2 simpleText.c "" &&
+    echo "line replaced_1" > tmpExpect2 &&
+    echo "line 2" >> tmpExpect2 &&
+    echo "line replaced_3" >> tmpExpect2 &&
+    echo "line 4" | q_to_nul >> tmpExpect2 &&
+    cmp cvswork2/multilineTxt.c tmpExpect2
+'
+
+test_done
index 1bc16f2b65f46650c53227758df8431cf5a77598..3ff851935ff8d018a926084b89cb31bbb3d30a07 100644 (file)
@@ -203,7 +203,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport)
 }
 
 static int fetch_objs_via_rsync(struct transport *transport,
-                                int nr_objs, struct ref **to_fetch)
+                               int nr_objs, const struct ref **to_fetch)
 {
        struct strbuf buf = STRBUF_INIT;
        struct child_process rsync;
@@ -350,7 +350,7 @@ static int rsync_transport_push(struct transport *transport,
 
 #ifndef NO_CURL /* http fetch is the only user */
 static int fetch_objs_via_walker(struct transport *transport,
-                                int nr_objs, struct ref **to_fetch)
+                                int nr_objs, const struct ref **to_fetch)
 {
        char *dest = xstrdup(transport->url);
        struct walker *walker = transport->data;
@@ -517,7 +517,7 @@ static struct ref *get_refs_via_curl(struct transport *transport)
 }
 
 static int fetch_objs_via_curl(struct transport *transport,
-                                int nr_objs, struct ref **to_fetch)
+                                int nr_objs, const struct ref **to_fetch)
 {
        if (!transport->data)
                transport->data = get_http_walker(transport->url,
@@ -554,7 +554,7 @@ static struct ref *get_refs_from_bundle(struct transport *transport)
 }
 
 static int fetch_refs_from_bundle(struct transport *transport,
-                              int nr_heads, struct ref **to_fetch)
+                              int nr_heads, const struct ref **to_fetch)
 {
        struct bundle_transport_data *data = transport->data;
        return unbundle(&data->header, data->fd);
@@ -628,7 +628,7 @@ static struct ref *get_refs_via_connect(struct transport *transport)
 }
 
 static int fetch_refs_via_pack(struct transport *transport,
-                              int nr_heads, struct ref **to_fetch)
+                              int nr_heads, const struct ref **to_fetch)
 {
        struct git_transport_data *data = transport->data;
        char **heads = xmalloc(nr_heads * sizeof(*heads));
@@ -796,12 +796,12 @@ const struct ref *transport_get_remote_refs(struct transport *transport)
        return transport->remote_refs;
 }
 
-int transport_fetch_refs(struct transport *transport, struct ref *refs)
+int transport_fetch_refs(struct transport *transport, const struct ref *refs)
 {
        int rc;
        int nr_heads = 0, nr_alloc = 0;
-       struct ref **heads = NULL;
-       struct ref *rm;
+       const struct ref **heads = NULL;
+       const struct ref *rm;
 
        for (rm = refs; rm; rm = rm->next) {
                if (rm->peer_ref &&
index 8abfc0ae60c18165b44ef93ed9115972b991e2dc..d0b52053fff9bc463438674232bffb6024f3b1fc 100644 (file)
@@ -19,7 +19,7 @@ struct transport {
                          const char *value);
 
        struct ref *(*get_refs_list)(struct transport *transport);
-       int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+       int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs);
        int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
 
        int (*disconnect)(struct transport *connection);
@@ -68,7 +68,7 @@ int transport_push(struct transport *connection,
 
 const struct ref *transport_get_remote_refs(struct transport *transport);
 
-int transport_fetch_refs(struct transport *transport, struct ref *refs);
+int transport_fetch_refs(struct transport *transport, const struct ref *refs);
 void transport_unlock_pack(struct transport *transport);
 int transport_disconnect(struct transport *transport);
 
index 65c66eb0bf34efee6485db3dbf8af11788c394f4..bcdc8bbb3b44a43aa43db6035a31478158e070af 100644 (file)
@@ -31,7 +31,7 @@ int main(int argc, char **argv)
                die("Not a valid object name %s", argv[1]);
 
        setup_git_directory();
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        puts(create_temp_file(sha1));
        return 0;
index 1ab28fda45be940b300b504ae029f3fba2c28fa9..0de5a31c0b68f87491e540fdc1978809fee00240 100644 (file)
@@ -8,6 +8,36 @@
 #include "progress.h"
 #include "refs.h"
 
+/*
+ * Error messages expected by scripts out of plumbing commands such as
+ * read-tree.  Non-scripted Porcelain is not required to use these messages
+ * and in fact are encouraged to reword them to better suit their particular
+ * situation better.  See how "git checkout" replaces not_uptodate_file to
+ * explain why it does not allow switching between branches when you have
+ * local changes, for example.
+ */
+static struct unpack_trees_error_msgs unpack_plumbing_errors = {
+       /* would_overwrite */
+       "Entry '%s' would be overwritten by merge. Cannot merge.",
+
+       /* not_uptodate_file */
+       "Entry '%s' not uptodate. Cannot merge.",
+
+       /* not_uptodate_dir */
+       "Updating '%s' would lose untracked files in it",
+
+       /* would_lose_untracked */
+       "Untracked working tree file '%s' would be %s by merge.",
+
+       /* bind_overlap */
+       "Entry '%s' overlaps with '%s'.  Cannot bind.",
+};
+
+#define ERRORMSG(o,fld) \
+       ( ((o) && (o)->msgs.fld) \
+       ? ((o)->msgs.fld) \
+       : (unpack_plumbing_errors.fld) )
+
 static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
        unsigned int set, unsigned int clear)
 {
@@ -383,10 +413,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 
 /* Here come the merge functions */
 
-static int reject_merge(struct cache_entry *ce)
+static int reject_merge(struct cache_entry *ce, struct unpack_trees_options *o)
 {
-       return error("Entry '%s' would be overwritten by merge. Cannot merge.",
-                    ce->name);
+       return error(ERRORMSG(o, would_overwrite), ce->name);
 }
 
 static int same(struct cache_entry *a, struct cache_entry *b)
@@ -430,7 +459,7 @@ static int verify_uptodate(struct cache_entry *ce,
        if (errno == ENOENT)
                return 0;
        return o->gently ? -1 :
-               error("Entry '%s' not uptodate. Cannot merge.", ce->name);
+               error(ERRORMSG(o, not_uptodate_file), ce->name);
 }
 
 static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o)
@@ -517,8 +546,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
        i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
        if (i)
                return o->gently ? -1 :
-                       error("Updating '%s' would lose untracked files in it",
-                             ce->name);
+                       error(ERRORMSG(o, not_uptodate_dir), ce->name);
        free(pathbuf);
        return cnt;
 }
@@ -618,8 +646,7 @@ static int verify_absent(struct cache_entry *ce, const char *action,
                }
 
                return o->gently ? -1 :
-                       error("Untracked working tree file '%s' "
-                             "would be %s by merge.", ce->name, action);
+                       error(ERRORMSG(o, would_lose_untracked), ce->name, action);
        }
        return 0;
 }
@@ -751,7 +778,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
        /* #14, #14ALT, #2ALT */
        if (remote && !df_conflict_head && head_match && !remote_match) {
                if (index && !same(index, remote) && !same(index, head))
-                       return o->gently ? -1 : reject_merge(index);
+                       return o->gently ? -1 : reject_merge(index, o);
                return merged_entry(remote, index, o);
        }
        /*
@@ -759,7 +786,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
         * make sure that it matches head.
         */
        if (index && !same(index, head))
-               return o->gently ? -1 : reject_merge(index);
+               return o->gently ? -1 : reject_merge(index, o);
 
        if (head) {
                /* #5ALT, #15 */
@@ -901,11 +928,11 @@ int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o)
                else {
                        /* all other failures */
                        if (oldtree)
-                               return o->gently ? -1 : reject_merge(oldtree);
+                               return o->gently ? -1 : reject_merge(oldtree, o);
                        if (current)
-                               return o->gently ? -1 : reject_merge(current);
+                               return o->gently ? -1 : reject_merge(current, o);
                        if (newtree)
-                               return o->gently ? -1 : reject_merge(newtree);
+                               return o->gently ? -1 : reject_merge(newtree, o);
                        return -1;
                }
        }
@@ -931,7 +958,7 @@ int bind_merge(struct cache_entry **src,
                             o->merge_size);
        if (a && old)
                return o->gently ? -1 :
-                       error("Entry '%s' overlaps with '%s'.  Cannot bind.", a->name, old->name);
+                       error(ERRORMSG(o, bind_overlap), a->name, old->name);
        if (!a)
                return keep_entry(old, o);
        else
index d436d6ced9939beeb4599dc8fddebe0890e55db8..94e567265af9a69a30dd5c578439b6444e50004d 100644 (file)
@@ -8,6 +8,14 @@ struct unpack_trees_options;
 typedef int (*merge_fn_t)(struct cache_entry **src,
                struct unpack_trees_options *options);
 
+struct unpack_trees_error_msgs {
+       const char *would_overwrite;
+       const char *not_uptodate_file;
+       const char *not_uptodate_dir;
+       const char *would_lose_untracked;
+       const char *bind_overlap;
+};
+
 struct unpack_trees_options {
        unsigned int reset:1,
                     merge:1,
@@ -23,6 +31,7 @@ struct unpack_trees_options {
        int pos;
        struct dir_struct *dir;
        merge_fn_t fn;
+       struct unpack_trees_error_msgs msgs;
 
        int head_idx;
        int merge_size;
diff --git a/var.c b/var.c
index c20ac919bdbe73c8cd7657d468d2dfbccf6d3aa2..724ba87a7c9ccb16bc506fc3f25710a4b78e3006 100644 (file)
--- a/var.c
+++ b/var.c
@@ -39,13 +39,13 @@ static const char *read_var(const char *var)
        return val;
 }
 
-static int show_config(const char *var, const char *value)
+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);
+       return git_default_config(var, value, cb);
 }
 
 int main(int argc, char **argv)
@@ -60,11 +60,11 @@ int main(int argc, char **argv)
        val = NULL;
 
        if (strcmp(argv[1], "-l") == 0) {
-               git_config(show_config);
+               git_config(show_config, NULL);
                list_vars();
                return 0;
        }
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
        val = read_var(argv[1]);
        if (!val)
                usage(var_usage);
index 6e6516cd874a92376a672dcdc181aed6a3b5636c..5b4d74c1f3f4c24aa23cf564a16592d188449da1 100644 (file)
@@ -367,7 +367,7 @@ void wt_status_print(struct wt_status *s)
        }
 }
 
-int git_status_config(const char *k, const char *v)
+int git_status_config(const char *k, const char *v, void *cb)
 {
        if (!strcmp(k, "status.submodulesummary")) {
                int is_bool;
@@ -391,5 +391,5 @@ int git_status_config(const char *k, const char *v)
                wt_status_relative_paths = git_config_bool(k, v);
                return 0;
        }
-       return git_color_default_config(k, v);
+       return git_color_default_config(k, v, cb);
 }
index f0675fdff33420ba42f3a700501003a43afda376..597c7ea988634409f920c86008f5ba70910722f9 100644 (file)
@@ -28,7 +28,7 @@ struct wt_status {
        const char *prefix;
 };
 
-int git_status_config(const char *var, const char *value);
+int git_status_config(const char *var, const char *value, void *cb);
 extern int wt_status_use_color;
 extern int wt_status_relative_paths;
 void wt_status_prepare(struct wt_status *s);