Merge branch 'jc/add-n-u'
authorJunio C Hamano <gitster@pobox.com>
Sun, 25 May 2008 21:03:50 +0000 (14:03 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 25 May 2008 21:03:50 +0000 (14:03 -0700)
* jc/add-n-u:
  Make git add -n and git -u -n output consistent
  "git-add -n -u" should not add but just report

Conflicts:

builtin-add.c
builtin-mv.c
cache.h
read-cache.c

109 files changed:
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/config.txt
Documentation/core-tutorial.txt
Documentation/diff-options.txt
Documentation/git-add.txt
Documentation/git-cat-file.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-cvsserver.txt
Documentation/git-daemon.txt
Documentation/git-hash-object.txt
Documentation/git-push.txt
Documentation/git-repack.txt
Documentation/git-rev-parse.txt
Documentation/git-show.txt
Documentation/git-svn.txt
Documentation/git-update-index.txt
Documentation/git.txt
Documentation/gitcvs-migration.txt [moved from Documentation/cvs-migration.txt with 93% similarity]
Documentation/gittutorial-2.txt [moved from Documentation/tutorial-2.txt with 96% similarity]
Documentation/gittutorial.txt [moved from Documentation/tutorial.txt with 97% similarity]
Documentation/rev-list-options.txt
Documentation/technical/api-history-graph.txt [new file with mode: 0644]
Documentation/user-manual.txt
Makefile
builtin-add.c
builtin-apply.c
builtin-cat-file.c
builtin-checkout.c
builtin-clone.c [new file with mode: 0644]
builtin-commit.c
builtin-fetch.c
builtin-gc.c
builtin-init-db.c
builtin-log.c
builtin-mv.c
builtin-pack-objects.c
builtin-rev-list.c
builtin-rev-parse.c
builtin-send-pack.c
builtin-update-index.c
builtin.h
cache.h
contrib/examples/git-clone.sh [moved from git-clone.sh with 100% similarity]
diff.c
diff.h
environment.c
git-am.sh
git-bisect.sh
git-cvsexportcommit.perl
git-cvsimport.perl
git-cvsserver.perl
git-merge.sh
git-pull.sh
git-rebase--interactive.sh
git-rebase.sh
git-repack.sh
git-send-email.perl
git-stash.sh
git-svn.perl
git.c
gitk-git/gitk
gitk-git/po/es.po [moved from gitk-git/gitk-git/po/es.po with 100% similarity]
gitweb/gitweb.perl
graph.c [new file with mode: 0644]
graph.h [new file with mode: 0644]
hash-object.c
http-push.c
lockfile.c
log-tree.c
perl/Git.pm
read-cache.c
refs.c
refs.h
remote.c
remote.h
revision.c
revision.h
sha1_file.c
t/Makefile
t/README
t/t0050-filesystem.sh
t/t1006-cat-file.sh [new file with mode: 0755]
t/t1007-hash-object.sh [new file with mode: 0755]
t/t3700-add.sh
t/t3701-add-interactive.sh
t/t4126-apply-empty.sh [new file with mode: 0755]
t/t5303-hash-object.sh [deleted file]
t/t5511-refspec.sh
t/t5516-fetch-push.sh
t/t5520-pull.sh
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/t7701-repack-unpack-unreachable.sh [new file with mode: 0755]
t/t9001-send-email.sh
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]
t/test-lib.sh
templates/hooks--prepare-commit-msg
transport.c
transport.h
unpack-trees.c
unpack-trees.h
wt-status.c
wt-status.h

index 994eb91..d2a0a76 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 4144d1e..9750334 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
index 217980f..c298dc2 100644 (file)
@@ -523,8 +523,10 @@ color.status.<slot>::
        one of `header` (the header text of the status message),
        `added` or `updated` (files which are added but not committed),
        `changed` (files which are changed but not added in the index),
-       or `untracked` (files which are not tracked by git). The values of
-       these variables may be specified as in color.branch.<slot>.
+       `untracked` (files which are not tracked by git), or
+       `nobranch` (the color the 'no branch' warning is shown in, defaulting
+       to red). The values of these variables may be specified as in
+       color.branch.<slot>.
 
 commit.template::
        Specify a file to use as the template for new commit messages.
@@ -660,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
@@ -695,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.
 
@@ -815,6 +831,12 @@ instaweb.port::
        The port number to bind the gitweb httpd to. See
        linkgit:git-instaweb[1].
 
+log.date::
+       Set default date-time mode for the log command. Setting log.date
+       value is similar to using git log's --date option. The value is one of
+       following alternatives: {relative,local,default,iso,rfc,short}.
+       See linkgit:git-log[1].
+
 log.showroot::
        If true, the initial commit will be shown as a big creation event.
        This is equivalent to a diff against an empty tree.
index 5a55312..b50b5dd 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
 ---------------------------
index 13234fa..859d679 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 e0e730b..bb4abe2 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git-add' [-n] [-v] [-f] [--interactive | -i] [--patch | -p] [-u] [--refresh]
-          [--] <filepattern>...
+         [--ignore-errors] [--] <filepattern>...
 
 DESCRIPTION
 -----------
@@ -83,6 +83,11 @@ OPTIONS
        Don't add the file(s), but only refresh their stat()
        information in the index.
 
+\--ignore-errors::
+       If some files could not be added because of errors indexing
+       them, do not abort the operation, but continue adding the
+       others. The command shall still exit with non-zero status.
+
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
index df42cb1..f6c394c 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 9a47b4c..f75afaa 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
@@ -65,11 +65,22 @@ OPTIONS
 -w::
        Specify the location of the CVS checkout to use for the export. This
        option does not require GIT_DIR to be set before execution if the
-       current directory is within a git repository.
+       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.
 
+CONFIGURATION
+-------------
+cvsexportcommit.cvsdir::
+       The default location of the CVS checkout to use for the export.
+
 EXAMPLES
 --------
 
index b110671..a33382e 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 fd83bc7..cf261dd 100644 (file)
@@ -174,7 +174,7 @@ upload-pack::
 upload-archive::
        This serves `git-archive --remote`.  It is disabled by
        default, but a repository can enable it by setting
-       `daemon.uploadarchive` configuration item to `true`.
+       `daemon.uploadarch` configuration item to `true`.
 
 receive-pack::
        This serves `git-send-pack` clients, allowing anonymous
@@ -257,7 +257,7 @@ selectively enable/disable services per repository::
 ----------------------------------------------------------------
        [daemon]
                uploadpack = false
-               uploadarchive = true
+               uploadarch = true
 ----------------------------------------------------------------
 
 
index 33030c0..99a2143 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 f06d94e..0cc44d7 100644 (file)
@@ -46,12 +46,6 @@ specified, the same ref that <src> referred to locally).  If
 the optional leading plus `+` is used, the remote ref is updated
 even if it does not result in a fast forward update.
 +
-Note: If no explicit refspec is found, (that is neither
-on the command line nor in any Push line of the
-corresponding remotes file---see below), then "matching" heads are
-pushed: for every head that exists on the local side, the remote side is
-updated if a head of the same name already exists on the remote side.
-+
 `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
 +
 A parameter <ref> without a colon pushes the <ref> from the source
@@ -59,6 +53,13 @@ repository to the destination repository under the same name.
 +
 Pushing an empty <src> allows you to delete the <dst> ref from
 the remote repository.
++
+The special refspec `:` (or `+:` to allow non-fast forward updates)
+directs git to push "matching" heads: for every head that exists on
+the local side, the remote side is updated if a head of the same name
+already exists on the remote side.  This is the default operation mode
+if no explicit refspec is found (that is neither on the command line
+nor in any Push line of the corresponding remotes file---see below).
 
 \--all::
        Instead of naming each ref to push, specifies that all
index 3d95749..f81a660 100644 (file)
@@ -8,7 +8,7 @@ git-repack - Pack unpacked objects in a repository
 
 SYNOPSIS
 --------
-'git-repack' [-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]
+'git-repack' [-a] [-A] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]
 
 DESCRIPTION
 -----------
@@ -37,6 +37,18 @@ OPTIONS
        leaves behind, but `git fsck --full` shows as
        dangling.
 
+-A::
+       Same as `-a`, but any unreachable objects in a previous
+       pack become loose, unpacked objects, instead of being
+       left in the old pack.  Unreachable objects are never
+       intentionally added to a pack, even when repacking.
+       When used with '-d', this option
+       prevents unreachable objects from being immediately
+       deleted by way of being left in the old pack and then
+       removed.  Instead, the loose unreachable objects
+       will be pruned according to normal expiry rules
+       with the next linkgit:git-gc[1].
+
 -d::
        After packing, if the newly created packs make some
        existing packs redundant, remove the redundant packs.
@@ -55,8 +67,11 @@ OPTIONS
        linkgit:git-pack-objects[1].
 
 -n::
-        Do not update the server information with
-        `git update-server-info`.
+       Do not update the server information with
+       `git update-server-info`.  This option skips
+       updating local catalog files needed to publish
+       this repository (or a direct copy of it)
+       over HTTP or FTP.  See gitlink:git-update-server-info[1].
 
 --window=[N], --depth=[N]::
        These two options affect how the objects contained in the pack are
index b6b2fe9..69599ff 100644 (file)
@@ -378,6 +378,31 @@ C?        option C with an optional argument"
 eval `echo "$OPTS_SPEC" | git-rev-parse --parseopt -- "$@" || echo exit $?`
 ------------
 
+EXAMPLES
+--------
+
+* Print the object name of the current commit:
++
+------------
+$ git rev-parse --verify HEAD
+------------
+
+* Print the commit object name from the revision in the $REV shell variable:
++
+------------
+$ git rev-parse --verify $REV
+------------
++
+This will error out if $REV is empty or not a valid revision.
+
+* Same as above:
++
+------------
+$ git rev-parse --default master --verify $REV
+------------
++
+but if $REV is empty, the commit object name from master will be printed.
+
 
 Author
 ------
index dccf0e2..29ed0ac 100644 (file)
@@ -79,8 +79,6 @@ Documentation
 -------------
 Documentation by David Greaves, Petr Baudis and the git-list <git@vger.kernel.org>.
 
-This manual page is a stub. You can help the git documentation by expanding it.
-
 GIT
 ---
 Part of the linkgit:git[7] suite
index c6b56b4..c9e4efe 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
@@ -196,10 +206,10 @@ Any other arguments are passed directly to `git log'
        independently of git-svn functions.
 
 'create-ignore'::
-
        Recursively finds the svn:ignore property on directories and
        creates matching .gitignore files. The resulting files are staged to
-       be committed, but are not committed.
+       be committed, but are not committed. Use -r/--revision to refer to a
+       specfic revision.
 
 'show-ignore'::
        Recursively finds and lists the svn:ignore property on
@@ -223,6 +233,19 @@ Any other arguments are passed directly to `git log'
        argument.  Use the --url option to output only the value of the
        'URL:' field.
 
+'proplist'::
+       Lists the properties stored in the Subversion repository about a
+       given file or directory.  Use -r/--revision to refer to a specific
+       Subversion revision.
+
+'propget'::
+       Gets the Subversion property given as the first argument, for a
+       file.  A specific revision can be specified with -r/--revision.
+
+'show-externals'::
+       Shows the Subversion externals.  Use -r/--revision to specify a
+       specific revision.
+
 --
 
 OPTIONS
index 66be18e..0664060 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 adcd3e0..735f0d1 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.
 
similarity index 93%
rename from Documentation/cvs-migration.txt
rename to Documentation/gitcvs-migration.txt
index 374bc87..c410805 100644 (file)
@@ -1,5 +1,16 @@
-git for CVS users
-=================
+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
@@ -8,7 +19,7 @@ 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
+linkgit:gittutorial[7][tutorial introduction to git] and the
 link:glossary.html[git glossary] should be sufficient.
 
 Developing against a shared repository
@@ -71,7 +82,7 @@ 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
+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"
@@ -170,3 +181,13 @@ 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.
similarity index 96%
rename from Documentation/tutorial-2.txt
rename to Documentation/gittutorial-2.txt
index 7fac47d..5bbbf43 100644 (file)
@@ -1,7 +1,18 @@
-A tutorial introduction to git: part two
-========================================
+gittutorial-2(7)
+================
 
-You should work through link:tutorial.html[A tutorial introduction to
+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
@@ -394,7 +405,7 @@ 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
+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.
 
@@ -404,3 +415,14 @@ 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.
similarity index 97%
rename from Documentation/tutorial.txt
rename to Documentation/gittutorial.txt
index e2bbda5..898acdb 100644 (file)
@@ -1,5 +1,16 @@
-A tutorial introduction to git (for version 1.5.1 or newer)
-===========================================================
+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.
@@ -381,7 +392,7 @@ 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].
+linkgit:gitcvs-migration[7][git for CVS users].
 
 Exploring history
 -----------------
@@ -560,7 +571,7 @@ is based:
     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
+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.
 
@@ -581,4 +592,15 @@ digressions that may be interesting at this point are:
 
   * link:everyday.html[Everyday GIT with 20 Commands Or So]
 
-  * link:cvs-migration.html[git for CVS users].
+  * 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 2648a55..dfcef79 100644 (file)
@@ -13,10 +13,11 @@ include::pretty-options.txt[]
 
        Synonym for `--date=relative`.
 
---date={relative,local,default,iso,rfc}::
+--date={relative,local,default,iso,rfc,short}::
 
        Only takes effect for dates shown in human-readable format, such
-       as when using "--pretty".
+       as when using "--pretty". `log.date` config variable sets a default
+       value for log command's --date option.
 +
 `--date=relative` shows dates relative to the current time,
 e.g. "2 hours ago".
@@ -75,6 +76,16 @@ you would get an output line this:
        -xxxxxxx... 1st on a
 -----------------------------------------------------------------------
 
+--graph::
+
+       Draw a text-based graphical representation of the commit history
+       on the left hand side of the output.  This may cause extra lines
+       to be printed in between commits, in order for the graph history
+       to be drawn properly.
++
+This implies the '--topo-order' option by default, but the
+'--date-order' option may also be specified.
+
 Diff Formatting
 ~~~~~~~~~~~~~~~
 
diff --git a/Documentation/technical/api-history-graph.txt b/Documentation/technical/api-history-graph.txt
new file mode 100644 (file)
index 0000000..ce1c08e
--- /dev/null
@@ -0,0 +1,179 @@
+history graph API
+=================
+
+The graph API is used to draw a text-based representation of the commit
+history.  The API generates the graph in a line-by-line fashion.
+
+Functions
+---------
+
+Core functions:
+
+* `graph_init()` creates a new `struct git_graph`
+
+* `graph_release()` destroys a `struct git_graph`, and frees the memory
+  associated with it.
+
+* `graph_update()` moves the graph to a new commit.
+
+* `graph_next_line()` outputs the next line of the graph into a strbuf.  It
+  does not add a terminating newline.
+
+* `graph_padding_line()` outputs a line of vertical padding in the graph.  It
+  is similar to `graph_next_line()`, but is guaranteed to never print the line
+  containing the current commit.  Where `graph_next_line()` would print the
+  commit line next, `graph_padding_line()` prints a line that simply extends
+  all branch lines downwards one row, leaving their positions unchanged.
+
+* `graph_is_commit_finished()` determines if the graph has output all lines
+  necessary for the current commit.  If `graph_update()` is called before all
+  lines for the current commit have been printed, the next call to
+  `graph_next_line()` will output an ellipsis, to indicate that a portion of
+  the graph was omitted.
+
+The following utility functions are wrappers around `graph_next_line()` and
+`graph_is_commit_finished()`.  They always print the output to stdout.
+They can all be called with a NULL graph argument, in which case no graph
+output will be printed.
+
+* `graph_show_commit()` calls `graph_next_line()` until it returns non-zero.
+  This prints all graph lines up to, and including, the line containing this
+  commit.  Output is printed to stdout.  The last line printed does not contain
+  a terminating newline.  This should not be called if the commit line has
+  already been printed, or it will loop forever.
+
+* `graph_show_oneline()` calls `graph_next_line()` and prints the result to
+  stdout.  The line printed does not contain a terminating newline.
+
+* `graph_show_padding()` calls `graph_padding_line()` and prints the result to
+  stdout.  The line printed does not contain a terminating newline.
+
+* `graph_show_remainder()` calls `graph_next_line()` until
+  `graph_is_commit_finished()` returns non-zero.  Output is printed to stdout.
+  The last line printed does not contain a terminating newline.  Returns 1 if
+  output was printed, and 0 if no output was necessary.
+
+* `graph_show_strbuf()` prints the specified strbuf to stdout, prefixing all
+  lines but the first with a graph line.  The caller is responsible for
+  ensuring graph output for the first line has already been printed to stdout.
+  (This can be done with `graph_show_commit()` or `graph_show_oneline()`.)  If
+  a NULL graph is supplied, the strbuf is printed as-is.
+
+* `graph_show_commit_msg()` is similar to `graph_show_strbuf()`, but it also
+  prints the remainder of the graph, if more lines are needed after the strbuf
+  ends.  It is better than directly calling `graph_show_strbuf()` followed by
+  `graph_show_remainder()` since it properly handles buffers that do not end in
+  a terminating newline.  The output printed by `graph_show_commit_msg()` will
+  end in a newline if and only if the strbuf ends in a newline.
+
+Data structure
+--------------
+`struct git_graph` is an opaque data type used to store the current graph
+state.
+
+Calling sequence
+----------------
+
+* Create a `struct git_graph` by calling `graph_init()`.  When using the
+  revision walking API, this is done automatically by `setup_revisions()` if
+  the '--graph' option is supplied.
+
+* Use the revision walking API to walk through a group of contiguous commits.
+  The `get_revision()` function automatically calls `graph_update()` each time
+  it is invoked.
+
+* For each commit, call `graph_next_line()` repeatedly, until
+  `graph_is_commit_finished()` returns non-zero.  Each call go
+  `graph_next_line()` will output a single line of the graph.  The resulting
+  lines will not contain any newlines.  `graph_next_line()` returns 1 if the
+  resulting line contains the current commit, or 0 if this is merely a line
+  needed to adjust the graph before or after the current commit.  This return
+  value can be used to determine where to print the commit summary information
+  alongside the graph output.
+
+Limitations
+-----------
+
+* `graph_update()` must be called with commits in topological order.  It should
+  not be called on a commit if it has already been invoked with an ancestor of
+  that commit, or the graph output will be incorrect.
+
+* `graph_update()` must be called on a contiguous group of commits.  If
+  `graph_update()` is called on a particular commit, it should later be called
+  on all parents of that commit.  Parents must not be skipped, or the graph
+  output will appear incorrect.
++
+`graph_update()` may be used on a pruned set of commits only if the parent list
+has been rewritten so as to include only ancestors from the pruned set.
+
+* The graph API does not currently support reverse commit ordering.  In
+  order to implement reverse ordering, the graphing API needs an
+  (efficient) mechanism to find the children of a commit.
+
+Sample usage
+------------
+
+------------
+struct commit *commit;
+struct git_graph *graph = graph_init();
+
+while ((commit = get_revision(opts)) != NULL) {
+       graph_update(graph, commit);
+       while (!graph_is_commit_finished(graph))
+       {
+               struct strbuf sb;
+               int is_commit_line;
+
+               strbuf_init(&sb, 0);
+               is_commit_line = graph_next_line(graph, &sb);
+               fputs(sb.buf, stdout);
+
+               if (is_commit_line)
+                       log_tree_commit(opts, commit);
+               else
+                       putchar(opts->diffopt.line_termination);
+       }
+}
+
+graph_release(graph);
+------------
+
+Sample output
+-------------
+
+The following is an example of the output from the graph API.  This output does
+not include any commit summary information--callers are responsible for
+outputting that information, if desired.
+
+------------
+*
+*
+M
+|\
+* |
+| | *
+| \ \
+|  \ \
+M-. \ \
+|\ \ \ \
+| | * | |
+| | | | | *
+| | | | | *
+| | | | | M
+| | | | | |\
+| | | | | | *
+| * | | | | |
+| | | | | M  \
+| | | | | |\  |
+| | | | * | | |
+| | | | * | | |
+* | | | | | | |
+| |/ / / / / /
+|/| / / / / /
+* | | | | | |
+|/ / / / / /
+* | | | | |
+| | | | | *
+| | | | |/
+| | | | *
+------------
index e2db850..fd8cdb6 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 a2de075..f08d5f7 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
@@ -346,6 +345,7 @@ LIB_H += diff.h
 LIB_H += dir.h
 LIB_H += fsck.h
 LIB_H += git-compat-util.h
+LIB_H += graph.h
 LIB_H += grep.h
 LIB_H += hash.h
 LIB_H += list-objects.h
@@ -411,6 +411,7 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
 LIB_OBJS += help.o
@@ -482,6 +483,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
index 8f81f3f..6e4e645 100644 (file)
@@ -79,12 +79,18 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec,
                prune_directory(dir, pathspec, baselen);
 }
 
+struct update_callback_data
+{
+       int flags;
+       int add_errors;
+};
+
 static void update_callback(struct diff_queue_struct *q,
                            struct diff_options *opt, void *cbdata)
 {
-       int i, flags;
+       int i;
+       struct update_callback_data *data = cbdata;
 
-       flags = *((int *)cbdata);
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
                const char *path = p->one->path;
@@ -94,28 +100,36 @@ static void update_callback(struct diff_queue_struct *q,
                case DIFF_STATUS_UNMERGED:
                case DIFF_STATUS_MODIFIED:
                case DIFF_STATUS_TYPE_CHANGED:
-                       add_file_to_cache(path, flags);
+                       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:
-                       if (!(flags & ADD_CACHE_PRETEND))
+                       if (!(data->flags & ADD_CACHE_PRETEND))
                                remove_file_from_cache(path);
-                       if (flags)
+                       if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
                                printf("remove '%s'\n", path);
                        break;
                }
        }
 }
 
-void add_files_to_cache(const char *prefix, const char **pathspec, int flags)
+int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
 {
+       struct update_callback_data data;
        struct rev_info rev;
        init_revisions(&rev, prefix);
        setup_revisions(0, NULL, &rev, NULL);
        rev.prune_data = pathspec;
        rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = update_callback;
-       rev.diffopt.format_callback_data = &flags;
+       data.flags = flags;
+       data.add_errors = 0;
+       rev.diffopt.format_callback_data = &data;
        run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+       return !!data.add_errors;
 }
 
 static void refresh(int verbose, const char **pathspec)
@@ -178,6 +192,7 @@ static const char ignore_error[] =
 "The following paths are ignored by one of your .gitignore files:\n";
 
 static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
+static int ignore_add_errors;
 
 static struct option builtin_add_options[] = {
        OPT__DRY_RUN(&show_only),
@@ -188,11 +203,22 @@ static struct option builtin_add_options[] = {
        OPT_BOOLEAN('f', NULL, &ignored_too, "allow adding otherwise ignored files"),
        OPT_BOOLEAN('u', NULL, &take_worktree_changes, "update tracked files"),
        OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
+       OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
        OPT_END(),
 };
 
+static int add_config(const char *var, const char *value)
+{
+       if (!strcasecmp(var, "add.ignore-errors")) {
+               ignore_add_errors = git_config_bool(var, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
 int cmd_add(int argc, const char **argv, const char *prefix)
 {
+       int exit_status = 0;
        int i, newfd;
        const char **pathspec;
        struct dir_struct dir;
@@ -205,19 +231,20 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        if (add_interactive)
                exit(interactive_add(argc, argv, prefix));
 
-       git_config(git_default_config);
+       git_config(add_config);
 
        newfd = hold_locked_index(&lock_file, 1);
 
        flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
-                (show_only ? ADD_CACHE_PRETEND : 0));
+                (show_only ? ADD_CACHE_PRETEND : 0) |
+                (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0));
 
        if (take_worktree_changes) {
                const char **pathspec;
                if (read_cache() < 0)
                        die("index file corrupt");
                pathspec = get_pathspec(prefix, argv);
-               add_files_to_cache(prefix, pathspec, flags);
+               exit_status = add_files_to_cache(prefix, pathspec, flags);
                goto finish;
        }
 
@@ -248,7 +275,11 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        }
 
        for (i = 0; i < dir.nr; i++)
-               add_file_to_cache(dir.entries[i]->name, flags);
+               if (add_file_to_cache(dir.entries[i]->name, flags)) {
+                       if (!ignore_add_errors)
+                               die("adding files failed");
+                       exit_status = 1;
+               }
 
  finish:
        if (active_cache_changed) {
@@ -257,5 +288,5 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        die("Unable to write new index file");
        }
 
-       return 0;
+       return exit_status;
 }
index 1103625..1540f28 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))
                /*
index f132d58..5ef15a4 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);
+
+       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 05c0642..68fffd2 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()) {
diff --git a/builtin-clone.c b/builtin-clone.c
new file mode 100644 (file)
index 0000000..2a3f673
--- /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);
+
+       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 924fca1..d752243 100644 (file)
@@ -179,9 +179,10 @@ static void add_remove_files(struct path_list *list)
                struct stat st;
                struct path_list_item *p = &(list->items[i]);
 
-               if (!lstat(p->path, &st))
-                       add_to_cache(p->path, &st, 0);
-               else
+               if (!lstat(p->path, &st)) {
+                       if (add_to_cache(p->path, &st, 0))
+                               die("updating files failed");
+               } else
                        remove_file_from_cache(p->path);
        }
 }
index f6584ec..bfe7711 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 f99ebc7..48f7d95 100644 (file)
@@ -219,7 +219,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        char buf[80];
 
        struct option builtin_gc_options[] = {
-               OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects"),
+               OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects (deprecated)"),
                OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
                OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
                OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"),
@@ -249,24 +249,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                /*
                 * Auto-gc should be least intrusive as possible.
                 */
-               prune = 0;
                if (!need_to_gc())
                        return 0;
                fprintf(stderr, "Auto packing your repository for optimum "
                        "performance. You may also\n"
                        "run \"git gc\" manually. See "
                        "\"git help gc\" for more information.\n");
-       } else {
-               /*
-                * Use safer (for shared repos) "-A" option to
-                * repack when not pruning. Auto-gc makes its
-                * own decision.
-                */
-               if (prune)
-                       append_option(argv_repack, "-a", MAX_ADD);
-               else
-                       append_option(argv_repack, "-A", MAX_ADD);
-       }
+       } else
+               append_option(argv_repack, "-A", MAX_ADD);
 
        if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
                return error(FAILED_RUN, argv_pack_refs[0]);
index a76f5d3..3968c99 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);
@@ -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,19 +188,15 @@ 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);
 
@@ -204,14 +205,10 @@ static int create_default_files(const char *git_dir, const char *template_path)
         * 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,12 +248,14 @@ 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);
+               }
        }
 
-       /* Check if symlink is supported in the work tree */
        if (!reinit) {
+               /* Check if symlink is supported in the work tree */
                path[len] = 0;
                strcpy(path + len, "tXXXXXX");
                if (!close(xmkstemp(path)) &&
@@ -267,47 +266,101 @@ static int create_default_files(const char *git_dir, const char *template_path)
                        unlink(path); /* good */
                else
                        git_config_set("core.symlinks", "false");
+
+               /* Check if the filesystem is case-insensitive */
+               path[len] = 0;
+               strcpy(path + len, "CoNfIg");
+               if (!access(path, F_OK))
+                       git_config_set("core.ignorecase", "true");
        }
 
        return reinit;
 }
 
-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[] =
@@ -322,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];
@@ -337,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);
        }
@@ -354,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 9d046b2..543855b 100644 (file)
@@ -18,6 +18,9 @@
 #include "run-command.h"
 #include "shortlog.h"
 
+/* Set a default date-time format for git log ("log.date" config variable) */
+static const char *default_date_mode = NULL;
+
 static int default_show_root = 1;
 static const char *fmt_patch_subject_prefix = "PATCH";
 static const char *fmt_pretty;
@@ -61,7 +64,12 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
        DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
        rev->show_root_diff = default_show_root;
        rev->subject_prefix = fmt_patch_subject_prefix;
+
+       if (default_date_mode)
+               rev->date_mode = parse_date_format(default_date_mode);
+
        argc = setup_revisions(argc, argv, rev, "HEAD");
+
        if (rev->diffopt.pickaxe || rev->diffopt.filter)
                rev->always_show_header = 0;
        if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
@@ -232,6 +240,8 @@ static int git_log_config(const char *var, const char *value)
                fmt_patch_subject_prefix = xstrdup(value);
                return 0;
        }
+       if (!strcmp(var, "log.date"))
+               return git_config_string(&default_date_mode, var, value);
        if (!strcmp(var, "log.showroot")) {
                default_show_root = git_config_bool(var, value);
                return 0;
index df9ea97..fb906b3 100644 (file)
@@ -256,7 +256,8 @@ 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;
-                       add_file_to_cache(path, verbose ? ADD_CACHE_VERBOSE : 0);
+                       if (add_file_to_cache(path, verbose ? ADD_CACHE_VERBOSE : 0))
+                               die("updating index entries failed");
                }
 
                for (i = 0; i < deleted.nr; i++)
index 777f272..f43eb67 100644 (file)
@@ -28,7 +28,8 @@ git-pack-objects [{ -q | --progress | --all-progress }] \n\
        [--window=N] [--window-memory=N] [--depth=N] \n\
        [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\
        [--threads=N] [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
-       [--stdout | base-name] [--include-tag] [--keep-unreachable] \n\
+       [--stdout | base-name] [--include-tag] \n\
+       [--keep-unreachable | --unpack-unreachable] \n\
        [<ref-list | <object-list]";
 
 struct object_entry {
@@ -43,6 +44,7 @@ struct object_entry {
                                             */
        void *delta_data;       /* cached delta (uncompressed) */
        unsigned long delta_size;       /* delta data size (uncompressed) */
+       unsigned long z_delta_size;     /* delta data size (compressed) */
        unsigned int hash;      /* name hint hash */
        enum object_type type;
        enum object_type in_pack_type;  /* could be delta */
@@ -65,7 +67,8 @@ static struct pack_idx_entry **written_list;
 static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
 
 static int non_empty;
-static int no_reuse_delta, no_reuse_object, keep_unreachable, include_tag;
+static int reuse_delta = 1, reuse_object = 1;
+static int keep_unreachable, unpack_unreachable, include_tag;
 static int local;
 static int incremental;
 static int allow_ofs_delta;
@@ -102,24 +105,53 @@ static uint32_t written, written_delta;
 static uint32_t reused, reused_delta;
 
 
-static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
+static void *get_delta(struct object_entry *entry)
 {
-       unsigned long othersize, delta_size;
+       unsigned long size, base_size, delta_size;
+       void *buf, *base_buf, *delta_buf;
        enum object_type type;
-       void *otherbuf = read_sha1_file(entry->delta->idx.sha1, &type, &othersize);
-       void *delta_buf;
 
-       if (!otherbuf)
+       buf = read_sha1_file(entry->idx.sha1, &type, &size);
+       if (!buf)
+               die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+       base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size);
+       if (!base_buf)
                die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1));
-        delta_buf = diff_delta(otherbuf, othersize,
+       delta_buf = diff_delta(base_buf, base_size,
                               buf, size, &delta_size, 0);
-        if (!delta_buf || delta_size != entry->delta_size)
+       if (!delta_buf || delta_size != entry->delta_size)
                die("delta size changed");
-        free(buf);
-        free(otherbuf);
+       free(buf);
+       free(base_buf);
        return delta_buf;
 }
 
+static unsigned long do_compress(void **pptr, unsigned long size)
+{
+       z_stream stream;
+       void *in, *out;
+       unsigned long maxsize;
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, pack_compression_level);
+       maxsize = deflateBound(&stream, size);
+
+       in = *pptr;
+       out = xmalloc(maxsize);
+       *pptr = out;
+
+       stream.next_in = in;
+       stream.avail_in = size;
+       stream.next_out = out;
+       stream.avail_out = maxsize;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               ; /* nothing */
+       deflateEnd(&stream);
+
+       free(in);
+       return stream.total_out;
+}
+
 /*
  * The per-object header is a pretty dense thing, which is
  *  - first byte: low four bits are "size", then three bits of "type",
@@ -222,42 +254,42 @@ static unsigned long write_object(struct sha1file *f,
                                  struct object_entry *entry,
                                  off_t write_offset)
 {
-       unsigned long size;
-       enum object_type type;
+       unsigned long size, limit, datalen;
        void *buf;
-       unsigned char header[10];
-       unsigned char dheader[10];
+       unsigned char header[10], dheader[10];
        unsigned hdrlen;
-       off_t datalen;
-       enum object_type obj_type;
-       int to_reuse = 0;
-       /* write limit if limited packsize and not first object */
-       unsigned long limit = pack_size_limit && nr_written ?
-                               pack_size_limit - write_offset : 0;
-                               /* no if no delta */
-       int usable_delta =      !entry->delta ? 0 :
-                               /* yes if unlimited packfile */
-                               !pack_size_limit ? 1 :
-                               /* no if base written to previous pack */
-                               entry->delta->idx.offset == (off_t)-1 ? 0 :
-                               /* otherwise double-check written to this
-                                * pack,  like we do below
-                                */
-                               entry->delta->idx.offset ? 1 : 0;
+       enum object_type type;
+       int usable_delta, to_reuse;
 
        if (!pack_to_stdout)
                crc32_begin(f);
 
-       obj_type = entry->type;
-       if (no_reuse_object)
+       type = entry->type;
+
+       /* write limit if limited packsize and not first object */
+       limit = pack_size_limit && nr_written ?
+                       pack_size_limit - write_offset : 0;
+
+       if (!entry->delta)
+               usable_delta = 0;       /* no delta */
+       else if (!pack_size_limit)
+              usable_delta = 1;        /* unlimited packfile */
+       else if (entry->delta->idx.offset == (off_t)-1)
+               usable_delta = 0;       /* base was written to another pack */
+       else if (entry->delta->idx.offset)
+               usable_delta = 1;       /* base already exists in this pack */
+       else
+               usable_delta = 0;       /* base could end up in another pack */
+
+       if (!reuse_object)
                to_reuse = 0;   /* explicit */
        else if (!entry->in_pack)
                to_reuse = 0;   /* can't reuse what we don't have */
-       else if (obj_type == OBJ_REF_DELTA || obj_type == OBJ_OFS_DELTA)
+       else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA)
                                /* check_object() decided it for us ... */
                to_reuse = usable_delta;
                                /* ... but pack split may override that */
-       else if (obj_type != entry->in_pack_type)
+       else if (type != entry->in_pack_type)
                to_reuse = 0;   /* pack has delta which is unusable */
        else if (entry->delta)
                to_reuse = 0;   /* we want to pack afresh */
@@ -267,50 +299,42 @@ static unsigned long write_object(struct sha1file *f,
                                 */
 
        if (!to_reuse) {
-               z_stream stream;
-               unsigned long maxsize;
-               void *out;
                if (!usable_delta) {
-                       buf = read_sha1_file(entry->idx.sha1, &obj_type, &size);
+                       buf = read_sha1_file(entry->idx.sha1, &type, &size);
                        if (!buf)
                                die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+                       /*
+                        * make sure no cached delta data remains from a
+                        * previous attempt before a pack split occured.
+                        */
+                       free(entry->delta_data);
+                       entry->delta_data = NULL;
+                       entry->z_delta_size = 0;
                } else if (entry->delta_data) {
                        size = entry->delta_size;
                        buf = entry->delta_data;
                        entry->delta_data = NULL;
-                       obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
+                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
                                OBJ_OFS_DELTA : OBJ_REF_DELTA;
                } else {
-                       buf = read_sha1_file(entry->idx.sha1, &type, &size);
-                       if (!buf)
-                               die("unable to read %s", sha1_to_hex(entry->idx.sha1));
-                       buf = delta_against(buf, size, entry);
+                       buf = get_delta(entry);
                        size = entry->delta_size;
-                       obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
+                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
                                OBJ_OFS_DELTA : OBJ_REF_DELTA;
                }
-               /* compress the data to store and put compressed length in datalen */
-               memset(&stream, 0, sizeof(stream));
-               deflateInit(&stream, pack_compression_level);
-               maxsize = deflateBound(&stream, size);
-               out = xmalloc(maxsize);
-               /* Compress it */
-               stream.next_in = buf;
-               stream.avail_in = size;
-               stream.next_out = out;
-               stream.avail_out = maxsize;
-               while (deflate(&stream, Z_FINISH) == Z_OK)
-                       /* nothing */;
-               deflateEnd(&stream);
-               datalen = stream.total_out;
+
+               if (entry->z_delta_size)
+                       datalen = entry->z_delta_size;
+               else
+                       datalen = do_compress(&buf, size);
 
                /*
                 * The object header is a byte of 'type' followed by zero or
                 * more bytes of length.
                 */
-               hdrlen = encode_header(obj_type, size, header);
+               hdrlen = encode_header(type, size, header);
 
-               if (obj_type == OBJ_OFS_DELTA) {
+               if (type == OBJ_OFS_DELTA) {
                        /*
                         * Deltas with relative base contain an additional
                         * encoding of the relative offset for the delta
@@ -322,20 +346,18 @@ static unsigned long write_object(struct sha1file *f,
                        while (ofs >>= 7)
                                dheader[--pos] = 128 | (--ofs & 127);
                        if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
-                               free(out);
                                free(buf);
                                return 0;
                        }
                        sha1write(f, header, hdrlen);
                        sha1write(f, dheader + pos, sizeof(dheader) - pos);
                        hdrlen += sizeof(dheader) - pos;
-               } else if (obj_type == OBJ_REF_DELTA) {
+               } else if (type == OBJ_REF_DELTA) {
                        /*
                         * Deltas with a base reference contain
                         * an additional 20 bytes for the base sha1.
                         */
                        if (limit && hdrlen + 20 + datalen + 20 >= limit) {
-                               free(out);
                                free(buf);
                                return 0;
                        }
@@ -344,14 +366,12 @@ static unsigned long write_object(struct sha1file *f,
                        hdrlen += 20;
                } else {
                        if (limit && hdrlen + datalen + 20 >= limit) {
-                               free(out);
                                free(buf);
                                return 0;
                        }
                        sha1write(f, header, hdrlen);
                }
-               sha1write(f, out, datalen);
-               free(out);
+               sha1write(f, buf, datalen);
                free(buf);
        }
        else {
@@ -361,11 +381,11 @@ static unsigned long write_object(struct sha1file *f,
                off_t offset;
 
                if (entry->delta) {
-                       obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
+                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
                                OBJ_OFS_DELTA : OBJ_REF_DELTA;
                        reused_delta++;
                }
-               hdrlen = encode_header(obj_type, entry->size, header);
+               hdrlen = encode_header(type, entry->size, header);
                offset = entry->in_pack_offset;
                revidx = find_pack_revindex(p, offset);
                datalen = revidx[1].offset - offset;
@@ -374,7 +394,7 @@ static unsigned long write_object(struct sha1file *f,
                        die("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
                offset += entry->in_pack_header_size;
                datalen -= entry->in_pack_header_size;
-               if (obj_type == OBJ_OFS_DELTA) {
+               if (type == OBJ_OFS_DELTA) {
                        off_t ofs = entry->idx.offset - entry->delta->idx.offset;
                        unsigned pos = sizeof(dheader) - 1;
                        dheader[pos] = ofs & 127;
@@ -385,7 +405,7 @@ static unsigned long write_object(struct sha1file *f,
                        sha1write(f, header, hdrlen);
                        sha1write(f, dheader + pos, sizeof(dheader) - pos);
                        hdrlen += sizeof(dheader) - pos;
-               } else if (obj_type == OBJ_REF_DELTA) {
+               } else if (type == OBJ_REF_DELTA) {
                        if (limit && hdrlen + 20 + datalen + 20 >= limit)
                                return 0;
                        sha1write(f, header, hdrlen);
@@ -452,11 +472,10 @@ static void write_pack_file(void)
        struct sha1file *f;
        off_t offset, offset_one, last_obj_offset = 0;
        struct pack_header hdr;
-       int do_progress = progress >> pack_to_stdout;
        uint32_t nr_remaining = nr_result;
        time_t last_mtime = 0;
 
-       if (do_progress)
+       if (progress > pack_to_stdout)
                progress_state = start_progress("Writing objects", nr_result);
        written_list = xmalloc(nr_objects * sizeof(*written_list));
 
@@ -1022,7 +1041,7 @@ static void check_object(struct object_entry *entry)
                        unuse_pack(&w_curs);
                        return;
                case OBJ_REF_DELTA:
-                       if (!no_reuse_delta && !entry->preferred_base)
+                       if (reuse_delta && !entry->preferred_base)
                                base_ref = use_pack(p, &w_curs,
                                                entry->in_pack_offset + used, NULL);
                        entry->in_pack_header_size = used + 20;
@@ -1045,7 +1064,7 @@ static void check_object(struct object_entry *entry)
                                die("delta base offset out of bound for %s",
                                    sha1_to_hex(entry->idx.sha1));
                        ofs = entry->in_pack_offset - ofs;
-                       if (!no_reuse_delta && !entry->preferred_base) {
+                       if (reuse_delta && !entry->preferred_base) {
                                struct revindex_entry *revidx;
                                revidx = find_pack_revindex(p, ofs);
                                base_ref = nth_packed_object_sha1(p, revidx->nr);
@@ -1233,7 +1252,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
         * We do not bother to try a delta that we discarded
         * on an earlier try, but only when reusing delta data.
         */
-       if (!no_reuse_delta && trg_entry->in_pack &&
+       if (reuse_delta && trg_entry->in_pack &&
            trg_entry->in_pack == src_entry->in_pack &&
            trg_entry->in_pack_type != OBJ_REF_DELTA &&
            trg_entry->in_pack_type != OBJ_OFS_DELTA)
@@ -1441,11 +1460,34 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
                                best_base = other_idx;
                }
 
+               /*
+                * If we decided to cache the delta data, then it is best
+                * to compress it right away.  First because we have to do
+                * it anyway, and doing it here while we're threaded will
+                * save a lot of time in the non threaded write phase,
+                * as well as allow for caching more deltas within
+                * the same cache size limit.
+                * ...
+                * But only if not writing to stdout, since in that case
+                * the network is most likely throttling writes anyway,
+                * and therefore it is best to go to the write phase ASAP
+                * instead, as we can afford spending more time compressing
+                * between writes at that moment.
+                */
+               if (entry->delta_data && !pack_to_stdout) {
+                       entry->z_delta_size = do_compress(&entry->delta_data,
+                                                         entry->delta_size);
+                       cache_lock();
+                       delta_cache_size -= entry->delta_size;
+                       delta_cache_size += entry->z_delta_size;
+                       cache_unlock();
+               }
+
                /* if we made n a delta, and if n is already at max
                 * depth, leaving it in the window is pointless.  we
                 * should evict it first.
                 */
-               if (entry->delta && depth <= n->depth)
+               if (entry->delta && max_depth <= n->depth)
                        continue;
 
                /*
@@ -1688,7 +1730,7 @@ static void prepare_pack(int window, int depth)
 
                if (entry->delta)
                        /* This happens if we decided to reuse existing
-                        * delta from a pack.  "!no_reuse_delta &&" is implied.
+                        * delta from a pack.  "reuse_delta &&" is implied.
                         */
                        continue;
 
@@ -1905,6 +1947,32 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
        free(in_pack.array);
 }
 
+static void loosen_unused_packed_objects(struct rev_info *revs)
+{
+       struct packed_git *p;
+       uint32_t i;
+       const unsigned char *sha1;
+
+       for (p = packed_git; p; p = p->next) {
+               for (i = 0; i < revs->num_ignore_packed; i++) {
+                       if (matches_pack_name(p, revs->ignore_packed[i]))
+                               break;
+               }
+               if (revs->num_ignore_packed <= i)
+                       continue;
+
+               if (open_pack_index(p))
+                       die("cannot open pack index");
+
+               for (i = 0; i < p->num_objects; i++) {
+                       sha1 = nth_packed_object_sha1(p, i);
+                       if (!locate_object_entry(sha1))
+                               if (force_object_loose(sha1, p->mtime))
+                                       die("unable to force loose object");
+               }
+       }
+}
+
 static void get_object_list(int ac, const char **av)
 {
        struct rev_info revs;
@@ -1939,6 +2007,8 @@ static void get_object_list(int ac, const char **av)
 
        if (keep_unreachable)
                add_objects_in_unpacked_packs(&revs);
+       if (unpack_unreachable)
+               loosen_unused_packed_objects(&revs);
 }
 
 static int adjust_perm(const char *path, mode_t mode)
@@ -2050,11 +2120,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!strcmp("--no-reuse-delta", arg)) {
-                       no_reuse_delta = 1;
+                       reuse_delta = 0;
                        continue;
                }
                if (!strcmp("--no-reuse-object", arg)) {
-                       no_reuse_object = no_reuse_delta = 1;
+                       reuse_object = reuse_delta = 0;
                        continue;
                }
                if (!strcmp("--delta-base-offset", arg)) {
@@ -2073,6 +2143,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        keep_unreachable = 1;
                        continue;
                }
+               if (!strcmp("--unpack-unreachable", arg)) {
+                       unpack_unreachable = 1;
+                       continue;
+               }
                if (!strcmp("--include-tag", arg)) {
                        include_tag = 1;
                        continue;
@@ -2138,6 +2212,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (!pack_to_stdout && thin)
                die("--thin cannot be used to build an indexable pack.");
 
+       if (keep_unreachable && unpack_unreachable)
+               die("--keep-unreachable and --unpack-unreachable are incompatible.");
+
 #ifdef THREADED_DELTA_SEARCH
        if (!delta_search_threads)      /* --threads=0 means autodetect */
                delta_search_threads = online_cpus();
index edc0bd3..54d55cc 100644 (file)
@@ -10,6 +10,7 @@
 #include "list-objects.h"
 #include "builtin.h"
 #include "log-tree.h"
+#include "graph.h"
 
 /* bits #0-15 in revision.h */
 
@@ -58,6 +59,8 @@ static const char *header_prefix;
 static void finish_commit(struct commit *commit);
 static void show_commit(struct commit *commit)
 {
+       graph_show_commit(revs.graph);
+
        if (show_timestamp)
                printf("%lu ", commit->date);
        if (header_prefix)
@@ -77,7 +80,7 @@ static void show_commit(struct commit *commit)
                      stdout);
        else
                fputs(sha1_to_hex(commit->object.sha1), stdout);
-       if (revs.parents) {
+       if (revs.print_parents) {
                struct commit_list *parents = commit->parents;
                while (parents) {
                        printf(" %s", sha1_to_hex(parents->item->object.sha1));
@@ -96,9 +99,48 @@ static void show_commit(struct commit *commit)
                pretty_print_commit(revs.commit_format, commit,
                                    &buf, revs.abbrev, NULL, NULL,
                                    revs.date_mode, 0);
-               if (buf.len)
-                       printf("%s%c", buf.buf, hdr_termination);
+               if (revs.graph) {
+                       if (buf.len) {
+                               if (revs.commit_format != CMIT_FMT_ONELINE)
+                                       graph_show_oneline(revs.graph);
+
+                               graph_show_commit_msg(revs.graph, &buf);
+
+                               /*
+                                * Add a newline after the commit message.
+                                *
+                                * Usually, this newline produces a blank
+                                * padding line between entries, in which case
+                                * we need to add graph padding on this line.
+                                *
+                                * However, the commit message may not end in a
+                                * newline.  In this case the newline simply
+                                * ends the last line of the commit message,
+                                * and we don't need any graph output.  (This
+                                * always happens with CMIT_FMT_ONELINE, and it
+                                * happens with CMIT_FMT_USERFORMAT when the
+                                * format doesn't explicitly end in a newline.)
+                                */
+                               if (buf.len && buf.buf[buf.len - 1] == '\n')
+                                       graph_show_padding(revs.graph);
+                               putchar('\n');
+                       } else {
+                               /*
+                                * If the message buffer is empty, just show
+                                * the rest of the graph output for this
+                                * commit.
+                                */
+                               if (graph_show_remainder(revs.graph))
+                                       putchar('\n');
+                       }
+               } else {
+                       if (buf.len)
+                               printf("%s%c", buf.buf, hdr_termination);
+               }
                strbuf_release(&buf);
+       } else {
+               if (graph_show_remainder(revs.graph))
+                       putchar('\n');
        }
        maybe_flush_or_die(stdout, "stdout");
        finish_commit(commit);
index f8d8548..ab3e850 100644 (file)
@@ -94,6 +94,14 @@ static void show(const char *arg)
                puts(arg);
 }
 
+/* Like show(), but with a negation prefix according to type */
+static void show_with_type(int type, const char *arg)
+{
+       if (type != show_type)
+               putchar('^');
+       show(arg);
+}
+
 /* Output a revision, only if filter allows it */
 static void show_rev(int type, const unsigned char *sha1, const char *name)
 {
@@ -101,8 +109,6 @@ static void show_rev(int type, const unsigned char *sha1, const char *name)
                return;
        def = NULL;
 
-       if (type != show_type)
-               putchar('^');
        if (symbolic && name) {
                if (symbolic == SHOW_SYMBOLIC_FULL) {
                        unsigned char discard[20];
@@ -119,20 +125,20 @@ static void show_rev(int type, const unsigned char *sha1, const char *name)
                                 */
                                break;
                        case 1: /* happy */
-                               show(full);
+                               show_with_type(type, full);
                                break;
                        default: /* ambiguous */
                                error("refname '%s' is ambiguous", name);
                                break;
                        }
                } else {
-                       show(name);
+                       show_with_type(type, name);
                }
        }
        else if (abbrev)
-               show(find_unique_abbrev(sha1, abbrev));
+               show_with_type(type, find_unique_abbrev(sha1, abbrev));
        else
-               show(sha1_to_hex(sha1));
+               show_with_type(type, sha1_to_hex(sha1));
 }
 
 /* Output a flag, only if filter allows it. */
index bb9c33a..d76260c 100644 (file)
@@ -537,9 +537,17 @@ static void verify_remote_names(int nr_heads, const char **heads)
        int i;
 
        for (i = 0; i < nr_heads; i++) {
+               const char *local = heads[i];
                const char *remote = strrchr(heads[i], ':');
 
-               remote = remote ? (remote + 1) : heads[i];
+               if (*local == '+')
+                       local++;
+
+               /* A matching refspec is okay.  */
+               if (remote == local && remote[1] == '\0')
+                       continue;
+
+               remote = remote ? (remote + 1) : local;
                switch (check_ref_format(remote)) {
                case 0: /* ok */
                case CHECK_REF_FORMAT_ONELEVEL:
index a8795d3..d4c85c0 100644 (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 95126fd..23a90de 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -24,6 +24,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 b1a8427..943bee9 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -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)
 
 /*
@@ -368,6 +373,7 @@ extern int remove_index_entry_at(struct index_state *, int pos);
 extern int remove_file_from_index(struct index_state *, const char *path);
 #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);
@@ -390,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 {
@@ -400,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);
@@ -523,6 +531,7 @@ extern void * read_sha1_file(const unsigned char *sha1, enum object_type *type,
 extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
 extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
 extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
+extern int force_object_loose(const unsigned char *sha1, time_t mtime);
 
 extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
 
@@ -616,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;
@@ -784,7 +794,11 @@ 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 */
-void add_files_to_cache(const char *prefix, const char **pathspec, int flags);
+/*
+ * return 0 if success, 1 - if addition of a file failed and
+ * ADD_FILES_IGNORE_ERRORS was specified in flags
+ */
+int add_files_to_cache(const char *prefix, const char **pathspec, int flags);
 
 /* diff.c */
 extern int diff_auto_refresh_index;
similarity index 100%
rename from git-clone.sh
rename to contrib/examples/git-clone.sh
diff --git a/diff.c b/diff.c
index 439d474..d57bc29 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -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 3a02d38..1dfe1f9 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)
index 4fcb471..73feb2d 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 75886a8..b48096e 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -11,7 +11,7 @@ git-am [options] --skip
 --
 d,dotest=       (removed -- do not use)
 i,interactive   run interactively
-b,binary        pass --allo-binary-replacement to git-apply
+b,binary        pass --allow-binary-replacement to git-apply
 3,3way          allow fall back on 3way merging if needed
 s,signoff       add a Signed-off-by line to the commit message
 u,utf8          recode into utf8 (default)
index 164e8ed..4bcbace 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 () {
index b6036bd..c6c70e9 100755 (executable)
@@ -6,16 +6,21 @@ use File::Temp qw(tempdir);
 use Data::Dumper;
 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();
 
 die "Need at least one commit identifier!" unless @ARGV;
 
-if ($opt_w) {
+# Get git-config settings
+my $repo = Git->repository();
+$opt_w = $repo->config('cvsexportcommit.cvsdir') unless defined $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
@@ -25,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";
        }
@@ -116,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
@@ -210,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;
@@ -259,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;
@@ -312,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);
 }
 
@@ -332,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 bdac5d5..5a02550 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 29dbfc9..920bbe1 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 69b35d8..5fc5f52 100755 (executable)
@@ -9,11 +9,9 @@ git-merge [options] <remote>...
 git-merge [options] <msg> HEAD <remote>
 --
 stat                 show a diffstat at the end of the merge
-n,no-stat            don't show a diffstat at the end of the merge
+n                    don't show a diffstat at the end of the merge
 summary              (synonym to --stat)
-no-summary           (synonym to --no-stat)
 log                  add list of one-line log to merge commit message
-no-log               don't add list of one-line log to merge commit message
 squash               create a single commit instead of doing a merge
 commit               perform a commit if the merge sucesses (default)
 ff                   allow fast forward (default)
index bf0c298..809e537 100755 (executable)
@@ -107,6 +107,11 @@ error_on_no_merge_candidates () {
 }
 
 test true = "$rebase" && {
+       git update-index --refresh &&
+       git diff-files --quiet &&
+       git diff-index --cached --quiet HEAD -- ||
+       die "refusing to pull with rebase: your working tree is not up-to-date"
+
        . git-parse-remote &&
        origin="$1"
        test -z "$origin" && origin=$(get_default_remote)
index 8aa7371..8ee08ff 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 68855c1..dd7dfe1 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 501519a..10f735c 100755 (executable)
@@ -8,7 +8,7 @@ OPTIONS_SPEC="\
 git-repack [options]
 --
 a               pack everything in a single pack
-A               same as -a, and keep unreachable objects too
+A               same as -a, and turn unreachable objects loose
 d               remove redundant packs, and run git-prune-packed
 f               pass --no-reuse-delta to git-pack-objects
 n               do not run git-update-server-info
@@ -23,7 +23,7 @@ max-pack-size=  maximum size of each packfile
 SUBDIRECTORY_OK='Yes'
 . git-sh-setup
 
-no_update_info= all_into_one= remove_redundant= keep_unreachable=
+no_update_info= all_into_one= remove_redundant= unpack_unreachable=
 local= quiet= no_reuse= extra=
 while test $# != 0
 do
@@ -31,7 +31,7 @@ do
        -n)     no_update_info=t ;;
        -a)     all_into_one=t ;;
        -A)     all_into_one=t
-               keep_unreachable=--keep-unreachable ;;
+               unpack_unreachable=--unpack-unreachable ;;
        -d)     remove_redundant=t ;;
        -q)     quiet=-q ;;
        -f)     no_reuse=--no-reuse-object ;;
@@ -79,9 +79,9 @@ case ",$all_into_one," in
        if test -z "$args"
        then
                args='--unpacked --incremental'
-       elif test -n "$keep_unreachable"
+       elif test -n "$unpack_unreachable"
        then
-               args="$args $keep_unreachable"
+               args="$args $unpack_unreachable"
        fi
        ;;
 esac
index 1e1d986..a598fdc 100755 (executable)
@@ -521,8 +521,30 @@ EOT
        open(C,"<",$compose_filename)
                or die "Failed to open $compose_filename : " . $!;
 
+       my $need_8bit_cte = file_has_nonascii($compose_filename);
+       my $in_body = 0;
        while(<C>) {
                next if m/^GIT: /;
+               if (!$in_body && /^\n$/) {
+                       $in_body = 1;
+                       if ($need_8bit_cte) {
+                               print C2 "MIME-Version: 1.0\n",
+                                        "Content-Type: text/plain; ",
+                                          "charset=utf-8\n",
+                                        "Content-Transfer-Encoding: 8bit\n";
+                       }
+               }
+               if (!$in_body && /^MIME-Version:/i) {
+                       $need_8bit_cte = 0;
+               }
+               if (!$in_body && /^Subject: ?(.*)/i) {
+                       my $subject = $1;
+                       $_ = "Subject: " .
+                               ($subject =~ /[^[:ascii:]]/ ?
+                                quote_rfc2047($subject) :
+                                $subject) .
+                               "\n";
+               }
                print C2 $_;
        }
        close(C);
@@ -613,6 +635,14 @@ sub unquote_rfc2047 {
        return wantarray ? ($_, $encoding) : $_;
 }
 
+sub quote_rfc2047 {
+       local $_ = shift;
+       my $encoding = shift || 'utf-8';
+       s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X", ord($1))/eg;
+       s/(.*)/=\?$encoding\?q\?$1\?=/;
+       return $_;
+}
+
 # use the simplest quoting being able to handle the recipient
 sub sanitize_address
 {
@@ -630,13 +660,12 @@ sub sanitize_address
 
        # rfc2047 is needed if a non-ascii char is included
        if ($recipient_name =~ /[^[:ascii:]]/) {
-               $recipient_name =~ s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X", ord($1))/eg;
-               $recipient_name =~ s/(.*)/=\?utf-8\?q\?$1\?=/;
+               $recipient_name = quote_rfc2047($recipient_name);
        }
 
        # double quotes are needed if specials or CTLs are included
        elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
-               $recipient_name =~ s/(["\\\r])/\\$1/;
+               $recipient_name =~ s/(["\\\r])/\\$1/g;
                $recipient_name = "\"$recipient_name\"";
        }
 
@@ -959,3 +988,13 @@ sub validate_patch {
        }
        return undef;
 }
+
+sub file_has_nonascii {
+       my $fn = shift;
+       open(my $fh, '<', $fn)
+               or die "unable to open $fn: $!\n";
+       while (my $line = <$fh>) {
+               return 1 if $line =~ /[^[:ascii:]]/;
+       }
+       return 0;
+}
index c2b6820..4938ade 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 2c53f39..37976f2 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 89b431f..2c9004f 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 },
index 9ab6dba..22bcd18 100644 (file)
@@ -495,7 +495,7 @@ proc reloadcommits {} {
        stop_rev_list $curview
     }
     resetvarcs $curview
-    catch {unset selectedline}
+    set selectedline {}
     catch {unset currentid}
     catch {unset thickerline}
     catch {unset treediffs}
@@ -927,7 +927,7 @@ proc removefakerow {id} {
     modify_arc $v $a $i
     if {[info exist currentid] && $id eq $currentid} {
        unset currentid
-       unset selectedline
+       set selectedline {}
     }
     if {[info exists targetid] && $targetid eq $id} {
        set targetid $p
@@ -1838,7 +1838,7 @@ proc makewindow {} {
     pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
        -side left
     global selectedline
-    trace add variable selectedline {write unset} selectedline_change
+    trace add variable selectedline write selectedline_change
 
     # Status label and progress bar
     set statusw .tf.bar.status
@@ -2185,7 +2185,7 @@ proc windows_mousewheel_redirector {W X Y D} {
 proc selectedline_change {n1 n2 op} {
     global selectedline rownumsel
 
-    if {$op eq "unset"} {
+    if {$selectedline eq {}} {
        set rownumsel {}
     } else {
        set rownumsel [expr {$selectedline + 1}]
@@ -3274,7 +3274,7 @@ proc showview {n} {
     set ytop [expr {[lindex $span 0] * $ymax}]
     set ybot [expr {[lindex $span 1] * $ymax}]
     set yscreen [expr {($ybot - $ytop) / 2}]
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        set selid $currentid
        set y [yc $selectedline]
        if {$ytop < $y && $y < $ybot} {
@@ -3388,7 +3388,7 @@ proc bolden {row font} {
 
     lappend boldrows $row
     $canv itemconf $linehtag($row) -font $font
-    if {[info exists selectedline] && $row == $selectedline} {
+    if {$row == $selectedline} {
        $canv delete secsel
        set t [eval $canv create rect [$canv bbox $linehtag($row)] \
                   -outline {{}} -tags secsel \
@@ -3402,7 +3402,7 @@ proc bolden_name {row font} {
 
     lappend boldnamerows $row
     $canv2 itemconf $linentag($row) -font $font
-    if {[info exists selectedline] && $row == $selectedline} {
+    if {$row == $selectedline} {
        $canv2 delete secsel
        set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
                   -outline {{}} -tags secsel \
@@ -3831,7 +3831,7 @@ proc askrelhighlight {row id} {
     global descendent highlight_related iddrawn rhighlights
     global selectedline ancestor
 
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     set isbold 0
     if {$highlight_related eq [mc "Descendant"] ||
        $highlight_related eq [mc "Not descendant"]} {
@@ -4005,7 +4005,7 @@ proc visiblerows {} {
 
 proc layoutmore {} {
     global commitidx viewcomplete curview
-    global numcommits pending_select selectedline curview
+    global numcommits pending_select curview
     global lastscrollset lastscrollrows commitinterest
 
     if {$lastscrollrows < 100 || $viewcomplete($curview) ||
@@ -4916,7 +4916,7 @@ proc drawcmittext {id row col} {
                            -text $name -font $nfont -tags text]
     set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
                            -text $date -font mainfont -tags text]
-    if {[info exists selectedline] && $selectedline == $row} {
+    if {$selectedline == $row} {
        make_secsel $row
     }
     set xr [expr {$xt + [font measure $font $headline]}]
@@ -5107,7 +5107,7 @@ proc drawvisible {} {
     if {$endrow >= $vrowmod($curview)} {
        update_arcrows $curview
     }
-    if {[info exists selectedline] &&
+    if {$selectedline ne {} &&
        $row <= $selectedline && $selectedline <= $endrow} {
        set targetrow $selectedline
     } elseif {[info exists targetid]} {
@@ -5125,10 +5125,16 @@ proc drawvisible {} {
 proc clear_display {} {
     global iddrawn linesegs need_redisplay nrows_drawn
     global vhighlights fhighlights nhighlights rhighlights
+    global linehtag linentag linedtag boldrows boldnamerows
 
     allcanvs delete all
     catch {unset iddrawn}
     catch {unset linesegs}
+    catch {unset linehtag}
+    catch {unset linentag}
+    catch {unset linedtag}
+    set boldrows {}
+    set boldnamerows {}
     catch {unset vhighlights}
     catch {unset fhighlights}
     catch {unset nhighlights}
@@ -5423,7 +5429,7 @@ proc dofind {{dirn 1} {wrap 1}} {
     }
     focus .
     if {$findstring eq {} || $numcommits == 0} return
-    if {![info exists selectedline]} {
+    if {$selectedline eq {}} {
        set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
     } else {
        set findstartline $selectedline
@@ -5619,7 +5625,7 @@ proc markmatches {canv l str tag matches font row} {
                   [expr {$x0+$xlen+2}] $y1 \
                   -outline {} -tags [list match$l matches] -fill yellow]
        $canv lower $t
-       if {[info exists selectedline] && $row == $selectedline} {
+       if {$row == $selectedline} {
            $canv raise $t secsel
        }
     }
@@ -5778,7 +5784,7 @@ proc appendrefs {pos ids var} {
 proc dispneartags {delay} {
     global selectedline currentid showneartags tagphase
 
-    if {![info exists selectedline] || !$showneartags} return
+    if {$selectedline eq {} || !$showneartags} return
     after cancel dispnexttag
     if {$delay} {
        after 200 dispnexttag
@@ -5792,7 +5798,7 @@ proc dispneartags {delay} {
 proc dispnexttag {} {
     global selectedline currentid showneartags tagphase ctext
 
-    if {![info exists selectedline] || !$showneartags} return
+    if {$selectedline eq {} || !$showneartags} return
     switch -- $tagphase {
        0 {
            set dtags [desctags $currentid]
@@ -6014,7 +6020,7 @@ proc sellastline {} {
 proc selnextline {dir} {
     global selectedline
     focus .
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     set l [expr {$selectedline + $dir}]
     unmarkmatches
     selectline $l 1
@@ -6029,7 +6035,7 @@ proc selnextpage {dir} {
     }
     allcanvs yview scroll [expr {$dir * $lpp}] units
     drawvisible
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     set l [expr {$selectedline + $dir * $lpp}]
     if {$l < 0} {
        set l 0
@@ -6043,7 +6049,7 @@ proc selnextpage {dir} {
 proc unselectline {} {
     global selectedline currentid
 
-    catch {unset selectedline}
+    set selectedline {}
     catch {unset currentid}
     allcanvs delete secsel
     rhighlight_none
@@ -6052,7 +6058,7 @@ proc unselectline {} {
 proc reselectline {} {
     global selectedline
 
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        selectline $selectedline 0
     }
 }
@@ -6864,7 +6870,7 @@ proc redisplay {} {
     setcanvscroll
     allcanvs yview moveto [lindex $span 0]
     drawvisible
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        selectline $selectedline 0
        allcanvs yview moveto [lindex $span 0]
     }
@@ -7185,8 +7191,7 @@ proc rowmenu {x y id} {
 
     stopfinding
     set rowmenuid $id
-    if {![info exists selectedline]
-       || [rowofcommit $id] eq $selectedline} {
+    if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
        set state disabled
     } else {
        set state normal
@@ -7210,7 +7215,7 @@ proc rowmenu {x y id} {
 proc diffvssel {dirn} {
     global rowmenuid selectedline
 
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     if {$dirn} {
        set oldid [commitonrow $selectedline]
        set newid $rowmenuid
@@ -9886,6 +9891,7 @@ set viewperm(0) 0
 set viewargs(0) {}
 set viewargscmd(0) {}
 
+set selectedline {}
 set numcommits 0
 set loginstance 0
 set cmdlineok 0
similarity index 100%
rename from gitk-git/gitk-git/po/es.po
rename to gitk-git/po/es.po
index 2facf2d..8308e22 100755 (executable)
@@ -866,6 +866,10 @@ sub chop_str {
        my $add_len = shift || 10;
        my $where = shift || 'right'; # 'left' | 'center' | 'right'
 
+       # Make sure perl knows it is utf8 encoded so we don't
+       # cut in the middle of a utf8 multibyte char.
+       $str = to_utf8($str);
+
        # allow only $len chars, but don't cut a word if it would fit in $add_len
        # if it doesn't fit, cut it if it's still longer than the dots we would add
        # remove chopped character entities entirely
diff --git a/graph.c b/graph.c
new file mode 100644 (file)
index 0000000..9d6ed30
--- /dev/null
+++ b/graph.c
@@ -0,0 +1,945 @@
+#include "cache.h"
+#include "commit.h"
+#include "graph.h"
+#include "diff.h"
+#include "revision.h"
+
+/*
+ * TODO:
+ * - Add colors to the graph.
+ *   Pick a color for each column, and print all characters
+ *   in that column with the specified color.
+ *
+ * - Limit the number of columns, similar to the way gitk does.
+ *   If we reach more than a specified number of columns, omit
+ *   sections of some columns.
+ *
+ * - The output during the GRAPH_PRE_COMMIT and GRAPH_COLLAPSING states
+ *   could be made more compact by printing horizontal lines, instead of
+ *   long diagonal lines.  For example, during collapsing, something like
+ *   this:          instead of this:
+ *   | | | | |      | | | | |
+ *   | |_|_|/       | | | |/
+ *   |/| | |        | | |/|
+ *   | | | |        | |/| |
+ *                  |/| | |
+ *                  | | | |
+ *
+ *   If there are several parallel diagonal lines, they will need to be
+ *   replaced with horizontal lines on subsequent rows.
+ */
+
+struct column {
+       /*
+        * The parent commit of this column.
+        */
+       struct commit *commit;
+       /*
+        * XXX: Once we add support for colors, struct column could also
+        * contain the color of its branch line.
+        */
+};
+
+enum graph_state {
+       GRAPH_PADDING,
+       GRAPH_SKIP,
+       GRAPH_PRE_COMMIT,
+       GRAPH_COMMIT,
+       GRAPH_POST_MERGE,
+       GRAPH_COLLAPSING
+};
+
+struct git_graph {
+       /*
+        * The commit currently being processed
+        */
+       struct commit *commit;
+       /*
+        * The number of parents this commit has.
+        * (Stored so we don't have to walk over them each time we need
+        * this number)
+        */
+       int num_parents;
+       /*
+        * The width of the graph output for this commit.
+        * All rows for this commit are padded to this width, so that
+        * messages printed after the graph output are aligned.
+        */
+       int width;
+       /*
+        * The next expansion row to print
+        * when state is GRAPH_PRE_COMMIT
+        */
+       int expansion_row;
+       /*
+        * The current output state.
+        * This tells us what kind of line graph_next_line() should output.
+        */
+       enum graph_state state;
+       /*
+        * The maximum number of columns that can be stored in the columns
+        * and new_columns arrays.  This is also half the number of entries
+        * that can be stored in the mapping and new_mapping arrays.
+        */
+       int column_capacity;
+       /*
+        * The number of columns (also called "branch lines" in some places)
+        */
+       int num_columns;
+       /*
+        * The number of columns in the new_columns array
+        */
+       int num_new_columns;
+       /*
+        * The number of entries in the mapping array
+        */
+       int mapping_size;
+       /*
+        * The column state before we output the current commit.
+        */
+       struct column *columns;
+       /*
+        * The new column state after we output the current commit.
+        * Only valid when state is GRAPH_COLLAPSING.
+        */
+       struct column *new_columns;
+       /*
+        * An array that tracks the current state of each
+        * character in the output line during state GRAPH_COLLAPSING.
+        * Each entry is -1 if this character is empty, or a non-negative
+        * integer if the character contains a branch line.  The value of
+        * the integer indicates the target position for this branch line.
+        * (I.e., this array maps the current column positions to their
+        * desired positions.)
+        *
+        * The maximum capacity of this array is always
+        * sizeof(int) * 2 * column_capacity.
+        */
+       int *mapping;
+       /*
+        * A temporary array for computing the next mapping state
+        * while we are outputting a mapping line.  This is stored as part
+        * of the git_graph simply so we don't have to allocate a new
+        * temporary array each time we have to output a collapsing line.
+        */
+       int *new_mapping;
+};
+
+struct git_graph *graph_init(void)
+{
+       struct git_graph *graph = xmalloc(sizeof(struct git_graph));
+       graph->commit = NULL;
+       graph->num_parents = 0;
+       graph->expansion_row = 0;
+       graph->state = GRAPH_PADDING;
+       graph->num_columns = 0;
+       graph->num_new_columns = 0;
+       graph->mapping_size = 0;
+
+       /*
+        * Allocate a reasonably large default number of columns
+        * We'll automatically grow columns later if we need more room.
+        */
+       graph->column_capacity = 30;
+       graph->columns = xmalloc(sizeof(struct column) *
+                                graph->column_capacity);
+       graph->new_columns = xmalloc(sizeof(struct column) *
+                                    graph->column_capacity);
+       graph->mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity);
+       graph->new_mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity);
+
+       return graph;
+}
+
+void graph_release(struct git_graph *graph)
+{
+       free(graph->columns);
+       free(graph->new_columns);
+       free(graph->mapping);
+       free(graph);
+}
+
+static void graph_ensure_capacity(struct git_graph *graph, int num_columns)
+{
+       if (graph->column_capacity >= num_columns)
+               return;
+
+       do {
+               graph->column_capacity *= 2;
+       } while (graph->column_capacity < num_columns);
+
+       graph->columns = xrealloc(graph->columns,
+                                 sizeof(struct column) *
+                                 graph->column_capacity);
+       graph->new_columns = xrealloc(graph->new_columns,
+                                     sizeof(struct column) *
+                                     graph->column_capacity);
+       graph->mapping = xrealloc(graph->mapping,
+                                 sizeof(int) * 2 * graph->column_capacity);
+       graph->new_mapping = xrealloc(graph->new_mapping,
+                                     sizeof(int) * 2 * graph->column_capacity);
+}
+
+static void graph_insert_into_new_columns(struct git_graph *graph,
+                                         struct commit *commit,
+                                         int *mapping_index)
+{
+       int i;
+
+       /*
+        * Ignore uinteresting and pruned commits
+        */
+       if (commit->object.flags & (UNINTERESTING | TREESAME))
+               return;
+
+       /*
+        * If the commit is already in the new_columns list, we don't need to
+        * add it.  Just update the mapping correctly.
+        */
+       for (i = 0; i < graph->num_new_columns; i++) {
+               if (graph->new_columns[i].commit == commit) {
+                       graph->mapping[*mapping_index] = i;
+                       *mapping_index += 2;
+                       return;
+               }
+       }
+
+       /*
+        * This commit isn't already in new_columns.  Add it.
+        */
+       graph->new_columns[graph->num_new_columns].commit = commit;
+       graph->mapping[*mapping_index] = graph->num_new_columns;
+       *mapping_index += 2;
+       graph->num_new_columns++;
+}
+
+static void graph_update_width(struct git_graph *graph,
+                              int is_commit_in_existing_columns)
+{
+       /*
+        * Compute the width needed to display the graph for this commit.
+        * This is the maximum width needed for any row.  All other rows
+        * will be padded to this width.
+        *
+        * Compute the number of columns in the widest row:
+        * Count each existing column (graph->num_columns), and each new
+        * column added by this commit.
+        */
+       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.
+        */
+       if (graph->num_parents < 1)
+               max_cols++;
+
+       /*
+        * We added a column for the the current commit as part of
+        * graph->num_parents.  If the current commit was already in
+        * graph->columns, then we have double counted it.
+        */
+       if (is_commit_in_existing_columns)
+               max_cols--;
+
+       /*
+        * Each column takes up 2 spaces
+        */
+       graph->width = max_cols * 2;
+}
+
+static void graph_update_columns(struct git_graph *graph)
+{
+       struct commit_list *parent;
+       struct column *tmp_columns;
+       int max_new_columns;
+       int mapping_idx;
+       int i, seen_this, is_commit_in_columns;
+
+       /*
+        * Swap graph->columns with graph->new_columns
+        * graph->columns contains the state for the previous commit,
+        * and new_columns now contains the state for our commit.
+        *
+        * We'll re-use the old columns array as storage to compute the new
+        * columns list for the commit after this one.
+        */
+       tmp_columns = graph->columns;
+       graph->columns = graph->new_columns;
+       graph->num_columns = graph->num_new_columns;
+
+       graph->new_columns = tmp_columns;
+       graph->num_new_columns = 0;
+
+       /*
+        * Now update new_columns and mapping with the information for the
+        * commit after this one.
+        *
+        * First, make sure we have enough room.  At most, there will
+        * be graph->num_columns + graph->num_parents columns for the next
+        * commit.
+        */
+       max_new_columns = graph->num_columns + graph->num_parents;
+       graph_ensure_capacity(graph, max_new_columns);
+
+       /*
+        * Clear out graph->mapping
+        */
+       graph->mapping_size = 2 * max_new_columns;
+       for (i = 0; i < graph->mapping_size; i++)
+               graph->mapping[i] = -1;
+
+       /*
+        * Populate graph->new_columns and graph->mapping
+        *
+        * Some of the parents of this commit may already be in
+        * graph->columns.  If so, graph->new_columns should only contain a
+        * single entry for each such commit.  graph->mapping should
+        * contain information about where each current branch line is
+        * supposed to end up after the collapsing is performed.
+        */
+       seen_this = 0;
+       mapping_idx = 0;
+       is_commit_in_columns = 1;
+       for (i = 0; i <= graph->num_columns; i++) {
+               struct commit *col_commit;
+               if (i == graph->num_columns) {
+                       if (seen_this)
+                               break;
+                       is_commit_in_columns = 0;
+                       col_commit = graph->commit;
+               } else {
+                       col_commit = graph->columns[i].commit;
+               }
+
+               if (col_commit == graph->commit) {
+                       seen_this = 1;
+                       for (parent = graph->commit->parents;
+                            parent;
+                            parent = parent->next) {
+                               graph_insert_into_new_columns(graph,
+                                                             parent->item,
+                                                             &mapping_idx);
+                       }
+               } else {
+                       graph_insert_into_new_columns(graph, col_commit,
+                                                     &mapping_idx);
+               }
+       }
+
+       /*
+        * Shrink mapping_size to be the minimum necessary
+        */
+       while (graph->mapping_size > 1 &&
+              graph->mapping[graph->mapping_size - 1] < 0)
+               graph->mapping_size--;
+
+       /*
+        * Compute graph->width for this commit
+        */
+       graph_update_width(graph, is_commit_in_columns);
+}
+
+void graph_update(struct git_graph *graph, struct commit *commit)
+{
+       struct commit_list *parent;
+
+       /*
+        * Set the new commit
+        */
+       graph->commit = commit;
+
+       /*
+        * Count how many parents this commit has
+        */
+       graph->num_parents = 0;
+       for (parent = commit->parents; parent; parent = parent->next)
+               graph->num_parents++;
+
+       /*
+        * Call graph_update_columns() to update
+        * columns, new_columns, and mapping.
+        */
+       graph_update_columns(graph);
+
+       graph->expansion_row = 0;
+
+       /*
+        * Update graph->state.
+        *
+        * If the previous commit didn't get to the GRAPH_PADDING state,
+        * it never finished its output.  Goto GRAPH_SKIP, to print out
+        * a line to indicate that portion of the graph is missing.
+        *
+        * Otherwise, if there are 3 or more parents, we need to print
+        * extra rows before the commit, to expand the branch lines around
+        * it and make room for it.
+        *
+        * If there are less than 3 parents, we can immediately print the
+        * commit line.
+        */
+       if (graph->state != GRAPH_PADDING)
+               graph->state = GRAPH_SKIP;
+       else if (graph->num_parents >= 3)
+               graph->state = GRAPH_PRE_COMMIT;
+       else
+               graph->state = GRAPH_COMMIT;
+}
+
+static int graph_is_mapping_correct(struct git_graph *graph)
+{
+       int i;
+
+       /*
+        * The mapping is up to date if each entry is at its target,
+        * or is 1 greater than its target.
+        * (If it is 1 greater than the target, '/' will be printed, so it
+        * will look correct on the next row.)
+        */
+       for (i = 0; i < graph->mapping_size; i++) {
+               int target = graph->mapping[i];
+               if (target < 0)
+                       continue;
+               if (target == (i / 2))
+                       continue;
+               return 0;
+       }
+
+       return 1;
+}
+
+static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)
+{
+       /*
+        * Add additional spaces to the end of the strbuf, so that all
+        * lines for a particular commit have the same width.
+        *
+        * This way, fields printed to the right of the graph will remain
+        * aligned for the entire commit.
+        */
+       int extra;
+       if (sb->len >= graph->width)
+               return;
+
+       extra = graph->width - sb->len;
+       strbuf_addf(sb, "%*s", (int) extra, "");
+}
+
+static void graph_output_padding_line(struct git_graph *graph,
+                                     struct strbuf *sb)
+{
+       int i;
+
+       /*
+        * We could conceivable be called with a NULL commit
+        * if our caller has a bug, and invokes graph_next_line()
+        * immediately after graph_init(), without first calling
+        * graph_update().  Return without outputting anything in this
+        * case.
+        */
+       if (!graph->commit)
+               return;
+
+       /*
+        * Output a padding row, that leaves all branch lines unchanged
+        */
+       for (i = 0; i < graph->num_new_columns; i++) {
+               strbuf_addstr(sb, "| ");
+       }
+
+       graph_pad_horizontally(graph, sb);
+}
+
+static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
+{
+       /*
+        * Output an ellipsis to indicate that a portion
+        * of the graph is missing.
+        */
+       strbuf_addstr(sb, "...");
+       graph_pad_horizontally(graph, sb);
+
+       if (graph->num_parents >= 3)
+               graph->state = GRAPH_PRE_COMMIT;
+       else
+               graph->state = GRAPH_COMMIT;
+}
+
+static void graph_output_pre_commit_line(struct git_graph *graph,
+                                        struct strbuf *sb)
+{
+       int num_expansion_rows;
+       int i, seen_this;
+
+       /*
+        * This function formats a row that increases the space around a commit
+        * with multiple parents, to make room for it.  It should only be
+        * called when there are 3 or more parents.
+        *
+        * We need 2 extra rows for every parent over 2.
+        */
+       assert(graph->num_parents >= 3);
+       num_expansion_rows = (graph->num_parents - 2) * 2;
+
+       /*
+        * graph->expansion_row tracks the current expansion row we are on.
+        * It should be in the range [0, num_expansion_rows - 1]
+        */
+       assert(0 <= graph->expansion_row &&
+              graph->expansion_row < num_expansion_rows);
+
+       /*
+        * Output the row
+        */
+       seen_this = 0;
+       for (i = 0; i < graph->num_columns; i++) {
+               struct column *col = &graph->columns[i];
+               if (col->commit == graph->commit) {
+                       seen_this = 1;
+                       strbuf_addf(sb, "| %*s", graph->expansion_row, "");
+               } else if (seen_this) {
+                       strbuf_addstr(sb, "\\ ");
+               } else {
+                       strbuf_addstr(sb, "| ");
+               }
+       }
+
+       graph_pad_horizontally(graph, sb);
+
+       /*
+        * Increment graph->expansion_row,
+        * and move to state GRAPH_COMMIT if necessary
+        */
+       graph->expansion_row++;
+       if (graph->expansion_row >= num_expansion_rows)
+               graph->state = GRAPH_COMMIT;
+}
+
+void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
+{
+       int seen_this = 0;
+       int i, j;
+
+       /*
+        * Output the row containing this commit
+        * Iterate up to and including graph->num_columns,
+        * since the current commit may not be in any of the existing
+        * columns.  (This happens when the current commit doesn't have any
+        * children that we have already processed.)
+        */
+       seen_this = 0;
+       for (i = 0; i <= graph->num_columns; i++) {
+               struct commit *col_commit;
+               if (i == graph->num_columns) {
+                       if (seen_this)
+                               break;
+                       col_commit = graph->commit;
+               } else {
+                       col_commit = graph->columns[i].commit;
+               }
+
+               if (col_commit == graph->commit) {
+                       seen_this = 1;
+                       if (graph->num_parents > 1)
+                               strbuf_addch(sb, 'M');
+                       else
+                               strbuf_addch(sb, '*');
+
+                       if (graph->num_parents < 2)
+                               strbuf_addch(sb, ' ');
+                       else if (graph->num_parents == 2)
+                               strbuf_addstr(sb, "  ");
+                       else {
+                               int num_dashes =
+                                       ((graph->num_parents - 2) * 2) - 1;
+                               for (j = 0; j < num_dashes; j++)
+                                       strbuf_addch(sb, '-');
+                               strbuf_addstr(sb, ". ");
+                       }
+               } else if (seen_this && (graph->num_parents > 1)) {
+                       strbuf_addstr(sb, "\\ ");
+               } else {
+                       strbuf_addstr(sb, "| ");
+               }
+       }
+
+       graph_pad_horizontally(graph, sb);
+
+       /*
+        * Update graph->state
+        */
+       if (graph->num_parents > 1)
+               graph->state = GRAPH_POST_MERGE;
+       else if (graph_is_mapping_correct(graph))
+               graph->state = GRAPH_PADDING;
+       else
+               graph->state = GRAPH_COLLAPSING;
+}
+
+void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
+{
+       int seen_this = 0;
+       int i, j;
+
+       /*
+        * Output the post-merge row
+        */
+       for (i = 0; i <= graph->num_columns; i++) {
+               struct commit *col_commit;
+               if (i == graph->num_columns) {
+                       if (seen_this)
+                               break;
+                       col_commit = graph->commit;
+               } else {
+                       col_commit = graph->columns[i].commit;
+               }
+
+               if (col_commit == graph->commit) {
+                       seen_this = 1;
+                       strbuf_addch(sb, '|');
+                       for (j = 0; j < graph->num_parents - 1; j++)
+                               strbuf_addstr(sb, "\\ ");
+                       if (graph->num_parents == 2)
+                               strbuf_addch(sb, ' ');
+               } else if (seen_this && (graph->num_parents > 2)) {
+                       strbuf_addstr(sb, "\\ ");
+               } else {
+                       strbuf_addstr(sb, "| ");
+               }
+       }
+
+       graph_pad_horizontally(graph, sb);
+
+       /*
+        * Update graph->state
+        */
+       if (graph_is_mapping_correct(graph))
+               graph->state = GRAPH_PADDING;
+       else
+               graph->state = GRAPH_COLLAPSING;
+}
+
+void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)
+{
+       int i;
+       int *tmp_mapping;
+
+       /*
+        * Clear out the new_mapping array
+        */
+       for (i = 0; i < graph->mapping_size; i++)
+               graph->new_mapping[i] = -1;
+
+       for (i = 0; i < graph->mapping_size; i++) {
+               int target = graph->mapping[i];
+               if (target < 0)
+                       continue;
+
+               /*
+                * Since update_columns() always inserts the leftmost
+                * column first, each branch's target location should
+                * always be either its current location or to the left of
+                * its current location.
+                *
+                * We never have to move branches to the right.  This makes
+                * the graph much more legible, since whenever branches
+                * cross, only one is moving directions.
+                */
+               assert(target * 2 <= i);
+
+               if (target * 2 == i) {
+                       /*
+                        * This column is already in the
+                        * correct place
+                        */
+                       assert(graph->new_mapping[i] == -1);
+                       graph->new_mapping[i] = target;
+               } else if (graph->new_mapping[i - 1] < 0) {
+                       /*
+                        * Nothing is to the left.
+                        * Move to the left by one
+                        */
+                       graph->new_mapping[i - 1] = target;
+               } else if (graph->new_mapping[i - 1] == target) {
+                       /*
+                        * There is a branch line to our left
+                        * already, and it is our target.  We
+                        * combine with this line, since we share
+                        * the same parent commit.
+                        *
+                        * We don't have to add anything to the
+                        * output or new_mapping, since the
+                        * existing branch line has already taken
+                        * care of it.
+                        */
+               } else {
+                       /*
+                        * There is a branch line to our left,
+                        * but it isn't our target.  We need to
+                        * cross over it.
+                        *
+                        * The space just to the left of this
+                        * branch should always be empty.
+                        */
+                       assert(graph->new_mapping[i - 1] > target);
+                       assert(graph->new_mapping[i - 2] < 0);
+                       graph->new_mapping[i - 2] = target;
+               }
+       }
+
+       /*
+        * The new mapping may be 1 smaller than the old mapping
+        */
+       if (graph->new_mapping[graph->mapping_size - 1] < 0)
+               graph->mapping_size--;
+
+       /*
+        * Output out a line based on the new mapping info
+        */
+       for (i = 0; i < graph->mapping_size; i++) {
+               int target = graph->new_mapping[i];
+               if (target < 0)
+                       strbuf_addch(sb, ' ');
+               else if (target * 2 == i)
+                       strbuf_addch(sb, '|');
+               else
+                       strbuf_addch(sb, '/');
+       }
+
+       graph_pad_horizontally(graph, sb);
+
+       /*
+        * Swap mapping and new_mapping
+        */
+       tmp_mapping = graph->mapping;
+       graph->mapping = graph->new_mapping;
+       graph->new_mapping = tmp_mapping;
+
+       /*
+        * If graph->mapping indicates that all of the branch lines
+        * are already in the correct positions, we are done.
+        * Otherwise, we need to collapse some branch lines together.
+        */
+       if (graph_is_mapping_correct(graph))
+               graph->state = GRAPH_PADDING;
+}
+
+int graph_next_line(struct git_graph *graph, struct strbuf *sb)
+{
+       switch (graph->state) {
+       case GRAPH_PADDING:
+               graph_output_padding_line(graph, sb);
+               return 0;
+       case GRAPH_SKIP:
+               graph_output_skip_line(graph, sb);
+               return 0;
+       case GRAPH_PRE_COMMIT:
+               graph_output_pre_commit_line(graph, sb);
+               return 0;
+       case GRAPH_COMMIT:
+               graph_output_commit_line(graph, sb);
+               return 1;
+       case GRAPH_POST_MERGE:
+               graph_output_post_merge_line(graph, sb);
+               return 0;
+       case GRAPH_COLLAPSING:
+               graph_output_collapsing_line(graph, sb);
+               return 0;
+       }
+
+       assert(0);
+       return 0;
+}
+
+void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
+{
+       int i, j;
+
+       if (graph->state != GRAPH_COMMIT) {
+               graph_next_line(graph, sb);
+               return;
+       }
+
+       /*
+        * Output the row containing this commit
+        * Iterate up to and including graph->num_columns,
+        * since the current commit may not be in any of the existing
+        * columns.  (This happens when the current commit doesn't have any
+        * children that we have already processed.)
+        */
+       for (i = 0; i < graph->num_columns; i++) {
+               struct commit *col_commit = graph->columns[i].commit;
+               if (col_commit == graph->commit) {
+                       strbuf_addch(sb, '|');
+
+                       if (graph->num_parents < 3)
+                               strbuf_addch(sb, ' ');
+                       else {
+                               int num_spaces = ((graph->num_parents - 2) * 2);
+                               for (j = 0; j < num_spaces; j++)
+                                       strbuf_addch(sb, ' ');
+                       }
+               } else {
+                       strbuf_addstr(sb, "| ");
+               }
+       }
+
+       graph_pad_horizontally(graph, sb);
+}
+
+int graph_is_commit_finished(struct git_graph const *graph)
+{
+       return (graph->state == GRAPH_PADDING);
+}
+
+void graph_show_commit(struct git_graph *graph)
+{
+       struct strbuf msgbuf;
+       int shown_commit_line = 0;
+
+       if (!graph)
+               return;
+
+       strbuf_init(&msgbuf, 0);
+
+       while (!shown_commit_line) {
+               shown_commit_line = graph_next_line(graph, &msgbuf);
+               fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
+               if (!shown_commit_line)
+                       putchar('\n');
+               strbuf_setlen(&msgbuf, 0);
+       }
+
+       strbuf_release(&msgbuf);
+}
+
+void graph_show_oneline(struct git_graph *graph)
+{
+       struct strbuf msgbuf;
+
+       if (!graph)
+               return;
+
+       strbuf_init(&msgbuf, 0);
+       graph_next_line(graph, &msgbuf);
+       fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
+       strbuf_release(&msgbuf);
+}
+
+void graph_show_padding(struct git_graph *graph)
+{
+       struct strbuf msgbuf;
+
+       if (!graph)
+               return;
+
+       strbuf_init(&msgbuf, 0);
+       graph_padding_line(graph, &msgbuf);
+       fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
+       strbuf_release(&msgbuf);
+}
+
+int graph_show_remainder(struct git_graph *graph)
+{
+       struct strbuf msgbuf;
+       int shown = 0;
+
+       if (!graph)
+               return 0;
+
+       if (graph_is_commit_finished(graph))
+               return 0;
+
+       strbuf_init(&msgbuf, 0);
+       for (;;) {
+               graph_next_line(graph, &msgbuf);
+               fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
+               strbuf_setlen(&msgbuf, 0);
+               shown = 1;
+
+               if (!graph_is_commit_finished(graph))
+                       putchar('\n');
+               else
+                       break;
+       }
+       strbuf_release(&msgbuf);
+
+       return shown;
+}
+
+
+void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
+{
+       char *p;
+
+       if (!graph) {
+               fwrite(sb->buf, sizeof(char), sb->len, stdout);
+               return;
+       }
+
+       /*
+        * Print the strbuf line by line,
+        * and display the graph info before each line but the first.
+        */
+       p = sb->buf;
+       while (p) {
+               size_t len;
+               char *next_p = strchr(p, '\n');
+               if (next_p) {
+                       next_p++;
+                       len = next_p - p;
+               } else {
+                       len = (sb->buf + sb->len) - p;
+               }
+               fwrite(p, sizeof(char), len, stdout);
+               if (next_p && *next_p != '\0')
+                       graph_show_oneline(graph);
+               p = next_p;
+       }
+}
+
+void graph_show_commit_msg(struct git_graph *graph,
+                          struct strbuf const *sb)
+{
+       int newline_terminated;
+
+       if (!graph) {
+               /*
+                * If there's no graph, just print the message buffer.
+                *
+                * The message buffer for CMIT_FMT_ONELINE and
+                * CMIT_FMT_USERFORMAT are already missing a terminating
+                * newline.  All of the other formats should have it.
+                */
+               fwrite(sb->buf, sizeof(char), sb->len, stdout);
+               return;
+       }
+
+       newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');
+
+       /*
+        * Show the commit message
+        */
+       graph_show_strbuf(graph, sb);
+
+       /*
+        * If there is more output needed for this commit, show it now
+        */
+       if (!graph_is_commit_finished(graph)) {
+               /*
+                * If sb doesn't have a terminating newline, print one now,
+                * so we can start the remainder of the graph output on a
+                * new line.
+                */
+               if (!newline_terminated)
+                       putchar('\n');
+
+               graph_show_remainder(graph);
+
+               /*
+                * If sb ends with a newline, our output should too.
+                */
+               if (newline_terminated)
+                       putchar('\n');
+       }
+}
diff --git a/graph.h b/graph.h
new file mode 100644 (file)
index 0000000..a7748a5
--- /dev/null
+++ b/graph.h
@@ -0,0 +1,121 @@
+#ifndef GRAPH_H
+#define GRAPH_H
+
+/* A graph is a pointer to this opaque structure */
+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();
+
+/*
+ * Destroy a struct git_graph and free associated memory.
+ */
+void graph_release(struct git_graph *graph);
+
+/*
+ * Update a git_graph with a new commit.
+ * This will cause the graph to begin outputting lines for the new commit
+ * the next time graph_next_line() is called.
+ *
+ * If graph_update() is called before graph_is_commit_finished() returns 1,
+ * the next call to graph_next_line() will output an ellipsis ("...")
+ * to indicate that a portion of the graph is missing.
+ */
+void graph_update(struct git_graph *graph, struct commit *commit);
+
+/*
+ * Output the next line for a graph.
+ * This formats the next graph line into the specified strbuf.  It is not
+ * terminated with a newline.
+ *
+ * Returns 1 if the line includes the current commit, and 0 otherwise.
+ * graph_next_line() will return 1 exactly once for each time
+ * graph_update() is called.
+ */
+int graph_next_line(struct git_graph *graph, struct strbuf *sb);
+
+/*
+ * Output a padding line in the graph.
+ * This is similar to graph_next_line().  However, it is guaranteed to
+ * never print the current commit line.  Instead, if the commit line is
+ * next, it will simply output a line of vertical padding, extending the
+ * branch lines downwards, but leaving them otherwise unchanged.
+ */
+void graph_padding_line(struct git_graph *graph, struct strbuf *sb);
+
+/*
+ * Determine if a graph has finished outputting lines for the current
+ * commit.
+ *
+ * Returns 1 if graph_next_line() needs to be called again before
+ * graph_update() should be called.  Returns 0 if no more lines are needed
+ * for this commit.  If 0 is returned, graph_next_line() may still be
+ * called without calling graph_update(), and it will merely output
+ * appropriate "vertical padding" in the graph.
+ */
+int graph_is_commit_finished(struct git_graph const *graph);
+
+
+/*
+ * graph_show_*: helper functions for printing to stdout
+ */
+
+
+/*
+ * If the graph is non-NULL, print the history graph to stdout,
+ * up to and including the line containing this commit.
+ * Does not print a terminating newline on the last line.
+ */
+void graph_show_commit(struct git_graph *graph);
+
+/*
+ * If the graph is non-NULL, print one line of the history graph to stdout.
+ * Does not print a terminating newline on the last line.
+ */
+void graph_show_oneline(struct git_graph *graph);
+
+/*
+ * If the graph is non-NULL, print one line of vertical graph padding to
+ * stdout.  Does not print a terminating newline on the last line.
+ */
+void graph_show_padding(struct git_graph *graph);
+
+/*
+ * If the graph is non-NULL, print the rest of the history graph for this
+ * commit to stdout.  Does not print a terminating newline on the last line.
+ */
+int graph_show_remainder(struct git_graph *graph);
+
+/*
+ * Print a strbuf to stdout.  If the graph is non-NULL, all lines but the
+ * first will be prefixed with the graph output.
+ *
+ * If the strbuf ends with a newline, the output will end after this
+ * newline.  A new graph line will not be printed after the final newline.
+ * If the strbuf is empty, no output will be printed.
+ *
+ * Since the first line will not include the graph ouput, the caller is
+ * responsible for printing this line's graph (perhaps via
+ * graph_show_commit() or graph_show_oneline()) before calling
+ * graph_show_strbuf().
+ */
+void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
+
+/*
+ * Print a commit message strbuf and the remainder of the graph to stdout.
+ *
+ * This is similar to graph_show_strbuf(), but it always prints the
+ * remainder of the graph.
+ *
+ * If the strbuf ends with a newline, the output printed by
+ * graph_show_commit_msg() will end with a newline.  If the strbuf is
+ * missing a terminating newline (including if it is empty), the output
+ * printed by graph_show_commit_msg() will also be missing a terminating
+ * newline.
+ */
+void graph_show_commit_msg(struct git_graph *graph, struct strbuf const *sb);
+
+#endif /* GRAPH_H */
index 61e7160..0a7ac2f 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,6 +63,7 @@ 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);
 
@@ -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;
index 42727c8..f173dcd 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");
index 663f18f..cfc7335 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 d3fb0e5..1474d1f 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "diff.h"
 #include "commit.h"
+#include "graph.h"
 #include "log-tree.h"
 #include "reflog-walk.h"
 
@@ -165,11 +166,16 @@ void log_write_email_headers(struct rev_info *opt, const char *name,
        }
 
        printf("From %s Mon Sep 17 00:00:00 2001\n", name);
-       if (opt->message_id)
+       graph_show_oneline(opt->graph);
+       if (opt->message_id) {
                printf("Message-Id: <%s>\n", opt->message_id);
-       if (opt->ref_message_id)
+               graph_show_oneline(opt->graph);
+       }
+       if (opt->ref_message_id) {
                printf("In-Reply-To: <%s>\nReferences: <%s>\n",
                       opt->ref_message_id, opt->ref_message_id);
+               graph_show_oneline(opt->graph);
+       }
        if (opt->mime_boundary) {
                static char subject_buffer[1024];
                static char buffer[1024];
@@ -220,6 +226,8 @@ void show_log(struct rev_info *opt)
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
+               graph_show_commit(opt->graph);
+
                if (commit->object.flags & BOUNDARY)
                        putchar('-');
                else if (commit->object.flags & UNINTERESTING)
@@ -231,9 +239,13 @@ void show_log(struct rev_info *opt)
                                putchar('>');
                }
                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
-               if (opt->parents)
+               if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
                show_decorations(commit);
+               if (opt->graph && !graph_is_commit_finished(opt->graph)) {
+                       putchar('\n');
+                       graph_show_remainder(opt->graph);
+               }
                putchar(opt->diffopt.line_termination);
                return;
        }
@@ -243,10 +255,32 @@ void show_log(struct rev_info *opt)
         * Otherwise, add a diffopt.line_termination character before all
         * entries but the first.  (IOW, as a separator between entries)
         */
-       if (opt->shown_one && !opt->use_terminator)
+       if (opt->shown_one && !opt->use_terminator) {
+               /*
+                * If entries are separated by a newline, the output
+                * should look human-readable.  If the last entry ended
+                * with a newline, print the graph output before this
+                * newline.  Otherwise it will end up as a completely blank
+                * line and will look like a gap in the graph.
+                *
+                * If the entry separator is not a newline, the output is
+                * primarily intended for programmatic consumption, and we
+                * never want the extra graph output before the entry
+                * separator.
+                */
+               if (opt->diffopt.line_termination == '\n' &&
+                   !opt->missing_newline)
+                       graph_show_padding(opt->graph);
                putchar(opt->diffopt.line_termination);
+       }
        opt->shown_one = 1;
 
+       /*
+        * If the history graph was requested,
+        * print the graph, up to this commit's line
+        */
+       graph_show_commit(opt->graph);
+
        /*
         * Print header line of header..
         */
@@ -271,7 +305,7 @@ void show_log(struct rev_info *opt)
                }
                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
                      stdout);
-               if (opt->parents)
+               if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
                if (parent)
                        printf(" (from %s)",
@@ -279,8 +313,19 @@ void show_log(struct rev_info *opt)
                                                  abbrev_commit));
                show_decorations(commit);
                printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET));
-               putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+               if (opt->commit_format == CMIT_FMT_ONELINE) {
+                       putchar(' ');
+               } else {
+                       putchar('\n');
+                       graph_show_oneline(opt->graph);
+               }
                if (opt->reflog_info) {
+                       /*
+                        * setup_revisions() ensures that opt->reflog_info
+                        * and opt->graph cannot both be set,
+                        * so we don't need to worry about printing the
+                        * graph info here.
+                        */
                        show_reflog_message(opt->reflog_info,
                                    opt->commit_format == CMIT_FMT_ONELINE,
                                    opt->date_mode);
@@ -304,13 +349,30 @@ void show_log(struct rev_info *opt)
 
        if (opt->add_signoff)
                append_signoff(&msgbuf, opt->add_signoff);
-       if (opt->show_log_size)
+       if (opt->show_log_size) {
                printf("log size %i\n", (int)msgbuf.len);
+               graph_show_oneline(opt->graph);
+       }
 
-       if (msgbuf.len)
+       /*
+        * Set opt->missing_newline if msgbuf doesn't
+        * end in a newline (including if it is empty)
+        */
+       if (!msgbuf.len || msgbuf.buf[msgbuf.len - 1] != '\n')
+               opt->missing_newline = 1;
+       else
+               opt->missing_newline = 0;
+
+       if (opt->graph)
+               graph_show_commit_msg(opt->graph, &msgbuf);
+       else
                fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
-       if (opt->use_terminator)
+       if (opt->use_terminator) {
+               if (!opt->missing_newline)
+                       graph_show_padding(opt->graph);
                putchar('\n');
+       }
+
        strbuf_release(&msgbuf);
 }
 
index 2e7f896..6ba8ee5 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 5d967e8..ac9a8e7 100644 (file)
@@ -472,7 +472,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        int pretend = flags & ADD_CACHE_PRETEND;
 
        if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode))
-               die("%s: can only add regular files, symbolic links or git-directories", path);
+               return error("%s: can only add regular files, symbolic links or git-directories", path);
 
        namelen = strlen(path);
        if (S_ISDIR(st_mode)) {
@@ -507,7 +507,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
                return 0;
        }
        if (index_path(ce->sha1, path, st, 1))
-               die("unable to index file %s", path);
+               return error("unable to index file %s", path);
        if (ignore_case && alias && different_name(ce, alias))
                ce = create_alias_ce(ce, alias);
        ce->ce_flags |= CE_ADDED;
@@ -521,7 +521,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        if (pretend)
                ;
        else if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
-               die("unable to add %s to index",path);
+               return error("unable to add %s to index",path);
        if (verbose && !was_same)
                printf("add '%s'\n", path);
        return 0;
@@ -953,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++) {
@@ -960,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))
diff --git a/refs.c b/refs.c
index 9b495eb..9e8e858 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 06abee1..06ad260 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 91cbb72..75a12c0 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;
@@ -434,6 +444,16 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
                }
 
                rhs = strrchr(lhs, ':');
+
+               /*
+                * Before going on, special case ":" (or "+:") as a refspec
+                * for matching refs.
+                */
+               if (!fetch && rhs == lhs && rhs[1] == '\0') {
+                       rs[i].matching = 1;
+                       continue;
+               }
+
                if (rhs) {
                        rhs++;
                        rlen = strlen(rhs);
@@ -855,7 +875,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
        const char *dst_value = rs->dst;
        char *dst_guess;
 
-       if (rs->pattern)
+       if (rs->pattern || rs->matching)
                return errs;
 
        matched_src = matched_dst = NULL;
@@ -945,13 +965,23 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
                                                 const struct ref *src)
 {
        int i;
+       int matching_refs = -1;
        for (i = 0; i < rs_nr; i++) {
+               if (rs[i].matching &&
+                   (matching_refs == -1 || rs[i].force)) {
+                       matching_refs = i;
+                       continue;
+               }
+
                if (rs[i].pattern &&
                    !prefixcmp(src->name, rs[i].src) &&
                    src->name[strlen(rs[i].src)] == '/')
                        return rs + i;
        }
-       return NULL;
+       if (matching_refs != -1)
+               return rs + matching_refs;
+       else
+               return NULL;
 }
 
 /*
@@ -962,11 +992,16 @@ static const struct refspec *check_pattern_match(const&nb