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
$(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))
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
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.
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
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.
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.
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
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
---------------------------
+++ /dev/null
-git for CVS users
-=================
-
-Git differs from CVS in that every working tree contains a repository with
-a full copy of the project history, and no repository is inherently more
-important than any other. However, you can emulate the CVS model by
-designating a single shared repository which people can synchronize with;
-this document explains how to do that.
-
-Some basic familiarity with git is required. This
-link:tutorial.html[tutorial introduction to git] and the
-link:glossary.html[git glossary] should be sufficient.
-
-Developing against a shared repository
---------------------------------------
-
-Suppose a shared repository is set up in /pub/repo.git on the host
-foo.com. Then as an individual committer you can clone the shared
-repository over ssh with:
-
-------------------------------------------------
-$ git clone foo.com:/pub/repo.git/ my-project
-$ cd my-project
-------------------------------------------------
-
-and hack away. The equivalent of `cvs update` is
-
-------------------------------------------------
-$ git pull origin
-------------------------------------------------
-
-which merges in any work that others might have done since the clone
-operation. If there are uncommitted changes in your working tree, commit
-them first before running git pull.
-
-[NOTE]
-================================
-The `pull` command knows where to get updates from because of certain
-configuration variables that were set by the first `git clone`
-command; see `git config -l` and the linkgit:git-config[1] man
-page for details.
-================================
-
-You can update the shared repository with your changes by first committing
-your changes, and then using the linkgit:git-push[1] command:
-
-------------------------------------------------
-$ git push origin master
-------------------------------------------------
-
-to "push" those commits to the shared repository. If someone else has
-updated the repository more recently, `git push`, like `cvs commit`, will
-complain, in which case you must pull any changes before attempting the
-push again.
-
-In the `git push` command above we specify the name of the remote branch
-to update (`master`). If we leave that out, `git push` tries to update
-any branches in the remote repository that have the same name as a branch
-in the local repository. So the last `push` can be done with either of:
-
-------------
-$ git push origin
-$ git push foo.com:/pub/project.git/
-------------
-
-as long as the shared repository does not have any branches
-other than `master`.
-
-Setting Up a Shared Repository
-------------------------------
-
-We assume you have already created a git repository for your project,
-possibly created from scratch or from a tarball (see the
-link:tutorial.html[tutorial]), or imported from an already existing CVS
-repository (see the next section).
-
-Assume your existing repo is at /home/alice/myproject. Create a new "bare"
-repository (a repository without a working tree) and fetch your project into
-it:
-
-------------------------------------------------
-$ mkdir /pub/my-repo.git
-$ cd /pub/my-repo.git
-$ git --bare init --shared
-$ git --bare fetch /home/alice/myproject master:master
-------------------------------------------------
-
-Next, give every team member read/write access to this repository. One
-easy way to do this is to give all the team members ssh access to the
-machine where the repository is hosted. If you don't want to give them a
-full shell on the machine, there is a restricted shell which only allows
-users to do git pushes and pulls; see linkgit:git-shell[1].
-
-Put all the committers in the same group, and make the repository
-writable by that group:
-
-------------------------------------------------
-$ chgrp -R $group /pub/my-repo.git
-------------------------------------------------
-
-Make sure committers have a umask of at most 027, so that the directories
-they create are writable and searchable by other group members.
-
-Importing a CVS archive
------------------------
-
-First, install version 2.1 or higher of cvsps from
-link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
-sure it is in your path. Then cd to a checked out CVS working directory
-of the project you are interested in and run linkgit:git-cvsimport[1]:
-
--------------------------------------------
-$ git cvsimport -C <destination> <module>
--------------------------------------------
-
-This puts a git archive of the named CVS module in the directory
-<destination>, which will be created if necessary.
-
-The import checks out from CVS every revision of every file. Reportedly
-cvsimport can average some twenty revisions per second, so for a
-medium-sized project this should not take more than a couple of minutes.
-Larger projects or remote repositories may take longer.
-
-The main trunk is stored in the git branch named `origin`, and additional
-CVS branches are stored in git branches with the same names. The most
-recent version of the main trunk is also left checked out on the `master`
-branch, so you can start adding your own changes right away.
-
-The import is incremental, so if you call it again next month it will
-fetch any CVS updates that have been made in the meantime. For this to
-work, you must not modify the imported branches; instead, create new
-branches for your own changes, and merge in the imported branches as
-necessary.
-
-Advanced Shared Repository Management
--------------------------------------
-
-Git allows you to specify scripts called "hooks" to be run at certain
-points. You can use these, for example, to send all commits to the shared
-repository to a mailing list. See linkgit:githooks[5][Hooks used by git].
-
-You can enforce finer grained permissions using update hooks. See
-link:howto/update-hook-example.txt[Controlling access to branches using
-update hooks].
-
-Providing CVS Access to a git Repository
-----------------------------------------
-
-It is also possible to provide true CVS access to a git repository, so
-that developers can still use CVS; see linkgit:git-cvsserver[1] for
-details.
-
-Alternative Development Models
-------------------------------
-
-CVS users are accustomed to giving a group of developers commit access to
-a common repository. As we've seen, this is also possible with git.
-However, the distributed nature of git allows other development models,
-and you may want to first consider whether one of them might be a better
-fit for your project.
-
-For example, you can choose a single person to maintain the project's
-primary public repository. Other developers then clone this repository
-and each work in their own clone. When they have a series of changes that
-they're happy with, they ask the maintainer to pull from the branch
-containing the changes. The maintainer reviews their changes and pulls
-them into the primary repository, which other developers pull from as
-necessary to stay coordinated. The Linux kernel and other projects use
-variants of this model.
-
-With a small group, developers may just pull changes from each other's
-repositories without the need for a central maintainer.
--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/".
--------
[verse]
'git-add' [-n] [-v] [-f] [--interactive | -i] [--patch | -p] [-u] [--refresh]
- [--] <filepattern>...
+ [--ignore-errors] [--] <filepattern>...
DESCRIPTION
-----------
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
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
-------
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>.
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
------
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
-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
--------
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
------------
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
----------------------------------------------------------------
[daemon]
uploadpack = false
- uploadarchive = true
+ uploadarch = true
----------------------------------------------------------------
SYNOPSIS
--------
-'git-hash-object' [-t <type>] [-w] [--stdin] [--] <file>...
+'git-hash-object' [-t <type>] [-w] [--stdin | --stdin-paths] [--] <file>...
DESCRIPTION
-----------
--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>
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
+
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
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
-----------
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.
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
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
------
-------------
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
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
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
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
[--cacheinfo <mode> <object> <file>]\*
[--chmod=(+|-)x]
[--assume-unchanged | --no-assume-unchanged]
+ [--ignore-submodules]
[--really-refresh] [--unresolve] [--again | -g]
[--info-only] [--index-info]
[-z] [--stdin]
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
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.
--- /dev/null
+gitcvs-migration(7)
+===================
+
+NAME
+----
+gitcvs-migration - git for CVS users
+
+SYNOPSIS
+--------
+git cvsimport *
+
+DESCRIPTION
+-----------
+
+Git differs from CVS in that every working tree contains a repository with
+a full copy of the project history, and no repository is inherently more
+important than any other. However, you can emulate the CVS model by
+designating a single shared repository which people can synchronize with;
+this document explains how to do that.
+
+Some basic familiarity with git is required. This
+linkgit:gittutorial[7][tutorial introduction to git] and the
+link:glossary.html[git glossary] should be sufficient.
+
+Developing against a shared repository
+--------------------------------------
+
+Suppose a shared repository is set up in /pub/repo.git on the host
+foo.com. Then as an individual committer you can clone the shared
+repository over ssh with:
+
+------------------------------------------------
+$ git clone foo.com:/pub/repo.git/ my-project
+$ cd my-project
+------------------------------------------------
+
+and hack away. The equivalent of `cvs update` is
+
+------------------------------------------------
+$ git pull origin
+------------------------------------------------
+
+which merges in any work that others might have done since the clone
+operation. If there are uncommitted changes in your working tree, commit
+them first before running git pull.
+
+[NOTE]
+================================
+The `pull` command knows where to get updates from because of certain
+configuration variables that were set by the first `git clone`
+command; see `git config -l` and the linkgit:git-config[1] man
+page for details.
+================================
+
+You can update the shared repository with your changes by first committing
+your changes, and then using the linkgit:git-push[1] command:
+
+------------------------------------------------
+$ git push origin master
+------------------------------------------------
+
+to "push" those commits to the shared repository. If someone else has
+updated the repository more recently, `git push`, like `cvs commit`, will
+complain, in which case you must pull any changes before attempting the
+push again.
+
+In the `git push` command above we specify the name of the remote branch
+to update (`master`). If we leave that out, `git push` tries to update
+any branches in the remote repository that have the same name as a branch
+in the local repository. So the last `push` can be done with either of:
+
+------------
+$ git push origin
+$ git push foo.com:/pub/project.git/
+------------
+
+as long as the shared repository does not have any branches
+other than `master`.
+
+Setting Up a Shared Repository
+------------------------------
+
+We assume you have already created a git repository for your project,
+possibly created from scratch or from a tarball (see the
+linkgit:gittutorial[7][tutorial]), or imported from an already existing CVS
+repository (see the next section).
+
+Assume your existing repo is at /home/alice/myproject. Create a new "bare"
+repository (a repository without a working tree) and fetch your project into
+it:
+
+------------------------------------------------
+$ mkdir /pub/my-repo.git
+$ cd /pub/my-repo.git
+$ git --bare init --shared
+$ git --bare fetch /home/alice/myproject master:master
+------------------------------------------------
+
+Next, give every team member read/write access to this repository. One
+easy way to do this is to give all the team members ssh access to the
+machine where the repository is hosted. If you don't want to give them a
+full shell on the machine, there is a restricted shell which only allows
+users to do git pushes and pulls; see linkgit:git-shell[1].
+
+Put all the committers in the same group, and make the repository
+writable by that group:
+
+------------------------------------------------
+$ chgrp -R $group /pub/my-repo.git
+------------------------------------------------
+
+Make sure committers have a umask of at most 027, so that the directories
+they create are writable and searchable by other group members.
+
+Importing a CVS archive
+-----------------------
+
+First, install version 2.1 or higher of cvsps from
+link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
+sure it is in your path. Then cd to a checked out CVS working directory
+of the project you are interested in and run linkgit:git-cvsimport[1]:
+
+-------------------------------------------
+$ git cvsimport -C <destination> <module>
+-------------------------------------------
+
+This puts a git archive of the named CVS module in the directory
+<destination>, which will be created if necessary.
+
+The import checks out from CVS every revision of every file. Reportedly
+cvsimport can average some twenty revisions per second, so for a
+medium-sized project this should not take more than a couple of minutes.
+Larger projects or remote repositories may take longer.
+
+The main trunk is stored in the git branch named `origin`, and additional
+CVS branches are stored in git branches with the same names. The most
+recent version of the main trunk is also left checked out on the `master`
+branch, so you can start adding your own changes right away.
+
+The import is incremental, so if you call it again next month it will
+fetch any CVS updates that have been made in the meantime. For this to
+work, you must not modify the imported branches; instead, create new
+branches for your own changes, and merge in the imported branches as
+necessary.
+
+Advanced Shared Repository Management
+-------------------------------------
+
+Git allows you to specify scripts called "hooks" to be run at certain
+points. You can use these, for example, to send all commits to the shared
+repository to a mailing list. See linkgit:githooks[5][Hooks used by git].
+
+You can enforce finer grained permissions using update hooks. See
+link:howto/update-hook-example.txt[Controlling access to branches using
+update hooks].
+
+Providing CVS Access to a git Repository
+----------------------------------------
+
+It is also possible to provide true CVS access to a git repository, so
+that developers can still use CVS; see linkgit:git-cvsserver[1] for
+details.
+
+Alternative Development Models
+------------------------------
+
+CVS users are accustomed to giving a group of developers commit access to
+a common repository. As we've seen, this is also possible with git.
+However, the distributed nature of git allows other development models,
+and you may want to first consider whether one of them might be a better
+fit for your project.
+
+For example, you can choose a single person to maintain the project's
+primary public repository. Other developers then clone this repository
+and each work in their own clone. When they have a series of changes that
+they're happy with, they ask the maintainer to pull from the branch
+containing the changes. The maintainer reviews their changes and pulls
+them into the primary repository, which other developers pull from as
+necessary to stay coordinated. The Linux kernel and other projects use
+variants of this model.
+
+With a small group, developers may just pull changes from each other's
+repositories without the need for a central maintainer.
+
+SEE ALSO
+--------
+linkgit:gittutorial[7], linkgit:gittutorial-2[7],
+link:everyday.html[Everyday Git],
+link:user-manual.html[The Git User's Manual]
+
+GIT
+---
+Part of the linkgit:git[7] suite.
--- /dev/null
+gittutorial-2(7)
+================
+
+NAME
+----
+gittutorial-2 - A tutorial introduction to git: part two
+
+SYNOPSIS
+--------
+git *
+
+DESCRIPTION
+-----------
+
+You should work through linkgit:gittutorial[7][A tutorial introduction to
+git] before reading this tutorial.
+
+The goal of this tutorial is to introduce two fundamental pieces of
+git's architecture--the object database and the index file--and to
+provide the reader with everything necessary to understand the rest
+of the git documentation.
+
+The git object database
+-----------------------
+
+Let's start a new project and create a small amount of history:
+
+------------------------------------------------
+$ mkdir test-project
+$ cd test-project
+$ git init
+Initialized empty Git repository in .git/
+$ echo 'hello world' > file.txt
+$ git add .
+$ git commit -a -m "initial commit"
+Created initial commit 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
+ create mode 100644 file.txt
+$ echo 'hello world!' >file.txt
+$ git commit -a -m "add emphasis"
+Created commit c4d59f390b9cfd4318117afde11d601c1085f241
+------------------------------------------------
+
+What are the 40 digits of hex that git responded to the commit with?
+
+We saw in part one of the tutorial that commits have names like this.
+It turns out that every object in the git history is stored under
+such a 40-digit hex name. That name is the SHA1 hash of the object's
+contents; among other things, this ensures that git will never store
+the same data twice (since identical data is given an identical SHA1
+name), and that the contents of a git object will never change (since
+that would change the object's name as well).
+
+It is expected that the content of the commit object you created while
+following the example above generates a different SHA1 hash than
+the one shown above because the commit object records the time when
+it was created and the name of the person performing the commit.
+
+We can ask git about this particular object with the cat-file
+command. Don't copy the 40 hex digits from this example but use those
+from your own version. Note that you can shorten it to only a few
+characters to save yourself typing all 40 hex digits:
+
+------------------------------------------------
+$ git-cat-file -t 54196cc2
+commit
+$ git-cat-file commit 54196cc2
+tree 92b8b694ffb1675e5975148e1121810081dbdffe
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+
+initial commit
+------------------------------------------------
+
+A tree can refer to one or more "blob" objects, each corresponding to
+a file. In addition, a tree can also refer to other tree objects,
+thus creating a directory hierarchy. You can examine the contents of
+any tree using ls-tree (remember that a long enough initial portion
+of the SHA1 will also work):
+
+------------------------------------------------
+$ git ls-tree 92b8b694
+100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad file.txt
+------------------------------------------------
+
+Thus we see that this tree has one file in it. The SHA1 hash is a
+reference to that file's data:
+
+------------------------------------------------
+$ git cat-file -t 3b18e512
+blob
+------------------------------------------------
+
+A "blob" is just file data, which we can also examine with cat-file:
+
+------------------------------------------------
+$ git cat-file blob 3b18e512
+hello world
+------------------------------------------------
+
+Note that this is the old file data; so the object that git named in
+its response to the initial tree was a tree with a snapshot of the
+directory state that was recorded by the first commit.
+
+All of these objects are stored under their SHA1 names inside the git
+directory:
+
+------------------------------------------------
+$ find .git/objects/
+.git/objects/
+.git/objects/pack
+.git/objects/info
+.git/objects/3b
+.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
+.git/objects/92
+.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
+.git/objects/54
+.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
+.git/objects/a0
+.git/objects/a0/423896973644771497bdc03eb99d5281615b51
+.git/objects/d0
+.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
+.git/objects/c4
+.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241
+------------------------------------------------
+
+and the contents of these files is just the compressed data plus a
+header identifying their length and their type. The type is either a
+blob, a tree, a commit, or a tag.
+
+The simplest commit to find is the HEAD commit, which we can find
+from .git/HEAD:
+
+------------------------------------------------
+$ cat .git/HEAD
+ref: refs/heads/master
+------------------------------------------------
+
+As you can see, this tells us which branch we're currently on, and it
+tells us this by naming a file under the .git directory, which itself
+contains a SHA1 name referring to a commit object, which we can
+examine with cat-file:
+
+------------------------------------------------
+$ cat .git/refs/heads/master
+c4d59f390b9cfd4318117afde11d601c1085f241
+$ git cat-file -t c4d59f39
+commit
+$ git cat-file commit c4d59f39
+tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
+parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
+
+add emphasis
+------------------------------------------------
+
+The "tree" object here refers to the new state of the tree:
+
+------------------------------------------------
+$ git ls-tree d0492b36
+100644 blob a0423896973644771497bdc03eb99d5281615b51 file.txt
+$ git cat-file blob a0423896
+hello world!
+------------------------------------------------
+
+and the "parent" object refers to the previous commit:
+
+------------------------------------------------
+$ git-cat-file commit 54196cc2
+tree 92b8b694ffb1675e5975148e1121810081dbdffe
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+
+initial commit
+------------------------------------------------
+
+The tree object is the tree we examined first, and this commit is
+unusual in that it lacks any parent.
+
+Most commits have only one parent, but it is also common for a commit
+to have multiple parents. In that case the commit represents a
+merge, with the parent references pointing to the heads of the merged
+branches.
+
+Besides blobs, trees, and commits, the only remaining type of object
+is a "tag", which we won't discuss here; refer to linkgit:git-tag[1]
+for details.
+
+So now we know how git uses the object database to represent a
+project's history:
+
+ * "commit" objects refer to "tree" objects representing the
+ snapshot of a directory tree at a particular point in the
+ history, and refer to "parent" commits to show how they're
+ connected into the project history.
+ * "tree" objects represent the state of a single directory,
+ associating directory names to "blob" objects containing file
+ data and "tree" objects containing subdirectory information.
+ * "blob" objects contain file data without any other structure.
+ * References to commit objects at the head of each branch are
+ stored in files under .git/refs/heads/.
+ * The name of the current branch is stored in .git/HEAD.
+
+Note, by the way, that lots of commands take a tree as an argument.
+But as we can see above, a tree can be referred to in many different
+ways--by the SHA1 name for that tree, by the name of a commit that
+refers to the tree, by the name of a branch whose head refers to that
+tree, etc.--and most such commands can accept any of these names.
+
+In command synopses, the word "tree-ish" is sometimes used to
+designate such an argument.
+
+The index file
+--------------
+
+The primary tool we've been using to create commits is "git commit
+-a", which creates a commit including every change you've made to
+your working tree. But what if you want to commit changes only to
+certain files? Or only certain changes to certain files?
+
+If we look at the way commits are created under the cover, we'll see
+that there are more flexible ways creating commits.
+
+Continuing with our test-project, let's modify file.txt again:
+
+------------------------------------------------
+$ echo "hello world, again" >>file.txt
+------------------------------------------------
+
+but this time instead of immediately making the commit, let's take an
+intermediate step, and ask for diffs along the way to keep track of
+what's happening:
+
+------------------------------------------------
+$ git diff
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+$ git add file.txt
+$ git diff
+------------------------------------------------
+
+The last diff is empty, but no new commits have been made, and the
+head still doesn't contain the new line:
+
+------------------------------------------------
+$ git-diff HEAD
+diff --git a/file.txt b/file.txt
+index a042389..513feba 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+------------------------------------------------
+
+So "git diff" is comparing against something other than the head.
+The thing that it's comparing against is actually the index file,
+which is stored in .git/index in a binary format, but whose contents
+we can examine with ls-files:
+
+------------------------------------------------
+$ git ls-files --stage
+100644 513feba2e53ebbd2532419ded848ba19de88ba00 0 file.txt
+$ git cat-file -t 513feba2
+blob
+$ git cat-file blob 513feba2
+hello world!
+hello world, again
+------------------------------------------------
+
+So what our "git add" did was store a new blob and then put
+a reference to it in the index file. If we modify the file again,
+we'll see that the new modifications are reflected in the "git-diff"
+output:
+
+------------------------------------------------
+$ echo 'again?' >>file.txt
+$ git diff
+index 513feba..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1,2 +1,3 @@
+ hello world!
+ hello world, again
++again?
+------------------------------------------------
+
+With the right arguments, git diff can also show us the difference
+between the working directory and the last commit, or between the
+index and the last commit:
+
+------------------------------------------------
+$ git diff HEAD
+diff --git a/file.txt b/file.txt
+index a042389..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,3 @@
+ hello world!
++hello world, again
++again?
+$ git diff --cached
+diff --git a/file.txt b/file.txt
+index a042389..513feba 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+------------------------------------------------
+
+At any time, we can create a new commit using "git commit" (without
+the -a option), and verify that the state committed only includes the
+changes stored in the index file, not the additional change that is
+still only in our working tree:
+
+------------------------------------------------
+$ git commit -m "repeat"
+$ git diff HEAD
+diff --git a/file.txt b/file.txt
+index 513feba..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1,2 +1,3 @@
+ hello world!
+ hello world, again
++again?
+------------------------------------------------
+
+So by default "git commit" uses the index to create the commit, not
+the working tree; the -a option to commit tells it to first update
+the index with all changes in the working tree.
+
+Finally, it's worth looking at the effect of "git add" on the index
+file:
+
+------------------------------------------------
+$ echo "goodbye, world" >closing.txt
+$ git add closing.txt
+------------------------------------------------
+
+The effect of the "git add" was to add one entry to the index file:
+
+------------------------------------------------
+$ git ls-files --stage
+100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0 closing.txt
+100644 513feba2e53ebbd2532419ded848ba19de88ba00 0 file.txt
+------------------------------------------------
+
+And, as you can see with cat-file, this new entry refers to the
+current contents of the file:
+
+------------------------------------------------
+$ git cat-file blob 8b9743b2
+goodbye, world
+------------------------------------------------
+
+The "status" command is a useful way to get a quick summary of the
+situation:
+
+------------------------------------------------
+$ git status
+# On branch master
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# new file: closing.txt
+#
+# Changed but not updated:
+# (use "git add <file>..." to update what will be committed)
+#
+# modified: file.txt
+#
+------------------------------------------------
+
+Since the current state of closing.txt is cached in the index file,
+it is listed as "Changes to be committed". Since file.txt has
+changes in the working directory that aren't reflected in the index,
+it is marked "changed but not updated". At this point, running "git
+commit" would create a commit that added closing.txt (with its new
+contents), but that didn't modify file.txt.
+
+Also, note that a bare "git diff" shows the changes to file.txt, but
+not the addition of closing.txt, because the version of closing.txt
+in the index file is identical to the one in the working directory.
+
+In addition to being the staging area for new commits, the index file
+is also populated from the object database when checking out a
+branch, and is used to hold the trees involved in a merge operation.
+See the link:core-tutorial.html[core tutorial] and the relevant man
+pages for details.
+
+What next?
+----------
+
+At this point you should know everything necessary to read the man
+pages for any of the git commands; one good place to start would be
+with the commands mentioned in link:everyday.html[Everyday git]. You
+should be able to find any unknown jargon in the
+link:glossary.html[Glossary].
+
+The link:user-manual.html[Git User's Manual] provides a more
+comprehensive introduction to git.
+
+The linkgit:gitcvs-migration[7][CVS migration] document explains how to
+import a CVS repository into git, and shows how to use git in a
+CVS-like way.
+
+For some interesting examples of git use, see the
+link:howto-index.html[howtos].
+
+For git developers, the link:core-tutorial.html[Core tutorial] goes
+into detail on the lower-level git mechanisms involved in, for
+example, creating a new commit.
+
+SEE ALSO
+--------
+linkgit:gittutorial[7],
+linkgit:gitcvs-migration[7],
+link:everyday.html[Everyday git],
+link:user-manual.html[The Git User's Manual]
+
+GIT
+---
+Part of the linkgit:git[7] suite.
--- /dev/null
+gittutorial(7)
+==============
+
+NAME
+----
+gittutorial - A tutorial introduction to git (for version 1.5.1 or newer)
+
+SYNOPSIS
+--------
+git *
+
+DESCRIPTION
+-----------
+
+This tutorial explains how to import a new project into git, make
+changes to it, and share changes with other developers.
+
+If you are instead primarily interested in using git to fetch a project,
+for example, to test the latest version, you may prefer to start with
+the first two chapters of link:user-manual.html[The Git User's Manual].
+
+First, note that you can get documentation for a command such as "git
+diff" with:
+
+------------------------------------------------
+$ man git-diff
+------------------------------------------------
+
+It is a good idea to introduce yourself to git with your name and
+public email address before doing any operation. The easiest
+way to do so is:
+
+------------------------------------------------
+$ git config --global user.name "Your Name Comes Here"
+$ git config --global user.email you@yourdomain.example.com
+------------------------------------------------
+
+
+Importing a new project
+-----------------------
+
+Assume you have a tarball project.tar.gz with your initial work. You
+can place it under git revision control as follows.
+
+------------------------------------------------
+$ tar xzf project.tar.gz
+$ cd project
+$ git init
+------------------------------------------------
+
+Git will reply
+
+------------------------------------------------
+Initialized empty Git repository in .git/
+------------------------------------------------
+
+You've now initialized the working directory--you may notice a new
+directory created, named ".git".
+
+Next, tell git to take a snapshot of the contents of all files under the
+current directory (note the '.'), with linkgit:git-add[1]:
+
+------------------------------------------------
+$ git add .
+------------------------------------------------
+
+This snapshot is now stored in a temporary staging area which git calls
+the "index". You can permanently store the contents of the index in the
+repository with linkgit:git-commit[1]:
+
+------------------------------------------------
+$ git commit
+------------------------------------------------
+
+This will prompt you for a commit message. You've now stored the first
+version of your project in git.
+
+Making changes
+--------------
+
+Modify some files, then add their updated contents to the index:
+
+------------------------------------------------
+$ git add file1 file2 file3
+------------------------------------------------
+
+You are now ready to commit. You can see what is about to be committed
+using linkgit:git-diff[1] with the --cached option:
+
+------------------------------------------------
+$ git diff --cached
+------------------------------------------------
+
+(Without --cached, linkgit:git-diff[1] will show you any changes that
+you've made but not yet added to the index.) You can also get a brief
+summary of the situation with linkgit:git-status[1]:
+
+------------------------------------------------
+$ git status
+# On branch master
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+# modified: file1
+# modified: file2
+# modified: file3
+#
+------------------------------------------------
+
+If you need to make any further adjustments, do so now, and then add any
+newly modified content to the index. Finally, commit your changes with:
+
+------------------------------------------------
+$ git commit
+------------------------------------------------
+
+This will again prompt your for a message describing the change, and then
+record a new version of the project.
+
+Alternatively, instead of running `git add` beforehand, you can use
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+which will automatically notice any modified (but not new) files, add
+them to the index, and commit, all in one step.
+
+A note on commit messages: Though not required, it's a good idea to
+begin the commit message with a single short (less than 50 character)
+line summarizing the change, followed by a blank line and then a more
+thorough description. Tools that turn commits into email, for
+example, use the first line on the Subject: line and the rest of the
+commit in the body.
+
+Git tracks content not files
+----------------------------
+
+Many revision control systems provide an "add" command that tells the
+system to start tracking changes to a new file. Git's "add" command
+does something simpler and more powerful: `git add` is used both for new
+and newly modified files, and in both cases it takes a snapshot of the
+given files and stages that content in the index, ready for inclusion in
+the next commit.
+
+Viewing project history
+-----------------------
+
+At any point you can view the history of your changes using
+
+------------------------------------------------
+$ git log
+------------------------------------------------
+
+If you also want to see complete diffs at each step, use
+
+------------------------------------------------
+$ git log -p
+------------------------------------------------
+
+Often the overview of the change is useful to get a feel of
+each step
+
+------------------------------------------------
+$ git log --stat --summary
+------------------------------------------------
+
+Managing branches
+-----------------
+
+A single git repository can maintain multiple branches of
+development. To create a new branch named "experimental", use
+
+------------------------------------------------
+$ git branch experimental
+------------------------------------------------
+
+If you now run
+
+------------------------------------------------
+$ git branch
+------------------------------------------------
+
+you'll get a list of all existing branches:
+
+------------------------------------------------
+ experimental
+* master
+------------------------------------------------
+
+The "experimental" branch is the one you just created, and the
+"master" branch is a default branch that was created for you
+automatically. The asterisk marks the branch you are currently on;
+type
+
+------------------------------------------------
+$ git checkout experimental
+------------------------------------------------
+
+to switch to the experimental branch. Now edit a file, commit the
+change, and switch back to the master branch:
+
+------------------------------------------------
+(edit file)
+$ git commit -a
+$ git checkout master
+------------------------------------------------
+
+Check that the change you made is no longer visible, since it was
+made on the experimental branch and you're back on the master branch.
+
+You can make a different change on the master branch:
+
+------------------------------------------------
+(edit file)
+$ git commit -a
+------------------------------------------------
+
+at this point the two branches have diverged, with different changes
+made in each. To merge the changes made in experimental into master, run
+
+------------------------------------------------
+$ git merge experimental
+------------------------------------------------
+
+If the changes don't conflict, you're done. If there are conflicts,
+markers will be left in the problematic files showing the conflict;
+
+------------------------------------------------
+$ git diff
+------------------------------------------------
+
+will show this. Once you've edited the files to resolve the
+conflicts,
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+will commit the result of the merge. Finally,
+
+------------------------------------------------
+$ gitk
+------------------------------------------------
+
+will show a nice graphical representation of the resulting history.
+
+At this point you could delete the experimental branch with
+
+------------------------------------------------
+$ git branch -d experimental
+------------------------------------------------
+
+This command ensures that the changes in the experimental branch are
+already in the current branch.
+
+If you develop on a branch crazy-idea, then regret it, you can always
+delete the branch with
+
+-------------------------------------
+$ git branch -D crazy-idea
+-------------------------------------
+
+Branches are cheap and easy, so this is a good way to try something
+out.
+
+Using git for collaboration
+---------------------------
+
+Suppose that Alice has started a new project with a git repository in
+/home/alice/project, and that Bob, who has a home directory on the
+same machine, wants to contribute.
+
+Bob begins with:
+
+------------------------------------------------
+$ git clone /home/alice/project myrepo
+------------------------------------------------
+
+This creates a new directory "myrepo" containing a clone of Alice's
+repository. The clone is on an equal footing with the original
+project, possessing its own copy of the original project's history.
+
+Bob then makes some changes and commits them:
+
+------------------------------------------------
+(edit files)
+$ git commit -a
+(repeat as necessary)
+------------------------------------------------
+
+When he's ready, he tells Alice to pull changes from the repository
+at /home/bob/myrepo. She does this with:
+
+------------------------------------------------
+$ cd /home/alice/project
+$ git pull /home/bob/myrepo master
+------------------------------------------------
+
+This merges the changes from Bob's "master" branch into Alice's
+current branch. If Alice has made her own changes in the meantime,
+then she may need to manually fix any conflicts. (Note that the
+"master" argument in the above command is actually unnecessary, as it
+is the default.)
+
+The "pull" command thus performs two operations: it fetches changes
+from a remote branch, then merges them into the current branch.
+
+When you are working in a small closely knit group, it is not
+unusual to interact with the same repository over and over
+again. By defining 'remote' repository shorthand, you can make
+it easier:
+
+------------------------------------------------
+$ git remote add bob /home/bob/myrepo
+------------------------------------------------
+
+With this, Alice can perform the first operation alone using the
+"git fetch" command without merging them with her own branch,
+using:
+
+-------------------------------------
+$ git fetch bob
+-------------------------------------
+
+Unlike the longhand form, when Alice fetches from Bob using a
+remote repository shorthand set up with `git remote`, what was
+fetched is stored in a remote tracking branch, in this case
+`bob/master`. So after this:
+
+-------------------------------------
+$ git log -p master..bob/master
+-------------------------------------
+
+shows a list of all the changes that Bob made since he branched from
+Alice's master branch.
+
+After examining those changes, Alice
+could merge the changes into her master branch:
+
+-------------------------------------
+$ git merge bob/master
+-------------------------------------
+
+This `merge` can also be done by 'pulling from her own remote
+tracking branch', like this:
+
+-------------------------------------
+$ git pull . remotes/bob/master
+-------------------------------------
+
+Note that git pull always merges into the current branch,
+regardless of what else is given on the command line.
+
+Later, Bob can update his repo with Alice's latest changes using
+
+-------------------------------------
+$ git pull
+-------------------------------------
+
+Note that he doesn't need to give the path to Alice's repository;
+when Bob cloned Alice's repository, git stored the location of her
+repository in the repository configuration, and that location is
+used for pulls:
+
+-------------------------------------
+$ git config --get remote.origin.url
+/home/alice/project
+-------------------------------------
+
+(The complete configuration created by git-clone is visible using
+"git config -l", and the linkgit:git-config[1] man page
+explains the meaning of each option.)
+
+Git also keeps a pristine copy of Alice's master branch under the
+name "origin/master":
+
+-------------------------------------
+$ git branch -r
+ origin/master
+-------------------------------------
+
+If Bob later decides to work from a different host, he can still
+perform clones and pulls using the ssh protocol:
+
+-------------------------------------
+$ git clone alice.org:/home/alice/project myrepo
+-------------------------------------
+
+Alternatively, git has a native protocol, or can use rsync or http;
+see linkgit:git-pull[1] for details.
+
+Git can also be used in a CVS-like mode, with a central repository
+that various users push changes to; see linkgit:git-push[1] and
+linkgit:gitcvs-migration[7][git for CVS users].
+
+Exploring history
+-----------------
+
+Git history is represented as a series of interrelated commits. We
+have already seen that the git log command can list those commits.
+Note that first line of each git log entry also gives a name for the
+commit:
+
+-------------------------------------
+$ git log
+commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
+Author: Junio C Hamano <junkio@cox.net>
+Date: Tue May 16 17:18:22 2006 -0700
+
+ merge-base: Clarify the comments on post processing.
+-------------------------------------
+
+We can give this name to git show to see the details about this
+commit.
+
+-------------------------------------
+$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
+-------------------------------------
+
+But there are other ways to refer to commits. You can use any initial
+part of the name that is long enough to uniquely identify the commit:
+
+-------------------------------------
+$ git show c82a22c39c # the first few characters of the name are
+ # usually enough
+$ git show HEAD # the tip of the current branch
+$ git show experimental # the tip of the "experimental" branch
+-------------------------------------
+
+Every commit usually has one "parent" commit
+which points to the previous state of the project:
+
+-------------------------------------
+$ git show HEAD^ # to see the parent of HEAD
+$ git show HEAD^^ # to see the grandparent of HEAD
+$ git show HEAD~4 # to see the great-great grandparent of HEAD
+-------------------------------------
+
+Note that merge commits may have more than one parent:
+
+-------------------------------------
+$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)
+$ git show HEAD^2 # show the second parent of HEAD
+-------------------------------------
+
+You can also give commits names of your own; after running
+
+-------------------------------------
+$ git-tag v2.5 1b2e1d63ff
+-------------------------------------
+
+you can refer to 1b2e1d63ff by the name "v2.5". If you intend to
+share this name with other people (for example, to identify a release
+version), you should create a "tag" object, and perhaps sign it; see
+linkgit:git-tag[1] for details.
+
+Any git command that needs to know a commit can take any of these
+names. For example:
+
+-------------------------------------
+$ git diff v2.5 HEAD # compare the current HEAD to v2.5
+$ git branch stable v2.5 # start a new branch named "stable" based
+ # at v2.5
+$ git reset --hard HEAD^ # reset your current branch and working
+ # directory to its state at HEAD^
+-------------------------------------
+
+Be careful with that last command: in addition to losing any changes
+in the working directory, it will also remove all later commits from
+this branch. If this branch is the only branch containing those
+commits, they will be lost. Also, don't use "git reset" on a
+publicly-visible branch that other developers pull from, as it will
+force needless merges on other developers to clean up the history.
+If you need to undo changes that you have pushed, use linkgit:git-revert[1]
+instead.
+
+The git grep command can search for strings in any version of your
+project, so
+
+-------------------------------------
+$ git grep "hello" v2.5
+-------------------------------------
+
+searches for all occurrences of "hello" in v2.5.
+
+If you leave out the commit name, git grep will search any of the
+files it manages in your current directory. So
+
+-------------------------------------
+$ git grep "hello"
+-------------------------------------
+
+is a quick way to search just the files that are tracked by git.
+
+Many git commands also take sets of commits, which can be specified
+in a number of ways. Here are some examples with git log:
+
+-------------------------------------
+$ git log v2.5..v2.6 # commits between v2.5 and v2.6
+$ git log v2.5.. # commits since v2.5
+$ git log --since="2 weeks ago" # commits from the last 2 weeks
+$ git log v2.5.. Makefile # commits since v2.5 which modify
+ # Makefile
+-------------------------------------
+
+You can also give git log a "range" of commits where the first is not
+necessarily an ancestor of the second; for example, if the tips of
+the branches "stable-release" and "master" diverged from a common
+commit some time ago, then
+
+-------------------------------------
+$ git log stable..experimental
+-------------------------------------
+
+will list commits made in the experimental branch but not in the
+stable branch, while
+
+-------------------------------------
+$ git log experimental..stable
+-------------------------------------
+
+will show the list of commits made on the stable branch but not
+the experimental branch.
+
+The "git log" command has a weakness: it must present commits in a
+list. When the history has lines of development that diverged and
+then merged back together, the order in which "git log" presents
+those commits is meaningless.
+
+Most projects with multiple contributors (such as the linux kernel,
+or git itself) have frequent merges, and gitk does a better job of
+visualizing their history. For example,
+
+-------------------------------------
+$ gitk --since="2 weeks ago" drivers/
+-------------------------------------
+
+allows you to browse any commits from the last 2 weeks of commits
+that modified files under the "drivers" directory. (Note: you can
+adjust gitk's fonts by holding down the control key while pressing
+"-" or "+".)
+
+Finally, most commands that take filenames will optionally allow you
+to precede any filename by a commit, to specify a particular version
+of the file:
+
+-------------------------------------
+$ git diff v2.5:Makefile HEAD:Makefile.in
+-------------------------------------
+
+You can also use "git show" to see any such file:
+
+-------------------------------------
+$ git show v2.5:Makefile
+-------------------------------------
+
+Next Steps
+----------
+
+This tutorial should be enough to perform basic distributed revision
+control for your projects. However, to fully understand the depth
+and power of git you need to understand two simple ideas on which it
+is based:
+
+ * The object database is the rather elegant system used to
+ store the history of your project--files, directories, and
+ commits.
+
+ * The index file is a cache of the state of a directory tree,
+ used to create commits, check out working directories, and
+ hold the various trees involved in a merge.
+
+linkgit:gittutorial-2[7][Part two of this tutorial] explains the object
+database, the index file, and a few other odds and ends that you'll
+need to make the most of git.
+
+If you don't want to continue with that right away, a few other
+digressions that may be interesting at this point are:
+
+ * linkgit:git-format-patch[1], linkgit:git-am[1]: These convert
+ series of git commits into emailed patches, and vice versa,
+ useful for projects such as the linux kernel which rely heavily
+ on emailed patches.
+
+ * linkgit:git-bisect[1]: When there is a regression in your
+ project, one way to track down the bug is by searching through
+ the history to find the exact commit that's to blame. Git bisect
+ can help you perform a binary search for that commit. It is
+ smart enough to perform a close-to-optimal search even in the
+ case of complex non-linear history with lots of merged branches.
+
+ * link:everyday.html[Everyday GIT with 20 Commands Or So]
+
+ * linkgit:gitcvs-migration[7][git for CVS users].
+
+SEE ALSO
+--------
+linkgit:gittutorial-2[7],
+linkgit:gitcvs-migration[7],
+link:everyday.html[Everyday git],
+link:user-manual.html[The Git User's Manual]
+
+GIT
+---
+Part of the linkgit:git[7] suite.
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".
-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
~~~~~~~~~~~~~~~
--- /dev/null
+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 \
+| | | | | |\ |
+| | | | * | | |
+| | | | * | | |
+* | | | | | | |
+| |/ / / / / /
+|/| / / / / /
+* | | | | | |
+|/ / / / / /
+* | | | | |
+| | | | | *
+| | | | |/
+| | | | *
+------------
+++ /dev/null
-A tutorial introduction to git: part two
-========================================
-
-You should work through link:tutorial.html[A tutorial introduction to
-git] before reading this tutorial.
-
-The goal of this tutorial is to introduce two fundamental pieces of
-git's architecture--the object database and the index file--and to
-provide the reader with everything necessary to understand the rest
-of the git documentation.
-
-The git object database
------------------------
-
-Let's start a new project and create a small amount of history:
-
-------------------------------------------------
-$ mkdir test-project
-$ cd test-project
-$ git init
-Initialized empty Git repository in .git/
-$ echo 'hello world' > file.txt
-$ git add .
-$ git commit -a -m "initial commit"
-Created initial commit 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
- create mode 100644 file.txt
-$ echo 'hello world!' >file.txt
-$ git commit -a -m "add emphasis"
-Created commit c4d59f390b9cfd4318117afde11d601c1085f241
-------------------------------------------------
-
-What are the 40 digits of hex that git responded to the commit with?
-
-We saw in part one of the tutorial that commits have names like this.
-It turns out that every object in the git history is stored under
-such a 40-digit hex name. That name is the SHA1 hash of the object's
-contents; among other things, this ensures that git will never store
-the same data twice (since identical data is given an identical SHA1
-name), and that the contents of a git object will never change (since
-that would change the object's name as well).
-
-It is expected that the content of the commit object you created while
-following the example above generates a different SHA1 hash than
-the one shown above because the commit object records the time when
-it was created and the name of the person performing the commit.
-
-We can ask git about this particular object with the cat-file
-command. Don't copy the 40 hex digits from this example but use those
-from your own version. Note that you can shorten it to only a few
-characters to save yourself typing all 40 hex digits:
-
-------------------------------------------------
-$ git-cat-file -t 54196cc2
-commit
-$ git-cat-file commit 54196cc2
-tree 92b8b694ffb1675e5975148e1121810081dbdffe
-author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-
-initial commit
-------------------------------------------------
-
-A tree can refer to one or more "blob" objects, each corresponding to
-a file. In addition, a tree can also refer to other tree objects,
-thus creating a directory hierarchy. You can examine the contents of
-any tree using ls-tree (remember that a long enough initial portion
-of the SHA1 will also work):
-
-------------------------------------------------
-$ git ls-tree 92b8b694
-100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad file.txt
-------------------------------------------------
-
-Thus we see that this tree has one file in it. The SHA1 hash is a
-reference to that file's data:
-
-------------------------------------------------
-$ git cat-file -t 3b18e512
-blob
-------------------------------------------------
-
-A "blob" is just file data, which we can also examine with cat-file:
-
-------------------------------------------------
-$ git cat-file blob 3b18e512
-hello world
-------------------------------------------------
-
-Note that this is the old file data; so the object that git named in
-its response to the initial tree was a tree with a snapshot of the
-directory state that was recorded by the first commit.
-
-All of these objects are stored under their SHA1 names inside the git
-directory:
-
-------------------------------------------------
-$ find .git/objects/
-.git/objects/
-.git/objects/pack
-.git/objects/info
-.git/objects/3b
-.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
-.git/objects/92
-.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
-.git/objects/54
-.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
-.git/objects/a0
-.git/objects/a0/423896973644771497bdc03eb99d5281615b51
-.git/objects/d0
-.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
-.git/objects/c4
-.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241
-------------------------------------------------
-
-and the contents of these files is just the compressed data plus a
-header identifying their length and their type. The type is either a
-blob, a tree, a commit, or a tag.
-
-The simplest commit to find is the HEAD commit, which we can find
-from .git/HEAD:
-
-------------------------------------------------
-$ cat .git/HEAD
-ref: refs/heads/master
-------------------------------------------------
-
-As you can see, this tells us which branch we're currently on, and it
-tells us this by naming a file under the .git directory, which itself
-contains a SHA1 name referring to a commit object, which we can
-examine with cat-file:
-
-------------------------------------------------
-$ cat .git/refs/heads/master
-c4d59f390b9cfd4318117afde11d601c1085f241
-$ git cat-file -t c4d59f39
-commit
-$ git cat-file commit c4d59f39
-tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
-parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
-author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
-committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
-
-add emphasis
-------------------------------------------------
-
-The "tree" object here refers to the new state of the tree:
-
-------------------------------------------------
-$ git ls-tree d0492b36
-100644 blob a0423896973644771497bdc03eb99d5281615b51 file.txt
-$ git cat-file blob a0423896
-hello world!
-------------------------------------------------
-
-and the "parent" object refers to the previous commit:
-
-------------------------------------------------
-$ git-cat-file commit 54196cc2
-tree 92b8b694ffb1675e5975148e1121810081dbdffe
-author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-
-initial commit
-------------------------------------------------
-
-The tree object is the tree we examined first, and this commit is
-unusual in that it lacks any parent.
-
-Most commits have only one parent, but it is also common for a commit
-to have multiple parents. In that case the commit represents a
-merge, with the parent references pointing to the heads of the merged
-branches.
-
-Besides blobs, trees, and commits, the only remaining type of object
-is a "tag", which we won't discuss here; refer to linkgit:git-tag[1]
-for details.
-
-So now we know how git uses the object database to represent a
-project's history:
-
- * "commit" objects refer to "tree" objects representing the
- snapshot of a directory tree at a particular point in the
- history, and refer to "parent" commits to show how they're
- connected into the project history.
- * "tree" objects represent the state of a single directory,
- associating directory names to "blob" objects containing file
- data and "tree" objects containing subdirectory information.
- * "blob" objects contain file data without any other structure.
- * References to commit objects at the head of each branch are
- stored in files under .git/refs/heads/.
- * The name of the current branch is stored in .git/HEAD.
-
-Note, by the way, that lots of commands take a tree as an argument.
-But as we can see above, a tree can be referred to in many different
-ways--by the SHA1 name for that tree, by the name of a commit that
-refers to the tree, by the name of a branch whose head refers to that
-tree, etc.--and most such commands can accept any of these names.
-
-In command synopses, the word "tree-ish" is sometimes used to
-designate such an argument.
-
-The index file
---------------
-
-The primary tool we've been using to create commits is "git commit
--a", which creates a commit including every change you've made to
-your working tree. But what if you want to commit changes only to
-certain files? Or only certain changes to certain files?
-
-If we look at the way commits are created under the cover, we'll see
-that there are more flexible ways creating commits.
-
-Continuing with our test-project, let's modify file.txt again:
-
-------------------------------------------------
-$ echo "hello world, again" >>file.txt
-------------------------------------------------
-
-but this time instead of immediately making the commit, let's take an
-intermediate step, and ask for diffs along the way to keep track of
-what's happening:
-
-------------------------------------------------
-$ git diff
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,2 @@
- hello world!
-+hello world, again
-$ git add file.txt
-$ git diff
-------------------------------------------------
-
-The last diff is empty, but no new commits have been made, and the
-head still doesn't contain the new line:
-
-------------------------------------------------
-$ git-diff HEAD
-diff --git a/file.txt b/file.txt
-index a042389..513feba 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,2 @@
- hello world!
-+hello world, again
-------------------------------------------------
-
-So "git diff" is comparing against something other than the head.
-The thing that it's comparing against is actually the index file,
-which is stored in .git/index in a binary format, but whose contents
-we can examine with ls-files:
-
-------------------------------------------------
-$ git ls-files --stage
-100644 513feba2e53ebbd2532419ded848ba19de88ba00 0 file.txt
-$ git cat-file -t 513feba2
-blob
-$ git cat-file blob 513feba2
-hello world!
-hello world, again
-------------------------------------------------
-
-So what our "git add" did was store a new blob and then put
-a reference to it in the index file. If we modify the file again,
-we'll see that the new modifications are reflected in the "git-diff"
-output:
-
-------------------------------------------------
-$ echo 'again?' >>file.txt
-$ git diff
-index 513feba..ba3da7b 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1,2 +1,3 @@
- hello world!
- hello world, again
-+again?
-------------------------------------------------
-
-With the right arguments, git diff can also show us the difference
-between the working directory and the last commit, or between the
-index and the last commit:
-
-------------------------------------------------
-$ git diff HEAD
-diff --git a/file.txt b/file.txt
-index a042389..ba3da7b 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,3 @@
- hello world!
-+hello world, again
-+again?
-$ git diff --cached
-diff --git a/file.txt b/file.txt
-index a042389..513feba 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,2 @@
- hello world!
-+hello world, again
-------------------------------------------------
-
-At any time, we can create a new commit using "git commit" (without
-the -a option), and verify that the state committed only includes the
-changes stored in the index file, not the additional change that is
-still only in our working tree:
-
-------------------------------------------------
-$ git commit -m "repeat"
-$ git diff HEAD
-diff --git a/file.txt b/file.txt
-index 513feba..ba3da7b 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1,2 +1,3 @@
- hello world!
- hello world, again
-+again?
-------------------------------------------------
-
-So by default "git commit" uses the index to create the commit, not
-the working tree; the -a option to commit tells it to first update
-the index with all changes in the working tree.
-
-Finally, it's worth looking at the effect of "git add" on the index
-file:
-
-------------------------------------------------
-$ echo "goodbye, world" >closing.txt
-$ git add closing.txt
-------------------------------------------------
-
-The effect of the "git add" was to add one entry to the index file:
-
-------------------------------------------------
-$ git ls-files --stage
-100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0 closing.txt
-100644 513feba2e53ebbd2532419ded848ba19de88ba00 0 file.txt
-------------------------------------------------
-
-And, as you can see with cat-file, this new entry refers to the
-current contents of the file:
-
-------------------------------------------------
-$ git cat-file blob 8b9743b2
-goodbye, world
-------------------------------------------------
-
-The "status" command is a useful way to get a quick summary of the
-situation:
-
-------------------------------------------------
-$ git status
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# new file: closing.txt
-#
-# Changed but not updated:
-# (use "git add <file>..." to update what will be committed)
-#
-# modified: file.txt
-#
-------------------------------------------------
-
-Since the current state of closing.txt is cached in the index file,
-it is listed as "Changes to be committed". Since file.txt has
-changes in the working directory that aren't reflected in the index,
-it is marked "changed but not updated". At this point, running "git
-commit" would create a commit that added closing.txt (with its new
-contents), but that didn't modify file.txt.
-
-Also, note that a bare "git diff" shows the changes to file.txt, but
-not the addition of closing.txt, because the version of closing.txt
-in the index file is identical to the one in the working directory.
-
-In addition to being the staging area for new commits, the index file
-is also populated from the object database when checking out a
-branch, and is used to hold the trees involved in a merge operation.
-See the link:core-tutorial.html[core tutorial] and the relevant man
-pages for details.
-
-What next?
-----------
-
-At this point you should know everything necessary to read the man
-pages for any of the git commands; one good place to start would be
-with the commands mentioned in link:everyday.html[Everyday git]. You
-should be able to find any unknown jargon in the
-link:glossary.html[Glossary].
-
-The link:user-manual.html[Git User's Manual] provides a more
-comprehensive introduction to git.
-
-The link:cvs-migration.html[CVS migration] document explains how to
-import a CVS repository into git, and shows how to use git in a
-CVS-like way.
-
-For some interesting examples of git use, see the
-link:howto-index.html[howtos].
-
-For git developers, the link:core-tutorial.html[Core tutorial] goes
-into detail on the lower-level git mechanisms involved in, for
-example, creating a new commit.
+++ /dev/null
-A tutorial introduction to git (for version 1.5.1 or newer)
-===========================================================
-
-This tutorial explains how to import a new project into git, make
-changes to it, and share changes with other developers.
-
-If you are instead primarily interested in using git to fetch a project,
-for example, to test the latest version, you may prefer to start with
-the first two chapters of link:user-manual.html[The Git User's Manual].
-
-First, note that you can get documentation for a command such as "git
-diff" with:
-
-------------------------------------------------
-$ man git-diff
-------------------------------------------------
-
-It is a good idea to introduce yourself to git with your name and
-public email address before doing any operation. The easiest
-way to do so is:
-
-------------------------------------------------
-$ git config --global user.name "Your Name Comes Here"
-$ git config --global user.email you@yourdomain.example.com
-------------------------------------------------
-
-
-Importing a new project
------------------------
-
-Assume you have a tarball project.tar.gz with your initial work. You
-can place it under git revision control as follows.
-
-------------------------------------------------
-$ tar xzf project.tar.gz
-$ cd project
-$ git init
-------------------------------------------------
-
-Git will reply
-
-------------------------------------------------
-Initialized empty Git repository in .git/
-------------------------------------------------
-
-You've now initialized the working directory--you may notice a new
-directory created, named ".git".
-
-Next, tell git to take a snapshot of the contents of all files under the
-current directory (note the '.'), with linkgit:git-add[1]:
-
-------------------------------------------------
-$ git add .
-------------------------------------------------
-
-This snapshot is now stored in a temporary staging area which git calls
-the "index". You can permanently store the contents of the index in the
-repository with linkgit:git-commit[1]:
-
-------------------------------------------------
-$ git commit
-------------------------------------------------
-
-This will prompt you for a commit message. You've now stored the first
-version of your project in git.
-
-Making changes
---------------
-
-Modify some files, then add their updated contents to the index:
-
-------------------------------------------------
-$ git add file1 file2 file3
-------------------------------------------------
-
-You are now ready to commit. You can see what is about to be committed
-using linkgit:git-diff[1] with the --cached option:
-
-------------------------------------------------
-$ git diff --cached
-------------------------------------------------
-
-(Without --cached, linkgit:git-diff[1] will show you any changes that
-you've made but not yet added to the index.) You can also get a brief
-summary of the situation with linkgit:git-status[1]:
-
-------------------------------------------------
-$ git status
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# modified: file1
-# modified: file2
-# modified: file3
-#
-------------------------------------------------
-
-If you need to make any further adjustments, do so now, and then add any
-newly modified content to the index. Finally, commit your changes with:
-
-------------------------------------------------
-$ git commit
-------------------------------------------------
-
-This will again prompt your for a message describing the change, and then
-record a new version of the project.
-
-Alternatively, instead of running `git add` beforehand, you can use
-
-------------------------------------------------
-$ git commit -a
-------------------------------------------------
-
-which will automatically notice any modified (but not new) files, add
-them to the index, and commit, all in one step.
-
-A note on commit messages: Though not required, it's a good idea to
-begin the commit message with a single short (less than 50 character)
-line summarizing the change, followed by a blank line and then a more
-thorough description. Tools that turn commits into email, for
-example, use the first line on the Subject: line and the rest of the
-commit in the body.
-
-Git tracks content not files
-----------------------------
-
-Many revision control systems provide an "add" command that tells the
-system to start tracking changes to a new file. Git's "add" command
-does something simpler and more powerful: `git add` is used both for new
-and newly modified files, and in both cases it takes a snapshot of the
-given files and stages that content in the index, ready for inclusion in
-the next commit.
-
-Viewing project history
------------------------
-
-At any point you can view the history of your changes using
-
-------------------------------------------------
-$ git log
-------------------------------------------------
-
-If you also want to see complete diffs at each step, use
-
-------------------------------------------------
-$ git log -p
-------------------------------------------------
-
-Often the overview of the change is useful to get a feel of
-each step
-
-------------------------------------------------
-$ git log --stat --summary
-------------------------------------------------
-
-Managing branches
------------------
-
-A single git repository can maintain multiple branches of
-development. To create a new branch named "experimental", use
-
-------------------------------------------------
-$ git branch experimental
-------------------------------------------------
-
-If you now run
-
-------------------------------------------------
-$ git branch
-------------------------------------------------
-
-you'll get a list of all existing branches:
-
-------------------------------------------------
- experimental
-* master
-------------------------------------------------
-
-The "experimental" branch is the one you just created, and the
-"master" branch is a default branch that was created for you
-automatically. The asterisk marks the branch you are currently on;
-type
-
-------------------------------------------------
-$ git checkout experimental
-------------------------------------------------
-
-to switch to the experimental branch. Now edit a file, commit the
-change, and switch back to the master branch:
-
-------------------------------------------------
-(edit file)
-$ git commit -a
-$ git checkout master
-------------------------------------------------
-
-Check that the change you made is no longer visible, since it was
-made on the experimental branch and you're back on the master branch.
-
-You can make a different change on the master branch:
-
-------------------------------------------------
-(edit file)
-$ git commit -a
-------------------------------------------------
-
-at this point the two branches have diverged, with different changes
-made in each. To merge the changes made in experimental into master, run
-
-------------------------------------------------
-$ git merge experimental
-------------------------------------------------
-
-If the changes don't conflict, you're done. If there are conflicts,
-markers will be left in the problematic files showing the conflict;
-
-------------------------------------------------
-$ git diff
-------------------------------------------------
-
-will show this. Once you've edited the files to resolve the
-conflicts,
-
-------------------------------------------------
-$ git commit -a
-------------------------------------------------
-
-will commit the result of the merge. Finally,
-
-------------------------------------------------
-$ gitk
-------------------------------------------------
-
-will show a nice graphical representation of the resulting history.
-
-At this point you could delete the experimental branch with
-
-------------------------------------------------
-$ git branch -d experimental
-------------------------------------------------
-
-This command ensures that the changes in the experimental branch are
-already in the current branch.
-
-If you develop on a branch crazy-idea, then regret it, you can always
-delete the branch with
-
--------------------------------------
-$ git branch -D crazy-idea
--------------------------------------
-
-Branches are cheap and easy, so this is a good way to try something
-out.
-
-Using git for collaboration
----------------------------
-
-Suppose that Alice has started a new project with a git repository in
-/home/alice/project, and that Bob, who has a home directory on the
-same machine, wants to contribute.
-
-Bob begins with:
-
-------------------------------------------------
-$ git clone /home/alice/project myrepo
-------------------------------------------------
-
-This creates a new directory "myrepo" containing a clone of Alice's
-repository. The clone is on an equal footing with the original
-project, possessing its own copy of the original project's history.
-
-Bob then makes some changes and commits them:
-
-------------------------------------------------
-(edit files)
-$ git commit -a
-(repeat as necessary)
-------------------------------------------------
-
-When he's ready, he tells Alice to pull changes from the repository
-at /home/bob/myrepo. She does this with:
-
-------------------------------------------------
-$ cd /home/alice/project
-$ git pull /home/bob/myrepo master
-------------------------------------------------
-
-This merges the changes from Bob's "master" branch into Alice's
-current branch. If Alice has made her own changes in the meantime,
-then she may need to manually fix any conflicts. (Note that the
-"master" argument in the above command is actually unnecessary, as it
-is the default.)
-
-The "pull" command thus performs two operations: it fetches changes
-from a remote branch, then merges them into the current branch.
-
-When you are working in a small closely knit group, it is not
-unusual to interact with the same repository over and over
-again. By defining 'remote' repository shorthand, you can make
-it easier:
-
-------------------------------------------------
-$ git remote add bob /home/bob/myrepo
-------------------------------------------------
-
-With this, Alice can perform the first operation alone using the
-"git fetch" command without merging them with her own branch,
-using:
-
--------------------------------------
-$ git fetch bob
--------------------------------------
-
-Unlike the longhand form, when Alice fetches from Bob using a
-remote repository shorthand set up with `git remote`, what was
-fetched is stored in a remote tracking branch, in this case
-`bob/master`. So after this:
-
--------------------------------------
-$ git log -p master..bob/master
--------------------------------------
-
-shows a list of all the changes that Bob made since he branched from
-Alice's master branch.
-
-After examining those changes, Alice
-could merge the changes into her master branch:
-
--------------------------------------
-$ git merge bob/master
--------------------------------------
-
-This `merge` can also be done by 'pulling from her own remote
-tracking branch', like this:
-
--------------------------------------
-$ git pull . remotes/bob/master
--------------------------------------
-
-Note that git pull always merges into the current branch,
-regardless of what else is given on the command line.
-
-Later, Bob can update his repo with Alice's latest changes using
-
--------------------------------------
-$ git pull
--------------------------------------
-
-Note that he doesn't need to give the path to Alice's repository;
-when Bob cloned Alice's repository, git stored the location of her
-repository in the repository configuration, and that location is
-used for pulls:
-
--------------------------------------
-$ git config --get remote.origin.url
-/home/alice/project
--------------------------------------
-
-(The complete configuration created by git-clone is visible using
-"git config -l", and the linkgit:git-config[1] man page
-explains the meaning of each option.)
-
-Git also keeps a pristine copy of Alice's master branch under the
-name "origin/master":
-
--------------------------------------
-$ git branch -r
- origin/master
--------------------------------------
-
-If Bob later decides to work from a different host, he can still
-perform clones and pulls using the ssh protocol:
-
--------------------------------------
-$ git clone alice.org:/home/alice/project myrepo
--------------------------------------
-
-Alternatively, git has a native protocol, or can use rsync or http;
-see linkgit:git-pull[1] for details.
-
-Git can also be used in a CVS-like mode, with a central repository
-that various users push changes to; see linkgit:git-push[1] and
-link:cvs-migration.html[git for CVS users].
-
-Exploring history
------------------
-
-Git history is represented as a series of interrelated commits. We
-have already seen that the git log command can list those commits.
-Note that first line of each git log entry also gives a name for the
-commit:
-
--------------------------------------
-$ git log
-commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
-Author: Junio C Hamano <junkio@cox.net>
-Date: Tue May 16 17:18:22 2006 -0700
-
- merge-base: Clarify the comments on post processing.
--------------------------------------
-
-We can give this name to git show to see the details about this
-commit.
-
--------------------------------------
-$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
--------------------------------------
-
-But there are other ways to refer to commits. You can use any initial
-part of the name that is long enough to uniquely identify the commit:
-
--------------------------------------
-$ git show c82a22c39c # the first few characters of the name are
- # usually enough
-$ git show HEAD # the tip of the current branch
-$ git show experimental # the tip of the "experimental" branch
--------------------------------------
-
-Every commit usually has one "parent" commit
-which points to the previous state of the project:
-
--------------------------------------
-$ git show HEAD^ # to see the parent of HEAD
-$ git show HEAD^^ # to see the grandparent of HEAD
-$ git show HEAD~4 # to see the great-great grandparent of HEAD
--------------------------------------
-
-Note that merge commits may have more than one parent:
-
--------------------------------------
-$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)
-$ git show HEAD^2 # show the second parent of HEAD
--------------------------------------
-
-You can also give commits names of your own; after running
-
--------------------------------------
-$ git-tag v2.5 1b2e1d63ff
--------------------------------------
-
-you can refer to 1b2e1d63ff by the name "v2.5". If you intend to
-share this name with other people (for example, to identify a release
-version), you should create a "tag" object, and perhaps sign it; see
-linkgit:git-tag[1] for details.
-
-Any git command that needs to know a commit can take any of these
-names. For example:
-
--------------------------------------
-$ git diff v2.5 HEAD # compare the current HEAD to v2.5
-$ git branch stable v2.5 # start a new branch named "stable" based
- # at v2.5
-$ git reset --hard HEAD^ # reset your current branch and working
- # directory to its state at HEAD^
--------------------------------------
-
-Be careful with that last command: in addition to losing any changes
-in the working directory, it will also remove all later commits from
-this branch. If this branch is the only branch containing those
-commits, they will be lost. Also, don't use "git reset" on a
-publicly-visible branch that other developers pull from, as it will
-force needless merges on other developers to clean up the history.
-If you need to undo changes that you have pushed, use linkgit:git-revert[1]
-instead.
-
-The git grep command can search for strings in any version of your
-project, so
-
--------------------------------------
-$ git grep "hello" v2.5
--------------------------------------
-
-searches for all occurrences of "hello" in v2.5.
-
-If you leave out the commit name, git grep will search any of the
-files it manages in your current directory. So
-
--------------------------------------
-$ git grep "hello"
--------------------------------------
-
-is a quick way to search just the files that are tracked by git.
-
-Many git commands also take sets of commits, which can be specified
-in a number of ways. Here are some examples with git log:
-
--------------------------------------
-$ git log v2.5..v2.6 # commits between v2.5 and v2.6
-$ git log v2.5.. # commits since v2.5
-$ git log --since="2 weeks ago" # commits from the last 2 weeks
-$ git log v2.5.. Makefile # commits since v2.5 which modify
- # Makefile
--------------------------------------
-
-You can also give git log a "range" of commits where the first is not
-necessarily an ancestor of the second; for example, if the tips of
-the branches "stable-release" and "master" diverged from a common
-commit some time ago, then
-
--------------------------------------
-$ git log stable..experimental
--------------------------------------
-
-will list commits made in the experimental branch but not in the
-stable branch, while
-
--------------------------------------
-$ git log experimental..stable
--------------------------------------
-
-will show the list of commits made on the stable branch but not
-the experimental branch.
-
-The "git log" command has a weakness: it must present commits in a
-list. When the history has lines of development that diverged and
-then merged back together, the order in which "git log" presents
-those commits is meaningless.
-
-Most projects with multiple contributors (such as the linux kernel,
-or git itself) have frequent merges, and gitk does a better job of
-visualizing their history. For example,
-
--------------------------------------
-$ gitk --since="2 weeks ago" drivers/
--------------------------------------
-
-allows you to browse any commits from the last 2 weeks of commits
-that modified files under the "drivers" directory. (Note: you can
-adjust gitk's fonts by holding down the control key while pressing
-"-" or "+".)
-
-Finally, most commands that take filenames will optionally allow you
-to precede any filename by a commit, to specify a particular version
-of the file:
-
--------------------------------------
-$ git diff v2.5:Makefile HEAD:Makefile.in
--------------------------------------
-
-You can also use "git show" to see any such file:
-
--------------------------------------
-$ git show v2.5:Makefile
--------------------------------------
-
-Next Steps
-----------
-
-This tutorial should be enough to perform basic distributed revision
-control for your projects. However, to fully understand the depth
-and power of git you need to understand two simple ideas on which it
-is based:
-
- * The object database is the rather elegant system used to
- store the history of your project--files, directories, and
- commits.
-
- * The index file is a cache of the state of a directory tree,
- used to create commits, check out working directories, and
- hold the various trees involved in a merge.
-
-link:tutorial-2.html[Part two of this tutorial] explains the object
-database, the index file, and a few other odds and ends that you'll
-need to make the most of git.
-
-If you don't want to continue with that right away, a few other
-digressions that may be interesting at this point are:
-
- * linkgit:git-format-patch[1], linkgit:git-am[1]: These convert
- series of git commits into emailed patches, and vice versa,
- useful for projects such as the linux kernel which rely heavily
- on emailed patches.
-
- * linkgit:git-bisect[1]: When there is a regression in your
- project, one way to track down the bug is by searching through
- the history to find the exact commit that's to blame. Git bisect
- can help you perform a binary search for that commit. It is
- smart enough to perform a close-to-optimal search even in the
- case of complex non-linear history with lots of merged branches.
-
- * link:everyday.html[Everyday GIT with 20 Commands Or So]
-
- * link:cvs-migration.html[git for CVS users].
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
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
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
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
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, verbose;
+ int i;
+ struct update_callback_data *data = cbdata;
- verbose = *((int *)cbdata);
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
const char *path = p->one->path;
case DIFF_STATUS_UNMERGED:
case DIFF_STATUS_MODIFIED:
case DIFF_STATUS_TYPE_CHANGED:
- add_file_to_cache(path, verbose);
+ if (add_file_to_cache(path, data->flags & ADD_FILES_VERBOSE)) {
+ if (!(data->flags & ADD_FILES_IGNORE_ERRORS))
+ die("updating files failed");
+ data->add_errors++;
+ }
break;
case DIFF_STATUS_DELETED:
remove_file_from_cache(path);
- if (verbose)
+ if (data->flags & ADD_FILES_VERBOSE)
printf("remove '%s'\n", path);
break;
}
}
}
-void add_files_to_cache(int verbose, const char *prefix, const char **pathspec)
+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 = &verbose;
+ 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)
"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),
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;
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);
if (take_worktree_changes) {
+ int flags = 0;
const char **pathspec;
if (read_cache() < 0)
die("index file corrupt");
pathspec = get_pathspec(prefix, argv);
- add_files_to_cache(verbose, prefix, pathspec);
+
+ if (verbose)
+ flags |= ADD_FILES_VERBOSE;
+ if (ignore_add_errors)
+ flags |= ADD_FILES_IGNORE_ERRORS;
+
+ exit_status = add_files_to_cache(prefix, pathspec, flags);
goto finish;
}
}
for (i = 0; i < dir.nr; i++)
- add_file_to_cache(dir.entries[i]->name, verbose);
+ if (add_file_to_cache(dir.entries[i]->name, verbose)) {
+ if (!ignore_add_errors)
+ die("adding files failed");
+ exit_status = 1;
+ }
finish:
if (active_cache_changed) {
die("Unable to write new index file");
}
- return 0;
+ return exit_status;
}
#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)
{
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':
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);
+}
* entries in the index.
*/
- add_files_to_cache(0, NULL, NULL);
+ add_files_to_cache(NULL, NULL, 0);
work = write_tree_from_memory();
ret = reset_to_new(new->commit->tree, opts->quiet);
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);
}
}
*/
if (all || (also && pathspec && *pathspec)) {
int fd = hold_locked_index(&index_lock, 1);
- add_files_to_cache(0, also ? prefix : NULL, pathspec);
+ add_files_to_cache(also ? prefix : NULL, pathspec, 0);
refresh_cache(REFRESH_QUIET);
if (write_cache(fd, active_cache, active_nr) ||
close_lock_file(&index_lock))
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"),
/*
* 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]);
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)) &&
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;
#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;
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)) {
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;
for (i = 0; i < added.nr; i++) {
const char *path = added.items[i].path;
- add_file_to_cache(path, verbose);
+ if (add_file_to_cache(path, verbose))
+ die("updating index entries failed");
}
for (i = 0; i < deleted.nr; i++)
[--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 {
*/
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 */
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;
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",
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 */
*/
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
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;
}
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 {
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;
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;
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);
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));
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;
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);
* 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)
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;
/*
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;
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;
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)
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)) {
keep_unreachable = 1;
continue;
}
+ if (!strcmp("--unpack-unreachable", arg)) {
+ unpack_unreachable = 1;
+ continue;
+ }
if (!strcmp("--include-tag", arg)) {
include_tag = 1;
continue;
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();
#include "list-objects.h"
#include "builtin.h"
#include "log-tree.h"
+#include "graph.h"
/* bits #0-15 in revision.h */
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)
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));
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);
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)
{
return;
def = NULL;
- if (type != show_type)
- putchar('^');
if (symbolic && name) {
if (symbolic == SHOW_SYMBOLIC_FULL) {
unsigned char discard[20];
*/
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. */
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:
refresh_flags |= REFRESH_QUIET;
continue;
}
+ if (!strcmp(path, "--ignore-submodules")) {
+ refresh_flags |= REFRESH_IGNORE_SUBMODULES;
+ continue;
+ }
if (!strcmp(path, "--add")) {
allow_add = 1;
continue;
#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 {
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);
extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
/* add */
-void add_files_to_cache(int verbose, const char *prefix, const char **pathspec);
+#define ADD_FILES_VERBOSE 01
+#define ADD_FILES_IGNORE_ERRORS 02
+/*
+ * return 0 if success, 1 - if addition of a file failed and
+ * ADD_FILES_IGNORE_ERRORS was specified in flags
+ */
+int add_files_to_cache(const char *prefix, const char **pathspec, int flags);
/* diff.c */
extern int diff_auto_refresh_index;
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"))
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
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;
#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)
--
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)
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
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
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() {
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() {
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"
}
*)
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 () {
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
}
# 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";
}
}
}
+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
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;
}
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;
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);
}
# 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.
$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";
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);
# $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 =
$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
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
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++;
}
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++;
argsplit("co");
my $module = $state->{args}[0];
+ $state->{module} = $module;
my $checkout_path = $module;
# use the user specified directory if we're given it
# 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 './'
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";
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";
$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 });
$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");
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";
}
{
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";
}
}
# 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 "/";
}
}
if ( $state->{method} eq 'pserver')
{
print "error 1 pserver access cannot commit\n";
+ cleanupWorkTree();
exit;
}
{
$log->warn("file 'index' already exists in the git repository");
print "error 1 Index already exists in git repo\n";
+ cleanupWorkTree();
exit;
}
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;
{
# fail everything if an up to date check fails
print "error 1 Up to date check failed for $filename\n";
- chdir "/";
+ cleanupWorkTree();
exit;
}
{
print "E No files to commit\n";
print "ok\n";
- chdir "/";
+ cleanupWorkTree();
return;
}
{
$log->warn("Commit failed (Invalid commit hash)");
print "error 1 Commit failed (unknown reason)\n";
- chdir "/";
+ cleanupWorkTree();
exit;
}
{
$log->warn("Commit failed (update hook declined to update ref)");
print "error 1 Commit failed (update hook declined)\n";
- chdir "/";
+ cleanupWorkTree();
exit;
}
}
"refs/heads/$state->{module}", $commithash, $parenthash)) {
$log->warn("update-ref for $state->{module} failed.");
print "error 1 Cannot commit -- update first\n";
+ cleanupWorkTree();
exit;
}
}
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";
}
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}} )
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);
# 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;
}
# done; get out of the tempdir
- chdir "/";
+ cleanupWorkDir();
print "ok\n";
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
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)
}
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)
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"
}
# 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
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
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
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"
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
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
-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 ;;
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
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);
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
{
# 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\"";
}
}
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;
+}
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 () {
}
apply_stash () {
- git diff-files --quiet ||
+ git diff-files --quiet --ignore-submodules ||
die 'Cannot restore on top of a dirty state'
unstash_index=
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@@';
'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);
}
$ENV{GIT_DIR} = $git_dir;
}
+ $_repository = Git->repository(Repository => $ENV{GIT_DIR});
}
my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
}
}
command_noisy(@init_db);
+ $_repository = Git->repository(Repository => ".git");
}
my $set;
my $pfx = "svn-remote.$Git::SVN::default_repo_id";
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 {
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 $!;
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/;
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
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 $!;
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 {
} 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 $!;
stop_rev_list $curview
}
resetvarcs $curview
- catch {unset selectedline}
+ set selectedline {}
catch {unset currentid}
catch {unset thickerline}
catch {unset treediffs}
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
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
proc selectedline_change {n1 n2 op} {
global selectedline rownumsel
- if {$op eq "unset"} {
+ if {$selectedline eq {}} {
set rownumsel {}
} else {
set rownumsel [expr {$selectedline + 1}]
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} {
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 \
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 \
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"]} {
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) ||
-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]}]
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]} {
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}
}
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
[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
}
}
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
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]
proc selnextline {dir} {
global selectedline
focus .
- if {![info exists selectedline]} return
+ if {$selectedline eq {}} return
set l [expr {$selectedline + $dir}]
unmarkmatches
selectline $l 1
}
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
proc unselectline {} {
global selectedline currentid
- catch {unset selectedline}
+ set selectedline {}
catch {unset currentid}
allcanvs delete secsel
rhighlight_none
proc reselectline {} {
global selectedline
- if {[info exists selectedline]} {
+ if {$selectedline ne {}} {
selectline $selectedline 0
}
}
setcanvscroll
allcanvs yview moveto [lindex $span 0]
drawvisible
- if {[info exists selectedline]} {
+ if {$selectedline ne {}} {
selectline $selectedline 0
allcanvs yview moveto [lindex $span 0]
}
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
proc diffvssel {dirn} {
global rowmenuid selectedline
- if {![info exists selectedline]} return
+ if {$selectedline eq {}} return
if {$dirn} {
set oldid [commitonrow $selectedline]
set newid $rowmenuid
set viewargs(0) {}
set viewargscmd(0) {}
+set selectedline {}
set numcommits 0
set loginstance 0
set cmdlineok 0
+++ /dev/null
-# Translation of gitk
-# Copyright (C) 2005-2008 Santiago Gala
-# This file is distributed under the same license as the gitk package.
-# Santiago Gala <santiago.gala@gmail.com>, 2008.
-#
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: gitk\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-03-13 17:29+0100\n"
-"PO-Revision-Date: 2008-03-25 11:20+0100\n"
-"Last-Translator: Santiago Gala <santiago.gala@gmail.com>\n"
-"Language-Team: Spanish\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: gitk:111
-msgid "Error executing git rev-list:"
-msgstr "Error al ejecutar git rev-list:"
-
-#: gitk:124
-msgid "Reading"
-msgstr "Leyendo"
-
-#: gitk:151 gitk:2191
-msgid "Reading commits..."
-msgstr "Leyendo revisiones..."
-
-#: gitk:275
-msgid "Can't parse git log output:"
-msgstr "Error analizando la salida de git log:"
-
-#: gitk:386 gitk:2195
-msgid "No commits selected"
-msgstr "No se seleccionaron revisiones"
-
-#: gitk:500
-msgid "No commit information available"
-msgstr "Falta información sobre las revisiones"
-
-#: gitk:599 gitk:621 gitk:1955 gitk:6423 gitk:7923 gitk:8082
-msgid "OK"
-msgstr "Aceptar"
-
-#: gitk:623 gitk:1956 gitk:6107 gitk:6178 gitk:6275 gitk:6321 gitk:6425
-#: gitk:7924 gitk:8083
-msgid "Cancel"
-msgstr "Cancelar"
-
-#: gitk:661
-msgid "File"
-msgstr "Archivo"
-
-#: gitk:663
-msgid "Update"
-msgstr "Actualizar"
-
-#: gitk:664
-msgid "Reread references"
-msgstr "Releer referencias"
-
-#: gitk:665
-msgid "List references"
-msgstr "Lista de referencias"
-
-#: gitk:666
-msgid "Quit"
-msgstr "Salir"
-
-#: gitk:668
-msgid "Edit"
-msgstr "Editar"
-
-#: gitk:669
-msgid "Preferences"
-msgstr "Preferencias"
-
-#: gitk:672 gitk:1892
-msgid "View"
-msgstr "Vista"
-
-#: gitk:673
-msgid "New view..."
-msgstr "Nueva vista..."
-
-#: gitk:674 gitk:2133 gitk:8722
-msgid "Edit view..."
-msgstr "Modificar vista..."
-
-#: gitk:676 gitk:2134 gitk:8723
-msgid "Delete view"
-msgstr "Eliminar vista"
-
-#: gitk:678
-msgid "All files"
-msgstr "Todos los archivos"
-
-#: gitk:682
-msgid "Help"
-msgstr "Ayuda"
-
-#: gitk:683 gitk:1317
-msgid "About gitk"
-msgstr "Acerca de gitk"
-
-#: gitk:684
-msgid "Key bindings"
-msgstr "Combinaciones de teclas"
-
-#: gitk:741
-msgid "SHA1 ID: "
-msgstr "SHA1 ID: "
-
-#: gitk:791
-msgid "Find"
-msgstr "Buscar"
-
-#: gitk:792
-msgid "next"
-msgstr "<<"
-
-#: gitk:793
-msgid "prev"
-msgstr ">>"
-
-#: gitk:794
-msgid "commit"
-msgstr "revisión"
-
-#: gitk:797 gitk:799 gitk:2356 gitk:2379 gitk:2403 gitk:4306 gitk:4369
-msgid "containing:"
-msgstr "que contiene:"
-
-#: gitk:800 gitk:1778 gitk:1783 gitk:2431
-msgid "touching paths:"
-msgstr "que modifica la ruta:"
-
-#: gitk:801 gitk:2436
-msgid "adding/removing string:"
-msgstr "que añade/elimina cadena:"
-
-#: gitk:810 gitk:812
-msgid "Exact"
-msgstr "Exacto"
-
-#: gitk:812 gitk:2514 gitk:4274
-msgid "IgnCase"
-msgstr "NoMayús"
-
-#: gitk:812 gitk:2405 gitk:2512 gitk:4270
-msgid "Regexp"
-msgstr "Regex"
-
-#: gitk:814 gitk:815 gitk:2533 gitk:2563 gitk:2570 gitk:4380 gitk:4436
-msgid "All fields"
-msgstr "Todos los campos"
-
-#: gitk:815 gitk:2531 gitk:2563 gitk:4336
-msgid "Headline"
-msgstr "Título"
-
-#: gitk:816 gitk:2531 gitk:4336 gitk:4436 gitk:4827
-msgid "Comments"
-msgstr "Comentarios"
-
-#: gitk:816 gitk:2531 gitk:2535 gitk:2570 gitk:4336 gitk:4763 gitk:5956
-#: gitk:5971
-msgid "Author"
-msgstr "Autor"
-
-#: gitk:816 gitk:2531 gitk:4336 gitk:4765
-msgid "Committer"
-msgstr ""
-
-#: gitk:845
-msgid "Search"
-msgstr "Buscar"
-
-#: gitk:852
-msgid "Diff"
-msgstr "Diferencia"
-
-#: gitk:854
-msgid "Old version"
-msgstr "Versión antigua"
-
-#: gitk:856
-msgid "New version"
-msgstr "Versión nueva"
-
-#: gitk:858
-msgid "Lines of context"
-msgstr "Líneas de contexto"
-
-#: gitk:868
-msgid "Ignore space change"
-msgstr "Ignora cambios de espaciado"
-
-#: gitk:926
-msgid "Patch"
-msgstr "Parche"
-
-#: gitk:928
-msgid "Tree"
-msgstr "Árbol"
-
-#: gitk:1053 gitk:1068 gitk:6022
-msgid "Diff this -> selected"
-msgstr "Diferencia de esta -> seleccionada"
-
-#: gitk:1055 gitk:1070 gitk:6023
-msgid "Diff selected -> this"
-msgstr "Diferencia de seleccionada -> esta"
-
-#: gitk:1057 gitk:1072 gitk:6024
-msgid "Make patch"
-msgstr "Crear patch"
-
-#: gitk:1058 gitk:6162
-msgid "Create tag"
-msgstr "Crear etiqueta"
-
-#: gitk:1059 gitk:6255
-msgid "Write commit to file"
-msgstr "Escribir revisiones a archivo"
-
-#: gitk:1060 gitk:6309
-msgid "Create new branch"
-msgstr "Crear nueva rama"
-
-#: gitk:1061
-msgid "Cherry-pick this commit"
-msgstr "Añadir esta revisión a la rama actual (cherry-pick)"
-
-#: gitk:1063
-msgid "Reset HEAD branch to here"
-msgstr "Traer la rama HEAD aquí"
-
-#: gitk:1079
-msgid "Check out this branch"
-msgstr "Cambiar a esta rama"
-
-#: gitk:1081
-msgid "Remove this branch"
-msgstr "Eliminar esta rama"
-
-#: gitk:1087
-msgid "Highlight this too"
-msgstr "Seleccionar también"
-
-#: gitk:1089
-msgid "Highlight this only"
-msgstr "Seleccionar sólo"
-
-#: gitk:1318
-msgid ""
-"\n"
-"Gitk - a commit viewer for git\n"
-"\n"
-"Copyright © 2005-2006 Paul Mackerras\n"
-"\n"
-"Use and redistribute under the terms of the GNU General Public License"
-msgstr ""
-"\n"
-"Gitk - un visualizador de revisiones para git\n"
-"\n"
-"Copyright © 2005-2006 Paul Mackerras\n"
-"\n"
-"Uso y redistribución permitidos según los términos de la Licencia Pública General de "
-"GNU (GNU GPL)"
-
-#: gitk:1326 gitk:1387 gitk:6581
-msgid "Close"
-msgstr "Cerrar"
-
-#: gitk:1345
-msgid "Gitk key bindings"
-msgstr "Combinaciones de tecla de Gitk"
-
-#: gitk:1347
-msgid "Gitk key bindings:"
-msgstr "Combinaciones de tecla de Gitk:"
-
-#: gitk:1349
-#, tcl-format
-msgid "<%s-Q>\t\tQuit"
-msgstr "<%s-Q>\t\tSalir"
-
-#: gitk:1350
-msgid "<Home>\t\tMove to first commit"
-msgstr "<Home>\t\tIr a la primera revisión"
-
-#: gitk:1351
-msgid "<End>\t\tMove to last commit"
-msgstr "<End>\t\tIr a la última revisión"
-
-#: gitk:1352
-msgid "<Up>, p, i\tMove up one commit"
-msgstr "<Up>, p, i\tSubir una revisión"
-
-#: gitk:1353
-msgid "<Down>, n, k\tMove down one commit"
-msgstr "<Down>, n, k\tBajar una revisión"
-
-#: gitk:1354
-msgid "<Left>, z, j\tGo back in history list"
-msgstr "<Left>, z, j\tRetroceder en la historia"
-
-#: gitk:1355
-msgid "<Right>, x, l\tGo forward in history list"
-msgstr "<Right>, x, l\tAvanzar en la historia"
-
-#: gitk:1356
-msgid "<PageUp>\tMove up one page in commit list"
-msgstr "<PageUp>\tSubir una página en la lista de revisiones"
-
-#: gitk:1357
-msgid "<PageDown>\tMove down one page in commit list"
-msgstr "<PageDown>\tBajar una página en la lista de revisiones"
-
-#: gitk:1358
-#, tcl-format
-msgid "<%s-Home>\tScroll to top of commit list"
-msgstr "<%s-Home>\tDesplazarse al inicio de la lista de revisiones"
-
-#: gitk:1359
-#, tcl-format
-msgid "<%s-End>\tScroll to bottom of commit list"
-msgstr "<%s-End>\tDesplazarse al final de la lista de revisiones"
-
-#: gitk:1360
-#, tcl-format
-msgid "<%s-Up>\tScroll commit list up one line"
-msgstr "<%s-Up>\tDesplazar una línea hacia arriba la lista de revisiones"
-
-#: gitk:1361
-#, tcl-format
-msgid "<%s-Down>\tScroll commit list down one line"
-msgstr "<%s-Down>\tDesplazar una línea hacia abajo la lista de revisiones"
-
-#: gitk:1362
-#, tcl-format
-msgid "<%s-PageUp>\tScroll commit list up one page"
-msgstr "<%s-PageUp>\tDesplazar una página hacia arriba la lista de revisiones"
-
-#: gitk:1363
-#, tcl-format
-msgid "<%s-PageDown>\tScroll commit list down one page"
-msgstr "<%s-PageDown>\tDesplazar una página hacia abajo la lista de revisiones"
-
-#: gitk:1364
-msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
-msgstr "<Shift-Up>\tBuscar hacia atrás (arriba, revisiones siguientes)"
-
-#: gitk:1365
-msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
-msgstr "<Shift-Down>\tBuscar hacia adelante (abajo, revisiones anteriores)"
-
-#: gitk:1366
-msgid "<Delete>, b\tScroll diff view up one page"
-msgstr "<Delete>, b\tDesplaza hacia arriba una página la vista de diferencias"
-
-#: gitk:1367
-msgid "<Backspace>\tScroll diff view up one page"
-msgstr "<Backspace>\tDesplaza hacia arriba una página la vista de diferencias"
-
-#: gitk:1368
-msgid "<Space>\t\tScroll diff view down one page"
-msgstr "<Space>\t\tDesplaza hacia abajo una página la vista de diferencias"
-
-#: gitk:1369
-msgid "u\t\tScroll diff view up 18 lines"
-msgstr "u\t\tDesplaza hacia arriba 18 líneas la vista de diferencias"
-
-#: gitk:1370
-msgid "d\t\tScroll diff view down 18 lines"
-msgstr "d\t\tDesplaza hacia abajo 18 líneas la vista de diferencias"
-
-#: gitk:1371
-#, tcl-format
-msgid "<%s-F>\t\tFind"
-msgstr "<%s-F>\t\tBuscar"
-
-#: gitk:1372
-#, tcl-format
-msgid "<%s-G>\t\tMove to next find hit"
-msgstr "<%s-G>\t\tBuscar el siguiente"
-
-#: gitk:1373
-msgid "<Return>\tMove to next find hit"
-msgstr "<Return>\tBuscar el siguiente"
-
-#: gitk:1374
-msgid "/\t\tMove to next find hit, or redo find"
-msgstr "/\t\tBuscar el siguiente, o reiniciar la búsqueda"
-
-#: gitk:1375
-msgid "?\t\tMove to previous find hit"
-msgstr "?\t\tBuscar el anterior"
-
-#: gitk:1376
-msgid "f\t\tScroll diff view to next file"
-msgstr "f\t\tDesplazar la vista de diferencias al archivo siguiente"
-
-#: gitk:1377
-#, tcl-format
-msgid "<%s-S>\t\tSearch for next hit in diff view"
-msgstr "<%s-S>\t\tBuscar siguiente en la vista de diferencias"
-
-#: gitk:1378
-#, tcl-format
-msgid "<%s-R>\t\tSearch for previous hit in diff view"
-msgstr "<%s-R>\t\tBuscar anterior en la vista de diferencias"
-
-#: gitk:1379
-#, tcl-format
-msgid "<%s-KP+>\tIncrease font size"
-msgstr "<%s-KP+>\tAumentar tamaño del texto"
-
-#: gitk:1380
-#, tcl-format
-msgid "<%s-plus>\tIncrease font size"
-msgstr "<%s-plus>\tAumentar tamaño del texto"
-
-#: gitk:1381
-#, tcl-format
-msgid "<%s-KP->\tDecrease font size"
-msgstr "<%s-KP->\tDisminuir tamaño del texto"
-
-#: gitk:1382
-#, tcl-format
-msgid "<%s-minus>\tDecrease font size"
-msgstr "<%s-minus>\tDisminuir tamaño del texto"
-
-#: gitk:1383
-msgid "<F5>\t\tUpdate"
-msgstr "<F5>\t\tActualizar"
-
-#: gitk:1896
-msgid "Gitk view definition"
-msgstr "Definición de vistas de Gitk"
-
-#: gitk:1921
-msgid "Name"
-msgstr "Nombre"
-
-#: gitk:1924
-msgid "Remember this view"
-msgstr "Recordar esta vista"
-
-#: gitk:1928
-msgid "Commits to include (arguments to git rev-list):"
-msgstr "Revisiones a incluir (argumentos a git rev-list):"
-
-#: gitk:1935
-msgid "Command to generate more commits to include:"
-msgstr "Comando que genera más revisiones a incluir:"
-
-#: gitk:1942
-msgid "Enter files and directories to include, one per line:"
-msgstr "Introducir archivos y directorios a incluir, uno por línea:"
-
-#: gitk:1989
-msgid "Error in commit selection arguments:"
-msgstr "Error en los argumentos de selección de las revisiones:"
-
-#: gitk:2043 gitk:2127 gitk:2583 gitk:2597 gitk:3781 gitk:8688 gitk:8689
-msgid "None"
-msgstr "Ninguno"
-
-#: gitk:2531 gitk:4336 gitk:5958 gitk:5973
-msgid "Date"
-msgstr "Fecha"
-
-#: gitk:2531 gitk:4336
-msgid "CDate"
-msgstr "Fecha de creación"
-
-#: gitk:2680 gitk:2685
-msgid "Descendant"
-msgstr "Descendiente"
-
-#: gitk:2681
-msgid "Not descendant"
-msgstr "No descendiente"
-
-#: gitk:2688 gitk:2693
-msgid "Ancestor"
-msgstr "Antepasado"
-
-#: gitk:2689
-msgid "Not ancestor"
-msgstr "No antepasado"
-
-#: gitk:2924
-msgid "Local changes checked in to index but not committed"
-msgstr "Cambios locales añadidos al índice pero sin completar revisión"
-
-#: gitk:2954
-msgid "Local uncommitted changes, not checked in to index"
-msgstr "Cambios locales sin añadir al índice"
-
-#: gitk:4305
-msgid "Searching"
-msgstr "Buscando"
-
-#: gitk:4767
-msgid "Tags:"
-msgstr "Etiquetas:"
-
-#: gitk:4784 gitk:4790 gitk:5951
-msgid "Parent"
-msgstr "Padre"
-
-#: gitk:4795
-msgid "Child"
-msgstr "Hija"
-
-#: gitk:4804
-msgid "Branch"
-msgstr "Rama"
-
-#: gitk:4807
-msgid "Follows"
-msgstr "Sigue-a"
-
-#: gitk:4810
-msgid "Precedes"
-msgstr "Precede-a"
-
-#: gitk:5093
-msgid "Error getting merge diffs:"
-msgstr "Error al leer las diferencias de fusión:"
-
-#: gitk:5778
-msgid "Goto:"
-msgstr "Ir a:"
-
-#: gitk:5780
-msgid "SHA1 ID:"
-msgstr "SHA1 ID:"
-
-#: gitk:5805
-#, tcl-format
-msgid "Short SHA1 id %s is ambiguous"
-msgstr "La id SHA1 abreviada %s es ambigua"
-
-#: gitk:5817
-#, tcl-format
-msgid "SHA1 id %s is not known"
-msgstr "La id SHA1 %s es desconocida"
-
-#: gitk:5819
-#, tcl-format
-msgid "Tag/Head %s is not known"
-msgstr "La etiqueta/rama %s es deconocida"
-
-#: gitk:5961
-msgid "Children"
-msgstr "Hijas"
-
-#: gitk:6018
-#, tcl-format
-msgid "Reset %s branch to here"
-msgstr "Poner la rama %s en esta revisión"
-
-#: gitk:6049
-msgid "Top"
-msgstr "Origen"
-
-#: gitk:6050
-msgid "From"
-msgstr "De"
-
-#: gitk:6055
-msgid "To"
-msgstr "A"
-
-#: gitk:6078
-msgid "Generate patch"
-msgstr "Generar parche"
-
-#: gitk:6080
-msgid "From:"
-msgstr "De:"
-
-#: gitk:6089
-msgid "To:"
-msgstr "Para:"
-
-#: gitk:6098
-msgid "Reverse"
-msgstr "Invertir"
-
-#: gitk:6100 gitk:6269
-msgid "Output file:"
-msgstr "Escribir a archivo:"
-
-#: gitk:6106
-msgid "Generate"
-msgstr "Generar"
-
-#: gitk:6142
-msgid "Error creating patch:"
-msgstr "Error en la creación del parche:"
-
-#: gitk:6164 gitk:6257 gitk:6311
-msgid "ID:"
-msgstr "ID:"
-
-#: gitk:6173
-msgid "Tag name:"
-msgstr "Nombre de etiqueta:"
-
-#: gitk:6177 gitk:6320
-msgid "Create"
-msgstr "Crear"
-
-#: gitk:6192
-msgid "No tag name specified"
-msgstr "No se ha especificado etiqueta"
-
-#: gitk:6196
-#, tcl-format
-msgid "Tag \"%s\" already exists"
-msgstr "La etiqueta \"%s\" ya existe"
-
-#: gitk:6202
-msgid "Error creating tag:"
-msgstr "Error al crear la etiqueta:"
-
-#: gitk:6266
-msgid "Command:"
-msgstr "Comando:"
-
-#: gitk:6274
-msgid "Write"
-msgstr "Escribir"
-
-#: gitk:6290
-msgid "Error writing commit:"
-msgstr "Error al escribir revisión:"
-
-#: gitk:6316
-msgid "Name:"
-msgstr "Nombre:"
-
-#: gitk:6335
-msgid "Please specify a name for the new branch"
-msgstr "Especifique un nombre para la nueva rama"
-
-#: gitk:6364
-#, tcl-format
-msgid "Commit %s is already included in branch %s -- really re-apply it?"
-msgstr "La revisión %s ya está incluida en la rama %s -- ¿Volver a aplicarla?"
-
-#: gitk:6369
-msgid "Cherry-picking"
-msgstr "Eligiendo revisiones (cherry-picking)"
-
-#: gitk:6381
-msgid "No changes committed"
-msgstr "No se han guardado cambios"
-
-#: gitk:6404
-msgid "Confirm reset"
-msgstr "Confirmar git reset"
-
-#: gitk:6406
-#, tcl-format
-msgid "Reset branch %s to %s?"
-msgstr "¿Reponer la rama %s a %s?"
-
-#: gitk:6410
-msgid "Reset type:"
-msgstr "Tipo de reposición:"
-
-#: gitk:6414
-msgid "Soft: Leave working tree and index untouched"
-msgstr "Suave: No altera la copia de trabajo ni el índice"
-
-#: gitk:6417
-msgid "Mixed: Leave working tree untouched, reset index"
-msgstr "Mixta: Actualiza el índice, no altera la copia de trabajo"
-
-#: gitk:6420
-msgid ""
-"Hard: Reset working tree and index\n"
-"(discard ALL local changes)"
-msgstr ""
-"Dura: Actualiza el índice y la copia de trabajo\n"
-"(abandona TODAS las modificaciones locales)"
-
-#: gitk:6436
-msgid "Resetting"
-msgstr "Reponiendo"
-
-#: gitk:6493
-msgid "Checking out"
-msgstr "Creando copia de trabajo"
-
-#: gitk:6523
-msgid "Cannot delete the currently checked-out branch"
-msgstr "No se puede borrar la rama actual"
-
-#: gitk:6529
-#, tcl-format
-msgid ""
-"The commits on branch %s aren't on any other branch.\n"
-"Really delete branch %s?"
-msgstr ""
-"Las revisiones de la rama %s no están presentes en otras ramas.\n"
-"¿Borrar la rama %s?"
-
-#: gitk:6560
-#, tcl-format
-msgid "Tags and heads: %s"
-msgstr "Etiquetas y ramas: %s"
-
-#: gitk:6574
-msgid "Filter"
-msgstr "Filtro"
-
-#: gitk:6868
-msgid ""
-"Error reading commit topology information; branch and preceding/following "
-"tag information will be incomplete."
-msgstr ""
-"Error al leer la topología de revisiones: la información sobre "
-"las ramas y etiquetas precedentes y siguientes será incompleta."
-
-#: gitk:7852
-msgid "Tag"
-msgstr "Etiqueta"
-
-#: gitk:7852
-msgid "Id"
-msgstr "Id"
-
-#: gitk:7892
-msgid "Gitk font chooser"
-msgstr "Selector de tipografías gitk"
-
-#: gitk:7909
-msgid "B"
-msgstr "B"
-
-#: gitk:7912
-msgid "I"
-msgstr "I"
-
-#: gitk:8005
-msgid "Gitk preferences"
-msgstr "Preferencias de gitk"
-
-#: gitk:8006
-msgid "Commit list display options"
-msgstr "Opciones de visualización de la lista de revisiones"
-
-#: gitk:8009
-msgid "Maximum graph width (lines)"
-msgstr "Ancho máximo del gráfico (en líneas)"
-
-#: gitk:8013
-#, tcl-format
-msgid "Maximum graph width (% of pane)"
-msgstr "Ancho máximo del gráfico (en % del panel)"
-
-#: gitk:8018
-msgid "Show local changes"
-msgstr "Mostrar cambios locales"
-
-#: gitk:8023
-msgid "Auto-select SHA1"
-msgstr "Seleccionar automáticamente SHA1 hash"
-
-#: gitk:8028
-msgid "Diff display options"
-msgstr "Opciones de visualización de diferencias"
-
-#: gitk:8030
-msgid "Tab spacing"
-msgstr "Espaciado de tabulador"
-
-#: gitk:8034
-msgid "Display nearby tags"
-msgstr "Mostrar etiquetas cercanas"
-
-#: gitk:8039
-msgid "Limit diffs to listed paths"
-msgstr "Limitar las diferencias a las rutas seleccionadas"
-
-#: gitk:8044
-msgid "Colors: press to choose"
-msgstr "Colores: pulse para seleccionar"
-
-#: gitk:8047
-msgid "Background"
-msgstr "Fondo"
-
-#: gitk:8051
-msgid "Foreground"
-msgstr "Primer plano"
-
-#: gitk:8055
-msgid "Diff: old lines"
-msgstr "Diff: líneas viejas"
-
-#: gitk:8060
-msgid "Diff: new lines"
-msgstr "Diff: líneas nuevas"
-
-#: gitk:8065
-msgid "Diff: hunk header"
-msgstr "Diff: cabecera de fragmento"
-
-#: gitk:8071
-msgid "Select bg"
-msgstr "Color de fondo de la selección"
-
-#: gitk:8075
-msgid "Fonts: press to choose"
-msgstr "Tipografías: pulse para elegir"
-
-#: gitk:8077
-msgid "Main font"
-msgstr "Tipografía principal"
-
-#: gitk:8078
-msgid "Diff display font"
-msgstr "Tipografía para diferencias"
-
-#: gitk:8079
-msgid "User interface font"
-msgstr "Tipografía para interfaz de usuario"
-
-#: gitk:8095
-#, tcl-format
-msgid "Gitk: choose color for %s"
-msgstr "Gitk: elegir color para %s"
-
-#: gitk:8476
-msgid ""
-"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
-" Gitk requires at least Tcl/Tk 8.4."
-msgstr ""
-"Esta versión de Tcl/Tk es demasiado antigua.\n"
-" Gitk requiere Tcl/Tk versión 8.4 o superior."
-
-#: gitk:8565
-msgid "Cannot find a git repository here."
-msgstr "No hay un repositorio git aquí."
-
-#: gitk:8569
-#, tcl-format
-msgid "Cannot find the git directory \"%s\"."
-msgstr "No hay directorio git \"%s\"."
-
-#: gitk:8612
-#, tcl-format
-msgid "Ambiguous argument '%s': both revision and filename"
-msgstr "Argumento ambiguo: '%s' es tanto una revisión como un nombre de archivo"
-
-#: gitk:8624
-msgid "Bad arguments to gitk:"
-msgstr "Argumentos incorrectos a Gitk:"
-
-#: gitk:8636
-msgid "Couldn't get list of unmerged files:"
-msgstr "Imposible obtener la lista de archivos pendientes de fusión:"
-
-#: gitk:8652
-msgid "No files selected: --merge specified but no files are unmerged."
-msgstr ""
-"No hay archivos seleccionados: se seleccionó la opción --merge pero no hay "
-"archivos pendientes de fusión."
-
-#: gitk:8655
-msgid ""
-"No files selected: --merge specified but no unmerged files are within file "
-"limit."
-msgstr ""
-"No hay archivos seleccionados: se seleccionó la opción --merge pero los archivos "
-"especificados no necesitan fusión."
-
-#: gitk:8716
-msgid "Command line"
-msgstr "Línea de comandos"
--- /dev/null
+# Translation of gitk
+# Copyright (C) 2005-2008 Santiago Gala
+# This file is distributed under the same license as the gitk package.
+# Santiago Gala <santiago.gala@gmail.com>, 2008.
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-03-13 17:29+0100\n"
+"PO-Revision-Date: 2008-03-25 11:20+0100\n"
+"Last-Translator: Santiago Gala <santiago.gala@gmail.com>\n"
+"Language-Team: Spanish\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gitk:111
+msgid "Error executing git rev-list:"
+msgstr "Error al ejecutar git rev-list:"
+
+#: gitk:124
+msgid "Reading"
+msgstr "Leyendo"
+
+#: gitk:151 gitk:2191
+msgid "Reading commits..."
+msgstr "Leyendo revisiones..."
+
+#: gitk:275
+msgid "Can't parse git log output:"
+msgstr "Error analizando la salida de git log:"
+
+#: gitk:386 gitk:2195
+msgid "No commits selected"
+msgstr "No se seleccionaron revisiones"
+
+#: gitk:500
+msgid "No commit information available"
+msgstr "Falta información sobre las revisiones"
+
+#: gitk:599 gitk:621 gitk:1955 gitk:6423 gitk:7923 gitk:8082
+msgid "OK"
+msgstr "Aceptar"
+
+#: gitk:623 gitk:1956 gitk:6107 gitk:6178 gitk:6275 gitk:6321 gitk:6425
+#: gitk:7924 gitk:8083
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: gitk:661
+msgid "File"
+msgstr "Archivo"
+
+#: gitk:663
+msgid "Update"
+msgstr "Actualizar"
+
+#: gitk:664
+msgid "Reread references"
+msgstr "Releer referencias"
+
+#: gitk:665
+msgid "List references"
+msgstr "Lista de referencias"
+
+#: gitk:666
+msgid "Quit"
+msgstr "Salir"
+
+#: gitk:668
+msgid "Edit"
+msgstr "Editar"
+
+#: gitk:669
+msgid "Preferences"
+msgstr "Preferencias"
+
+#: gitk:672 gitk:1892
+msgid "View"
+msgstr "Vista"
+
+#: gitk:673
+msgid "New view..."
+msgstr "Nueva vista..."
+
+#: gitk:674 gitk:2133 gitk:8722
+msgid "Edit view..."
+msgstr "Modificar vista..."
+
+#: gitk:676 gitk:2134 gitk:8723
+msgid "Delete view"
+msgstr "Eliminar vista"
+
+#: gitk:678
+msgid "All files"
+msgstr "Todos los archivos"
+
+#: gitk:682
+msgid "Help"
+msgstr "Ayuda"
+
+#: gitk:683 gitk:1317
+msgid "About gitk"
+msgstr "Acerca de gitk"
+
+#: gitk:684
+msgid "Key bindings"
+msgstr "Combinaciones de teclas"
+
+#: gitk:741
+msgid "SHA1 ID: "
+msgstr "SHA1 ID: "
+
+#: gitk:791
+msgid "Find"
+msgstr "Buscar"
+
+#: gitk:792
+msgid "next"
+msgstr "<<"
+
+#: gitk:793
+msgid "prev"
+msgstr ">>"
+
+#: gitk:794
+msgid "commit"
+msgstr "revisión"
+
+#: gitk:797 gitk:799 gitk:2356 gitk:2379 gitk:2403 gitk:4306 gitk:4369
+msgid "containing:"
+msgstr "que contiene:"
+
+#: gitk:800 gitk:1778 gitk:1783 gitk:2431
+msgid "touching paths:"
+msgstr "que modifica la ruta:"
+
+#: gitk:801 gitk:2436
+msgid "adding/removing string:"
+msgstr "que añade/elimina cadena:"
+
+#: gitk:810 gitk:812
+msgid "Exact"
+msgstr "Exacto"
+
+#: gitk:812 gitk:2514 gitk:4274
+msgid "IgnCase"
+msgstr "NoMayús"
+
+#: gitk:812 gitk:2405 gitk:2512 gitk:4270
+msgid "Regexp"
+msgstr "Regex"
+
+#: gitk:814 gitk:815 gitk:2533 gitk:2563 gitk:2570 gitk:4380 gitk:4436
+msgid "All fields"
+msgstr "Todos los campos"
+
+#: gitk:815 gitk:2531 gitk:2563 gitk:4336
+msgid "Headline"
+msgstr "Título"
+
+#: gitk:816 gitk:2531 gitk:4336 gitk:4436 gitk:4827
+msgid "Comments"
+msgstr "Comentarios"
+
+#: gitk:816 gitk:2531 gitk:2535 gitk:2570 gitk:4336 gitk:4763 gitk:5956
+#: gitk:5971
+msgid "Author"
+msgstr "Autor"
+
+#: gitk:816 gitk:2531 gitk:4336 gitk:4765
+msgid "Committer"
+msgstr ""
+
+#: gitk:845
+msgid "Search"
+msgstr "Buscar"
+
+#: gitk:852
+msgid "Diff"
+msgstr "Diferencia"
+
+#: gitk:854
+msgid "Old version"
+msgstr "Versión antigua"
+
+#: gitk:856
+msgid "New version"
+msgstr "Versión nueva"
+
+#: gitk:858
+msgid "Lines of context"
+msgstr "Líneas de contexto"
+
+#: gitk:868
+msgid "Ignore space change"
+msgstr "Ignora cambios de espaciado"
+
+#: gitk:926
+msgid "Patch"
+msgstr "Parche"
+
+#: gitk:928
+msgid "Tree"
+msgstr "Árbol"
+
+#: gitk:1053 gitk:1068 gitk:6022
+msgid "Diff this -> selected"
+msgstr "Diferencia de esta -> seleccionada"
+
+#: gitk:1055 gitk:1070 gitk:6023
+msgid "Diff selected -> this"
+msgstr "Diferencia de seleccionada -> esta"
+
+#: gitk:1057 gitk:1072 gitk:6024
+msgid "Make patch"
+msgstr "Crear patch"
+
+#: gitk:1058 gitk:6162
+msgid "Create tag"
+msgstr "Crear etiqueta"
+
+#: gitk:1059 gitk:6255
+msgid "Write commit to file"
+msgstr "Escribir revisiones a archivo"
+
+#: gitk:1060 gitk:6309
+msgid "Create new branch"
+msgstr "Crear nueva rama"
+
+#: gitk:1061
+msgid "Cherry-pick this commit"
+msgstr "Añadir esta revisión a la rama actual (cherry-pick)"
+
+#: gitk:1063
+msgid "Reset HEAD branch to here"
+msgstr "Traer la rama HEAD aquí"
+
+#: gitk:1079
+msgid "Check out this branch"
+msgstr "Cambiar a esta rama"
+
+#: gitk:1081
+msgid "Remove this branch"
+msgstr "Eliminar esta rama"
+
+#: gitk:1087
+msgid "Highlight this too"
+msgstr "Seleccionar también"
+
+#: gitk:1089
+msgid "Highlight this only"
+msgstr "Seleccionar sólo"
+
+#: gitk:1318
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright © 2005-2006 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - un visualizador de revisiones para git\n"
+"\n"
+"Copyright © 2005-2006 Paul Mackerras\n"
+"\n"
+"Uso y redistribución permitidos según los términos de la Licencia Pública General de "
+"GNU (GNU GPL)"
+
+#: gitk:1326 gitk:1387 gitk:6581
+msgid "Close"
+msgstr "Cerrar"
+
+#: gitk:1345
+msgid "Gitk key bindings"
+msgstr "Combinaciones de tecla de Gitk"
+
+#: gitk:1347
+msgid "Gitk key bindings:"
+msgstr "Combinaciones de tecla de Gitk:"
+
+#: gitk:1349
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tSalir"
+
+#: gitk:1350
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\tIr a la primera revisión"
+
+#: gitk:1351
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\tIr a la última revisión"
+
+#: gitk:1352
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Up>, p, i\tSubir una revisión"
+
+#: gitk:1353
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Down>, n, k\tBajar una revisión"
+
+#: gitk:1354
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Left>, z, j\tRetroceder en la historia"
+
+#: gitk:1355
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Right>, x, l\tAvanzar en la historia"
+
+#: gitk:1356
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tSubir una página en la lista de revisiones"
+
+#: gitk:1357
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tBajar una página en la lista de revisiones"
+
+#: gitk:1358
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\tDesplazarse al inicio de la lista de revisiones"
+
+#: gitk:1359
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tDesplazarse al final de la lista de revisiones"
+
+#: gitk:1360
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\tDesplazar una línea hacia arriba la lista de revisiones"
+
+#: gitk:1361
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\tDesplazar una línea hacia abajo la lista de revisiones"
+
+#: gitk:1362
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tDesplazar una página hacia arriba la lista de revisiones"
+
+#: gitk:1363
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tDesplazar una página hacia abajo la lista de revisiones"
+
+#: gitk:1364
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Shift-Up>\tBuscar hacia atrás (arriba, revisiones siguientes)"
+
+#: gitk:1365
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Shift-Down>\tBuscar hacia adelante (abajo, revisiones anteriores)"
+
+#: gitk:1366
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\tDesplaza hacia arriba una página la vista de diferencias"
+
+#: gitk:1367
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\tDesplaza hacia arriba una página la vista de diferencias"
+
+#: gitk:1368
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Space>\t\tDesplaza hacia abajo una página la vista de diferencias"
+
+#: gitk:1369
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tDesplaza hacia arriba 18 líneas la vista de diferencias"
+
+#: gitk:1370
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tDesplaza hacia abajo 18 líneas la vista de diferencias"
+
+#: gitk:1371
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tBuscar"
+
+#: gitk:1372
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tBuscar el siguiente"
+
+#: gitk:1373
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\tBuscar el siguiente"
+
+#: gitk:1374
+msgid "/\t\tMove to next find hit, or redo find"
+msgstr "/\t\tBuscar el siguiente, o reiniciar la búsqueda"
+
+#: gitk:1375
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tBuscar el anterior"
+
+#: gitk:1376
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tDesplazar la vista de diferencias al archivo siguiente"
+
+#: gitk:1377
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tBuscar siguiente en la vista de diferencias"
+
+#: gitk:1378
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tBuscar anterior en la vista de diferencias"
+
+#: gitk:1379
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\tAumentar tamaño del texto"
+
+#: gitk:1380
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\tAumentar tamaño del texto"
+
+#: gitk:1381
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\tDisminuir tamaño del texto"
+
+#: gitk:1382
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\tDisminuir tamaño del texto"
+
+#: gitk:1383
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tActualizar"
+
+#: gitk:1896
+msgid "Gitk view definition"
+msgstr "Definición de vistas de Gitk"
+
+#: gitk:1921
+msgid "Name"
+msgstr "Nombre"
+
+#: gitk:1924
+msgid "Remember this view"
+msgstr "Recordar esta vista"
+
+#: gitk:1928
+msgid "Commits to include (arguments to git rev-list):"
+msgstr "Revisiones a incluir (argumentos a git rev-list):"
+
+#: gitk:1935
+msgid "Command to generate more commits to include:"
+msgstr "Comando que genera más revisiones a incluir:"
+
+#: gitk:1942
+msgid "Enter files and directories to include, one per line:"
+msgstr "Introducir archivos y directorios a incluir, uno por línea:"
+
+#: gitk:1989
+msgid "Error in commit selection arguments:"
+msgstr "Error en los argumentos de selección de las revisiones:"
+
+#: gitk:2043 gitk:2127 gitk:2583 gitk:2597 gitk:3781 gitk:8688 gitk:8689
+msgid "None"
+msgstr "Ninguno"
+
+#: gitk:2531 gitk:4336 gitk:5958 gitk:5973
+msgid "Date"
+msgstr "Fecha"
+
+#: gitk:2531 gitk:4336
+msgid "CDate"
+msgstr "Fecha de creación"
+
+#: gitk:2680 gitk:2685
+msgid "Descendant"
+msgstr "Descendiente"
+
+#: gitk:2681
+msgid "Not descendant"
+msgstr "No descendiente"
+
+#: gitk:2688 gitk:2693
+msgid "Ancestor"
+msgstr "Antepasado"
+
+#: gitk:2689
+msgid "Not ancestor"
+msgstr "No antepasado"
+
+#: gitk:2924
+msgid "Local changes checked in to index but not committed"
+msgstr "Cambios locales añadidos al índice pero sin completar revisión"
+
+#: gitk:2954
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Cambios locales sin añadir al índice"
+
+#: gitk:4305
+msgid "Searching"
+msgstr "Buscando"
+
+#: gitk:4767
+msgid "Tags:"
+msgstr "Etiquetas:"
+
+#: gitk:4784 gitk:4790 gitk:5951
+msgid "Parent"
+msgstr "Padre"
+
+#: gitk:4795
+msgid "Child"
+msgstr "Hija"
+
+#: gitk:4804
+msgid "Branch"
+msgstr "Rama"
+
+#: gitk:4807
+msgid "Follows"
+msgstr "Sigue-a"
+
+#: gitk:4810
+msgid "Precedes"
+msgstr "Precede-a"
+
+#: gitk:5093
+msgid "Error getting merge diffs:"
+msgstr "Error al leer las diferencias de fusión:"
+
+#: gitk:5778
+msgid "Goto:"
+msgstr "Ir a:"
+
+#: gitk:5780
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:5805
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "La id SHA1 abreviada %s es ambigua"
+
+#: gitk:5817
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "La id SHA1 %s es desconocida"
+
+#: gitk:5819
+#, tcl-format
+msgid "Tag/Head %s is not known"
+msgstr "La etiqueta/rama %s es deconocida"
+
+#: gitk:5961
+msgid "Children"
+msgstr "Hijas"
+
+#: gitk:6018
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Poner la rama %s en esta revisión"
+
+#: gitk:6049
+msgid "Top"
+msgstr "Origen"
+
+#: gitk:6050
+msgid "From"
+msgstr "De"
+
+#: gitk:6055
+msgid "To"
+msgstr "A"
+
+#: gitk:6078
+msgid "Generate patch"
+msgstr "Generar parche"
+
+#: gitk:6080
+msgid "From:"
+msgstr "De:"
+
+#: gitk:6089
+msgid "To:"
+msgstr "Para:"
+
+#: gitk:6098
+msgid "Reverse"
+msgstr "Invertir"
+
+#: gitk:6100 gitk:6269
+msgid "Output file:"
+msgstr "Escribir a archivo:"
+
+#: gitk:6106
+msgid "Generate"
+msgstr "Generar"
+
+#: gitk:6142
+msgid "Error creating patch:"
+msgstr "Error en la creación del parche:"
+
+#: gitk:6164 gitk:6257 gitk:6311
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:6173
+msgid "Tag name:"
+msgstr "Nombre de etiqueta:"
+
+#: gitk:6177 gitk:6320
+msgid "Create"
+msgstr "Crear"
+
+#: gitk:6192
+msgid "No tag name specified"
+msgstr "No se ha especificado etiqueta"
+
+#: gitk:6196
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "La etiqueta \"%s\" ya existe"
+
+#: gitk:6202
+msgid "Error creating tag:"
+msgstr "Error al crear la etiqueta:"
+
+#: gitk:6266
+msgid "Command:"
+msgstr "Comando:"
+
+#: gitk:6274
+msgid "Write"
+msgstr "Escribir"
+
+#: gitk:6290
+msgid "Error writing commit:"
+msgstr "Error al escribir revisión:"
+
+#: gitk:6316
+msgid "Name:"
+msgstr "Nombre:"
+
+#: gitk:6335
+msgid "Please specify a name for the new branch"
+msgstr "Especifique un nombre para la nueva rama"
+
+#: gitk:6364
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr "La revisión %s ya está incluida en la rama %s -- ¿Volver a aplicarla?"
+
+#: gitk:6369
+msgid "Cherry-picking"
+msgstr "Eligiendo revisiones (cherry-picking)"
+
+#: gitk:6381
+msgid "No changes committed"
+msgstr "No se han guardado cambios"
+
+#: gitk:6404
+msgid "Confirm reset"
+msgstr "Confirmar git reset"
+
+#: gitk:6406
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "¿Reponer la rama %s a %s?"
+
+#: gitk:6410
+msgid "Reset type:"
+msgstr "Tipo de reposición:"
+
+#: gitk:6414
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Suave: No altera la copia de trabajo ni el índice"
+
+#: gitk:6417
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Mixta: Actualiza el índice, no altera la copia de trabajo"
+
+#: gitk:6420
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Dura: Actualiza el índice y la copia de trabajo\n"
+"(abandona TODAS las modificaciones locales)"
+
+#: gitk:6436
+msgid "Resetting"
+msgstr "Reponiendo"
+
+#: gitk:6493
+msgid "Checking out"
+msgstr "Creando copia de trabajo"
+
+#: gitk:6523
+msgid "Cannot delete the currently checked-out branch"
+msgstr "No se puede borrar la rama actual"
+
+#: gitk:6529
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"Las revisiones de la rama %s no están presentes en otras ramas.\n"
+"¿Borrar la rama %s?"
+
+#: gitk:6560
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Etiquetas y ramas: %s"
+
+#: gitk:6574
+msgid "Filter"
+msgstr "Filtro"
+
+#: gitk:6868
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Error al leer la topología de revisiones: la información sobre "
+"las ramas y etiquetas precedentes y siguientes será incompleta."
+
+#: gitk:7852
+msgid "Tag"
+msgstr "Etiqueta"
+
+#: gitk:7852
+msgid "Id"
+msgstr "Id"
+
+#: gitk:7892
+msgid "Gitk font chooser"
+msgstr "Selector de tipografías gitk"
+
+#: gitk:7909
+msgid "B"
+msgstr "B"
+
+#: gitk:7912
+msgid "I"
+msgstr "I"
+
+#: gitk:8005
+msgid "Gitk preferences"
+msgstr "Preferencias de gitk"
+
+#: gitk:8006
+msgid "Commit list display options"
+msgstr "Opciones de visualización de la lista de revisiones"
+
+#: gitk:8009
+msgid "Maximum graph width (lines)"
+msgstr "Ancho máximo del gráfico (en líneas)"
+
+#: gitk:8013
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Ancho máximo del gráfico (en % del panel)"
+
+#: gitk:8018
+msgid "Show local changes"
+msgstr "Mostrar cambios locales"
+
+#: gitk:8023
+msgid "Auto-select SHA1"
+msgstr "Seleccionar automáticamente SHA1 hash"
+
+#: gitk:8028
+msgid "Diff display options"
+msgstr "Opciones de visualización de diferencias"
+
+#: gitk:8030
+msgid "Tab spacing"
+msgstr "Espaciado de tabulador"
+
+#: gitk:8034
+msgid "Display nearby tags"
+msgstr "Mostrar etiquetas cercanas"
+
+#: gitk:8039
+msgid "Limit diffs to listed paths"
+msgstr "Limitar las diferencias a las rutas seleccionadas"
+
+#: gitk:8044
+msgid "Colors: press to choose"
+msgstr "Colores: pulse para seleccionar"
+
+#: gitk:8047
+msgid "Background"
+msgstr "Fondo"
+
+#: gitk:8051
+msgid "Foreground"
+msgstr "Primer plano"
+
+#: gitk:8055
+msgid "Diff: old lines"
+msgstr "Diff: líneas viejas"
+
+#: gitk:8060
+msgid "Diff: new lines"
+msgstr "Diff: líneas nuevas"
+
+#: gitk:8065
+msgid "Diff: hunk header"
+msgstr "Diff: cabecera de fragmento"
+
+#: gitk:8071
+msgid "Select bg"
+msgstr "Color de fondo de la selección"
+
+#: gitk:8075
+msgid "Fonts: press to choose"
+msgstr "Tipografías: pulse para elegir"
+
+#: gitk:8077
+msgid "Main font"
+msgstr "Tipografía principal"
+
+#: gitk:8078
+msgid "Diff display font"
+msgstr "Tipografía para diferencias"
+
+#: gitk:8079
+msgid "User interface font"
+msgstr "Tipografía para interfaz de usuario"
+
+#: gitk:8095
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: elegir color para %s"
+
+#: gitk:8476
+msgid ""
+"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+" Gitk requires at least Tcl/Tk 8.4."
+msgstr ""
+"Esta versión de Tcl/Tk es demasiado antigua.\n"
+" Gitk requiere Tcl/Tk versión 8.4 o superior."
+
+#: gitk:8565
+msgid "Cannot find a git repository here."
+msgstr "No hay un repositorio git aquí."
+
+#: gitk:8569
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "No hay directorio git \"%s\"."
+
+#: gitk:8612
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "Argumento ambiguo: '%s' es tanto una revisión como un nombre de archivo"
+
+#: gitk:8624
+msgid "Bad arguments to gitk:"
+msgstr "Argumentos incorrectos a Gitk:"
+
+#: gitk:8636
+msgid "Couldn't get list of unmerged files:"
+msgstr "Imposible obtener la lista de archivos pendientes de fusión:"
+
+#: gitk:8652
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"No hay archivos seleccionados: se seleccionó la opción --merge pero no hay "
+"archivos pendientes de fusión."
+
+#: gitk:8655
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"No hay archivos seleccionados: se seleccionó la opción --merge pero los archivos "
+"especificados no necesitan fusión."
+
+#: gitk:8716
+msgid "Command line"
+msgstr "Línea de comandos"
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
--- /dev/null
+#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');
+ }
+}
--- /dev/null
+#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 */
*/
#include "cache.h"
#include "blob.h"
+#include "quote.h"
static void hash_object(const char *path, enum object_type type, int write_object)
{
? "Unable to add %s to database"
: "Unable to hash %s", path);
printf("%s\n", sha1_to_hex(sha1));
+ maybe_flush_or_die(stdout, "hash to stdout");
}
static void hash_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)
{
int prefix_length = -1;
int no_more_flags = 0;
int hashstdin = 0;
+ int stdin_paths = 0;
git_config(git_default_config);
}
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;
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;
no_more_flags = 1;
}
}
+
+ if (stdin_paths)
+ hash_stdin_paths(type, write_object);
+
if (hashstdin)
hash_stdin(type, write_object);
return 0;
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);
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");
static void remove_lock_file_on_signal(int signo)
{
remove_lock_file();
- signal(SIGINT, SIG_DFL);
+ signal(signo, SIG_DFL);
raise(signo);
}
#include "cache.h"
#include "diff.h"
#include "commit.h"
+#include "graph.h"
#include "log-tree.h"
#include "reflog-walk.h"
}
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];
opt->loginfo = NULL;
if (!opt->verbose_header) {
+ graph_show_commit(opt->graph);
+
if (commit->object.flags & BOUNDARY)
putchar('-');
else if (commit->object.flags & UNINTERESTING)
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;
}
* 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..
*/
}
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)",
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);
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);
}
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
# 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);
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);
}
bless $self, $class;
}
-
=back
=head1 METHODS
_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... ] )
}
+=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
}
-sub DESTROY { }
+sub DESTROY {
+ my ($self) = @_;
+ $self->_close_hash_and_insert_object();
+ $self->_close_cat_blob();
+}
# Pipe implementation for ActiveState Perl.
unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
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)) {
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;
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)
printf("add '%s'\n", path);
return 0;
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++) {
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))
}
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);
const char *dst_value = rs->dst;
char *dst_guess;
- if (rs->pattern)
+ if (rs->pattern || rs->matching)
return errs;
matched_src = matched_dst = NULL;
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;
}
/*
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
int nr_refspec, const char **refspec, int flags)
{
- struct refspec *rs =
- parse_push_refspec(nr_refspec, (const char **) refspec);
+ struct refspec *rs;
int send_all = flags & MATCH_REFS_ALL;
int send_mirror = flags & MATCH_REFS_MIRROR;
+ static const char *default_refspec[] = { ":", 0 };
+ if (!nr_refspec) {
+ nr_refspec = 1;
+ refspec = default_refspec;
+ }
+ rs = parse_push_refspec(nr_refspec, (const char **) refspec);
if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
return -1;
char *dst_name;
if (src->peer_ref)
continue;
- if (nr_refspec) {
- pat = check_pattern_match(rs, nr_refspec, src);
- if (!pat)
- continue;
- }
- else if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
+
+ pat = check_pattern_match(rs, nr_refspec, src);
+ if (!pat)
+ continue;
+
+ if (pat->matching) {
/*
* "matching refs"; traditionally we pushed everything
* including refs outside refs/heads/ hierarchy, but
* that does not make much sense these days.
*/
- continue;
+ if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
+ continue;
+ dst_name = xstrdup(src->name);
- if (pat) {
+ } else {
const char *dst_side = pat->dst ? pat->dst : pat->src;
dst_name = xmalloc(strlen(dst_side) +
strlen(src->name) -
strlen(pat->src) + 2);
strcpy(dst_name, dst_side);
strcat(dst_name, src->name + strlen(pat->src));
- } else
- dst_name = xstrdup(src->name);
+ }
dst_peer = find_ref_by_name(dst, dst_name);
- if (dst_peer && dst_peer->peer_ref)
- /* We're already sending something to this ref. */
- goto free_name;
+ if (dst_peer) {
+ if (dst_peer->peer_ref)
+ /* We're already sending something to this ref. */
+ goto free_name;
+
+ } else {
+ if (pat->matching && !(send_all || send_mirror))
+ /*
+ * Remote doesn't have it, and we have no
+ * explicit pattern, and we don't have
+ * --all nor --mirror.
+ */
+ goto free_name;
- if (!dst_peer && !nr_refspec && !(send_all || send_mirror))
- /*
- * Remote doesn't have it, and we have no
- * explicit pattern, and we don't have
- * --all nor --mirror.
- */
- goto free_name;
- if (!dst_peer) {
/* Create a new one and link it */
dst_peer = make_linked_ref(dst_name, dst_tail);
hashcpy(dst_peer->new_sha1, src->new_sha1);
}
dst_peer->peer_ref = src;
- if (pat)
- dst_peer->force = pat->force;
+ dst_peer->force = pat->force;
free_name:
free(dst_name);
}
struct refspec {
unsigned force : 1;
unsigned pattern : 1;
+ unsigned matching : 1;
char *src;
char *dst;
#include "diff.h"
#include "refs.h"
#include "revision.h"
+#include "graph.h"
#include "grep.h"
#include "reflog-walk.h"
#include "patch-ids.h"
{
struct commit_list *parent = commit->parents;
unsigned left_flag;
- int add, rest;
if (commit->object.flags & ADDED)
return 0;
left_flag = (commit->object.flags & SYMMETRIC_LEFT);
- rest = !revs->first_parent_only;
- for (parent = commit->parents, add = 1; parent; add = rest) {
+ for (parent = commit->parents; parent; parent = parent->next) {
struct commit *p = parent->item;
- parent = parent->next;
if (parse_commit(p) < 0)
return -1;
p->object.flags |= left_flag;
- if (p->object.flags & SEEN)
- continue;
- p->object.flags |= SEEN;
- if (add)
+ if (!(p->object.flags & SEEN)) {
+ p->object.flags |= SEEN;
insert_by_date(p, list);
+ }
+ if(revs->first_parent_only)
+ break;
}
return 0;
}
}
}
if (!strcmp(arg, "--parents")) {
- revs->parents = 1;
+ revs->rewrite_parents = 1;
+ revs->print_parents = 1;
continue;
}
if (!strcmp(arg, "--dense")) {
get_commit_format(arg+8, revs);
continue;
}
+ if (!prefixcmp(arg, "--graph")) {
+ revs->topo_order = 1;
+ revs->rewrite_parents = 1;
+ revs->graph = graph_init();
+ continue;
+ }
if (!strcmp(arg, "--root")) {
revs->show_root_diff = 1;
continue;
if (revs->reverse && revs->reflog_info)
die("cannot combine --reverse with --walk-reflogs");
+ /*
+ * Limitations on the graph functionality
+ */
+ if (revs->reverse && revs->graph)
+ die("cannot combine --reverse with --graph");
+
+ if (revs->reflog_info && revs->graph)
+ die("cannot combine --walk-reflogs with --graph");
+
return left;
}
/* Commit without changes? */
if (commit->object.flags & TREESAME) {
/* drop merges unless we want parenthood */
- if (!revs->parents)
+ if (!revs->rewrite_parents)
return commit_ignore;
/* non-merge - always ignore it */
if (!commit->parents || !commit->parents->next)
return commit_ignore;
}
- if (revs->parents && rewrite_parents(revs, commit) < 0)
+ if (revs->rewrite_parents && rewrite_parents(revs, commit) < 0)
return commit_error;
}
return commit_show;
}
}
-struct commit *get_revision(struct rev_info *revs)
+static struct commit *get_revision_internal(struct rev_info *revs)
{
struct commit *c = NULL;
struct commit_list *l;
return c;
}
+
+struct commit *get_revision(struct rev_info *revs)
+{
+ struct commit *c = get_revision_internal(revs);
+ if (c && revs->graph)
+ graph_update(revs->graph, c);
+ return c;
+}
unpacked:1, /* see also ignore_packed below */
boundary:2,
left_right:1,
- parents:1,
+ rewrite_parents:1,
+ print_parents:1,
reverse:1,
cherry_pick:1,
first_parent_only:1;
/* Format info */
unsigned int shown_one:1,
abbrev_commit:1,
- use_terminator:1;
+ use_terminator:1,
+ missing_newline:1;
enum date_mode date_mode;
const char **ignore_packed; /* pretend objects in these are unpacked */
/* Filter by commit log message */
struct grep_opt *grep_filter;
+ /* Display history graph */
+ struct git_graph *graph;
+
/* special limits */
int skip_count;
int max_count;
return base;
}
-char *sha1_pack_name(const unsigned char *sha1)
+static char *sha1_get_pack_name(const unsigned char *sha1,
+ char **name, char **base)
{
static const char hex[] = "0123456789abcdef";
- static char *name, *base, *buf;
+ char *buf;
int i;
- if (!base) {
+ if (!*base) {
const char *sha1_file_directory = get_object_directory();
int len = strlen(sha1_file_directory);
- base = xmalloc(len + 60);
- sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory);
- name = base + len + 11;
+ *base = xmalloc(len + 60);
+ sprintf(*base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory);
+ *name = *base + len + 11;
}
- buf = name;
+ buf = *name;
for (i = 0; i < 20; i++) {
unsigned int val = *sha1++;
*buf++ = hex[val & 0xf];
}
- return base;
+ return *base;
}
-char *sha1_pack_index_name(const unsigned char *sha1)
+char *sha1_pack_name(const unsigned char *sha1)
{
- static const char hex[] = "0123456789abcdef";
- static char *name, *base, *buf;
- int i;
-
- if (!base) {
- const char *sha1_file_directory = get_object_directory();
- int len = strlen(sha1_file_directory);
- base = xmalloc(len + 60);
- sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.idx", sha1_file_directory);
- name = base + len + 11;
- }
+ static char *name, *base;
- buf = name;
+ return sha1_get_pack_name(sha1, &name, &base);
+}
- for (i = 0; i < 20; i++) {
- unsigned int val = *sha1++;
- *buf++ = hex[val >> 4];
- *buf++ = hex[val & 0xf];
- }
+char *sha1_pack_index_name(const unsigned char *sha1)
+{
+ static char *name, *base;
- return base;
+ return sha1_get_pack_name(sha1, &name, &base);
}
struct alternate_object_database *alt_odb_list;
return 0;
}
-int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
+static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
+ void *buf, unsigned long len, time_t mtime)
{
- int size, ret;
+ int fd, size, ret;
unsigned char *compressed;
z_stream stream;
- unsigned char sha1[20];
char *filename;
static char tmpfile[PATH_MAX];
- char hdr[32];
- int fd, hdrlen;
- /* Normally if we have it in the pack then we do not bother writing
- * it out into .git/objects/??/?{38} file.
- */
- write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
filename = sha1_file_name(sha1);
- if (returnsha1)
- hashcpy(returnsha1, sha1);
- if (has_sha1_file(sha1))
- return 0;
fd = open(filename, O_RDONLY);
if (fd >= 0) {
/*
die("unable to write sha1 file");
free(compressed);
+ if (mtime) {
+ struct utimbuf utb;
+ utb.actime = mtime;
+ utb.modtime = mtime;
+ if (utime(tmpfile, &utb) < 0)
+ warning("failed utime() on %s: %s",
+ tmpfile, strerror(errno));
+ }
+
return move_temp_to_file(tmpfile, filename);
}
+int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
+{
+ unsigned char sha1[20];
+ char hdr[32];
+ int hdrlen;
+
+ /* Normally if we have it in the pack then we do not bother writing
+ * it out into .git/objects/??/?{38} file.
+ */
+ write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+ if (returnsha1)
+ hashcpy(returnsha1, sha1);
+ if (has_sha1_file(sha1))
+ return 0;
+ return write_loose_object(sha1, hdr, hdrlen, buf, len, 0);
+}
+
+int force_object_loose(const unsigned char *sha1, time_t mtime)
+{
+ struct stat st;
+ void *buf;
+ unsigned long len;
+ enum object_type type;
+ char hdr[32];
+ int hdrlen;
+
+ if (find_sha1_file(sha1, &st))
+ return 0;
+ buf = read_packed_sha1(sha1, &type, &len);
+ if (!buf)
+ return error("cannot read sha1_file for %s", sha1_to_hex(sha1));
+ hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
+ return write_loose_object(sha1, hdr, hdrlen, buf, len, mtime);
+}
+
/*
* We need to unpack and recompress the object for writing
* it out to a different file.
@echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
clean:
- $(RM) -r trash
+ $(RM) -r 'trash directory'
# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
full-svn-test:
(or -h), it shows the test_description and exits.
- Creates an empty test directory with an empty .git/objects
- database and chdir(2) into it. This directory is 't/trash'
+ database and chdir(2) into it. This directory is 't/trash directory'
if you must know, but I do not think you care.
- Defines standard test helper functions for your scripts to
auml=`printf '\xc3\xa4'`
aumlcdiar=`printf '\x61\xcc\x88'`
+case_insensitive=
test_expect_success 'see if we expect ' '
test_case=test_expect_success
if test "$(cat junk/CamelCase)" != good
then
test_case=test_expect_failure
+ case_insensitive=t
say "will test on a case insensitive filesystem"
fi &&
rm -fr junk &&
rm -fr junk
'
+if test "$case_insensitive"
+then
+test_expect_success "detection of case insensitive filesystem during repo init" '
+
+ test $(git config --bool core.ignorecase) = true
+'
+else
+test_expect_success "detection of case insensitive filesystem during repo init" '
+
+ ! git config --bool core.ignorecase >/dev/null ||
+ test $(git config --bool core.ignorecase) = false
+'
+fi
+
test_expect_success "setup case tests" '
+ git config core.ignorecase true &&
touch camelcase &&
git add camelcase &&
git commit -m "initial" &&
$test_case 'merge (case change)' '
+ rm -f CamelCase &&
+ rm -f camelcase &&
git reset --hard initial &&
git merge topic
'
+$test_case 'add (with different case)' '
+
+ git reset --hard initial &&
+ rm camelcase &&
+ echo 1 >CamelCase &&
+ git add CamelCase &&
+ test $(git-ls-files | grep -i camelcase | wc -l) = 1
+
+'
+
test_expect_success "setup unicode normalization tests" '
test_create_repo unicode &&
--- /dev/null
+#!/bin/sh
+
+test_description='git cat-file'
+
+. ./test-lib.sh
+
+echo_without_newline () {
+ printf '%s' "$*"
+}
+
+strlen () {
+ echo_without_newline "$1" | wc -c | sed -e 's/^ *//'
+}
+
+maybe_remove_timestamp () {
+ if test -z "$2"; then
+ echo_without_newline "$1"
+ else
+ echo_without_newline "$(printf '%s\n' "$1" | sed -e 's/ [0-9][0-9]* [-+][0-9][0-9][0-9][0-9]$//')"
+ fi
+}
+
+run_tests () {
+ type=$1
+ sha1=$2
+ size=$3
+ content=$4
+ pretty_content=$5
+ no_ts=$6
+
+ batch_output="$sha1 $type $size
+$content"
+
+ test_expect_success "$type exists" '
+ git cat-file -e $sha1
+ '
+
+ test_expect_success "Type of $type is correct" '
+ test $type = "$(git cat-file -t $sha1)"
+ '
+
+ test_expect_success "Size of $type is correct" '
+ test $size = "$(git cat-file -s $sha1)"
+ '
+
+ test -z "$content" ||
+ test_expect_success "Content of $type is correct" '
+ expect="$(maybe_remove_timestamp "$content" $no_ts)"
+ actual="$(maybe_remove_timestamp "$(git cat-file $type $sha1)" $no_ts)"
+
+ if test "z$expect" = "z$actual"
+ then
+ : happy
+ else
+ echo "Oops: expected $expect"
+ echo "but got $actual"
+ false
+ fi
+ '
+
+ test_expect_success "Pretty content of $type is correct" '
+ expect="$(maybe_remove_timestamp "$pretty_content" $no_ts)"
+ actual="$(maybe_remove_timestamp "$(git cat-file -p $sha1)" $no_ts)"
+ if test "z$expect" = "z$actual"
+ then
+ : happy
+ else
+ echo "Oops: expected $expect"
+ echo "but got $actual"
+ false
+ fi
+ '
+
+ test -z "$content" ||
+ test_expect_success "--batch output of $type is correct" '
+ expect="$(maybe_remove_timestamp "$batch_output" $no_ts)"
+ actual="$(maybe_remove_timestamp "$(echo $sha1 | git cat-file --batch)" no_ts)"
+ if test "z$expect" = "z$actual"
+ then
+ : happy
+ else
+ echo "Oops: expected $expect"
+ echo "but got $actual"
+ false
+ fi
+ '
+
+ test_expect_success "--batch-check output of $type is correct" '
+ expect="$sha1 $type $size"
+ actual="$(echo_without_newline $sha1 | git cat-file --batch-check)"
+ if test "z$expect" = "z$actual"
+ then
+ : happy
+ else
+ echo "Oops: expected $expect"
+ echo "but got $actual"
+ false
+ fi
+ '
+}
+
+hello_content="Hello World"
+hello_size=$(strlen "$hello_content")
+hello_sha1=$(echo_without_newline "$hello_content" | git hash-object --stdin)
+
+test_expect_success "setup" '
+ echo_without_newline "$hello_content" > hello &&
+ git update-index --add hello
+'
+
+run_tests 'blob' $hello_sha1 $hello_size "$hello_content" "$hello_content"
+
+tree_sha1=$(git write-tree)
+tree_size=33
+tree_pretty_content="100644 blob $hello_sha1 hello"
+
+run_tests 'tree' $tree_sha1 $tree_size "" "$tree_pretty_content"
+
+commit_message="Intial commit"
+commit_sha1=$(echo_without_newline "$commit_message" | git commit-tree $tree_sha1)
+commit_size=176
+commit_content="tree $tree_sha1
+author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0000000000 +0000
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0000000000 +0000
+
+$commit_message"
+
+run_tests 'commit' $commit_sha1 $commit_size "$commit_content" "$commit_content" 1
+
+tag_header_without_timestamp="object $hello_sha1
+type blob
+tag hellotag
+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+tag_description="This is a tag"
+tag_content="$tag_header_without_timestamp 0000000000 +0000
+
+$tag_description"
+tag_pretty_content="$tag_header_without_timestamp Thu Jan 1 00:00:00 1970 +0000
+
+$tag_description"
+
+tag_sha1=$(echo_without_newline "$tag_content" | git mktag)
+tag_size=$(strlen "$tag_content")
+
+run_tests 'tag' $tag_sha1 $tag_size "$tag_content" "$tag_pretty_content" 1
+
+test_expect_success \
+ "Reach a blob from a tag pointing to it" \
+ "test '$hello_content' = \"\$(git cat-file blob $tag_sha1)\""
+
+for batch in batch batch-check
+do
+ for opt in t s e p
+ do
+ test_expect_success "Passing -$opt with --$batch fails" '
+ test_must_fail git cat-file --$batch -$opt $hello_sha1
+ '
+
+ test_expect_success "Passing --$batch with -$opt fails" '
+ test_must_fail git cat-file -$opt --$batch $hello_sha1
+ '
+ done
+
+ test_expect_success "Passing <type> with --$batch fails" '
+ test_must_fail git cat-file --$batch blob $hello_sha1
+ '
+
+ test_expect_success "Passing --$batch with <type> fails" '
+ test_must_fail git cat-file blob --$batch $hello_sha1
+ '
+
+ test_expect_success "Passing sha1 with --$batch fails" '
+ test_must_fail git cat-file --$batch $hello_sha1
+ '
+done
+
+test_expect_success "--batch-check for a non-existent object" '
+ test "deadbeef missing" = \
+ "$(echo_without_newline deadbeef | git cat-file --batch-check)"
+'
+
+test_expect_success "--batch-check for an emtpy line" '
+ test " missing" = "$(echo | git cat-file --batch-check)"
+'
+
+batch_input="$hello_sha1
+$commit_sha1
+$tag_sha1
+deadbeef
+
+"
+
+batch_output="$hello_sha1 blob $hello_size
+$hello_content
+$commit_sha1 commit $commit_size
+$commit_content
+$tag_sha1 tag $tag_size
+$tag_content
+deadbeef missing
+ missing"
+
+test_expect_success '--batch with multiple sha1s gives correct format' '
+ test "$(maybe_remove_timestamp "$batch_output" 1)" = "$(maybe_remove_timestamp "$(echo_without_newline "$batch_input" | git cat-file --batch)" 1)"
+'
+
+batch_check_input="$hello_sha1
+$tree_sha1
+$commit_sha1
+$tag_sha1
+deadbeef
+
+"
+
+batch_check_output="$hello_sha1 blob $hello_size
+$tree_sha1 tree $tree_size
+$commit_sha1 commit $commit_size
+$tag_sha1 tag $tag_size
+deadbeef missing
+ missing"
+
+test_expect_success "--batch-check with multiple sha1s gives correct format" '
+ test "$batch_check_output" = \
+ "$(echo_without_newline "$batch_check_input" | git cat-file --batch-check)"
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description=git-hash-object
+
+. ./test-lib.sh
+
+echo_without_newline() {
+ printf '%s' "$*"
+}
+
+test_blob_does_not_exist() {
+ test_expect_success 'blob does not exist in database' "
+ test_must_fail git cat-file blob $1
+ "
+}
+
+test_blob_exists() {
+ test_expect_success 'blob exists in database' "
+ git cat-file blob $1
+ "
+}
+
+hello_content="Hello World"
+hello_sha1=5e1c309dae7f45e0f39b1bf3ac3cd9db12e7d689
+
+example_content="This is an example"
+example_sha1=ddd3f836d3e3fbb7ae289aa9ae83536f76956399
+
+setup_repo() {
+ echo_without_newline "$hello_content" > hello
+ echo_without_newline "$example_content" > example
+}
+
+test_repo=test
+push_repo() {
+ test_create_repo $test_repo
+ cd $test_repo
+
+ setup_repo
+}
+
+pop_repo() {
+ cd ..
+ rm -rf $test_repo
+}
+
+setup_repo
+
+# Argument checking
+
+test_expect_success "multiple '--stdin's are rejected" '
+ test_must_fail git hash-object --stdin --stdin < example
+'
+
+test_expect_success "Can't use --stdin and --stdin-paths together" '
+ test_must_fail git hash-object --stdin --stdin-paths &&
+ test_must_fail git hash-object --stdin-paths --stdin
+'
+
+test_expect_success "Can't pass filenames as arguments with --stdin-paths" '
+ test_must_fail git hash-object --stdin-paths hello < example
+'
+
+# Behavior
+
+push_repo
+
+test_expect_success 'hash a file' '
+ test $hello_sha1 = $(git hash-object hello)
+'
+
+test_blob_does_not_exist $hello_sha1
+
+test_expect_success 'hash from stdin' '
+ test $example_sha1 = $(git hash-object --stdin < example)
+'
+
+test_blob_does_not_exist $example_sha1
+
+test_expect_success 'hash a file and write to database' '
+ test $hello_sha1 = $(git hash-object -w hello)
+'
+
+test_blob_exists $hello_sha1
+
+test_expect_success 'git hash-object --stdin file1 <file0 first operates on file0, then file1' '
+ echo foo > file1 &&
+ obname0=$(echo bar | git hash-object --stdin) &&
+ obname1=$(git hash-object file1) &&
+ obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&
+ obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&
+ test "$obname0" = "$obname0new" &&
+ test "$obname1" = "$obname1new"
+'
+
+pop_repo
+
+for args in "-w --stdin" "--stdin -w"; do
+ push_repo
+
+ test_expect_success "hash from stdin and write to database ($args)" '
+ test $example_sha1 = $(git hash-object $args < example)
+ '
+
+ test_blob_exists $example_sha1
+
+ pop_repo
+done
+
+filenames="hello
+example"
+
+sha1s="$hello_sha1
+$example_sha1"
+
+test_expect_success "hash two files with names on stdin" '
+ test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object --stdin-paths)"
+'
+
+for args in "-w --stdin-paths" "--stdin-paths -w"; do
+ push_repo
+
+ test_expect_success "hash two files with names on stdin and write to database ($args)" '
+ test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object $args)"
+ '
+
+ test_blob_exists $hello_sha1
+ test_blob_exists $example_sha1
+
+ pop_repo
+done
+
+test_done
test -z "`git diff-index HEAD -- foo`"
'
+test_expect_success 'git add should fail atomically upon an unreadable file' '
+ git reset --hard &&
+ date >foo1 &&
+ date >foo2 &&
+ chmod 0 foo2 &&
+ test_must_fail git add --verbose . &&
+ ! ( git ls-files foo1 | grep foo1 )
+'
+
+rm -f foo2
+
+test_expect_success 'git add --ignore-errors' '
+ git reset --hard &&
+ date >foo1 &&
+ date >foo2 &&
+ chmod 0 foo2 &&
+ test_must_fail git add --verbose --ignore-errors . &&
+ git ls-files foo1 | grep foo1
+'
+
+rm -f foo2
+
+test_expect_success 'git add (add.ignore-errors)' '
+ git config add.ignore-errors 1 &&
+ git reset --hard &&
+ date >foo1 &&
+ date >foo2 &&
+ chmod 0 foo2 &&
+ test_must_fail git add --verbose . &&
+ git ls-files foo1 | grep foo1
+'
+rm -f foo2
+
+test_expect_success 'git add (add.ignore-errors = false)' '
+ git config add.ignore-errors 0 &&
+ git reset --hard &&
+ date >foo1 &&
+ date >foo2 &&
+ chmod 0 foo2 &&
+ test_must_fail git add --verbose . &&
+ ! ( git ls-files foo1 | grep foo1 )
+'
+
test_done
grep "unchanged *+3/-0 file" output
'
+if test "$(git config --bool core.filemode)" = false
+then
+ say 'skipping filemode tests (filesystem does not properly support modes)'
+else
+
test_expect_success 'patch does not affect mode' '
git reset --hard &&
echo content >>file &&
git diff file | grep "+content"
'
+fi
+# end of tests disabled when filemode is not usable
test_done
+++ /dev/null
-#!/bin/sh
-
-test_description=git-hash-object
-
-. ./test-lib.sh
-
-test_expect_success \
- 'git hash-object -w --stdin saves the object' \
- 'obname=$(echo foo | git hash-object -w --stdin) &&
- obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
- test -r .git/objects/"$obpath" &&
- rm -f .git/objects/"$obpath"'
-
-test_expect_success \
- 'git hash-object --stdin -w saves the object' \
- 'obname=$(echo foo | git hash-object --stdin -w) &&
- obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
- test -r .git/objects/"$obpath" &&
- rm -f .git/objects/"$obpath"'
-
-test_expect_success \
- 'git hash-object --stdin file1 <file0 first operates on file0, then file1' \
- 'echo foo > file1 &&
- obname0=$(echo bar | git hash-object --stdin) &&
- obname1=$(git hash-object file1) &&
- obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&
- obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&
- test "$obname0" = "$obname0new" &&
- test "$obname1" = "$obname1new"'
-
-test_expect_success \
- 'git hash-object refuses multiple --stdin arguments' \
- '! git hash-object --stdin --stdin < file1'
-
-test_done
}
test_refspec push '' invalid
-test_refspec push ':' invalid
+test_refspec push ':'
+test_refspec push '::' invalid
+test_refspec push '+:'
test_refspec fetch ''
test_refspec fetch ':'
+test_refspec fetch '::' invalid
test_refspec push 'refs/heads/*:refs/remotes/frotz/*'
test_refspec push 'refs/heads/*:refs/remotes/frotz' invalid
'
+test_expect_success 'push with matching heads on the command line' '
+
+ mk_test heads/master &&
+ git push testrepo : &&
+ check_push_result $the_commit heads/master
+
+'
+
+test_expect_success 'failed (non-fast-forward) push with matching heads' '
+
+ mk_test heads/master &&
+ git push testrepo : &&
+ git commit --amend -massaged &&
+ ! git push testrepo &&
+ check_push_result $the_commit heads/master &&
+ git reset --hard $the_commit
+
+'
+
+test_expect_success 'push --force with matching heads' '
+
+ mk_test heads/master &&
+ git push testrepo : &&
+ git commit --amend -massaged &&
+ git push --force testrepo &&
+ ! check_push_result $the_commit heads/master &&
+ git reset --hard $the_commit
+
+'
+
+test_expect_success 'push with matching heads and forced update' '
+
+ mk_test heads/master &&
+ git push testrepo : &&
+ git commit --amend -massaged &&
+ git push testrepo +: &&
+ ! check_push_result $the_commit heads/master &&
+ git reset --hard $the_commit
+
+'
+
test_expect_success 'push with no ambiguity (1)' '
mk_test heads/master &&
'
+test_expect_success 'pull --rebase dies early with dirty working directory' '
+
+ git update-ref refs/remotes/me/copy copy^ &&
+ COPY=$(git rev-parse --verify me/copy) &&
+ git rebase --onto $COPY copy &&
+ git config branch.to-rebase.remote me &&
+ git config branch.to-rebase.merge refs/heads/copy &&
+ git config branch.to-rebase.rebase true &&
+ echo dirty >> file &&
+ git add file &&
+ test_must_fail git pull &&
+ test $COPY = $(git rev-parse --verify me/copy) &&
+ git checkout HEAD -- file &&
+ git pull &&
+ test $COPY != $(git rev-parse --verify me/copy)
+
+'
+
test_done
test -z "$(git for-each-ref "refs/heads/bisect")"
'
+test_expect_success 'bisect start: back in good branch' '
+ git branch > branch.output &&
+ grep "* other" branch.output > /dev/null &&
+ git bisect start $HASH4 $HASH1 -- &&
+ git bisect good &&
+ git bisect start $HASH4 $HASH1 -- &&
+ git bisect bad &&
+ git bisect reset &&
+ git branch > branch.output &&
+ grep "* other" branch.output > /dev/null
+'
+
+test_expect_success 'bisect start: no ".git/BISECT_START" if junk rev' '
+ git bisect start $HASH4 $HASH1 -- &&
+ git bisect good &&
+ test_must_fail git bisect start $HASH4 foo -- &&
+ git branch > branch.output &&
+ grep "* other" branch.output > /dev/null &&
+ test_must_fail test -e .git/BISECT_START
+'
+
+test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
+ git bisect start $HASH4 $HASH1 -- &&
+ git bisect good &&
+ test_must_fail git bisect start $HASH1 $HASH4 -- &&
+ git branch > branch.output &&
+ grep "* other" branch.output > /dev/null &&
+ test_must_fail test -e .git/BISECT_START
+'
+
+test_expect_success 'bisect start: no ".git/BISECT_START" if checkout error' '
+ echo "temp stuff" > hello &&
+ test_must_fail git bisect start $HASH4 $HASH1 -- &&
+ git branch &&
+ git branch > branch.output &&
+ grep "* other" branch.output > /dev/null &&
+ test_must_fail test -e .git/BISECT_START &&
+ test -z "$(git for-each-ref "refs/bisect/*")" &&
+ git checkout HEAD hello
+'
+
# $HASH1 is good, $HASH4 is bad, we skip $HASH3
# but $HASH2 is bad,
# so we should find $HASH2 as the first bad commit
test $HEAD = $(cat .git/BISECT_START) &&
git bisect reset &&
test $HEAD = $(git rev-parse --verify HEAD)
-
-'
-
-test_expect_success 'bisect refuses to start if branch bisect exists' '
- git bisect reset &&
- git branch bisect &&
- test_must_fail git bisect start &&
- git branch -d bisect &&
- git checkout -b bisect &&
- test_must_fail git bisect start &&
- git checkout master &&
- git branch -d bisect
-'
-
-test_expect_success 'bisect refuses to start if branch new-bisect exists' '
- git bisect reset &&
- git branch new-bisect &&
- test_must_fail git bisect start &&
- git branch -d new-bisect
'
test_expect_success 'bisect errors out if bad and good are mistaken' '
git bisect reset
'
+test_expect_success 'bisect does not create a "bisect" branch' '
+ git bisect reset &&
+ git bisect start $HASH7 $HASH1 &&
+ git branch bisect &&
+ rev_hash4=$(git rev-parse --verify HEAD) &&
+ test "$rev_hash4" = "$HASH4" &&
+ git branch -D bisect &&
+ git bisect good &&
+ git branch bisect &&
+ rev_hash6=$(git rev-parse --verify HEAD) &&
+ test "$rev_hash6" = "$HASH6" &&
+ git bisect good > my_bisect_log.txt &&
+ grep "$HASH7 is first bad commit" my_bisect_log.txt &&
+ git bisect reset &&
+ rev_hash6=$(git rev-parse --verify bisect) &&
+ test "$rev_hash6" = "$HASH6" &&
+ git branch -D bisect
+'
+
#
#
test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2008 Johannes Schindelin
+#
+
+test_description='Test rebasing and stashing with dirty submodules'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ echo file > file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ git clone . submodule &&
+ git add submodule &&
+ test_tick &&
+ git commit -m submodule &&
+ echo second line >> file &&
+ (cd submodule && git pull) &&
+ test_tick &&
+ git commit -m file-and-submodule -a
+
+'
+
+test_expect_success 'rebase with a dirty submodule' '
+
+ (cd submodule &&
+ echo 3rd line >> file &&
+ test_tick &&
+ git commit -m fork -a) &&
+ echo unrelated >> file2 &&
+ git add file2 &&
+ test_tick &&
+ git commit -m unrelated file2 &&
+ echo other line >> file &&
+ test_tick &&
+ git commit -m update file &&
+ CURRENT=$(cd submodule && git rev-parse HEAD) &&
+ EXPECTED=$(git rev-parse HEAD~2:submodule) &&
+ GIT_TRACE=1 git rebase --onto HEAD~2 HEAD^ &&
+ STORED=$(git rev-parse HEAD:submodule) &&
+ test $EXPECTED = $STORED &&
+ test $CURRENT = $(cd submodule && git rev-parse HEAD)
+
+'
+
+cat > fake-editor.sh << \EOF
+#!/bin/sh
+echo $EDITOR_TEXT
+EOF
+chmod a+x fake-editor.sh
+
+test_expect_success 'interactive rebase with a dirty submodule' '
+
+ test submodule = $(git diff --name-only) &&
+ HEAD=$(git rev-parse HEAD) &&
+ GIT_EDITOR="\"$(pwd)/fake-editor.sh\"" EDITOR_TEXT="pick $HEAD" \
+ git rebase -i HEAD^ &&
+ test submodule = $(git diff --name-only)
+
+'
+
+test_expect_success 'rebase with dirty file and submodule fails' '
+
+ echo yet another line >> file &&
+ test_tick &&
+ git commit -m next file &&
+ echo rewrite > file &&
+ test_tick &&
+ git commit -m rewrite file &&
+ echo dirty > file &&
+ ! git rebase --onto HEAD~2 HEAD^
+
+'
+
+test_expect_success 'stash with a dirty submodule' '
+
+ echo new > file &&
+ CURRENT=$(cd submodule && git rev-parse HEAD) &&
+ git stash &&
+ test new != $(cat file) &&
+ test submodule = $(git diff --name-only) &&
+ test $CURRENT = $(cd submodule && git rev-parse HEAD) &&
+ git stash apply &&
+ test new = $(cat file) &&
+ test $CURRENT = $(cd submodule && git rev-parse HEAD)
+
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git-repack works correctly'
+
+. ./test-lib.sh
+
+test_expect_success '-A option leaves unreachable objects unpacked' '
+ echo content > file1 &&
+ git add . &&
+ git commit -m initial_commit &&
+ # create a transient branch with unique content
+ git checkout -b transient_branch &&
+ echo more content >> file1 &&
+ # record the objects created in the database for file, commit, tree
+ fsha1=$(git hash-object file1) &&
+ git commit -a -m more_content &&
+ csha1=$(git rev-parse HEAD^{commit}) &&
+ tsha1=$(git rev-parse HEAD^{tree}) &&
+ git checkout master &&
+ echo even more content >> file1 &&
+ git commit -a -m even_more_content &&
+ # delete the transient branch
+ git branch -D transient_branch &&
+ # pack the repo
+ git repack -A -d -l &&
+ # verify objects are packed in repository
+ test 3 = $(git verify-pack -v -- .git/objects/pack/*.idx |
+ grep -e "^$fsha1 " -e "^$csha1 " -e "^$tsha1 " |
+ sort | uniq | wc -l) &&
+ git show $fsha1 &&
+ git show $csha1 &&
+ git show $tsha1 &&
+ # now expire the reflog
+ sleep 1 &&
+ git reflog expire --expire-unreachable=now --all &&
+ # and repack
+ git repack -A -d -l &&
+ # verify objects are retained unpacked
+ test 0 = $(git verify-pack -v -- .git/objects/pack/*.idx |
+ grep -e "^$fsha1 " -e "^$csha1 " -e "^$tsha1 " |
+ sort | uniq | wc -l) &&
+ git show $fsha1 &&
+ git show $csha1 &&
+ git show $tsha1
+'
+
+test_done
test_cmp expected-show-all-headers actual-show-all-headers
'
+test_expect_success '--compose adds MIME for utf8 body' '
+ clean_fake_sendmail &&
+ (echo "#!/bin/sh" &&
+ echo "echo utf8 body: àéìöú >>\"\$1\""
+ ) >fake-editor-utf8 &&
+ chmod +x fake-editor-utf8 &&
+ echo y | \
+ GIT_EDITOR="\"$(pwd)/fake-editor-utf8\"" \
+ GIT_SEND_EMAIL_NOTTY=1 \
+ git send-email \
+ --compose --subject foo \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches &&
+ grep "^utf8 body" msgtxt1 &&
+ grep "^Content-Type: text/plain; charset=utf-8" msgtxt1
+'
+
+test_expect_success '--compose respects user mime type' '
+ clean_fake_sendmail &&
+ (echo "#!/bin/sh" &&
+ echo "(echo MIME-Version: 1.0"
+ echo " echo Content-Type: text/plain\\; charset=iso-8859-1"
+ echo " echo Content-Transfer-Encoding: 8bit"
+ echo " echo Subject: foo"
+ echo " echo "
+ echo " echo utf8 body: àéìöú) >\"\$1\""
+ ) >fake-editor-utf8-mime &&
+ chmod +x fake-editor-utf8-mime &&
+ echo y | \
+ GIT_EDITOR="\"$(pwd)/fake-editor-utf8-mime\"" \
+ GIT_SEND_EMAIL_NOTTY=1 \
+ git send-email \
+ --compose --subject foo \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches &&
+ grep "^utf8 body" msgtxt1 &&
+ grep "^Content-Type: text/plain; charset=iso-8859-1" msgtxt1 &&
+ ! grep "^Content-Type: text/plain; charset=utf-8" msgtxt1
+'
+
+test_expect_success '--compose adds MIME for utf8 subject' '
+ clean_fake_sendmail &&
+ echo y | \
+ GIT_EDITOR="\"$(pwd)/fake-editor\"" \
+ GIT_SEND_EMAIL_NOTTY=1 \
+ git send-email \
+ --compose --subject utf8-sübjëct \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches &&
+ grep "^fake edit" msgtxt1 &&
+ grep "^Subject: =?utf-8?q?utf8-s=C3=BCbj=C3=ABct?=" msgtxt1
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git svn authorship'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup svn repository' '
+ svn checkout "$svnrepo" work.svn &&
+ (
+ cd work.svn &&
+ echo >file
+ svn add file
+ svn commit -m "first commit" file
+ )
+'
+
+test_expect_success 'interact with it via git-svn' '
+ mkdir work.git &&
+ (
+ cd work.git &&
+ git svn init "$svnrepo"
+ git svn fetch &&
+
+ echo modification >file &&
+ test_tick &&
+ git commit -a -m second &&
+
+ test_tick &&
+ git svn dcommit &&
+
+ echo "further modification" >file &&
+ test_tick &&
+ git commit -a -m third &&
+
+ test_tick &&
+ git svn --add-author-from dcommit &&
+
+ echo "yet further modification" >file &&
+ test_tick &&
+ git commit -a -m fourth &&
+
+ test_tick &&
+ git svn --add-author-from --use-log-author dcommit &&
+
+ git log &&
+
+ git show -s HEAD^^ >../actual.2 &&
+ git show -s HEAD^ >../actual.3 &&
+ git show -s HEAD >../actual.4
+
+ ) &&
+
+ # Make sure that --add-author-from without --use-log-author
+ # did not affect the authorship information
+ myself=$(grep "^Author: " actual.2) &&
+ unaffected=$(grep "^Author: " actual.3) &&
+ test "z$myself" = "z$unaffected" &&
+
+ # Make sure lack of --add-author-from did not add cruft
+ ! grep "^ From: A U Thor " actual.2 &&
+
+ # Make sure --add-author-from added cruft
+ grep "^ From: A U Thor " actual.3 &&
+ grep "^ From: A U Thor " actual.4 &&
+
+ # Make sure --add-author-from with --use-log-author affected
+ # the authorship information
+ grep "^Author: A U Thor " actual.4
+'
+
+test_done
'
+test_expect_success 'use the same checkout for Git and CVS' '
+
+ (mkdir shared &&
+ cd shared &&
+ unset GIT_DIR &&
+ cvs co . &&
+ git init &&
+ git add " space" &&
+ git commit -m "fake initial commit" &&
+ echo Hello >> " space" &&
+ git commit -m "Another change" " space" &&
+ git cvsexportcommit -W -p -u -c HEAD &&
+ grep Hello " space" &&
+ git diff-files)
+
+'
+
test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2008 Matthew Ogilvie
+# Parts adapted from other tests.
+#
+
+test_description='git-cvsserver -kb modes
+
+tests -kb mode for binary files when accessing a git
+repository using cvs CLI client via git-cvsserver server'
+
+. ./test-lib.sh
+
+q_to_nul () {
+ perl -pe 'y/Q/\000/'
+}
+
+q_to_cr () {
+ tr Q '\015'
+}
+
+marked_as () {
+ foundEntry="$(grep "^/$2/" "$1/CVS/Entries")"
+ if [ x"$foundEntry" = x"" ] ; then
+ echo "NOT FOUND: $1 $2 1 $3" >> "${WORKDIR}/marked.log"
+ return 1
+ fi
+ test x"$(grep "^/$2/" "$1/CVS/Entries" | cut -d/ -f5)" = x"$3"
+ stat=$?
+ echo "$1 $2 $stat '$3'" >> "${WORKDIR}/marked.log"
+ return $stat
+}
+
+not_present() {
+ foundEntry="$(grep "^/$2/" "$1/CVS/Entries")"
+ if [ -r "$1/$2" ] ; then
+ echo "Error: File still exists: $1 $2" >> "${WORKDIR}/marked.log"
+ return 1;
+ fi
+ if [ x"$foundEntry" != x"" ] ; then
+ echo "Error: should not have found: $1 $2" >> "${WORKDIR}/marked.log"
+ return 1;
+ else
+ echo "Correctly not found: $1 $2" >> "${WORKDIR}/marked.log"
+ return 0;
+ fi
+}
+
+cvs >/dev/null 2>&1
+if test $? -ne 1
+then
+ test_expect_success 'skipping git-cvsserver tests, cvs not found' :
+ test_done
+ exit
+fi
+perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
+ test_expect_success 'skipping git-cvsserver tests, Perl SQLite interface unavailable' :
+ test_done
+ exit
+}
+
+unset GIT_DIR GIT_CONFIG
+WORKDIR=$(pwd)
+SERVERDIR=$(pwd)/gitcvs.git
+git_config="$SERVERDIR/config"
+CVSROOT=":fork:$SERVERDIR"
+CVSWORK="$(pwd)/cvswork"
+CVS_SERVER=git-cvsserver
+export CVSROOT CVS_SERVER
+
+rm -rf "$CVSWORK" "$SERVERDIR"
+test_expect_success 'setup' '
+ echo "Simple text file" >textfile.c &&
+ echo "File with embedded NUL: Q <- there" | q_to_nul > binfile.bin &&
+ mkdir subdir &&
+ echo "Another text file" > subdir/file.h &&
+ echo "Another binary: Q (this time CR)" | q_to_cr > subdir/withCr.bin &&
+ echo "Mixed up NUL, but marked text: Q <- there" | q_to_nul > mixedUp.c
+ echo "Unspecified" > subdir/unspecified.other &&
+ echo "/*.bin -crlf" > .gitattributes &&
+ echo "/*.c crlf" >> .gitattributes &&
+ echo "subdir/*.bin -crlf" >> .gitattributes &&
+ echo "subdir/*.c crlf" >> .gitattributes &&
+ echo "subdir/file.h crlf" >> .gitattributes &&
+ git add .gitattributes textfile.c binfile.bin mixedUp.c subdir/* &&
+ git commit -q -m "First Commit" &&
+ git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
+ GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
+ GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log"
+'
+
+test_expect_success 'cvs co (default crlf)' '
+ GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+ test x"$(grep '/-k' cvswork/CVS/Entries cvswork/subdir/CVS/Entries)" = x""
+'
+
+rm -rf cvswork
+test_expect_success 'cvs co (allbinary)' '
+ GIT_DIR="$SERVERDIR" git config --bool gitcvs.allbinary true &&
+ GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+ marked_as cvswork textfile.c -kb &&
+ marked_as cvswork binfile.bin -kb &&
+ marked_as cvswork .gitattributes -kb &&
+ marked_as cvswork mixedUp.c -kb &&
+ marked_as cvswork/subdir withCr.bin -kb &&
+ marked_as cvswork/subdir file.h -kb &&
+ marked_as cvswork/subdir unspecified.other -kb
+'
+
+rm -rf cvswork cvs.log
+test_expect_success 'cvs co (use attributes/allbinary)' '
+ GIT_DIR="$SERVERDIR" git config --bool gitcvs.usecrlfattr true &&
+ GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+ marked_as cvswork textfile.c "" &&
+ marked_as cvswork binfile.bin -kb &&
+ marked_as cvswork .gitattributes -kb &&
+ marked_as cvswork mixedUp.c "" &&
+ marked_as cvswork/subdir withCr.bin -kb &&
+ marked_as cvswork/subdir file.h "" &&
+ marked_as cvswork/subdir unspecified.other -kb
+'
+
+rm -rf cvswork
+test_expect_success 'cvs co (use attributes)' '
+ GIT_DIR="$SERVERDIR" git config --bool gitcvs.allbinary false &&
+ GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+ marked_as cvswork textfile.c "" &&
+ marked_as cvswork binfile.bin -kb &&
+ marked_as cvswork .gitattributes "" &&
+ marked_as cvswork mixedUp.c "" &&
+ marked_as cvswork/subdir withCr.bin -kb &&
+ marked_as cvswork/subdir file.h "" &&
+ marked_as cvswork/subdir unspecified.other ""
+'
+
+test_expect_success 'adding files' '
+ cd cvswork/subdir &&
+ echo "more text" > src.c &&
+ GIT_CONFIG="$git_config" cvs -Q add src.c >cvs.log 2>&1 &&
+ marked_as . src.c "" &&
+ echo "psuedo-binary" > temp.bin &&
+ cd .. &&
+ GIT_CONFIG="$git_config" cvs -Q add subdir/temp.bin >cvs.log 2>&1 &&
+ marked_as subdir temp.bin "-kb" &&
+ cd subdir &&
+ GIT_CONFIG="$git_config" cvs -Q ci -m "adding files" >cvs.log 2>&1 &&
+ marked_as . temp.bin "-kb" &&
+ marked_as . src.c ""
+'
+
+cd "$WORKDIR"
+test_expect_success 'updating' '
+ git pull gitcvs.git &&
+ echo 'hi' > subdir/newfile.bin &&
+ echo 'junk' > subdir/file.h &&
+ echo 'hi' > subdir/newfile.c &&
+ echo 'hello' >> binfile.bin &&
+ git add subdir/newfile.bin subdir/file.h subdir/newfile.c binfile.bin &&
+ git commit -q -m "Add and change some files" &&
+ git push gitcvs.git >/dev/null &&
+ cd cvswork &&
+ GIT_CONFIG="$git_config" cvs -Q update &&
+ cd .. &&
+ marked_as cvswork textfile.c "" &&
+ marked_as cvswork binfile.bin -kb &&
+ marked_as cvswork .gitattributes "" &&
+ marked_as cvswork mixedUp.c "" &&
+ marked_as cvswork/subdir withCr.bin -kb &&
+ marked_as cvswork/subdir file.h "" &&
+ marked_as cvswork/subdir unspecified.other "" &&
+ marked_as cvswork/subdir newfile.bin -kb &&
+ marked_as cvswork/subdir newfile.c "" &&
+ echo "File with embedded NUL: Q <- there" | q_to_nul > tmpExpect1 &&
+ echo "hello" >> tmpExpect1 &&
+ cmp cvswork/binfile.bin tmpExpect1
+'
+
+rm -rf cvswork
+test_expect_success 'cvs co (use attributes/guess)' '
+ GIT_DIR="$SERVERDIR" git config gitcvs.allbinary guess &&
+ GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+ marked_as cvswork textfile.c "" &&
+ marked_as cvswork binfile.bin -kb &&
+ marked_as cvswork .gitattributes "" &&
+ marked_as cvswork mixedUp.c "" &&
+ marked_as cvswork/subdir withCr.bin -kb &&
+ marked_as cvswork/subdir file.h "" &&
+ marked_as cvswork/subdir unspecified.other "" &&
+ marked_as cvswork/subdir newfile.bin -kb &&
+ marked_as cvswork/subdir newfile.c ""
+'
+
+test_expect_success 'setup multi-line files' '
+ ( echo "line 1" &&
+ echo "line 2" &&
+ echo "line 3" &&
+ echo "line 4 with NUL: Q <-" ) | q_to_nul > multiline.c &&
+ git add multiline.c &&
+ ( echo "line 1" &&
+ echo "line 2" &&
+ echo "line 3" &&
+ echo "line 4" ) | q_to_nul > multilineTxt.c &&
+ git add multilineTxt.c &&
+ git commit -q -m "multiline files" &&
+ git push gitcvs.git >/dev/null
+'
+
+rm -rf cvswork
+test_expect_success 'cvs co (guess)' '
+ GIT_DIR="$SERVERDIR" git config --bool gitcvs.usecrlfattr false &&
+ GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 &&
+ marked_as cvswork textfile.c "" &&
+ marked_as cvswork binfile.bin -kb &&
+ marked_as cvswork .gitattributes "" &&
+ marked_as cvswork mixedUp.c -kb &&
+ marked_as cvswork multiline.c -kb &&
+ marked_as cvswork multilineTxt.c "" &&
+ marked_as cvswork/subdir withCr.bin -kb &&
+ marked_as cvswork/subdir file.h "" &&
+ marked_as cvswork/subdir unspecified.other "" &&
+ marked_as cvswork/subdir newfile.bin "" &&
+ marked_as cvswork/subdir newfile.c ""
+'
+
+test_expect_success 'cvs co another copy (guess)' '
+ GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
+ marked_as cvswork2 textfile.c "" &&
+ marked_as cvswork2 binfile.bin -kb &&
+ marked_as cvswork2 .gitattributes "" &&
+ marked_as cvswork2 mixedUp.c -kb &&
+ marked_as cvswork2 multiline.c -kb &&
+ marked_as cvswork2 multilineTxt.c "" &&
+ marked_as cvswork2/subdir withCr.bin -kb &&
+ marked_as cvswork2/subdir file.h "" &&
+ marked_as cvswork2/subdir unspecified.other "" &&
+ marked_as cvswork2/subdir newfile.bin "" &&
+ marked_as cvswork2/subdir newfile.c ""
+'
+
+test_expect_success 'add text (guess)' '
+ cd cvswork &&
+ echo "simpleText" > simpleText.c &&
+ GIT_CONFIG="$git_config" cvs -Q add simpleText.c &&
+ cd .. &&
+ marked_as cvswork simpleText.c ""
+'
+
+test_expect_success 'add bin (guess)' '
+ cd cvswork &&
+ echo "simpleBin: NUL: Q <- there" | q_to_nul > simpleBin.bin &&
+ GIT_CONFIG="$git_config" cvs -Q add simpleBin.bin &&
+ cd .. &&
+ marked_as cvswork simpleBin.bin -kb
+'
+
+test_expect_success 'remove files (guess)' '
+ cd cvswork &&
+ GIT_CONFIG="$git_config" cvs -Q rm -f subdir/file.h &&
+ cd subdir &&
+ GIT_CONFIG="$git_config" cvs -Q rm -f withCr.bin &&
+ cd ../.. &&
+ marked_as cvswork/subdir withCr.bin -kb &&
+ marked_as cvswork/subdir file.h ""
+'
+
+test_expect_success 'cvs ci (guess)' '
+ cd cvswork &&
+ GIT_CONFIG="$git_config" cvs -Q ci -m "add/rm files" >cvs.log 2>&1 &&
+ cd .. &&
+ marked_as cvswork textfile.c "" &&
+ marked_as cvswork binfile.bin -kb &&
+ marked_as cvswork .gitattributes "" &&
+ marked_as cvswork mixedUp.c -kb &&
+ marked_as cvswork multiline.c -kb &&
+ marked_as cvswork multilineTxt.c "" &&
+ not_present cvswork/subdir withCr.bin &&
+ not_present cvswork/subdir file.h &&
+ marked_as cvswork/subdir unspecified.other "" &&
+ marked_as cvswork/subdir newfile.bin "" &&
+ marked_as cvswork/subdir newfile.c "" &&
+ marked_as cvswork simpleBin.bin -kb &&
+ marked_as cvswork simpleText.c ""
+'
+
+test_expect_success 'update subdir of other copy (guess)' '
+ cd cvswork2/subdir &&
+ GIT_CONFIG="$git_config" cvs -Q update &&
+ cd ../.. &&
+ marked_as cvswork2 textfile.c "" &&
+ marked_as cvswork2 binfile.bin -kb &&
+ marked_as cvswork2 .gitattributes "" &&
+ marked_as cvswork2 mixedUp.c -kb &&
+ marked_as cvswork2 multiline.c -kb &&
+ marked_as cvswork2 multilineTxt.c "" &&
+ not_present cvswork2/subdir withCr.bin &&
+ not_present cvswork2/subdir file.h &&
+ marked_as cvswork2/subdir unspecified.other "" &&
+ marked_as cvswork2/subdir newfile.bin "" &&
+ marked_as cvswork2/subdir newfile.c "" &&
+ not_present cvswork2 simpleBin.bin &&
+ not_present cvswork2 simpleText.c
+'
+
+echo "starting update/merge" >> "${WORKDIR}/marked.log"
+test_expect_success 'update/merge full other copy (guess)' '
+ git pull gitcvs.git master &&
+ sed "s/3/replaced_3/" < multilineTxt.c > ml.temp &&
+ mv ml.temp multilineTxt.c &&
+ git add multilineTxt.c &&
+ git commit -q -m "modify multiline file" >> "${WORKDIR}/marked.log" &&
+ git push gitcvs.git >/dev/null &&
+ cd cvswork2 &&
+ sed "s/1/replaced_1/" < multilineTxt.c > ml.temp &&
+ mv ml.temp multilineTxt.c &&
+ GIT_CONFIG="$git_config" cvs update > cvs.log 2>&1 &&
+ cd .. &&
+ marked_as cvswork2 textfile.c "" &&
+ marked_as cvswork2 binfile.bin -kb &&
+ marked_as cvswork2 .gitattributes "" &&
+ marked_as cvswork2 mixedUp.c -kb &&
+ marked_as cvswork2 multiline.c -kb &&
+ marked_as cvswork2 multilineTxt.c "" &&
+ not_present cvswork2/subdir withCr.bin &&
+ not_present cvswork2/subdir file.h &&
+ marked_as cvswork2/subdir unspecified.other "" &&
+ marked_as cvswork2/subdir newfile.bin "" &&
+ marked_as cvswork2/subdir newfile.c "" &&
+ marked_as cvswork2 simpleBin.bin -kb &&
+ marked_as cvswork2 simpleText.c "" &&
+ echo "line replaced_1" > tmpExpect2 &&
+ echo "line 2" >> tmpExpect2 &&
+ echo "line replaced_3" >> tmpExpect2 &&
+ echo "line 4" | q_to_nul >> tmpExpect2 &&
+ cmp cvswork2/multilineTxt.c tmpExpect2
+'
+
+test_done
case "$test_failure" in
0)
# We could:
- # cd .. && rm -fr trash
+ # cd .. && rm -fr 'trash directory'
# but that means we forbid any tests that use their own
# subdirectory from calling test_done without coming back
# to where they started from.
}
test_create_repo "$test"
-cd "$test"
+cd "$test" || exit 1
this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$')
for skp in $GIT_SKIP_TESTS
# The third example adds a Signed-off-by line to the message, that can
# still be edited. This is rarely a good idea.
-case "$2 $3" in
- merge)
- sed -i '/^Conflicts:/,/#/!b;s/^/# &/;s/^# #/#/' "$1" ;;
+case "$2,$3" in
+ merge,)
+ perl -i -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
-# ""|template)
+# ,|template,)
# perl -i -pe '
# print "\n" . `git diff --cached --name-status -r`
# if /^#/ && $first++ == 0' "$1" ;;
"\033[32m", /* WT_STATUS_UPDATED: green */
"\033[31m", /* WT_STATUS_CHANGED: red */
"\033[31m", /* WT_STATUS_UNTRACKED: red */
+ "\033[31m", /* WT_STATUS_NOBRANCH: red */
};
static const char use_add_msg[] =
return WT_STATUS_CHANGED;
if (!strcasecmp(var+offset, "untracked"))
return WT_STATUS_UNTRACKED;
+ if (!strcasecmp(var+offset, "nobranch"))
+ return WT_STATUS_NOBRANCH;
die("bad config variable '%s'", var);
}
void wt_status_print(struct wt_status *s)
{
unsigned char sha1[20];
- s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+ const char *branch_color = color(WT_STATUS_HEADER);
+ s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
if (s->branch) {
const char *on_what = "On branch ";
const char *branch_name = s->branch;
branch_name += 11;
else if (!strcmp(branch_name, "HEAD")) {
branch_name = "";
+ branch_color = color(WT_STATUS_NOBRANCH);
on_what = "Not currently on any branch.";
}
- color_fprintf_ln(s->fp, color(WT_STATUS_HEADER),
- "# %s%s", on_what, branch_name);
+ color_fprintf(s->fp, color(WT_STATUS_HEADER), "# ");
+ color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
}
if (s->is_initial) {
WT_STATUS_UPDATED,
WT_STATUS_CHANGED,
WT_STATUS_UNTRACKED,
+ WT_STATUS_NOBRANCH,
};
struct wt_status {